Android Service保活攻防

木木

公司在15年6月份上线了点我达项目,类似于出行软件应用需要实时上传经纬度,涉及到了后台Service保活的问题。由于业务场景,保活的问题其实很多开发者都遇到了,网上也有很多人提问,如:怎么让 Android 程序一直在后台运行,像 QQ、微信一样不被杀死?回答者众多,一时间方案纷飞,可是又有几个真能达到希望效果呢?下面结合自身开发经验聊聊各个方案。

一、为什么要保活?

保活的源头是由于我们希望自己服务或者进程能够一直在后台运行,但是总有各种各样的原因导致我们的希望破灭,主要原因有如下几种:1、Android系统回收;2、手机厂商定制管理系统,如电源管理、内存管理等;3、第三方清理软件;4、用户手动结束。

二、保活手段

1、修改Service的onStartCommand 方法返回值

当服务被异常终止,是否能重启服务? 一般的做法是修改返回值,返回START_ STICKY。 onStartCommand()返回一个整型值,用来描述系统在杀掉服务后是否要继续启动服务,返回值有三种:

START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象,随后系统会尝试重新创建service。

START_NOT_STICKY:使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统将会把它置为started状态,系统不会自动重启该服务。

START_REDELIVER_INTENT:使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,它将会在隔一段时间后自动重启该服务,并将Intent的值传入。

【可行性】看到三个返回值的解释,貌似看到了保活的希望,是否只要把返回值设置为START_ STICKY或者START_REDELIVER _ INTENT就能实现我们的目的?但实际呢,经过实际检测除了因为内存不足而被系统回收时少数情况及机型可以重启,其它基本无效。


2、Service onDestory方法重新启动

在onDestory方法发送一个广播,收到广播后,重新启动Service

@Override  
public void onDestroy() {  
  stopForeground(true);  
  Intent intent = new   Intent("com.dwd.service.run.action");  
  sendBroadcast(intent);  
  super.onDestroy();  
}  

【可行性】在上诉4种原因下Service被干掉,APP进程基本就直接被干掉了,onDestroy方法都不会执行,所以也没法启动服务了。


3、提高Service优先级

Service注册时提高优先级


<service android:name="com.dwd.service.LocationService" android:exported="false" >
     <intent-filter android:priority="1000"></intent-filter>
 </service>

【可行性】此方式对Service无效,service无此属性。


4、前台服务

前台服务是被认为是用户已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程,前台服务必须有一个 notification 在状态栏中显示。

NotificationCompat.Builder nb = new NotificationCompat.Builder(this);
nb.setOngoing(true);
nb.setContentTitle(getString(R.string.app_name));
nb.setContentText(getString(R.string.app_name));
nb.setSmallIcon(R.drawable.icon);
PendingIntent pendingintent =PendingIntent.getActivity(this, 0,  new Intent(this, Main.class), 0);  
nb.setContentIntent(pendingIntent);
startForeground(1423, nb.build());

【可行性】此方法对防止系统回收有一定的效果,可以减少被回收的概率,但是系统在内存极低的情况下,该Service还是会被kill掉,并且不一定会重启。而清理工具或者手动强制结束,进程直接挂掉,并不会重启。


5、进程守护

此方案有两种实现方式,一个是双服务或者两个进程,启动两个服务,相互监听,其中一个挂了,另外一个就会随之把挂了的服务启动起来。另外一种方式是在native层fork一个子进程来与主进程互拉。

【可行性】第一种方式两个进程或者两个服务会同应用进程一起挂掉,所以并不能启动。第二种方式被杀死后确实能唤醒,但是在Android 5.0及以上系统会把fork出来的进程放到一个进程组里, 当程序主进程挂掉后,也会把整个进程组杀掉,因此用fork的方式也无法在Android5.0及以上系统上实现唤醒。


6、监听系统广播

通过监听系统的一些广播,比如:手机开机、解锁屏、网络连接状态变更、应用状态改变等等,然后判断Service是否存活,若否则启动Service。

【可行性】Android系统在3.1版本以后为了加强系统安全性和优化性能对系统广播进行了限制,应用监控手机开机、解锁屏、网络连接状态改变等有规律的系统广播在android3.1以后,首次安装未启动或者用户强制停止后,应用无法监听到。另外最新的Android N取消了网络切换广播,真是心酸啊,一个个都不能用了。


7、应用之间互拉

利用不同的app进程使用广播来进行相互唤醒,比如支付宝、淘宝、天猫、等阿里系的app,如果打开其中任意一个应用,其它阿里系的app也会唤醒了,其实BAT系都差不多。另外现在很多推送sdk也会唤醒app。

【可行性】多个app应用唤醒需要相互之间有关联才能实现,推送sdk应用间唤醒当用户强制停止后无法唤醒。


8、activity一个像素点

在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死,此方案是小米曝光腾讯QQ的做法。

【可行性】依然会被杀死。


9、将APK安装到/system/app,变身系统级应用

【可行性】此方法只适合预安装应用,普通应用无法做到变身系统级应用。


10、利用Android系统提供的帐号和同步机制实现

在应用中建立一个帐号,然后开启自动同步并设置同步间隔时间,利用同步唤醒app。账号建立后在手机设置-账号中能看到应用的账号,用户可能会删除账号或者停止同步,故需要经常检测账号是否能正常同步。

//建立账号
AccountManager accountManager = AccountManager.get(mContext);

Account riderAccount = new Account(mContext.getString(R.string.app_name), Constant.ACCOUNT_TYPE);
    accountManager.addAccountExplicitly(riderAccount, mContext.getString(R.string.app_name), null);
    ContentResolver.setIsSyncable(riderAccount, Constant.ACCOUNT_AUTHORITY, 1);
    ContentResolver.addPeriodicSync(riderAccount, Constant.ACCOUNT_AUTHORITY, new Bundle(), 60);

//开启同步
ContentResolver.setSyncAutomatically(riderAccount, Constant.ACCOUNT_AUTHORITY, true);

【可行性】除魅族手机外在大部分手机上此方案可以成功唤醒app,不管是何种方式杀死的。另外小米手机需要关闭神隐模式。此方案提出来将近一年时间,现在已经有很多开发者在使用了。


11、白名单

把应用放入手机或者安全软件的白名单中,保证进程不被系统回收,如微信、QQ都在小米的白名单中,所以微信基本不会被系统干掉,但用户可以强制停止。

【可行性】此方案成功率还是不错的,不过用户还是可以手动干掉应用。另外普通的应用如果用户基数不够大,应用开发者去找各大厂商谈,国内android手机厂商多如牛毛,成本太高。但是当应用的装机量及活跃用户达到微信这个规模,也许厂商会主动将你的应用加入白名单呢……


点我达应用中综合使用了4、6、7及10这四个方案,能保证90%以上的手机成功保活。Service保活其实是个攻防战,应用为了需求需要实现后台运行,但是系统为了性能安全等因素考虑又不得不干掉偷跑的后台服务。而且保活也是一项持久战,说不定现在可行的方案,某一天又被谷大爷干掉了。学习永无止境,探索永不止步。