英文:
Xamarin/Maui app still goes to sleep even though told not to
问题
My .Net Maui app,在 Android (API 33) 上运行,需要保持运行。我曾认为使用 Android 前台服务可以解决这个问题,但现在发现 Android 在几分钟后会将应用程序及其前台服务休眠。我相信它正在休眠,因为应用程序应该定期更新的日志文件停止更新,只有当我解锁手机时,应用程序才会“唤醒”并开始记录。如果我在真实设备上运行应用程序,并启用无线调试,连接到我的开发笔记本电脑,应用程序会保持唤醒状态,但如果不进行调试,它会休眠。
我尝试了两种方法,但都无法保持应用程序的运行:
- 我告诉 Android 不要对应用程序应用电池优化,方法是进入设置 - > 应用程序 - > 我的应用程序名称 - > 电池 - > 选择不受限制
- 我告诉 Android 将我的应用程序设置为永不休眠的应用程序,方法是进入设置 - > 电池与设备护理 - > 电池 - > 背景使用限制 - > 从不休眠的应用程序 - > 添加我的应用程序名称(请注意,这会撤消第1步的操作)
是否有其他方法可以保持应用程序的前台服务运行?
英文:
My .Net Maui app, running on Android (API 33), needs to stay running. I had thought that using an Android Foreground Service took care of this, but have now discovered that Android sends the app and its foreground service to sleep after a few minutes. Well, I believe it is sleeping, as the log file that the app should update regularly stops getting updated, and the app "wakes up" and starts logging again if I unlock the phone. If I run the app on a real device and with wireless debugging running, connected to my dev laptop, the app stays awake, but goes to sleep if not debugging.
I have tried 2 approaches and neither is keeping my app running:
- I told Android to not apply battery optimisations to the app, by going into Settings -> Apps -> My App Name -> Battery -> select Unrestricted
- I told Android to set my app as a never-sleeping app, by going into Settings -> Battery and device care -> Battery -> Background usage limits -> Never sleeping apps -> add My App Name (NOTE this undoes what was done in step 1)
Is there some other way to keep the app's foreground service running?
答案1
得分: 1
由于您的请求是代码,我将只翻译代码的部分。以下是您提供的代码的翻译:
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
{
// ... 翻译的部分省略 ...
}
[BroadcastReceiver(Name = "com.eip.MobileApp.ScreenOffBroadcastReceiver", Label = "ScreenOffBroadcastReceiver", Exported = true)]
[IntentFilter(new[] { Intent.ActionScreenOff }, Priority = (int)IntentFilterPriority.HighPriority)]
public class ScreenOffBroadcastReceiver : BroadcastReceiver
{
// ... 翻译的部分省略 ...
}
请注意,这只是代码的翻译,不包括解释或其他附加信息。
英文:
Since posting this question I've done more research found that creating a wake lock via a foreground service keeps the app running even when the screen is off.
To do this, I created a foreground service (ScreenOffService, inherited from Android.App.Service).
I also created a BroadcastReceiver (ScreenOffBroadcastReceiver, inherited from Android.Content.BroadcastReceiver) that recieves SCREEN_OFF events and, when it does, acquires an Android.OS.PowerManager.WakeLock with WakeLockFlags.Partial.
ScreenOffService calls its base class's RegisterReceiver(new ScreenOffBroadcastReceiver()).
My app stays awake now.
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();
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论