Android 雙進(jìn)程守護的實現(xiàn)代碼
前言
最近有在項目中用到高德的定位SDK,功能是每隔一定的時間獲取一次用戶的地理位置,采取的方案是在后臺開啟一個 Service,監(jiān)聽高德地圖的位置變化。
該功能在用戶手機屏幕亮?xí)r完美實現(xiàn),但是當(dāng)屏幕被關(guān)閉的時候,位置信息卻無法被獲取了,經(jīng)過原因的排查,發(fā)現(xiàn)是由于在用戶手機息屏后,后臺的 Service 被系統(tǒng)清除,所以功能無法起作用,也就是所謂的進(jìn)程被殺了。

殺進(jìn)程,一方面是因為手機內(nèi)存不足,另一方面其實是 Google 從用戶的方面考慮,把一些常駐后臺的程序通過一定的算法進(jìn)行管理,將那些過度消耗系統(tǒng)資源的流氓軟件殺除,保證手機的性能和續(xù)航。但是有的軟件,像定位這類的必須要保持后臺的運行,如何才能避免被系統(tǒng)殺掉呢。其實避免被殺進(jìn)程很難做到,除非是像微信、QQ、支付寶這類系統(tǒng)廠商認(rèn)可的軟件被官方加入白名單可以避免被殺進(jìn)程。那其他的小軟件怎么辦,我們可以另辟蹊徑,無法避免被殺進(jìn)程,那就讓我們的軟件在被殺進(jìn)程后,能自動重啟。
我這里介紹一下雙進(jìn)程守護的方法,來實現(xiàn)進(jìn)程被殺后的拉起。
雙進(jìn)程守護

雙進(jìn)程守護的思想就是,兩個進(jìn)程共同運行,如果有其中一個進(jìn)程被殺,那么另一個進(jìn)程就會將被殺的進(jìn)程重新拉起,相互保護,在一定的意義上,維持進(jìn)程的不斷運行。
雙進(jìn)程守護的兩個進(jìn)程,一個進(jìn)程用于我們所需的后臺操作,且叫它本地進(jìn)程,另一個進(jìn)程只負(fù)責(zé)監(jiān)聽著本地進(jìn)程的狀態(tài),在本地進(jìn)程被殺的時候拉起,于此同時本地進(jìn)程也在監(jiān)聽著這個進(jìn)程,準(zhǔn)備在它被殺時拉起,我們將這個進(jìn)程稱為遠(yuǎn)端進(jìn)程。
由于在 Android 中,兩個進(jìn)程之間無法直接交互,所以我們這里還要用到 AIDL (Android interface definition Language ),進(jìn)行兩個進(jìn)程間的交互。
代碼實現(xiàn)
先來看一下demo代碼結(jié)構(gòu),結(jié)構(gòu)很簡單,我這里創(chuàng)建了一個 Activity 作為界面,以及兩個 Service ,一個是后臺操作的 本地Service,另一個是守護進(jìn)程的 遠(yuǎn)端Service,還有一個 AIDL文件用作進(jìn)程間交互用。

項目結(jié)構(gòu)
Activity 的定義很簡單,就幾個按鈕,控制 Service 的狀態(tài),我這邊定義了三個按鈕,一個是開啟后臺服務(wù),另外兩個分別是關(guān)閉本地Service和遠(yuǎn)端的Service。
/**
* @author chaochaowu
*/
public class GuardActivity extends AppCompatActivity {
@BindView(R.id.button)
Button button;
@BindView(R.id.button2)
Button button2;
@BindView(R.id.button3)
Button button3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
setContentView(R.layout.activity_guard);
ButterKnife.bind(this);
}
@OnClick({R.id.button, R.id.button2, R.id.button3})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.button:
startService(new Intent(this, LocalService.class));
break;
case R.id.button2:
stopService(new Intent(this, LocalService.class));
break;
case R.id.button3:
stopService(new Intent(this, RemoteService.class));
break;
default:
break;
}
}
}
可以看一下界面。

