Android 应用/进程保活策略总结
1.将Service设置为前台服务
思路:启用前台服务,主要是startForeground() 。
保活程度:一般情况下不被杀,部分定制ROM会在应用切到后台即杀 ,会被 用户手动杀进程(force stop)杀死。
使用场景:大部分音乐播放器通知栏的实现,可以保证后台听歌时应用正常运行。
2.在service的onstart方法里返回 STATR_STICK
思路:其实就是onStartCommand中返回STATR_STICK
保活程度:有次数和时间的限制 ,会被 force stop 杀死
3.添加Manifest文件属性值为android:persistent=“true”
代码实现(清单文件中配置):
<application android:name="PhoneApp"
android:persistent="true"
android:label="@string/dialerIconLabel"
android:icon="@drawable/ic_launcher_phone">
保活程度:一般情况下不被杀,会被 force stop 杀死
PS:该方法需要系统签名
4.覆写Service的onDestroy方法
思路:在onDestroy中再次启动该服务
保活程度:很弱,只在两种情况下work:正在运行里杀服务、DDMS里stop进程
代码实现:
@Override
public void onDestroy() {
Intent intent = new Intent(this, KeeLiveService.class);
startService(intent);
super.onDestroy();
}
5.监听一堆系统静态广播
思路:在发生特定系统事件时,系统会发出响应的广播,通过在 AndroidManifest 中“静态”注册对应的广播监听器,即可在发生响应事件时拉活。
保活强度:我们可以发现,这个方法都是监听系统的一些广播,所以我们需要在我们的应用中注册静态广播,但是静态广播又会出现问题,那就是在4.0版本以上,没有启动过的应用或Force-Stop后收不到静态广播,也就是说4.0以后,如果我们应用从未启动过,或者被Force-Stop杀死过,是无法接收到静态广播的。
如果是两个应用相互拉起,那么在一个应用内可发送带FLAG_INCLUDE_STOPPED_PACKAGES的Intent,那即使另一个应用也是以上两种情况,也可以接收到系统的广播。
应用1的代码实现:
//应用1,发送拉起服务的广播
Intent intent = new Intent();
intent.setAction("com.action.keepLive");
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
this.sendBroadcast(intent);
应用2的代码实现:
//清单文件中配置
<receiver android:name="com.yzy.supercleanmaster.receiver.KeepLiveReceiver">
<intent-filter>
<action android:name="com.action.keepLive" />
</intent-filter>
</receiver>
//源码实现
public class KeepLiveReceiver extends BroadcastReceiver{
//应用2中,接受应用1发送的广播,进行服务的拉起
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, KeeLiveService.class);
context.startService(i);
}
}
6.监听第三方应用的静态广播
思路:通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。
保活强度:
该方案的局限性除与系统广播一样的因素外,主要受如下因素限制:
1) 反编译分析过的第三方应用的多少;
2) 第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发,这些因素都影响了拉活的效果。
7.AlarmManager唤醒
思路:通过AlarmManager设置一个定时器,定时的唤醒服务
保活强度:killBackgroundProcess下,大部分情况work,
不敌force-stop,闹钟会被清除。
8.账户同步,定时唤醒
思路:android系统里有一个账户系统,系统定期唤醒账号更新服务,同步的事件间隔是有限制的,最短1分钟。
难点:需要手动设置账户,你如何骗你的用户给你手动设置账户完了之后不卸载你,必须联网。
保活强度: 该方案适用于所有的 Android 版本,包括被 forestop 掉的进程也可以进行拉活。最新 Android 版本(Android N)中系统好像对账户同步这里做了变动,该方法不再有效。
9.1像素悬浮层
思路:1像素悬浮层是传说的QQ黑科技,监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。
保活强度: 前台进程,跟前台服务差不多。需要权限,可以被force-stop杀死。有些手机系统禁止一像素保活,如魅族手机。
10.应用间互相拉起
思路:app之间知道包名就可以相互唤醒了,比如你杀了我qq,只要微信还在就能确保随时唤醒qq。还有百度全系app都通过bdshare实现互拉互保,自定义一个广播,定时发,其他app收广播自起等。
11.心跳唤醒
思路:微信保活技术,依赖系统特性:长连接网络回包机制。
保活强度:不敌force-stop,需要网络,API level >= 23的doze模式会关闭所有的网络。
12.双进程保活策略
思路:在应用被打开的时候,启动两个后台服务,这两个后台服务是相互依存的,也就是说当一个进程被干掉的时候,另一个存活的进程就立马将其拉起唤醒,也就是打一个时间差。
在一个服务断开连接的时候开启另一个服务,示例代码如下:
class MyServiceConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "建立连接成功!");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "LocalService服务被干掉了~~~~断开连接!");
Toast.makeText(RemoteService.this, "断开连接", 0).show();
//启动被干掉的
RemoteService.this.startService(new Intent(RemoteService.this, LocalService.class));
RemoteService.this.bindService(new Intent(RemoteService.this, LocalService.class), conn, Context.BIND_IMPORTANT);
}
}
改进思路:利用JobService保证在息屏后,CPU进入休眠状态时如果服务没有在工作,则进行唤醒。示例代码如下:
public class JobHandleService extends JobService{
private final String TAG = "JobHandleService";
private int kJobId = 0;
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "jobService create");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "jobService start");
scheduleJob(getJobInfo());
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
}
@Override
public boolean onStartJob(JobParameters params) {
// TODO Auto-generated method stub
Log.i(TAG, "job start");
// scheduleJob(getJobInfo());
boolean isLocalServiceWork = isServiceWork(this, 你的本地服务ref----XXXX.LocalService);
boolean isRemoteServiceWork = isServiceWork(this, 你的远程服务ref----XXXX.RemoteService);
// Log.i(TAG, "localSericeWork:"+isLocalServiceWork);
// Log.i(TAG, "remoteSericeWork:"+isRemoteServiceWork);
if(!isLocalServiceWork||
!isRemoteServiceWork){
this.startService(new Intent(this,LocalService.class));
this.startService(new Intent(this,RemoteService.class));
Toast.makeText(this, "process start", Toast.LENGTH_SHORT).show();
}
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "job stop");
// Toast.makeText(this, "process stop", Toast.LENGTH_SHORT).show();
scheduleJob(getJobInfo());
return true;
}
/** Send job to the JobScheduler. */
public void scheduleJob(JobInfo t) {
Log.i(TAG, "Scheduling job");
JobScheduler tm =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
tm.schedule(t);
}
public JobInfo getJobInfo(){
JobInfo.Builder builder = new JobInfo.Builder(kJobId++, new ComponentName(this, JobHandleService.class));
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
builder.setPersisted(true);
builder.setRequiresCharging(false);
builder.setRequiresDeviceIdle(false);
builder.setPeriodic(10);//间隔时间--周期
return builder.build();
}
/**
* 判断某个服务是否正在运行的方法
*
* @param mContext
* @param serviceName
* 是包名+服务的类名(例如:net.loonggg.testbackstage.TestService)
* @return true代表正在运行,false代表服务没有正在运行
*/
public boolean isServiceWork(Context mContext, String serviceName) {
boolean isWork = false;
ActivityManager myAM = (ActivityManager) mContext
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> myList = myAM.getRunningServices(100);
if (myList.size() <= 0) {
return false;
}
for (int i = 0; i < myList.size(); i++) {
String mName = myList.get(i).service.getClassName().toString();
if (mName.equals(serviceName)) {
isWork = true;
break;
}
}
return isWork;
}
}
13.音乐静音播放保活
思路:在保活服务中开启一个MediaPlayer,循环播放静音文件。服务被杀的时候在onDestroy中重启服务。示例代码如下:
public class ResidentService extends Service {
private final static String TAG = "ResidentService";
private MediaPlayer mMediaPlayer;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
if (mMediaPlayer != null) {
mMediaPlayer.setLooping(true);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 服务启动后开始播放静音文件
startPlayMusic();
}
}).start();
return START_STICKY;
}
private void startPlayMusic() {
if (mMediaPlayer != null) {
mMediaPlayer.start();
}
}
private void pausePlayMusic() {
if (mMediaPlayer != null) {
mMediaPlayer.pause();
}
}
private void stopPlayMusic() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
}
}
@Override
public void onDestroy() {
super.onDestroy();
// 服务被杀掉时,停止播放静音文件,并重启保活服务
stopPlayMusic();
Intent intent = new Intent(getApplicationContext(), GameResidentService.class);
startService(intent);
}
}
14.Native进程拉起
思路:开启native子进程,定时发intent。
保活强度:单杀可以杀死,force close 5.0以上无效,5.0以下部分手机无效,第三方软件下无效,且无法保证实时常驻。该策略建立在保证c进程不挂的基础上,才能轮询,但是就目前来看,只有5.0以下的非国产机才会有这样的漏洞。也就是说在force close的时候,系统忽略c进程的存在,5.0以上包括5.0的哪怕源生系统也会连同c进程一起清理掉,国产机就更不用说了。即使这样,在5.0以下的非国产机上,如果安装了获取root权限的360\cm的话,也是可以直接清理掉,也就是说会失效。
Native进程守护缺点非常明显,那就是守护是单向的,也就是说只能a保b,b保不了a;a保b也不是在b死了立刻拉起来,要等到了时间才会去拉。
15.总结
Android应用的保活策略很多,每种方式都有其优缺点和实用范围,具体采用哪一种保活策略,需要结合应用的类型,如是系统应用还是第三方独立应用,也要结合应用的保活需求,如什么场景下保活、保活多久等等。有很多时候一种保活策略往往不能达到很好的保活效果,可以考虑结合几种保活策略。