Приложение Xamarin/Maui по-прежнему переходит в спящий режим, хотя ему было сказано не делать этого

Мое приложение .Net Maui, работающее на Android (API 33), должно продолжать работать. Я думал, что об этом позаботится использование службы переднего плана Android, но теперь обнаружил, что Android отправляет приложение и его службу переднего плана в спящий режим через несколько минут. Ну, я полагаю, что он спит, так как файл журнала, который приложение должно регулярно обновлять, перестает обновляться, а приложение «просыпается» и снова начинает ведение журнала, если я разблокирую телефон. Если я запускаю приложение на реальном устройстве с запущенной беспроводной отладкой, подключенной к моему ноутбуку разработчика, приложение остается бодрствующим, но переходит в спящий режим, если не выполняет отладку. Я пробовал 2 подхода, и ни один из них не поддерживает работу моего приложения:

  1. Я сказал Android не применять оптимизацию батареи к приложению, перейдя в «Настройки» -> «Приложения» -> «Имя моего приложения» -> «Аккумулятор» -> выберите «Неограниченно».
  2. Я сказал Android установить мое приложение как никогда не спящее приложение, зайдя в «Настройки» -> «Аккумулятор и уход за устройством» -> «Аккумулятор» -> «Ограничения фонового использования» -> «Никогда не спящие приложения» -> добавить имя моего приложения (ПРИМЕЧАНИЕ, это отменяет то, что было сделано на шаге 1)

Есть ли другой способ поддерживать работу службы переднего плана приложения?

Вы пытались сохранить разблокировку экрана? Насколько я знаю, когда экран заблокирован, приложение будет в состоянии паузы.

Guangyu Bai - MSFT 31.03.2023 10:01

@GuangyuBai-MSFT, приложение должно работать всегда, даже когда экран заблокирован. По этой причине я решил внедрить в приложение службу переднего плана, но она все равно засыпает.

Netricity 31.03.2023 10:07

Разве такая функциональность не должна быть в фоновой службе? Из документа Android Only use a foreground service when your app needs to perform a task that is noticeable by the user, even when they're not directly interacting with the app запись журналов не похожа на то, о чем пользователь должен знать

Poulpynator 31.03.2023 14:58

@Poulpynator, мое приложение просто записывает в файл журнала, но в конечном итоге оно будет отслеживать акселерометр и должно будет отображать уведомление, если оно обнаружит воздействие, превышающее определенный порог.

Netricity 31.03.2023 15:30

Вот несколько ссылок, которые могут вам помочь: developer.android.com/training/scheduling/wakelock и Learn.microsoft.com/en-us/dotnet/api/… все же я бы переосмыслил это, я бы Лично мне не нравится приложение, которое постоянно держит мой телефон в активном состоянии, чтобы в какой-то момент отправить уведомление. Я думаю, что у вас может быть фоновая служба, которая будит ваш телефон только тогда, когда это необходимо (через wakelock).

Poulpynator 31.03.2023 15:40

@Poulpynator, спасибо. После публикации этого вопроса я провел дополнительные исследования и на самом деле просто попытался использовать блокировку пробуждения через службу переднего плана; Я создал еще одну службу переднего плана (ScreenOffService) и BroadcastReceiver (ScreenOffBroadcastReceiver), который получает события SCREEN_OFF и, когда это происходит, получает WakeLock с флагом Partial; это позволяет экрану погаснуть и заблокироваться. ScreenOffService вызывает RegisterReceiver (новый ScreenOffBroadcastReceiver()). Мое приложение, похоже, не спит.

Netricity 31.03.2023 15:55
0
6
202
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

После публикации этого вопроса я провел дополнительные исследования и обнаружил, что создание wake lock через службу переднего плана поддерживает работу приложения, даже когда экран выключен.

Для этого я создал службу переднего плана (ScreenOffService, унаследованную от Android.App.Service).

Я также создал BroadcastReceiver (ScreenOffBroadcastReceiver, унаследованный от Android.Content.BroadcastReceiver), который получает события SCREEN_OFF и, когда это происходит, получает Android.OS.PowerManager.WakeLock с помощью WakeLockFlags.Partial.

ScreenOffService вызывает RegisterReceiver (новый ScreenOffBroadcastReceiver()) своего базового класса. Мое приложение теперь бодрствует.

