第一章 Service介紹
service服務是一個應用程序的四大組件之一,可以再後臺執行長時間運行的操作,不提供用戶界面。一個應用程序組件可以啓動一個服務,它將繼續在後臺運行,即使用戶切到另一個應用程序。此外,一個組件可以綁定到一個服務與它交互,甚至執行進程間的通信(IPC)。
1.1 基礎介紹
Service中比較重要的方法有以下幾個:
onStartCommand()
當其他組件,如Activity請求服務啓動的時候,系統會調用這個方法。一旦這個方法執行,服務就開始執行。如果實現這個方法,當服務完成任務後,需要你調用stopSelf()
或者stopService()
來停止服務。如果只想提供綁定,不需要自己實現這個方法。onBind()
當有其他組件想通過bindService()
方法綁定這個服務時系統就會調用此方法。在實現的方法裏面,必須添加一個供客戶端使用的接口通過返回IBinder
來與服務通信,這個方法必須實現。當然想禁止綁定的話,返回null
即可。onCreate()
服務第一次建立的時候會調用這個方法,執行一次性設置程序,在上面2個方法執行前調用。如果服務已存在,則不執行該方法。onDestory()
服務不再使用則調用該方法。服務應該事先這個方法來清理諸如線程,註冊的監聽器等資源。這是最後調用的方法。
Android系統只會在內存佔用很高,必須回覆系統資源供當前運行程序的情況下強制停掉一個運行中的服務。如果服務綁定在當前的運行程序中,就幾乎不會被kill,如果服務聲明瞭在前臺運行(其實在後臺,只是給系統一個錯誤的信息來提高優先級),就幾乎不會被kill。另外,如果一個服務正在運行,且運行了很久,系統就會根據運行時間把其排在後臺任務列表的後面,則這個服務很容易被殺掉。根據onStartCommand()
的返回值設置,服務被殺掉後仍然可以再資源充足的條件下立即重啓。
1.2啓動服務的兩種方式:
- started啓動:
started
形式的服務是指當一個應用組件(比如activity
)通過startService()
方法開啓服務。一旦開啓,該服務就可以永久的在後臺運行,哪怕開啓它的組件被銷燬掉。通常開啓的服務執行一個單獨的操作並且不向調用者返回一個結果。比如,從網絡下載文件,當文件下載完成,服務就應該自己停止。
關閉服務則需要服務自己調用方法stopSelf()
或者由啓動服務的地方調用stopService(Intent)
方法來關閉。 - Bound綁定:
bound
形式的服務是指一個應用組件通過調用bindService()
方法與服務綁定。一個綁定的服務提供一個接口,允許組件與服務交互,發送請求、獲得結果、甚至進行進程間通信。一個綁定的服務只和與其綁定的組件同時運行。多個組件可以同時綁定到一個服務,當全部解除綁定後,服務就會被銷燬。
雖然分爲兩類,但是一個服務可以同時使用這兩種方式-使用started
永久運行,同時允許綁定。只要在服務中實現兩個回調方法:onStartCommand()
允許組件開啓服務,onBind()
允許綁定。
不論引用程序是怎麼起服務的,任何應用程序都可以用這個服務。同樣的,任何組件可以使用一個Activity
通過傳遞Intent
開啓服務。你也可以在配置文件設置服務爲私有來防止其他應用訪問該服務。
注意:一個服務在進程中的主線程運行,服務不會自己創建線程和進程(除非特別指定或者開啓一個線程)。這意味着,如果服務需要做一些頻繁佔用CPU的工作或者會發生阻塞的操作,需要在服務另外開啓線程。
1.3 Service生命週期
- 啓動服務:
startService()->onCreate()->onStartCommand()->running->stopService()/stopSelf()->onDestroy()->stopped
,其中,服務未運行時會調用一次onCreate()
,運行時不會調用。 - 綁定服務:
bindService()->onCreate()->onBind()->running->onUnbind()->onDestory()->stopped
。
在上面兩個過程中,只有onStart
第二章 實現Service
實現服務有兩種方式,繼承service
或者IntentService
。後者是前者的子類。前者包含上一章節中Service的幾個重要的方法,用於普通的服務。後者可以自己開一個工作線程,串行的處理多個請求。
2.1 繼承Service
繼承Service
就可以實現對請求多線程的處理,前面介紹了Service
的生命週期,可以按照生命週期實現方法,就不放示例了。
2.2 繼承IntentService
大多數服務不需要同時處理多個請求,繼承IntentService
是最好的選擇。
IntentService處理流程:
- 創建按默認的一個
worker
線程處理傳遞給onStartCommand()
所有的intent
,在非UI線程中區工作。 - 創建一個工作隊列依次傳遞一個
intent
到你實現的onHandleIntent()
方法,避免了多線程。 - 在所有啓動請求被處理後自動關閉服務,不需要調用
stopSelf()
- 默認提供
onBind()
的實現,並返回null
- 默認提供
onStartCommand()
的實現,實現發送intent
到工作隊列再到你的onHandleIntent()
方法實現。
這些都加入到IntentService
中了,你需要做的就是實現構造方法和onHandleIntent(),如下:
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}
第三章 如何保證服務不被殺死
在有些特定的情況下,服務需要保持開啓不能被殺死。主要有以下幾種方法保持Service不被殺死。
3.1 onStartCommand
方法中,返回START_STICKY
在StartCommand()
幾個常量:
START_STICKY
系統重新創建服務並且調用onStartCommand()
方法,但並不會傳遞最後一次傳遞的intent
,只是傳遞一個空的intent
。除非存在將要傳遞來的intent
,那麼就會傳遞這些intent
。這個適合播放器一類的服務,不需要執行命令,只需要獨自運行,等待任務。START_NOT_STICKY
系統不重新創建服務,除非有將要傳遞來的intent
。這是最安全的選項,可以避免在不必要的時候運行服務。START_REDELIVER_INTENT
系統重新創建服務並且調用onStartCommand()
方法,傳遞最後一次傳遞的intent
。其餘存在的需要傳遞的intent
會按順序傳遞進來。這適合像下載一樣的服務,立即恢復,積極執行。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
手動返回START_STICKY,親測當service因內存不足被kill,當內存又有的時候,service又被重新創建,比較不錯,但是不能保證任何情況下都被重建,比如進程被幹掉了….
3.2 提升Service優先級
前臺服務是被認爲用於已知的正在運行的服務,當系統需要釋放內存時不會優先殺掉該進程。前臺進程必須發一個notification
在狀態欄中顯示,知道進程被殺死。因爲前臺服務一直消耗一部分資源,但不像一般服務那樣會在需要的時候被殺掉,所以爲了節約資源,保護電池壽命,一定要在建前臺服務的時候發送notification
,提示用戶。當然系統提供的方法就必須有notification
參數的,所以不要想着怎麼把notification
隱藏掉。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Intent notificationIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification noti = new Notification.Builder(this)
.setContentTitle("Title")
.setContentText("Message")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(123456,noti);
return Service.START_STICKY;
}
startForeground()
方法就是將服務設置爲前臺服務,參數123456就是這個通知的唯一的id,只要不爲0即可。
3.3 在onDestory()中發送廣播開啓自己
service+broadcast方式,就是當service調用到ondestory()
的時候,發送一個自定義的廣播,當收到廣播的時候,重新啓動service;
<receiver android:name="com.example.demo.MyReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.USER_PRESENT"/>
<action android:name=com.example.demo.destroy"/>// 這個是自定義的action
</intent-filter>
</receiver>
在service中的ondestroy()
時候:
@Override
public void onDestroy(){
stopForeground(true);
Intent intent = new Intent("com.example.demo.destroy");
sendBroadcast(intent);
super.onDestroy();
}
在MyReceiver中
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("com.example.demo.destroy")){
Intent sevice = new Intent(this, MyService.class);
this.startService(sevice);
}
}
}
當然,從理論上來講這個方案是可行的,實驗一下結果也是可行的。但是有些情況下,發送的廣播在消息隊列中排的靠後,就有可能服務還沒有接收到廣播就銷燬了(只是猜想)。所以爲了能讓這個機制完美運行,可以開啓兩個服務,相互監聽,相互啓動。服務A監聽B的廣播來啓動B,服務B監聽A的廣播來啓動A。經過實驗,這個方案是可行的。