我正在尝试创建一个允许用户记录路线(位置/ GPS)的应用程序。为了确保即使屏幕关闭也记录了位置,我已经为位置记录创建了一个foreground service
。我将位置存储在Room Database
中,使用Dagger2
将其注入我的服务中。
但是,这项服务被Android杀死,当然这并不好。我可以订阅低内存警告,但这并没有解决我的服务在运行Android 8.0的现代高端手机上大约30分钟后被杀的根本问题
我创建了一个只有“Hello world”活动和服务的最小项目:https://github.com/RandomStuffAndCode/AndroidForegroundService
该服务在我的Application
类中启动,路由记录通过Binder
启动:
// Application
@Override
public void onCreate() {
super.onCreate();
mComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
Intent startBackgroundIntent = new Intent();
startBackgroundIntent.setClass(this, LocationService.class);
startService(startBackgroundIntent);
}
// Binding activity
bindService(new Intent(this, LocationService.class), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
// mConnection starts the route logging through `Binder` once connected. The binder calls startForeground()
我可能不需要BIND_AUTO_CREATE
标志,我一直在测试不同的标志,试图不让我的服务被杀 - 到目前为止没有运气。
使用分析器看起来我没有任何内存泄漏,内存使用率稳定在~35mb:
使用adb shell dumpsys activity processes > tmp.txt
我可以确认foregroundServices=true
和我的服务列在LRU列表中的第8位:
Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)
似乎无法创建一个您可以信任的前台服务,以免被杀死。所以,我们能做些什么?好...
START_STICKY
启动服务。这似乎有点浪费,并没有导致非常漂亮的代码,但它可能会工作......有点。根据Android杀死服务后重新创建服务所需的时间,很大一部分地点可能会丢失。这真的是在Android背景下做事情的当前状态吗?有没有更好的方法?
编辑:将应用程序列入白名单以进行电池优化(禁用它)并不会阻止我的服务被杀死
编辑:使用Context.startForegroundService()
启动服务并没有改善这种情况
编辑:所以这确实只发生在某些设备上,但它始终存在于它们上面。我想你必须选择不支持大量用户或编写非常难看的代码。真棒。
由startForeground
开始的服务是第二个最重要的组织visible process:
- 一个可见的过程正在进行用户当前意识到的工作,因此杀死它会对用户体验产生明显的负面影响。在以下条件下,可以看到一个过程: 它正在运行一个活动,该活动在屏幕上对用户可见,但不在前台(已调用其onPause()方法)。例如,如果前景活动显示为允许在其后面看到前一个活动的对话框,则可能会发生这种情况。 它有一个作为前台服务运行的服务,通过Service.startForeground()(它要求系统将服务视为用户知道或基本上对他们可见的东西)。 它正在托管系统用于用户知道的特定功能的服务,例如动态壁纸,输入法服务等。 在系统中运行的这些进程的数量比前台进程更少,但仍然是相对受控的。这些进程被认为是非常重要的,除非需要这样做以保持所有前台进程运行,否则不会被杀死。
话虽这么说,你永远不能确定你的服务在任何时候都不会被杀死。例如。记忆压力,电池电量低等。见who-lives-and-who-dies。
对于如何处理它,基本上你自己回答了这个问题。要走的路是START_STICKY
:
对于已启动的服务,他们可以决定运行另外两种主要操作模式,具体取决于它们从
onStartCommand():
返回的值START_STICKY
用于根据需要明确启动和停止的服务,而START_NOT_STICKY
或START_REDELIVER_INTENT
用于服务应该只在处理发送给它们的任何命令时保持运行。有关语义的更多详细信息,请参阅链接的文档。
作为一般准则,您应该在后台(矿石前景)服务中尽可能少地执行,即仅进行位置跟踪并将其他所有内容保留在前台活动中。只有跟踪应该需要很少的配置才能快速加载。此外,您的服务越小,被杀的可能性就越小。您的活动将在系统进入后台之前的状态下恢复,只要它没有被杀死。另一方面,前景活动的“冷启动”应该不是问题。 我不认为这很难看,因为这可以保证手机始终为用户提供最佳体验。这是它必须做的最重要的事情。有些设备在30分钟后关闭服务(可能没有用户交互)是不幸的。
所以,正如你所说,你必须这样做
保留服务中的所有内容,例如房间数据库。每个变量,每个自定义类,每次更改任何变量,然后使用START_STICKY启动服务。
见creating a never ending service
隐含的问题:
根据Android杀死服务后重新创建服务所需的时间,很大一部分地点可能会丢失。
这通常只需要很短的时间。特别是因为你可以使用Fused Location Provider Api进行位置更新,这是一个独立的系统服务,不太可能被杀死。所以它主要取决于你需要在onStartCommand
中重新创建服务的时间。
另请注意,从Android 8.0起,您需要使用
forground service
,因为background location limits。
编辑:最近新闻报道:有些制造商可能会给你一个很难保持服务运行的时间。网站https://dontkillmyapp.com/会跟踪您的设备的制造商和可能的缓解措施。 Oneplus目前(29.01.19)是最严重的罪犯之一。
在推出1 + 5和1 + 6手机时,OnePlus推出了迄今为止市场上最严苛的背景限制之一,甚至使小米或华为的手机相形见绌。用户不仅需要启用额外的设置才能使他们的应用程序正常运行,而且这些设置甚至会通过固件更新重置,以便应用程序再次中断,并且用户需要定期重新启用这些设置。
用户解决方案
关闭系统设置>应用程序>齿轮图标>特殊访问>电池优化。
遗憾的是有
开发人员端没有已知的解决方案
我知道现在已经很晚了,但这可能对某人有所帮助。我也面临同样的问题,即保持前台服务的活跃,而不会被不同制造商的操作系统杀死。大多数中国制造商的操作系统都会杀死前台服务,即使它被添加到例外列表(电池,清洁器等)并允许自动启动。
我发现这个link解决了我长期保持服务活着的问题。
您所要做的就是在一个单独的过程中运行前台服务。而已。
您可以通过在AndroidManifest.xml中将android:process
添加到您的服务来实现。
例如:
<service android:name=".YourService"
android:process=":yourProcessName" />
你可以参考docs了解更多有关android:process
的信息
编辑:SharedPreferences不能跨多个进程工作。在这种情况下,您必须使用IPC(进程间通信)方法,或者您可以使用ContentProviders
存储和访问要跨进程使用的数据。来自docs。
我建议你使用这些:AlarmManager
,PowerManager
,WakeLock
,Thread
,WakefulBroadcastReceiver
,Handler
,Looper
我假设你已经在使用那些“单独的过程”和其他调整。
所以在你的Application
课程中:
MyApp.java
:
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;
public final class MyApp extends Application{
public static PendingIntent pendingIntent = null;
public static Thread infiniteRunningThread;
public static PowerManager pm;
public static PowerManager.WakeLock wl;
@Override
public void onCreate(){
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->restartApp(this, "MyApp uncaughtException:", e));
}catch(SecurityException e){
restartApp(this, "MyApp uncaughtException SecurityException", e);
e.printStackTrace();
}
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if(pm != null){
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
wl.acquire(10 * 60 * 1000L /*10 minutes*/);
}
infiniteRunningThread = new Thread();
super.onCreate();
}
public static void restartApp(Context ctx, String callerName, Throwable e){
Log.w("TAG", "restartApp called from " + callerName);
wl.release();
if(pendingIntent == null){
pendingIntent =
PendingIntent.getActivity(ctx, 0,
new Intent(ctx, ActivityMain.class), 0);
}
AlarmManager mgr = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
if(mgr != null){
mgr.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + 10, pendingIntent);
}
if(e != null){
e.printStackTrace();
}
System.exit(2);
}
}
然后在您的服务中:
ServiceTrackerTest.java
:
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.WakefulBroadcastReceiver;
public class ServiceTrackerTest extends Service{
private static final int SERVICE_ID = 2018;
private static PowerManager.WakeLock wl;
@Override
public IBinder onBind(Intent intent){
return null;
}
@Override
public void onCreate(){
super.onCreate();
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->MyApp.restartApp(this,
"called from ServiceTracker onCreate "
+ "uncaughtException:", e));
}catch(SecurityException e){
MyApp.restartApp(this,
"called from ServiceTracker onCreate uncaughtException "
+ "SecurityException", e);
e.printStackTrace();
}
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if(pm != null){
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
wl.acquire(10 * 60 * 1000L /*10 minutes*/);
}
Handler h = new Handler();
h.postDelayed(()->{
MyApp.infiniteRunningThread = new Thread(()->{
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->MyApp.restartApp(this,
"called from ServiceTracker onCreate "
+ "uncaughtException "
+ "infiniteRunningThread:", e));
}catch(SecurityException e){
MyApp.restartApp(this,
"called from ServiceTracker onCreate uncaughtException "
+ "SecurityException "
+ "infiniteRunningThread", e);
e.printStackTrace();
}
Looper.prepare();
infiniteRunning();
Looper.loop();
});
MyApp.infiniteRunningThread.start();
}, 5000);
}
@Override
public void onDestroy(){
wl.release();
MyApp.restartApp(this, "ServiceTracker onDestroy", null);
}
@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId){
if(intent != null){
try{
WakefulBroadcastReceiver.completeWakefulIntent(intent);
}catch(Exception e){
e.printStackTrace();
}
}
startForeground(SERVICE_ID, getNotificationBuilder().build());
return START_STICKY;
}
private void infiniteRunning(){
//do your stuff here
Handler h = new Handler();
h.postDelayed(this::infiniteRunning, 300000);//5 minutes interval
}
@SuppressWarnings("deprecation")
private NotificationCompat.Builder getNotificationBuilder(){
return new NotificationCompat.Builder(this)
.setContentIntent(MyApp.pendingIntent)
.setContentText(getString(R.string.notification_text))
.setContentTitle(getString(R.string.app_name))
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher))
.setSmallIcon(R.drawable.ic_stat_tracking_service);
}
}
忽略“弃用”和东西,当你别无选择时使用它们。我认为代码很清楚,不需要解释。这只是解决方法的建议和解决方案。