Продолжайте запускать фоновую службу (Android)

#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

Честно говоря, для большинства пользователей это на самом деле не имеет большого значения, и с отключенной оптимизацией заряда батареи приложение работает довольно хорошо для большинства из них. Однако для части из них вообще нет решения — ни программного, ни ручного. Не пытайтесь решить эту проблему вообще для всех телефонов.