本文分為兩個部分,第一部分為雙Service守護,第二部分為雙進程守護
第一部分:
一、Service簡介:
Java.lang.Object
???Android.content.Context
???????android.content.ContextWrapper
???????????android.app.Service
Service是應用程序Application的一個組件(component)。
它的作用有兩點:1.用來提供一個長期在后臺運行并且不與用戶交互的操作,2.也可以為其他應用程序提供服務。
Service必須和其他四大組件一樣,使用<service>標簽在AndroidManifest.xml中進行聲明。
啟動service有兩種方式Context.startService() 和 Context.bindService()。
注意,除了特別指定外,service并不是單獨的進程,一般service在其宿主進程的主線程(UI Thread)中運行【當然也可以在新的線程中startService,這樣Service就不是在MainThread了】。這意味著,如果您的服務要做任何 耗時(如 MP3 播放) 或阻塞 (比如網絡) 操作,它應該產生它自己的線程,用來做那項工作。(service不是單獨的進程也不是單獨的線程)
Service提供了兩大功能:
Context.startService()用來在后臺啟動一個服務;
Context.bindService()用來綁定其他服務,以此來獲取其他service提供的服務;
?
本地服務 Local Service?用于應用程序內部
它可以啟動并運行,直至有人停止了它或它自己停止。在這種方式下,它以調用Context.startService()啟動,而以調用Context.stopService()結束。它可以調用Service.stopSelf() 或 Service.stopSelfResult()來自己停止。不論調用了多少次startService()方法,你只需要調用一次stopService()來停止服務。
【用于實現應用程序自己的一些耗時任務,比如查詢升級信息,并不占用應用程序比如Activity所屬線程,而是單開線程后臺執行,這樣用戶體驗比較好】
?
遠程服務 Remote Service?用于android系統內部的應用程序之間
它可以通過自己定義并暴露出來的接口進行程序操作。客戶端建立一個到服務對象的連接,并通過那個連接來調用服務。連接以調用Context.bindService()方法建立,以調用 Context.unbindService()關閉。多個客戶端可以綁定至同一個服務。如果服務此時還沒有加載,bindService()會先加載它。
【可被其他應用程序復用,比如天氣預報服務,其他應用程序不需要再寫這樣的服務,調用已有的即可】
?
?
二、Service運行方式和生命周期圖:
以startService()啟動服務,系統將通過傳入的Intent在底層搜索相關符合Intent里面信息的service。如果服務沒有啟動則先運行onCreate,然后運行onStartCommand (可在里面處理啟動時傳過來的Intent和其他參數),直到明顯調用stopService或者stopSelf才將停止Service。無論運行startService多少次,只要調用一次stopService或者stopSelf,Service都會停止。使用stopSelf(int)方法可以保證在處理好intent后再停止。onStartCommand ,在2.0后被引入用于service的啟動函數,2.0之前為public void onStart(Intent intent, int startId) 。
以bindService()方法啟用服務,調用者與服務綁定在了一起,調用者一旦退出,服務也就終止。onBind()只有采用Context.bindService()方法啟動服務時才會回調該方法。該方法在調用者與服務綁定時被調用,當調用者與服務已經綁定,多次調用Context.bindService()方法并不會導致該方法被多次調用。采用Context.bindService()方法啟動服務時只能調用onUnbind()方法解除調用者與服務解除,服務結束時會調用onDestroy()方法。
?
(注意這個新老API的改變)
void onStart(Intent intent, int startId)
This method was deprecated????? in API level 5.??? Implement onStartCommand(Intent, int, int) instead.
?
int onStartCommand(Intent intent, int flags, int startId)
Called by the system every time a client explicitly starts the service by calling? startService(Intent), providing the arguments it supplied and a? unique integer token representing the start request.
?
三、Service的優先級
官方文檔告訴我們,Android系統會盡量保持擁有service的進程運行,只要在該service已經被啟動(start)或者客戶端連接(bindService)到它。當內存不足時,需要保持,擁有service的進程具有較高的優先級。
1. 如果service正在調用onCreate,onStartCommand或者onDestory方法,那么用于當前service的進程則變為前臺進程以避免被killed。
2. 如果當前service已經被啟動(start),擁有它的進程則比那些用戶可見的進程優先級低一些,但是比那些不可見的進程更重要,這就意味著service一般不會被killed.
3. 如果客戶端已經連接到service (bindService),那么擁有Service的進程則擁有最高的優先級,可以認為service是可見的。
4. 如果service可以使用startForeground(int, Notification)方法來將service設置為前臺狀態,那么系統就認為是對用戶可見的,并不會在內存不足時killed。
5. 如果有其他的應用組件作為Service,Activity等運行在相同的進程中,那么將會增加該進程的重要性。
?
四、保持service不被kill掉
方法一:
START_STICKY is used for services that are explicitly started and stopped as needed, while START_NOT_STICKY or START_REDELIVER_INTENT are used for services that should only remain running while processing any commands sent to them
onStartCommand方法幾個返回值簡介:
?
1、START_STICKY
?
在運行onStartCommand后service進程被kill后,那將保留在開始狀態,但是不保留那些傳入的intent。不久后service就會再次嘗試重新創建,因為保留在開始狀態,在創建???? service后將保證調用onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent。
?
2、START_NOT_STICKY
?
在運行onStartCommand后service進程被kill后,并且沒有新的intent傳遞給它。Service將移出開始狀態,并且直到新的明顯的方法(startService)調用才重新創建。因為如果沒有傳遞任何未決定的intent那么service是不會啟動,也就是期間onstartCommand不會接收到任何null的intent。
?
3、START_REDELIVER_INTENT
?
在運行onStartCommand后service進程被kill后,系統將會再次啟動service,并傳入最后一個intent給onstartCommand。直到調用stopSelf(int)才停止傳遞intent。如果在被kill后還有未處理好的intent,那被kill后服務還是會自動啟動。因此onstartCommand不會接收到任何null的intent。
@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {flags = START_STICKY;return super.onStartCommand(intent, flags, startId);}
【結論】 手動返回START_STICKY,親測當service因內存不足被kill,當內存又有的時候,service又被重新創建,比較不錯,但是不能保證任何情況下都被重建,比如進程被干掉了....
方法二:
提升service優先級
?在AndroidManifest.xml文件中對于intent-filter可以通過android:priority = "1000"這個屬性設置最高優先級,1000是最高值,如果數字越小則優先級越低,同時適用于廣播。
<serviceandroid:name="com.dbjtech.acbxt.waiqin.UploadService"android:enabled="true" ><intent-filter android:priority="1000" ><action android:name="com.dbjtech.myservice" /></intent-filter></service>
【結論】目前看來,priority這個屬性貌似只適用于broadcast,對于Service來說可能無效
方法三:
提升service進程優先級
Android中的進程是托管的,當系統進程空間緊張的時候,會依照優先級自動進行進程的回收。Android將進程分為6個等級,它們按優先級順序由高到低依次是:
?? 1.前臺進程( FOREGROUND_APP)
?? 2.可視進程(VISIBLE_APP )
?? 3. 次要服務進程(SECONDARY_SERVER )
?? 4.后臺進程 (HIDDEN_APP)
?? 5.內容供應節點(CONTENT_PROVIDER)
?? 6.空進程(EMPTY_APP)
當service運行在低內存的環境時,將會kill掉一些存在的進程。因此進程的優先級將會很重要,可以使用startForeground將service放到前臺狀態。這樣在低內存時被kill的幾率會低一些。
在onStartCommand方法內添加如下代碼:
Notification notification = new Notification(R.drawable.ic_launcher,getString(R.string.app_name), System.currentTimeMillis());PendingIntent pendingintent = PendingIntent.getActivity(this, 0,new Intent(this, AppMain.class), 0);notification.setLatestEventInfo(this, "uploadservice", "請保持程序在后臺運行", pendingintent);startForeground(0x111, notification);
注意在onDestroy里還需要stopForeground(true),運行時在下拉列表會看到自己的APP在:
【結論】如果在極度極度低內存的壓力下,該service還是會被kill掉,并且不一定會restart?
保持Service不被Kill掉的方法--雙Service守護,代碼如下:
AndroidManifest.xml:
<activityandroid:name=".MainActivity"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><serviceandroid:name="ServiceOne"android:process=":remote" ><intent-filter><action android:name="com.example.servicedemo.ServiceOne" /></intent-filter></service><serviceandroid:name="ServiceTwo"android:process=":remote" ><intent-filter><action android:name="com.example.servicedemo.ServiceTwo" /></intent-filter></service>
MainActivity.java:
package com.example.servicedemo;import java.util.ArrayList;import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; import android.content.Context; import android.content.Intent; import android.os.Bundle;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Intent serviceOne = new Intent();serviceOne.setClass(MainActivity.this, ServiceOne.class);startService(serviceOne);Intent serviceTwo = new Intent();serviceTwo.setClass(MainActivity.this, ServiceTwo.class);startService(serviceTwo);}public static boolean isServiceWorked(Context context, String serviceName) {ActivityManager myManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);ArrayList<RunningServiceInfo> runningService = (ArrayList<RunningServiceInfo>) myManager.getRunningServices(Integer.MAX_VALUE);for (int i = 0; i < runningService.size(); i++) {if (runningService.get(i).service.getClassName().toString().equals(serviceName)) {return true;}}return false;} }
ServiceOne.java:
package com.example.servicedemo;import java.util.Timer; import java.util.TimerTask;import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log;public class ServiceOne extends Service {public final static String TAG = "com.example.servicedemo.ServiceOne";@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.e(TAG, "onStartCommand");thread.start();return START_STICKY;}Thread thread = new Thread(new Runnable() {@Overridepublic void run() {Timer timer = new Timer();TimerTask task = new TimerTask() {@Overridepublic void run() {Log.e(TAG, "ServiceOne Run: "+System.currentTimeMillis());boolean b = MainActivity.isServiceWorked(ServiceOne.this, "com.example.servicedemo.ServiceTwo");if(!b) {Intent service = new Intent(ServiceOne.this, ServiceTwo.class);startService(service);Log.e(TAG, "Start ServiceTwo");}}};timer.schedule(task, 0, 1000);}});@Overridepublic IBinder onBind(Intent arg0) {return null;}}
ServiceTwo.java:
package com.example.servicedemo;import java.util.Timer; import java.util.TimerTask;import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log;public class ServiceTwo extends Service {public final static String TAG = "com.example.servicedemo.ServiceTwo";@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.e(TAG, "onStartCommand");thread.start();return START_REDELIVER_INTENT;}Thread thread = new Thread(new Runnable() {@Overridepublic void run() {Timer timer = new Timer();TimerTask task = new TimerTask() {@Overridepublic void run() {Log.e(TAG, "ServiceTwo Run: " + System.currentTimeMillis());boolean b = MainActivity.isServiceWorked(ServiceTwo.this, "com.example.servicedemo.ServiceOne");if(!b) {Intent service = new Intent(ServiceTwo.this, ServiceOne.class);startService(service);}}};timer.schedule(task, 0, 1000);}});@Overridepublic IBinder onBind(Intent arg0) {return null;}}
第二部分:
做過android開發的人應該都知道應用會在系統資源匱乏的情況下被系統殺死!當后臺的應用被系統回收之后,如何重新恢復它呢?網上對此問題有很多的討論。這里先總結一下網上流傳的各種解決方案,看看這些辦法是不是真的可行。
1.提高優先級
這個辦法對普通應用而言,應該只是降低了應用被殺死的概率,但是如果真的被系統回收了,還是無法讓應用自動重新啟動!
2.讓service.onStartCommand返回START_STICKY
通過實驗發現,如果在adb shell當中kill掉進程模擬應用被意外殺死的情況(或者用360手機衛士進行清理操作),如果服務的onStartCommand返回START_STICKY,在eclipse的進程管理器中會發現過一小會后被殺死的進程的確又會出現在任務管理器中,貌似這是一個可行的辦法。但是如果在系統設置的App管理中選擇強行關閉應用,這時候會發現即使onStartCommand返回了START_STICKY,應用還是沒能重新啟動起來!
3.android:persistent="true"
網上還提出了設置這個屬性的辦法,通過實驗發現即使設置了這個屬性,應用程序被kill之后還是不能重新啟動起來的!
4.讓應用成為系統應用
實驗發現即使成為系統應用,被殺死之后也不能自動重新啟動。但是如果對一個系統應用設置了persistent="true",情況就不一樣了。實驗表明對一個設置了persistent屬性的系統應用,即使kill掉會立刻重啟。一個設置了persistent="true"的系統應用,在android中具有core service優先級,這種優先級的應用對系統的low memory killer是免疫的!
OK,說了半天,只有core service優先級的應用才能保證在被意外殺死之后做到立刻滿血復活。而普通應用要想成為系統應用就必須要用目標機器的簽名文件進行簽名,但這樣又造成了應用無法保證兼容所有不同廠商的產品。那么該怎么辦呢?這里就來說一說雙進程守護。網上也有人提到過雙進程守護的辦法,但是很少能搜索到類似的源碼!如果從進程管理器重觀察會發現新浪微博或者360衛視都有兩個相關的進程,其中一個就是守護進程,由此可以猜到這些商業級的軟件也采用了雙進程守護的辦法。
什么是雙進程守護呢?顧名思義就是兩個進程互相監視對方,發現對方掛掉就立刻重啟!不知道應該把這樣的一對進程是叫做相依為命呢還是難兄難弟好呢,但總之,雙進程守護的確是一個解決問題的辦法!相信說到這里,很多人已經迫切的想知道如何實現雙進程守護了。這篇文章就介紹一個用NDK來實現雙進程保護的辦法,不過首先說明一點,下面要介紹的方法中,會損失不少的效率,反應到現實中就是會使手機的耗電量變大!但是這篇文章僅僅是拋磚引玉,相信看完之后會有更多高人指點出更妙的實現辦法。
需要了解些什么?
這篇文章中實現雙進程保護的方法基本上是純的NDK開發,或者說全部是用C++來實現的,需要雙進程保護的程序,只需要在程序的任何地方調用一下JAVA接口即可。下面幾個知識點是需要了解的:
1.Linux中多進程;
2.unix domain套接字實現跨進程通信;
3.linux的信號處理;
4.exec函數族的用法;
其實這些東西本身并不是多復雜的技術,只是我們把他們組合起來實現了一個雙進程守護而已,沒有想象中那么神秘!在正式貼出代碼之前,先來說說幾個實現雙進程守護時的關鍵點:
1.父進程如何監視到子進程(監視進程)的死亡?
很簡單,在linux中,子進程被終止時,會向父進程發送SIG_CHLD信號,于是我們可以安裝信號處理函數,并在此信號處理函數中重新啟動創建監視進程;
2.子進程(監視進程)如何監視到父進程死亡?
當父進程死亡以后,子進程就成為了孤兒進程由Init進程領養,于是我們可以在一個循環中讀取子進程的父進程PID,當變為1就說明其父進程已經死亡,于是可以重啟父進程。這里因為采用了循環,所以就引出了之前提到的耗電量的問題。
3.父子進程間的通信
有一種辦法是父子進程間建立通信通道,然后通過監視此通道來感知對方的存在,這樣不會存在之前提到的耗電量的問題,在本文的實現中,為了簡單,還是采用了輪詢父進程PID的辦法,但是還是留出了父子進程的通信通道,雖然暫時沒有用到,但可備不時之需!
騰訊的面試官問我:應用程序死了如何恢復?確實,雙進程守護只能做到進程被殺死后重新啟動,但是重啟后如何恢復到之前的狀態這是一個問題。因為進程被意外殺死的情況,onSaveInstance是來不及執行的,所以程序的狀態沒法保存!對于雙進程守護來說,不知道是不是可以再父進程進入后臺以后(onStop),把數據收集起來保存到子進程中,然后父進程重啟以后從子進程中取出這些信息呢?這是一個辦法,但是上面說明的雙進程守護程序的實現中還做不到,因為父進程重啟以后,子進程也掛掉重新建立了,要想實現優雅的恢復,還得在做出點改進才是!只能實時保存數據到數據庫等。
參考:
Android實現雙進程守護 - 天山折梅 - 博客頻道 - CSDN.NET
http://blog.csdn.net/ztemt_sw2/article/details/27101681