App sometimes crashes with "Background start not allowed: service Intent"
Asked Answered
C

1

6

I only do Android development part time (which seems challenging considering the many subtleties and low-levelness of the APIs) and have a problem with receiving notifications while my app is in the background. I am targeting Android 8.1. My app happens to use Azure and Xamarin, but I do not know if those details are inherent to the problem.

If my app is in the background sometimes notifications can be displayed, other times my app crashes with this error:

2019-03-06 09:12:24.486 9495-9702/? D/tyresense.azurelistenerservice: OnMessage() done
2019-03-06 09:12:28.278 1548-3232/? W/ActivityManager: Background start not allowed: service Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x1000010 pkg=com.rimex.tyresense cmp=com.rimex.tyresense/md5bf34da605c12d097dc5a495af95e9376.AzureListenerService (has extras) } to com.rimex.tyresense/md5bf34da605c12d097dc5a495af95e9376.AzureListenerService from pid=9495 uid=10173 pkg=com.rimex.tyresense
2019-03-06 09:12:28.341 9495-9495/? I/MonoDroid: Java.Lang.IllegalStateException: Not allowed to start service Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x1000010 pkg=com.rimex.tyresense cmp=com.rimex.tyresense/md5bf34da605c12d097dc5a495af95e9376.AzureListenerService (has extras) }: app is in background uid UidRecord{d582ba5 u0a173 RCVR bg:+1m0s848ms idle procs:1 seq(0,0,0)}
2019-03-06 09:12:28.342 9495-9495/? I/MonoDroid: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x1000010 pkg=com.rimex.tyresense cmp=com.rimex.tyresense/md5bf34da605c12d097dc5a495af95e9376.AzureListenerService (has extras) }: app is in background uid UidRecord{d582ba5 u0a173 RCVR bg:+1m0s848ms idle procs:1 seq(0,0,0)}
2019-03-06 09:12:28.347 9495-9495/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.rimex.tyresense, PID: 9495
    java.lang.RuntimeException: Unable to start receiver md5bf34da605c12d097dc5a495af95e9376.TyreSenseBroadcastReceiver: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x1000010 pkg=com.rimex.tyresense cmp=com.rimex.tyresense/md5bf34da605c12d097dc5a495af95e9376.AzureListenerService (has extras) }: app is in background uid UidRecord{d582ba5 u0a173 RCVR bg:+1m0s848ms idle procs:1 seq(0,0,0)}
        at android.app.ActivityThread.handleReceiver(ActivityThread.java:3285)
        at android.app.ActivityThread.-wrap17(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1713)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6626)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)
     Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x1000010 pkg=com.rimex.tyresense cmp=com.rimex.tyresense/md5bf34da605c12d097dc5a495af95e9376.AzureListenerService (has extras) }: app is in background uid UidRecord{d582ba5 u0a173 RCVR bg:+1m0s848ms idle procs:1 seq(0,0,0)}
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1536)
        at android.app.ContextImpl.startService(ContextImpl.java:1492)
        at android.content.ContextWrapper.startService(ContextWrapper.java:650)
        at android.content.ContextWrapper.startService(ContextWrapper.java:650)
        at md513d040a829b3f298fbeeee5a6e2c042a.GcmBroadcastReceiverBase_1.n_onReceive(Native Method)
        at md513d040a829b3f298fbeeee5a6e2c042a.GcmBroadcastReceiverBase_1.onReceive(GcmBroadcastReceiverBase_1.java:29)
        at android.app.ActivityThread.handleReceiver(ActivityThread.java:3278)
        at android.app.ActivityThread.-wrap17(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1713) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6626) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811) 