AndroidManifest.xml
    <uses-permission android:name = "android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name = "android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name = "android.permission.WAKE_LOCK" />

    var intent = new Intent(Android.App.Application.Context, typeof(ScreenOffService));
    intent.SetAction(ScreenOffService.ActionStartScreenOffService);
    Android.App.Application.Context.StartForegroundService(intent);


    [Service(Label = nameof(ScreenOffService))]
    [RequiresApi(Api = (int)BuildVersionCodes.R)]
    public class ScreenOffService : Service
    {
        private static readonly string TypeName = typeof(ScreenOffService).FullName;
        public static readonly string ActionStartScreenOffService = TypeName + ".action.START";

        internal const int NOTIFICATION_ID = 12345678;
        private const string NOTIFICATION_CHANNEL_ID = "screen_off_service_channel_01";
        private const string NOTIFICATION_CHANNEL_NAME = "screen_off_service_channel_name";
        private NotificationManager _notificationManager;

        private bool _isStarted;

        private readonly ScreenOffBroadcastReceiver _screenOffBroadcastReceiver;

        public ScreenOffService()
        {
            _screenOffBroadcastReceiver = Microsoft.Maui.Controls.Application.Current.Handler.MauiContext.Services.GetService<ScreenOffBroadcastReceiver>();
        }

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

            _notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;

            RegisterScreenOffBroadcastReceiver();
        }

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

            UnregisterScreenOffBroadcastReceiver();
        }

        [return: GeneratedEnum]
        public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
        {
            CreateNotificationChannel(); // Elsewhere we must've prompted user to allow Notifications

            if (intent.Action == ActionStartScreenOffService)
            {
                try
                {
                    StartForeground();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Unable to start Screen On/Off foreground svc: " + ex);
                }
            }

            return StartCommandResult.Sticky;
        }

        private void RegisterScreenOffBroadcastReceiver()
        {
            var filter = new IntentFilter();
            filter.AddAction(Intent.ActionScreenOff);
            RegisterReceiver(_screenOffBroadcastReceiver, filter);
        }

        private void UnregisterScreenOffBroadcastReceiver()
        {
            try
            {
                if (_screenOffBroadcastReceiver != null)
                {
                    UnregisterReceiver(_screenOffBroadcastReceiver);
                }
            }
            catch (Java.Lang.IllegalArgumentException ex)
            {
                Console.WriteLine($"Error while unregistering {nameof(ScreenOffBroadcastReceiver)}. {ex}");
            }
        }

        private void StartForeground()
        {
            if (!_isStarted)
            {
                Notification notification = BuildInitialNotification();
                StartForeground(NOTIFICATION_ID, notification);

                _isStarted = true;
            }
        }

        private Notification BuildInitialNotification()
        {
            var intentToShowMainActivity = BuildIntentToShowMainActivity();

            var notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .SetContentTitle(Resources.GetString(Resource.String.app_name))
                .SetContentText(Resources.GetString(Resource.String.screen_off_service_started_notification_text))
                .SetSmallIcon(Resource.Drawable.eip_logo_symbol_yellow) // Android top bar icon and Notification drawer item LHS icon
                .SetLargeIcon(global::Android.Graphics.BitmapFactory.DecodeResource(Resources, Resource.Drawable.eip_logo_yellow)) // Notification drawer item RHS icon
                .SetContentIntent(intentToShowMainActivity)
                .SetOngoing(true)
                .Build();

            return notification;
        }

        private PendingIntent BuildIntentToShowMainActivity()
        {
            var mainActivityIntent = new Intent(this, typeof(MainActivity));
            mainActivityIntent.SetAction(Constants.ACTION_MAIN_ACTIVITY);
            mainActivityIntent.SetFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTask);
            mainActivityIntent.PutExtra(Constants.SERVICE_STARTED_KEY, true);

            PendingIntent pendingIntent = PendingIntent.GetActivity(this, 0, mainActivityIntent, PendingIntentFlags.UpdateCurrent | PendingIntentFlags.Immutable);

            return pendingIntent;
        }

        private void CreateNotificationChannel()
        {
            NotificationChannel chan = new(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationImportance.Default)
            {
                LightColor = Microsoft.Maui.Graphics.Color.FromRgba(0, 0, 255, 0).ToInt(),
                LockscreenVisibility = NotificationVisibility.Public
            };

            _notificationManager.CreateNotificationChannel(chan);
        }

        public override IBinder OnBind(Intent intent)
        {
            return null;
        }
    }

    [BroadcastReceiver(Name = "com.eip.MobileApp.ScreenOffBroadcastReceiver", Label = "ScreenOffBroadcastReceiver", Exported = true)]
    [IntentFilter(new[] { Intent.ActionScreenOff }, Priority = (int)IntentFilterPriority.HighPriority)]
    public class ScreenOffBroadcastReceiver : BroadcastReceiver
    {
        private readonly ILogger<ScreenOffBroadcastReceiver> _logger;

        private PowerManager.WakeLock _wakeLock;

        public ScreenOffBroadcastReceiver()
        {
            _logger = Microsoft.Maui.Controls.Application.Current.Handler.MauiContext.Services.GetService<ILogger<ScreenOffBroadcastReceiver>>();
        }

        public override void OnReceive(Context context, Intent intent)
        {
            if (intent.Action == Intent.ActionScreenOff)
            {
                AcquireWakeLock();
            }
        }

        private void AcquireWakeLock()
        {
            _wakeLock?.Release();

            WakeLockFlags wakeFlags = WakeLockFlags.Partial;

            PowerManager pm = (PowerManager)global::Android.App.Application.Context.GetSystemService(global::Android.Content.Context.PowerService);
            _wakeLock = pm.NewWakeLock(wakeFlags, typeof(ScreenOffBroadcastReceiver).FullName);
            _wakeLock.Acquire();
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            _wakeLock?.Release();
        }
    }

Другие вопросы по теме