0

My app has a service that starts and stops independently of the main activity. The service can be considered to be running all the time. I am looking for the correct way to pass data to this service. There are several ways that I can see:

  • Using a BroadcastReceiver on a service side
  • Using a LocalBroadcastManager on a service side (currently used)
  • Binding to a service

Right now I have some code that works on LocalBroadcastManager. It looks something like this:

Service:

using Android.App;
using Android.Content;
using Android.OS;
using AndroidX.LocalBroadcastManager.Content;
using System.Threading;
using Android.Util;

namespace ExSample {
    [Service]
    public class LoggingService : Service {
        public override IBinder OnBind(Intent intent) {
            return null;
        }

        LocalBroadcastManager localBroadcastManager;
        SettingsReceiver receiver;

        int sleep_delay;

        public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) {
            localBroadcastManager = LocalBroadcastManager.GetInstance(this);
            receiver = new SettingsReceiver(this);
            localBroadcastManager.RegisterReceiver(receiver, new IntentFilter("SET_SLEEP_DELAY"));

            sleep_delay = intent.GetIntExtra("NEW_SLEEP_DELAY", 1000);
            new Thread(() => {
                while (true) {
                    Log.Debug("LOOP", "EXECUTE_ME");
                    Thread.Sleep(sleep_delay);
                }
            }).Start();
            return StartCommandResult.Sticky;
        }

        public override void OnDestroy() {
            localBroadcastManager.UnregisterReceiver(receiver);
            base.OnDestroy();
        }

        class SettingsReceiver : BroadcastReceiver {
            LoggingService context;
            internal SettingsReceiver(LoggingService context) {
                this.context = context;
            }

            public override void OnReceive(Context ctx, Intent intent) {
                context.sleep_delay = intent.GetIntExtra("NEW_SLEEP_DELAY", 1000);
            }
        }
    }
}

Activity:

namespace ExSample {
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        EditText textbox_delay;
        Button log_button;

        Intent? service_logging;
        LocalBroadcastManager localBroadcastManager;
        int sleep_delay = 1000;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);

            localBroadcastManager = LocalBroadcastManager.GetInstance(this);

            log_button = (Button)FindViewById(Resource.Id.button1);
            log_button.Click += Log_button_Click;

            textbox_delay = (EditText)FindViewById(Resource.Id.editText1);
            textbox_delay.TextChanged += Textbox_delay_TextChanged;
        }

        bool service_enabled = false;
        private void Log_button_Click(object sender, EventArgs e) {
            if (service_enabled) {
                service_enabled = false;
                log_button.Text = GetText(Resource.String.main_button_start);

                StopService(service_logging);
                service_logging = null;
            } else {
                service_enabled = true;
                log_button.Text = GetText(Resource.String.main_button_stop);

                service_logging = new Intent(this, typeof(LoggingService));
                service_logging.SetAction("SET_SLEEP_DELAY");
                service_logging.PutExtra("NEW_SLEEP_DELAY", sleep_delay);
                StartService(service_logging);
            }
        }

        private void Textbox_delay_TextChanged(object sender, Android.Text.TextChangedEventArgs e) {
            sleep_delay = int.Parse(textbox_delay.Text);

            Intent intent = new Intent("SET_SLEEP_DELAY");
            intent.PutExtra("NEW_SLEEP_DELAY", sleep_delay);
            localBroadcastManager.SendBroadcast(intent);
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

Is the implementation with OnBind more correct in this case? However, there is also a lot of code there. For example, additional flags are needed, implementation of OnUnbind, OnRebind... Would a binding implementation be best solution under my conditions (when the service itself is on a different thread)? Is the cost of binding and unbinding less expensive than sending Intent?

In addition, I do not fully understand how the service will behave during long-term work. The fact is that the OnStartCommand method uses the creation of a new thread, since otherwise the UI hangs. It seems like this thread can be interrupted by the system. Here I do not fully understand the correctness of the flow created in the OnStartCommand method. What's the right thing to do for a long running background service?

A local service (not exported) is assumed that can work even when the application is closed.

Alex A.
  • 422
  • 3
  • 12
  • You can check foreground services https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/services/foreground-services – Adrain Aug 09 '21 at 09:39
  • @Adrain Zhu -MSFT yes, I thought about it. In theory, such a service will have fewer restrictions than just a background service. – Alex A. Aug 10 '21 at 21:02

0 Answers0