主界面
AIDL文件可以根據(jù)業(yè)務(wù)需要添加接口。
/**
* @author chaochaowu
*/
interface IMyAidlInterface {
String getServiceName();
}
重點是在兩個 Service 上。
在定義Service時,需要在 AndroidManifest 中聲明一下 遠(yuǎn)端Service 的 process 屬性,保證 本地Service 和 遠(yuǎn)端Service 兩者跑在不同的進(jìn)程上,如果跑在同一個進(jìn)程上,該進(jìn)程被殺,那就什么都沒了,就沒有了雙進(jìn)程守護的說法了。
<service android:name=".guard.LocalService" android:enabled="true" android:exported="true" /> <service android:name=".guard.RemoteService" android:enabled="true" android:exported="true" android:process=":RemoteProcess"/>
先來看 LocalService 的代碼,重點關(guān)注 onStartCommand 方法 和 ServiceConnection 中重寫的方法。onStartCommand 方法是在 Service 啟動后被調(diào)用,在 LocalService 被啟動后,我們將 RemoteService 進(jìn)行了啟動,并將 LocalService 和 RemoteService 兩者綁定了起來(因為遠(yuǎn)端Service 對于用戶來說是不可見的,相對于我們實際工作的進(jìn)程也是獨立的,它的作用僅僅是守護線程,所以說 RemoteService 僅與 LocalService 有關(guān)系,應(yīng)該只能由 LocalService 將它啟動)。
啟動并綁定之后,我們需要重寫 ServiceConnection 中的方法,監(jiān)聽兩者之間的綁定關(guān)系,關(guān)鍵的是對兩者綁定關(guān)系斷開時的監(jiān)聽。
當(dāng)其中一個進(jìn)程被殺掉時,兩者的綁定關(guān)系就會被斷開,觸發(fā)方法 onServiceDisconnected ,所以,我們要在斷開時,進(jìn)行進(jìn)程拉起的操作,重寫 onServiceDisconnected 方法,在方法中將另外一個 Service 重新啟動,并將兩者重新綁定。
/**
* @author chaochaowu
*/
public class LocalService extends Service {
private MyBinder mBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
try {
Log.i("LocalService", "connected with " + iMyAidlInterface.getServiceName());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Toast.makeText(LocalService.this,"鏈接斷開,重新啟動 RemoteService",Toast.LENGTH_LONG).show();
startService(new Intent(LocalService.this,RemoteService.class));
bindService(new Intent(LocalService.this,RemoteService.class),connection, Context.BIND_IMPORTANT);
}
};
public LocalService() {
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this,"LocalService 啟動",Toast.LENGTH_LONG).show();
startService(new Intent(LocalService.this,RemoteService.class));
bindService(new Intent(this,RemoteService.class),connection, Context.BIND_IMPORTANT);
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
mBinder = new MyBinder();
return mBinder;
}
private class MyBinder extends IMyAidlInterface.Stub{
@Override
public String getServiceName() throws RemoteException {
return LocalService.class.getName();
}
}
}
在另外一個 RemoteService 中也一樣,在與 LocalService 斷開鏈接的時候,由于監(jiān)聽到綁定的斷開,說明 RemoteService 還存活著,LocalService 被殺進(jìn)程,所以要將 LocalService 進(jìn)行拉起,并重新綁定。方法寫在 onServiceDisconnected 中。
/**
* @author chaochaowu
*/
public class RemoteService extends Service {
private MyBinder mBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
try {
Log.i("RemoteService", "connected with " + iMyAidlInterface.getServiceName());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Toast.makeText(RemoteService.this,"鏈接斷開,重新啟動 LocalService",Toast.LENGTH_LONG).show();
startService(new Intent(RemoteService.this,LocalService.class));
bindService(new Intent(RemoteService.this,LocalService.class),connection, Context.BIND_IMPORTANT);
}
};
public RemoteService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this,"RemoteService 啟動",Toast.LENGTH_LONG).show();
bindService(new Intent(this,LocalService.class),connection,Context.BIND_IMPORTANT);
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
mBinder = new MyBinder();
return mBinder;
}
private class MyBinder extends IMyAidlInterface.Stub{
@Override
public String getServiceName() throws RemoteException {
return RemoteService.class.getName();
}
}
}
運行效果
啟動 Activity 點擊開啟 LocalService 啟動本地服務(wù),提示中可以看到, LocalService 啟動后 RemotService 守護線程也被啟動。此時,兩者已經(jīng)綁定在了一起。