2019-03-06 09:12:28.348 2761-2761/? W/GCM: broadcast intent callback: result=CANCELLED forIntent { act=com.google.android.c2dm.intent.RECEIVE pkg=com.rimex.tyresense (has extras) }
2019-03-06 09:12:28.353 9495-9495/? E/tyresense.splashscreen: System.UnhandledExceptionEventArgs
2019-03-06 09:12:28.357 1548-7265/? W/ActivityManager:   Force finishing activity com.rimex.tyresense/md5bf34da605c12d097dc5a495af95e9376.MainActivity
2019-03-06 09:12:28.363 1548-1632/? I/ActivityManager: Showing crash dialog for package com.rimex.tyresense u0

I see articles like this one: https://blog.xamarin.com/replacing-services-jobs-android-oreo-8-0/

But it is not clear to me what exactly is the problem, so I do not know if that will solve it.

Maybe here?

using System;
using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Util;
using Gcm.Client;
using WindowsAzure.Messaging;
using SharedMobile; // keep it simple, the app might not be running
using MyApp.Android; // we need to reference ourselves...
using Android.Support.V4.App;
using Android.Content.PM;
using System.Threading.Tasks;

[assembly: Permission(Name = "@[email protected]_MESSAGE")]
[assembly: UsesPermission(Name = "@[email protected]_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]
[assembly: Xamarin.Forms.Dependency(typeof(DependencyListener))]
namespace MyApp.Android
{
    [BroadcastReceiver(Permission = Constants.PERMISSION_GCM_INTENTS)]
    [IntentFilter(new string[] { Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "@PACKAGE_NAME@" })]
    public class MyAppBroadcastReceiver : GcmBroadcastReceiverBase<AzureListenerService>
    {
        // this declaration somehow enables messages
    }

    [Service]
    public class AzureListenerService : GcmServiceBase // this service might be started at system boot, when the app is otherwise not active
    {
        const string TAG = "MyApp.azurelistenerservice";
        const string HUBNAME = "myhubname";
        const string CONNECTION =
               "Endpoint=sb://MyApp.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=secretstuff...";

        public const string GcmProjectNumber = "1234567890";
        private static NotificationHub noteHub = null;

        public AzureListenerService() : base(GcmProjectNumber)
        {
        }

        // need client and area ids before this is called. Application.Context is not available if called from system boot
        public static void InitializeAzure(Context context = null)
        {
            try
            {
                if (context == null)
                    context = Application.Context;
                var enabled = NotificationManagerCompat.From(context).AreNotificationsEnabled();
                SharedMobile.API.ApiService.Instance.NotificationsEnabled(enabled);
                if (enabled)
                {
                    GcmClient.CheckDevice(context);
                    GcmClient.CheckManifest(context);
                    GcmClient.Register(context, GcmProjectNumber);
                }
            }
            catch (Exception ex)
            {
                Log.Error(TAG, ex.Message);
                Messenger.SendException(ex.Message);
            }
        }

        // incoming GCM registration
        protected override void OnRegistered(Context context, string token)
        {
            try
            {
                noteHub = new NotificationHub(HUBNAME, CONNECTION, context);
                var customerId = Int32.Parse(PropertyHelper.ClientId);
                var tags = BuildTagList(customerId, PropertyHelper.AreaIds);
                noteHub.Register(token, tags.ToArray());
                Messenger.RegisterDevice(token);
            }
            catch (Exception ex)
            {
                Log.Error(TAG, "OnRegistered() " + ex.Message); // ignore register failure when using the Android emulator
            }
        }

        protected override void OnMessage(Context context, Intent intent)
        {
            try
            {
                var areaIds = PropertyHelper.AreaIds;
                if (string.IsNullOrWhiteSpace(areaIds)) // in case the user logged out
                    return;
                new Notifications().CreateEventNotification(intent);
            }
            catch (Exception ex)
            {
                Log.Error(TAG, "OnMessage() " + ex.Message);
            }
        }

        protected List<string> BuildTagList(int clientId, string areaIds)
        {
            var list = new List<string>();
            list.Add($"client_{clientId}");
            foreach (var id in areaIds.Split(','))
                list.Add($"area_{id}");
            return list;
        }

        protected override void OnUnRegistered(Context context, string registrationId)
        {
            Log.Error(TAG, "OnUnRegistered(): " + registrationId);
        }

        protected override bool OnRecoverableError(Context context, string errorId)
        {
            Log.Error(TAG, "OnRecoverableError(): " + errorId);
            return base.OnRecoverableError(context, errorId);
        }

        protected override void OnError(Context context, string errorId)
        {
            Log.Error(TAG, "OnError(): " + errorId);
        }
    }
}


using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Util;
using Exception = System.Exception;

namespace My.Android
{
    internal class Notifications
    {
        const string TAG = "My.notifications";
        const string TITLE = "Application is running";
        const string DESC = "Tap to view";
        const int MainProgramNotificationId = 775566;

        // unique ID for our notification: 
        static readonly string CHANNEL_ID = "My_channel_23";
        static readonly string CHANNEL_NAME = "[My_channel]";

        internal void CreateProgramIcon_on_hold(Intent intent)
        {
            var extras = new Bundle();
            extras.PutString("scopeDescription", TITLE);
            extras.PutString("stateDescription", DESC);
            intent.PutExtras(extras);

            ShowNotification(intent.Extras, false);
        }

        internal void CreateEventNotification(Intent extra)
        {
            if (extra.Extras != null && !extra.Extras.IsEmpty)
                ShowNotification(extra.Extras, true);

            var notificationId = extra?.Extras?.GetString("notification-id", null);
            if (notificationId != null && notificationId.Length > 0)
                SharedMobile.API.ApiService.NotificationConfirmation(notificationId);
        }

        private void ShowNotification(Bundle extras, bool autoCancel)
        {
            try
            {
                string title = TITLE; 
                string description = DESC;
                if (extras != null && !extras.IsEmpty)
                {
                    title = extras.GetString("scopeDescription", null);
                    description = extras.GetString("stateDescription", "");
                }

                Intent intent = new Intent(Application.Context, typeof(SplashScreen));
                BuildNotification(intent, extras, title, description, autoCancel);

            }
            catch (Exception ex)
            {
                Log.Debug(TAG, ex.Message);
            }
        }

        private void BuildNotification(Intent intent, Bundle extras, string title, string description, bool autoCancel)
        {
            bool onGoing = !autoCancel;
            int icon = IconId(null, null); // code not shown
            intent.AddFlags(ActivityFlags.ReorderToFront);
            var pendingIntent = PendingIntent.GetActivity(Application.Context, 0, intent, PendingIntentFlags.UpdateCurrent);

            CreateNotificationChannel(autoCancel);

            var notificationManager = Application.Context.GetSystemService(Context.NotificationService) as NotificationManager;
            NotificationCompat.Builder builder = new NotificationCompat.Builder(Application.Context, CHANNEL_ID);

            builder.SetSmallIcon(icon)
                .SetContentTitle(title)
                .SetContentText(description)
                .SetSmallIcon(Resource.Drawable.notification_icon)
                .SetAutoCancel(autoCancel)
                .SetOngoing(onGoing)
                .SetContentIntent(pendingIntent);

            if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
                builder.SetColor(ContextCompat.GetColor(Application.Context, Resource.Color.exBackgroundColor));

            string utcTicks = null;
            if (extras != null)
                utcTicks = extras.GetString("utcTicks", null);
            if (utcTicks != null)
            {
                var ticks = Int64.Parse(utcTicks);
                DateTimeOffset dto = new DateTimeOffset(ticks, TimeSpan.Zero);
                builder.SetWhen(dto.ToUnixTimeMilliseconds());
            }
            else
                builder.SetShowWhen(true);

            notificationManager?.Notify(MainProgramNotificationId, builder.Build());
        }

        private void CreateNotificationChannel(bool evnt)
        {
            if (Build.VERSION.SdkInt < BuildVersionCodes.O)
            {
                // There is no need to create a notification channel on older versions of Android.
                return;
            }
            NotificationImportance ni;
            if (evnt)
                ni = NotificationImportance.Default;
            else
                ni = NotificationImportance.Low;
            var channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, ni)
            {
                Description = "My App",
                LockscreenVisibility = NotificationVisibility.Private,
            };
            var notificationManager = Application.Context.GetSystemService(Context.NotificationService) as NotificationManager;
            notificationManager.CreateNotificationChannel(channel);
        }

        string FormatTimeStamp(string utcTicks)
        {
            try
            {
                var ticks = Int64.Parse(utcTicks);
                DateTimeOffset dto = new DateTimeOffset(ticks, TimeSpan.Zero);
                return dto.ToLocalTime().ToString("MM/dd HH:mm:ss");
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }

        bool IgnoreActionProcess(string actionProcessId, int ackedProcessId)
        {
            try
            {
                if (int.Parse(actionProcessId) == ackedProcessId)
                    return true;
            }
            catch (Exception ex)
            {
                Log.Debug(TAG, $"int parse: {actionProcessId} {ex.Message}");
            }
            return false;
        }

    }
}

Update: I change the OnMessage in public class AzureListenerService : GcmServiceBase to do nothing and it still crashes:

Unhandled Exception: Java.Lang.IllegalStateException: Not allowed to start service Intent { act=com.google.android.c2dm.intent.RECEIVE flg=0x1000010 pkg=com.mycompany.myapp cmp=com.mycompany.myapp/md5bf34da605c12d097dc5a495af95e9376.AzureListenerService (has extras) }: app is in background uid UidRecord{d45626f u0a173 RCVR bg:+1m29s589ms idle change:uncached procs:1 seq(0,0,0)} occurred

Update I implemented the JobScheduler, that made no difference. Also, the app has to be in the background for "some time" and the problem will then always occur. I also changed my app to do nothing at all (but log) when a notification arrives, and it still crashes.

Update I got this working: https://developer.xamarin.com/samples/monodroid/Firebase/FCMNotifications/ By adding it "as is" to my firebase account, downloading the google-services.json and testing with http://pushtry.com/. No luck with Azure portal test send yet. Hope to merge code from FCMNotifications into my app, if I can get Azure working...

Crusted answered 6/3, 2019 at 17:47 Comment(6)
Use FCM instead and its FirebaseMessagingService you can read more about it here: learn.microsoft.com/en-us/xamarin/android/data-cloud/…Submersible
My app can receive messages just fine, when running in the foreground. The problem only happens when the user hits the home button, putting app into the background, and has been in the background for "some time".Crusted
Somebody wrote somewhere to use JobIntentService, but I don't have any control about what GcmServiceBase inherits from (IntentService). Maybe some other class.Crusted
That is doze kicking in on the device. The user needs to whitelist the app to not be battery optimized.Submersible
Maybe FCM inherits from JobIntentService and therefore does not crash?Crusted
You should switch to FCM either way, GCM is deprecated and will be turned off in April.Submersible
G
6

Android 8.0 (API level 26) and above have limitation with respect to background service. It includes the following changes to specific methods:

The startService() method now throws an IllegalStateException if an app targeting Android 8.0 tries to use that method in a situation when it isn't permitted to create background services. The new Context.startForegroundService() method starts a foreground service. The system allows apps to call Context.startForegroundService() even while the app is in the background. However, the app must call that service's startForeground() method within five seconds after the service is created.

Granicus answered 6/3, 2019 at 17:58 Comment(4)
Thanks, I will research those topicsCrusted
Working in a Xamarin/Azure environment. No luck so far with foreground service calls, but I'm not really sure where to put them anywayCrusted
As far as I know Context.startForegroundService() must be called from the MainActivity's OnCreate. But then my app must do a lot of work (making API calls) before the service can be initialized, no way I can do that in less than 5 seconds.Crusted
Your are correct in saying that 'Context.startForegroundService() must be called from the MainActivity's OnCreate'; lot of work you can move to your service.Granicus

© 2022 - 2024 — McMap. All rights reserved.