[英]Keep running background service (Android)
我創建的后台服務在用戶登錄后啟動,即使在活動被“扔到垃圾桶”之后也應該工作。 服務顯示它正在運行的通知,注冊兩個廣播接收器以監視 wifi 和電話狀態,並且應該繼續運行直到令牌未過期或用戶注銷。
一切正常,但服務被android殺死。真正有效的唯一解決方案是這篇文章的說明http://nine-faq.9folders.com/articles/37422-stop-your-huawei-smartphone-from-closure-apps-當你鎖定屏幕不幸的是,這個解決方案是不可接受的,因為它依賴於用戶。
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>
從活動開始服務
活動
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
您必須請求許可才能讓您的應用免於省電和應用待機模式。
您可以首先從“設置”>“應用程序”>“特殊應用程序訪問”>“電池優化”>“關閉應用程序優化”中手動嘗試。
如果它適合您,那么您可以要求用戶使用以下權限將您的應用列入白名單
android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
我是Floating Apps 的作者,經過 7 年的開發,我已經看到它在近 11.000 種不同的設備上運行。 我還發布了一系列關於后台服務和浮動窗口的文章。
首先要說的是,如果沒有用戶交互,就不可能在所有設備上實現這一點。 一些供應商對 Android 系統進行了過多的更改,並添加了激進的內存或進程管理,有時需要手動操作。 此外,有些設備根本無法解決此問題。
什么有助於在不同的進程中運行服務(在AndroidManifest.xml
使用multiprocess
),並且您應該為服務添加android:stopWithTask
以便它不會被活動殺死 - 在某些設備上有幫助。
但基本上,在 Floating Apps 中,我嘗試檢測存在已知問題的手機,並要求用戶設置手機以使應用正常運行 - 禁用電池優化,添加到華為受保護的應用等。此外,很多問題都得到了解決通過我們的客戶支持(我的妻子實際上正在休產假 :-))。
最后一個實例是將用戶發送到: https : //dontkillmyapp.com
坦率地說,對於大多數用戶來說,這實際上並不是什么大問題,並且在禁用電池優化的情況下,該應用程序對大多數用戶來說都運行良好。 但是,對於其中的一部分,根本沒有解決方案 - 不是程序化的,也不是手動的。 不要試圖為所有手機解決這個問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.