開啟服務(wù)
點擊關(guān)閉 LocalService 模擬本地進(jìn)程被殺,Toast 提示鏈接斷開,并嘗試重新啟動 LocalService,第二個Toast 提示 LocalService 被重新拉起。

關(guān)閉本地服務(wù)
點擊關(guān)閉 RemoteService 模擬遠(yuǎn)端進(jìn)程被殺,Toast 提示鏈接斷開,并嘗試重新啟動 RemoteService ,第二個Toast 提示 RemoteService 被重新拉起。

關(guān)閉遠(yuǎn)端服務(wù)
可以發(fā)現(xiàn),無論我們怎么殺進(jìn)程,進(jìn)程都會被重新拉起,這就達(dá)到了 Service ?;睿p進(jìn)程相互守護的目的。
總結(jié)
在開發(fā)的過程中總是有些無法避免的麻煩,但是方法總比困難多,耐心研究研究就行了。關(guān)于進(jìn)程的保活,其實是沒有辦法的辦法,我們應(yīng)該盡量避免將進(jìn)程常駐后臺,如果真的需要,在完成后臺工作后,也要及時將他們銷毀。否則后臺進(jìn)程無端地消耗系統(tǒng)資源,用戶又不知道,咱們的軟件就也就成了流氓軟件。開發(fā)人員應(yīng)該有自己的良心,嗯。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android仿Adapter實現(xiàn)自定義PagerAdapter方法示例
這篇文章主要給大家介紹了關(guān)于android仿Adapter實現(xiàn)自定義PagerAdapter的相關(guān)資料,文中詳細(xì)介紹了關(guān)于PagerAdapter的用法,對大家的理解和學(xué)習(xí)具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11
Android啟動頁優(yōu)化之實現(xiàn)應(yīng)用秒開
現(xiàn)在很多應(yīng)用都會在進(jìn)入主界面之前,添加一個啟動頁,然后加入幾秒鐘的廣告,我覺得這個不能算是 “真正意義上的 “ 啟動頁,應(yīng)該叫廣告頁。2021-05-05
Android DataBinding單向數(shù)據(jù)綁定深入探究
看了谷歌官方文章確實寫的太簡略了,甚至看完之后有很多地方還不知道怎么回事兒或者怎么用,那么接下來我將通過文章全面介紹一下DataBinding單向數(shù)據(jù)綁定2022-11-11
Android性能優(yōu)化以及數(shù)據(jù)優(yōu)化方法
我和大家之前一起探討了在Android中對SQLite數(shù)據(jù)庫的操作優(yōu)化細(xì)節(jié)。今天談?wù)凙ndroid性能數(shù)據(jù)優(yōu)化方法,需要的朋友可以參考下2016-05-05
Android編程實現(xiàn)錄音及保存播放功能的方法【附demo源碼下載】
這篇文章主要介紹了Android編程實現(xiàn)錄音及保存播放功能的方法,結(jié)合實例形式分析了Android基于MediaRecorder類進(jìn)行錄音機保存播放功能的相關(guān)操作技巧,并附帶demo源碼供讀者下載,需要的朋友可以參考下2018-01-01
Android實現(xiàn)自定義圓角對話框Dialog的示例代碼
項目中多處用到對話框,本篇文章主要介紹了Android實現(xiàn)圓角對話框Dialog的示例代碼,有興趣的可以了解一下。2017-03-03

