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應用的保活策略很多,每種方式都有其優缺點和實用範圍,具體採用哪一種保活策略,需要結合應用的類型,如是系統應用還是第三方獨立應用,也要結合應用的保活需求,如什麼場景下保活、保活多久等等。有很多時候一種保活策略往往不能達到很好的保活效果,可以考慮結合幾種保活策略。