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

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

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

??Activity 的定義很簡單,就幾個按鈕,控制 Service 的狀態(tài),我這邊定義了三個按鈕,一個是開啟后臺服務,另外兩個分別是關(guān)閉本地Service和遠端的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è)務需要添加接口。
/**
* @author chaochaowu
*/
interface IMyAidlInterface {
String getServiceName();
}
??重點是在兩個 Service 上。
??在定義Service時,需要在 AndroidManifest 中聲明一下 遠端Service 的 process 屬性,保證 本地Service 和 遠端Service 兩者跑在不同的進程上,如果跑在同一個進程上,該進程被殺,那就什么都沒了,就沒有了雙進程守護的說法了。
<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 進行了啟動,并將 LocalService 和 RemoteService 兩者綁定了起來(因為遠端Service 對于用戶來說是不可見的,相對于我們實際工作的進程也是獨立的,它的作用僅僅是守護線程,所以說 RemoteService 僅與 LocalService 有關(guān)系,應該只能由 LocalService 將它啟動)。
??啟動并綁定之后,我們需要重寫 ServiceConnection 中的方法,監(jiān)聽兩者之間的綁定關(guān)系,關(guān)鍵的是對兩者綁定關(guān)系斷開時的監(jiān)聽。
??當其中一個進程被殺掉時,兩者的綁定關(guān)系就會被斷開,觸發(fā)方法 onServiceDisconnected ,所以,我們要在斷開時,進行進程拉起的操作,重寫 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 被殺進程,所以要將 LocalService 進行拉起,并重新綁定。方法寫在 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 啟動本地服務,提示中可以看到, LocalService 啟動后 RemotService 守護線程也被啟動。此時,兩者已經(jīng)綁定在了一起。

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

??點擊關(guān)閉 RemoteService 模擬遠端進程被殺,Toast 提示鏈接斷開,并嘗試重新啟動 RemoteService ,第二個Toast 提示 RemoteService 被重新拉起。

??可以發(fā)現(xiàn),無論我們怎么殺進程,進程都會被重新拉起,這就達到了 Service ?;?,雙進程相互守護的目的。
總結(jié)
??在開發(fā)的過程中總是有些無法避免的麻煩,但是方法總比困難多,耐心研究研究就行了。關(guān)于進程的?;?,其實是沒有辦法的辦法,我們應該盡量避免將進程常駐后臺,如果真的需要,在完成后臺工作后,也要及時將他們銷毀。否則后臺進程無端地消耗系統(tǒng)資源,用戶又不知道,咱們的軟件就也就成了流氓軟件。開發(fā)人員應該有自己的良心,嗯。
以上。