Android 雙進(jìn)程守護(hù)的實現(xiàn)代碼
前言
最近有在項目中用到高德的定位SDK,功能是每隔一定的時間獲取一次用戶的地理位置,采取的方案是在后臺開啟一個 Service,監(jiān)聽高德地圖的位置變化。
該功能在用戶手機(jī)屏幕亮?xí)r完美實現(xiàn),但是當(dāng)屏幕被關(guān)閉的時候,位置信息卻無法被獲取了,經(jīng)過原因的排查,發(fā)現(xiàn)是由于在用戶手機(jī)息屏后,后臺的 Service 被系統(tǒng)清除,所以功能無法起作用,也就是所謂的進(jìn)程被殺了。
殺進(jìn)程,一方面是因為手機(jī)內(nèi)存不足,另一方面其實是 Google 從用戶的方面考慮,把一些常駐后臺的程序通過一定的算法進(jìn)行管理,將那些過度消耗系統(tǒng)資源的流氓軟件殺除,保證手機(jī)的性能和續(xù)航。但是有的軟件,像定位這類的必須要保持后臺的運(yùn)行,如何才能避免被系統(tǒng)殺掉呢。其實避免被殺進(jìn)程很難做到,除非是像微信、QQ、支付寶這類系統(tǒng)廠商認(rèn)可的軟件被官方加入白名單可以避免被殺進(jìn)程。那其他的小軟件怎么辦,我們可以另辟蹊徑,無法避免被殺進(jìn)程,那就讓我們的軟件在被殺進(jìn)程后,能自動重啟。
我這里介紹一下雙進(jìn)程守護(hù)的方法,來實現(xiàn)進(jìn)程被殺后的拉起。
雙進(jìn)程守護(hù)
雙進(jìn)程守護(hù)的思想就是,兩個進(jìn)程共同運(yùn)行,如果有其中一個進(jìn)程被殺,那么另一個進(jìn)程就會將被殺的進(jìn)程重新拉起,相互保護(hù),在一定的意義上,維持進(jìn)程的不斷運(yùn)行。
雙進(jìn)程守護(hù)的兩個進(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,另一個是守護(hù)進(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(); }
重點(diǎn)是在兩個 Service 上。
在定義Service時,需要在 AndroidManifest 中聲明一下 遠(yuǎn)端Service 的 process 屬性,保證 本地Service 和 遠(yuǎn)端Service 兩者跑在不同的進(jìn)程上,如果跑在同一個進(jìn)程上,該進(jìn)程被殺,那就什么都沒了,就沒有了雙進(jìn)程守護(hù)的說法了。
<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 的代碼,重點(diǎn)關(guān)注 onStartCommand 方法 和 ServiceConnection 中重寫的方法。onStartCommand 方法是在 Service 啟動后被調(diào)用,在 LocalService 被啟動后,我們將 RemoteService 進(jìn)行了啟動,并將 LocalService 和 RemoteService 兩者綁定了起來(因為遠(yuǎn)端Service 對于用戶來說是不可見的,相對于我們實際工作的進(jìn)程也是獨(dú)立的,它的作用僅僅是守護(hù)線程,所以說 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(); } } }
運(yùn)行效果
啟動 Activity 點(diǎn)擊開啟 LocalService 啟動本地服務(wù),提示中可以看到, LocalService 啟動后 RemotService 守護(hù)線程也被啟動。此時,兩者已經(jīng)綁定在了一起。
開啟服務(wù)
點(diǎn)擊關(guān)閉 LocalService 模擬本地進(jìn)程被殺,Toast 提示鏈接斷開,并嘗試重新啟動 LocalService,第二個Toast 提示 LocalService 被重新拉起。
關(guān)閉本地服務(wù)
點(diǎn)擊關(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)程相互守護(hù)的目的。
總結(jié)
在開發(fā)的過程中總是有些無法避免的麻煩,但是方法總比困難多,耐心研究研究就行了。關(guān)于進(jìn)程的?;睿鋵嵤菦]有辦法的辦法,我們應(yīng)該盡量避免將進(jìn)程常駐后臺,如果真的需要,在完成后臺工作后,也要及時將他們銷毀。否則后臺進(jìn)程無端地消耗系統(tǒng)資源,用戶又不知道,咱們的軟件就也就成了流氓軟件。開發(fā)人員應(yīng)該有自己的良心,嗯。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android學(xué)習(xí)項目之簡易版微信為例(二)
這篇文章主要以簡易版微信為例,實現(xiàn)簡易版微信的登陸、注冊界面的編寫與簡單交互,感興趣的小伙伴們可以參考一下2016-06-06Android App內(nèi)監(jiān)聽截圖加二維碼功能代碼
Android截屏功能是一個常用的功能,可以方便的用來分享或者發(fā)送給好友,本文介紹了如何實現(xiàn)app內(nèi)截屏監(jiān)控功能,需要的朋友可以參考下2017-08-08Android實現(xiàn)簡單的分批加載ListView
這篇文章主要介紹了Android實現(xiàn)簡單的分批加載ListView的相關(guān)資料,需要的朋友可以參考下2016-03-03Android實現(xiàn)夜間模式切換功能實現(xiàn)代碼
現(xiàn)在很多App都有夜間模式,特別是閱讀類的App,夜間模式現(xiàn)在已經(jīng)是閱讀類App的標(biāo)配,本篇文章主要介紹了Android實現(xiàn)夜間模式功能實現(xiàn)代碼,有興趣的可以了解一下。2017-03-035分鐘學(xué)會Android設(shè)計模式之策略模式Strategy Pattern教程
這篇文章主要為大家介紹了5分鐘學(xué)會Android設(shè)計模式之策略模式Strategy Pattern教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Android實現(xiàn)環(huán)形進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)環(huán)形進(jìn)度條,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07