Forum Xamarin.Android
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Background task disposed after application closed

Hey!

Task: Create a background task to run when application is stopped/paused that periodically (3-7 seconds) performs HTTP requests and stores response information in mysqlite and displays local notifications when required.

I have create a background service like the following,

[Service(Enabled = true)]
public class MyRequestService : Service

That is started like an intent from MainActivity,

public void StartMyRequestService()
{
    var serviceToStart = new Intent(this, typeof(MyRequestService));
    StartService(serviceToStart);
}

public void StopMyRequestService()
{
    var serviceToStart = new Intent(this, typeof(MyRequestService));
    StopService(serviceToStart);
}

protected override void OnPause()
{
    base.OnPause();
    StartMyRequestService();
}

protected override void OnDestroy()
{
    base.OnDestroy();
    StartMyRequestService();
}

protected override void OnResume()
{
    base.OnResume();
    StopMyRequestService();
}

In my service I have the following features in use,

  1. return STICKY in OnStartCommand
  2. create a 'permanent' native notification with channel
  3. power manager lock

And the code looks like the following,

private Handler handler;
private Action runnable;
private bool isStarted

private WakeLock wakeLock;

public override void OnCreate()
{
    base.OnCreate();

    handler = new Handler();

    runnable = new Action(() =>
    {
        DispatchNotificationThatAlarmIsGenerated("I'm running");
        handler.PostDelayed(runnable, DELAY_BETWEEN_LOG_MESSAGES);
    });
}

public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
    if (isStarted)
    {
        // service is already started
    }
    else
    {
        CreateNotificationChannel();
        DispatchNotificationThatServiceIsRunning();

        handler.PostDelayed(runnable, DELAY_BETWEEN_LOG_MESSAGES);
        isStarted = true;

        PowerManager powerManager = (PowerManager)this.GetSystemService(Context.PowerService);
        WakeLock wakeLock = powerManager.NewWakeLock(WakeLockFlags.Full, "Client Lock");
        wakeLock.Acquire();
    }
    return StartCommandResult.Sticky;
}

public override void OnTaskRemoved(Intent rootIntent)
{
    //base.OnTaskRemoved(rootIntent);
}

public override IBinder OnBind(Intent intent)
{
    // Return null because this is a pure started service. A hybrid service would return a binder that would
    // allow access to the GetFormattedStamp() method.
    return null;
}

public override void OnDestroy()
{
    // Stop the handler.
    handler.RemoveCallbacks(runnable);

    // Remove the notification from the status bar.
    var notificationManager = (NotificationManager)GetSystemService(NotificationService);
    notificationManager.Cancel(NOTIFICATION_SERVICE_ID);

    isStarted = false;
    wakeLock.Release();
    base.OnDestroy();
}

private void CreateNotificationChannel()
{
    //Notification Channel
    NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationImportance.Max);
    notificationChannel.EnableLights(true);
    notificationChannel.LightColor = Color.Red;
    notificationChannel.EnableVibration(true);
    notificationChannel.SetVibrationPattern(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 });


    NotificationManager notificationManager = (NotificationManager)this.GetSystemService(Context.NotificationService);
    notificationManager.CreateNotificationChannel(notificationChannel);
}

private void DispatchNotificationThatServiceIsRunning()
{
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
           .SetDefaults((int)NotificationDefaults.All)
           .SetSmallIcon(Resource.Drawable.icon)
           .SetVibrate(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 })
           .SetSound(null)
           .SetChannelId(NOTIFICATION_CHANNEL_ID)
           .SetPriority(NotificationCompat.PriorityDefault)
           .SetAutoCancel(false)
           .SetContentTitle("Mobile")
           .SetContentText("My service started")
           .SetOngoing(true);

    NotificationManagerCompat notificationManager = NotificationManagerCompat.From(this);

    notificationManager.Notify(NOTIFICATION_SERVICE_ID, builder.Build());
}

private void DispatchNotificationThatAlarmIsGenerated(string message)
{
    var intent = new Intent(this, typeof(MainActivity));
    intent.AddFlags(ActivityFlags.ClearTop);
    var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);

    Notification.Builder notificationBuilder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
        .SetSmallIcon(Resource.Drawable.icon_round)
        .SetContentTitle("Alarm")
        .SetContentText(message)
        .SetAutoCancel(true)
        .SetContentIntent(pendingIntent);

    var notificationManager = (NotificationManager)GetSystemService(NotificationService);
    notificationManager.Notify(App.NOTIFICATION_ALARM, notificationBuilder.Build());
}

This is just a sample, the code is not making any HTTP requests, nor works with entities, db connection etc, it just simply dispatches a new notification every X seconds. What I should see is, when application is closed, the service starts and native notification is created, which is what I see. Then for some time I see 'Alarm' notification is being generated and then my service notification is killed, service disposed and that is all. If I click the power button on my phone, to light up the screen, I see 'Alarm' notifications come to live again. I've checked on several mobile devices with different Android OS (6, 7 and 8) and with disabled power saving modes, no difference, the service notification is killed. What is the issue, what am I doing wrong?

Thanks in advance for any help or guidance!

Best Answer

Answers

  • yurylankovskiyyurylankovskiy Member ✭✭

    Fantastic, exactly what I needed. I must have missed it previously because of the naming 'foreground'.

    Since I have you 'on the line' here, let me ask you another question off this thread topic.

    If I need to start the service on boot, and perform http requests with work on mysqlite entities, can I call my Model methods in Xamarin.Forms project from Xamarin.Android service? Here I understand the Activity is not started and my App is not initiated with all it's services like mysqlite. I just don't want to duplicate all my entities inside Xamarin.Android project.

    Regarding on boot, I have the following code,

    [BroadcastReceiver(Enabled = true, DirectBootAware = true)]
    [IntentFilter(new string[] { Intent.ActionBootCompleted, Intent.ActionLockedBootCompleted, "android.intent.action.QUICKBOOT_POWERON", "com.htc.intent.action.QUICKBOOT_POWERON" })]
    public class BootComplete : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {
            try
            {
                var serviceToStart = new Intent(context, typeof(MyRequestService));
                if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
                {
                    context.StartForegroundService(serviceToStart);
                }
                else
                {
                    context.StartService(serviceToStart);
                }
            }
            catch(Exception ex)
            {
            }
        }
    }
    

    However, there's absolutely nothing happening, OnReceive is never entered. I have included RECEIVE_BOOT_COMPLETED permission.

    I seem to have decorated the BroadcastReceiver correctly, looking at the AndroidManifest compiled file, but what could be wrong, I'm not sure.

  • LandLuLandLu Member, Xamarin Team Xamurai

    I think MessagingCenter is what you need.
    It could help you communicate between Forms and Android directly.

  • yurylankovskiyyurylankovskiy Member ✭✭

    Let's break this up into separate questions.

    1. The broadcast receiver above that should be called on Boot Completed is not functioning. What could be wrong?
    2. When I have service started on boot completed signal in android, can I use models from the forms project or I must create the required entities in the native code to work with the database?
  • yurylankovskiyyurylankovskiy Member ✭✭

    Sorry for the late reply on this.

    Actually everything works well, as expected. The problem was with the service itself, as it was not started as foreground initially, in the code above I already made the fix, but didn't think it would fix anything. So disregard this question.

Sign In or Register to comment.