Мое приложение .Net Maui, работающее на Android (API 33), должно продолжать работать. Я думал, что об этом позаботится использование службы переднего плана Android, но теперь обнаружил, что Android отправляет приложение и его службу переднего плана в спящий режим через несколько минут. Ну, я полагаю, что он спит, так как файл журнала, который приложение должно регулярно обновлять, перестает обновляться, а приложение «просыпается» и снова начинает ведение журнала, если я разблокирую телефон. Если я запускаю приложение на реальном устройстве с запущенной беспроводной отладкой, подключенной к моему ноутбуку разработчика, приложение остается бодрствующим, но переходит в спящий режим, если не выполняет отладку. Я пробовал 2 подхода, и ни один из них не поддерживает работу моего приложения:
Есть ли другой способ поддерживать работу службы переднего плана приложения?
@GuangyuBai-MSFT, приложение должно работать всегда, даже когда экран заблокирован. По этой причине я решил внедрить в приложение службу переднего плана, но она все равно засыпает.
Разве такая функциональность не должна быть в фоновой службе? Из документа 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, мое приложение просто записывает в файл журнала, но в конечном итоге оно будет отслеживать акселерометр и должно будет отображать уведомление, если оно обнаружит воздействие, превышающее определенный порог.
Вот несколько ссылок, которые могут вам помочь: developer.android.com/training/scheduling/wakelock и Learn.microsoft.com/en-us/dotnet/api/… все же я бы переосмыслил это, я бы Лично мне не нравится приложение, которое постоянно держит мой телефон в активном состоянии, чтобы в какой-то момент отправить уведомление. Я думаю, что у вас может быть фоновая служба, которая будит ваш телефон только тогда, когда это необходимо (через wakelock).
@Poulpynator, спасибо. После публикации этого вопроса я провел дополнительные исследования и на самом деле просто попытался использовать блокировку пробуждения через службу переднего плана; Я создал еще одну службу переднего плана (ScreenOffService) и BroadcastReceiver (ScreenOffBroadcastReceiver), который получает события SCREEN_OFF и, когда это происходит, получает WakeLock с флагом Partial; это позволяет экрану погаснуть и заблокироваться. ScreenOffService вызывает RegisterReceiver (новый ScreenOffBroadcastReceiver()). Мое приложение, похоже, не спит.
После публикации этого вопроса я провел дополнительные исследования и обнаружил, что создание 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();
}
}
Вы пытались сохранить разблокировку экрана? Насколько я знаю, когда экран заблокирован, приложение будет в состоянии паузы.