#android #xamarin.android #android-service #android-permissions #android-background
#Android #xamarin.android #android-сервис #android-разрешения #android-фон
Вопрос:
Я создаю фоновую службу, которая запускается после входа пользователя в систему и должна работать даже после того, как действие было «выброшено в корзину». Служба показывает уведомления о том, что она запущена, регистрирует два широковещательных приемника для мониторинга состояния Wi-Fi и телефона и должна продолжать работать до истечения срока действия токена или выхода пользователя из системы.
Все работает, но служба отключена Android.Единственное решение, которое ДЕЙСТВИТЕЛЬНО РАБОТАЕТ, — это инструкция из этой статьи http://nine-faq.9folders.com/articles/37422-stop-your-huawei-smartphone-from-closing-apps-when-you-lock-the-screen К сожалению, это решение неприемлемо, потому что оно зависит от пользователя.
Код, созданный C # с использованием Xamarin, но если кто-нибудь знает, как программно реализовать решение из статьи, я буду рад полезным советам даже на других языках (java, kotlin)
Манифест
<?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" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
</manifest>
Запустите службу из activity
Активность
using System;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Provider;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Support.V7.App;
using Android.Views;
using Android.Widget;
using AlertDialog = Android.Support.V7.App.AlertDialog;
namespace com.BGAppTest
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
public static string HOPE = "Nothing";
string[] perms = new string[] { "android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_NETWORK_STATE", "android.permission.READ_PHONE_STATE","android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"};
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;
HOPE = DateTime.Now.ToString();
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);
}
protected void IGnoreBatteryActivity()
{
PowerManager m = GetSystemService(PowerService) as PowerManager;
Intent intent = new Intent();
if (m.IsIgnoringBatteryOptimizations(this.PackageName))
{
//intent.SetAction(Settings.ActionIgnoreBatteryOptimizationSettings);
}
else
{
intent.SetAction(Settings.ActionRequestIgnoreBatteryOptimizations);
intent.SetData(Android.Net.Uri.Parse("package:" PackageName));
StartActivity(intent);
}
}
void StartService()
{
foreach (var p in perms)
{
if (CheckSelfPermission(p) == Permission.Denied)
{
RequestPermissions(perms, 2);
return;
}
}
IGnoreBatteryActivity();
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))
{
RunOnUiThread(() =>
{
new AlertDialog.Builder(this)
.SetMessage("Uprawnienia są wymagane.Chcesz nadać uprawnienia?")
.SetNegativeButton("Nie", delegate
{
this.FinishAffinity();
})
.SetPositiveButton("Tak", delegate
{
RequestPermissions(perms, requestCode);
})
.SetCancelable(false)
.Create()
.Show();
});
}
else
StartService();
}
}
}
Обслуживание
[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 { }
}
}
Приемники
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();
}
}
}
ОБНОВЛЕНИЕ 26.09.2020
Я добавил полный код моего примера приложения, сделав его настолько простым, насколько мог
ОБНОВЛЕНИЕ 30.09.2020 Добавить android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
и ActionRequestIgnoreBatteryOptimizations
Комментарии:
1. Если вы получите исключение, служба остановится. Вам нужно обернуть код в обработчик исключений, чтобы предотвратить остановку кода.
2. Я считаю, что ошибок нет, потому что, когда я вручную отключаю автоматическое управление батареей для приложения на устройстве, все работает нормально
3. Это кажется ожидаемым результатом, который вызван дизайном системы HUAWEI (EMUI). Вы могли бы сначала протестировать свое приложение в Google Pixel или эмуляторе.
4. Это неверное предположение. Если бы ошибок не было, приложение продолжало бы работать. Вам необходимо просмотреть свои заметки по ГЕОМЕТРИИ на предмет правильных предположений. Итак, теорема: когда я оставляю управление батареей выключенным, я не получаю никаких ошибок. Контрапозитивно: я получаю ошибки при включении управления батареей.
5. @jdweng , после 1 часа работы приложение снова умерло. Я установил обработчик для каждого метода обслуживания и широковещательных приемников
Ответ №1:
Вы должны запросить разрешение, чтобы освободить ваше приложение от режима экономии заряда батареи и ожидания приложения.
Сначала вы можете попробовать это вручную из Настроек> Приложений> Специального доступа к приложению > Оптимизации заряда батареи > Отключить оптимизацию для вашего приложения.
Если у вас это работает, вы можете попросить пользователя внести ваше приложение в белый список, используя следующее разрешение
android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
Комментарии:
1. Да, как я упоминал ранее, ручное отключение оптимизации отлично работает для меня. Спасибо за ваш ответ, я попробую это решение и сообщу вам, как оно работает
2. @YousseShamass к сожалению, это разрешение не помогло. Это работает, но не помогает. Есть ли у вас какие-либо предложения по реализации прагматично работающего решения, которое я описал?
Ответ №2:
Я автор плавающих приложений, и за 7 лет разработки я видел, как они запускались почти на 11 000 различных устройствах. Я также опубликовал серию статей о фоновых службах и плавающих окнах.
Во-первых, этого невозможно достичь на всех устройствах без взаимодействия с пользователем. Некоторые производители слишком сильно меняют систему Android и добавляют агрессивную память или управление процессами, и иногда требуется ручное действие. Кроме того, есть устройства, на которых это вообще не может быть решено.
Что помогает, так это запустить службу в другом процессе (используя multiprocess
in AndroidManifest.xml
), и вы должны добавить android:stopWithTask
для службы, чтобы она не была уничтожена с помощью activity — помогает на некоторых устройствах.
Но в основном, в плавающих приложениях я пытаюсь обнаружить телефоны с известными проблемами и прошу пользователей настроить свой телефон, чтобы приложение работало правильно — отключить оптимизацию заряда батареи, добавить в защищенные приложения на Huawei и т.д. Кроме того, наша служба поддержки клиентов решает множество проблем (моя жена на самом деле в декретном отпуске :-)).
Последний экземпляр — отправлять пользователей по адресу:https://dontkillmyapp.com
Честно говоря, для большинства пользователей это на самом деле не имеет большого значения, и с отключенной оптимизацией заряда батареи приложение работает довольно хорошо для большинства из них. Однако для части из них вообще нет решения — ни программного, ни ручного. Не пытайтесь решить эту проблему вообще для всех телефонов.