Monday 28 September 2020

Keep running background service (Android)

I create background service with is started after user signed in and should work even after activity was 'throw to trash'. Service show notifications that it's running ,register two broadcast receivers for monitoring wifi and phone state and should keep running until token isn't expired or user logout.

Everything works but service is killed by android. In huawei P9 it's take 30 min , in huawei P30 it takes about 3hours and the only think that prevent this is turn of automatic battery management for this particular application in battery manager settings. Unfortunate this solution isn't acceptable because it's dependence on user.

Code created by C# using Xamarin , but I'll be glad for useful advice even in other languages (java ,kotlin)

Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
          android:versionCode="1" 
          android:versionName="1.0" 
          package="com.companyname.com.bgapptest">
  <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="28" />
  <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
    <service
       android:name="com.BGAppTest.BackgroundService"
       android:enabled="true"
       android:exported="false"/>
  </application>
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.CALL_PHONE" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  <uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>

Start service from activity

Activity

using System;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Support.V7.App;
using Android.Views;

namespace com.BGAppTest
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        string[] perms = new string[] { "android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_NETWORK_STATE", "android.permission.READ_PHONE_STATE" };

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.activity_main);

            Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            SetSupportActionBar(toolbar);

            FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);
            fab.Click += FabOnClick;
            StartService();
        }

        public override bool OnCreateOptionsMenu(IMenu menu)
        {
            MenuInflater.Inflate(Resource.Menu.menu_main, menu);
            return true;
        }

        public override bool OnOptionsItemSelected(IMenuItem item)
        {
            int id = item.ItemId;
            if (id == Resource.Id.action_settings)
            {
                return true;
            }

            return base.OnOptionsItemSelected(item);
        }

        private void FabOnClick(object sender, EventArgs eventArgs)
        {
            View view = (View) sender;
            Intent service = new Intent(this, typeof(BackgroundService));
            StopService(service);
        }


        void StartService()
        {
            foreach (var p in perms)
            {
                if (CheckSelfPermission(p) == Permission.Denied)
                {
                    RequestPermissions(perms, 2);
                    return;
                }
            }
            Intent service = new Intent(this, typeof(BackgroundService));
            if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
                StartForegroundService(service);
            else
                StartService(service);

        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            if (grantResults.Any(x => x == Permission.Denied))
            {
                ///some code
            }
            else
                StartService();
        }

    }
}

Service

    [Service(Name = "com.BGAppTest.BackgroundService")]
public class BackgroundService : Service
{
    NetworkChangeReceiver networkReceiver;
    PhoneCallsReceiver receiver;
    const int Service_Running_Notification_ID = 937;
    public bool isStarted = false;
    PowerManager.WakeLock wakeLock;
    public BackgroundService()
    {

    }
    [return: GeneratedEnum]
    public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
    {
        if (isStarted)
            return StartCommandResult.Sticky;
        isStarted = true;
        PowerManager m = GetSystemService(Context.PowerService) as PowerManager;
        wakeLock = m.NewWakeLock(WakeLockFlags.Partial, "MYWeakLock");
        wakeLock.Acquire();
        return StartCommandResult.Sticky;
    }
    public override void OnCreate()
    {
        base.OnCreate();
        RegisterForegroundService();
        RegisterWifiReceiver();
        RegisterPhoneReceiver();

    }



    public void RegisterForegroundService()
    {
        Notification notification = BuildNotification("Title","Text");
        StartForeground(Service_Running_Notification_ID, notification);
    }


    Notification BuildNotification(string title, string message)
    {
        NotificationCompat.Builder notificationBuilder = null;
        if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
        {
            String NOTIFICATION_CHANNEL_ID = "com.BGAppTest";
            NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "MY_Foreground", NotificationImportance.High);
            NotificationManager manager = (NotificationManager)GetSystemService(Context.NotificationService);
            manager.CreateNotificationChannel(chan);
            notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
        }
        else
            notificationBuilder = new NotificationCompat.Builder(this);

        return notificationBuilder
          .SetSmallIcon(Resource.Drawable.ic_mtrl_chip_checked_black)
           .SetContentTitle(Resources.GetString(Resource.String.app_name))
           .SetContentIntent(BuildIntentToShowMainActivity())
           .SetContentTitle(title)
           .SetStyle(new NotificationCompat.BigTextStyle().BigText(message))
           .SetOngoing(true)
           .Build();

    }

    private PendingIntent BuildIntentToShowMainActivity()
    {
        var intent = this.PackageManager.GetLaunchIntentForPackage(this.PackageName);
        intent.AddFlags(ActivityFlags.ClearTop);
        var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent);
        return pendingIntent;

    }


    public void RegisterWifiReceiver()
    {
        var networkReceiver = new NetworkChangeReceiver();
        IntentFilter intentFilters = new IntentFilter();
        intentFilters.AddAction("android.net.conn.CONNECTIVITY_CHANGE");
        RegisterReceiver(networkReceiver, intentFilters);
    }
    public void RegisterPhoneReceiver()
    {
        var receiver = new PhoneCallsReceiver();
        IntentFilter intentFilters = new IntentFilter();
        intentFilters.AddAction("android.intent.action.PHONE_STATE");
        RegisterReceiver(receiver, intentFilters);

    }

    public override IBinder OnBind(Intent intent)
    {
        return null;
    }
    #region Session checker
    //TODO:przenieść tą fukcionalność do odzielnego serwisu
    System.Timers.Timer timers = new System.Timers.Timer();
    private void StartTokenExpiredTimer()
    {
    }

    #endregion
    public override void OnDestroy()
    {
        base.OnDestroy();
        try
        {
            UnregisterReceiver(receiver);
            UnregisterReceiver(networkReceiver);
        }
        catch { }

    }
}

Receivers

using Android.Content;
using Android.Widget;


namespace com.BGAppTestReceivers
{
    public class NetworkChangeReceiver: BroadcastReceiver
    {

        static NetworkChangeReceiver()
        {

        }
        public NetworkChangeReceiver()
        {
        }
        public override async void OnReceive(Context context, Intent intent)
        {
            Toast.MakeText(context, "BGNetworkChange", ToastLength.Short).Show();

        }


    }

    public class PhoneCallsReceiver : BroadcastReceiver
    {

        static PhoneCallsReceiver()
        {

        }
        public PhoneCallsReceiver()
        {
        }
        public override async void OnReceive(Context context, Intent intent)
        {
            Toast.MakeText(context, "BGPhoneChange", ToastLength.Short).Show();
        }

    }


}

UPDATE

I add full code of my example application, made it as simple as I could



from Keep running background service (Android)

No comments:

Post a Comment