Android-Service詳解

前言

Service 是長期運行在後臺的應用程序組件 。 Service 是和應用程序在同一個進程中,所以應用程序關掉了,Service也會關掉。可以理解爲

Service是不能直接處理耗時操作的,如果直接把耗時操作放在 Service 的 onStartCommand() 中,很容易引起 ANR;如果有耗時操作就必須開啓一個單獨的線程來處理。

IntentService 是繼承於 Service 並處理異步請求的一個類,在 IntentService 內有一個工作線程來處理耗時操作, 啓動 IntentService 的方式和啓動傳統 Service 一樣,同時,當任務執行完後, IntentService 會自動停止 ,而不需要我們去手動控制。 另外,可以啓動 IntentService 多次,而每一個耗時操作會以工作隊列的方式在IntentService 的 onHandleIntent 回調方法中執行, 並且,每次只會執行一個工作線程,執行完第一個再執行第二個, 有序執行。

PS:每一個安卓應用都會啓動一個進程,然後進程會啓動一個Dalvik虛擬機,即,每個Android應用進程對應着一個獨立的Dalvik虛擬機實例,然後啓動的應用程序再在虛擬機上被解釋執行(dalvik虛擬機,類似於jvm)。

Service使用

創建android服務的類需要繼承Service父類。

創建Service可以通過右鍵文件夾,new—service—service創建。

下面我們創建一個服務,新建後可以通過Ctrl+O重載重要的方法。

public class MyService extends Service {
    public MyService() {
    }
    /**
     * 綁定服務時纔會調用
     * 必須要實現的方法
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        //本服務不綁定組件
        throw new UnsupportedOperationException("Not yet implemented");
    }
    /**
     * 首次創建服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或 onBind() 之前)。
     * 如果服務已在運行,則不會調用此方法。該方法只被調用一次
     */
    @Override
    public void onCreate() {
        System.out.println("服務創建:onCreate被調用");
        super.onCreate();
    }
​
    /**
     * 每次通過startService()方法啓動Service時都會被回調。
     * @param intent 啓動時,啓動組件傳遞過來的Intent, Activity可利用Intent封裝所需要的參數並傳遞給Service,intentUser.putExtra("KEY", "518");
     * @param flags 表示啓動請求時是否有額外數據,可選值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY,0代表沒有,它們具體含義如下:
     *              START_FLAG_REDELIVERY 這個值代表了onStartCommand方法的返回值爲
     *              START_REDELIVER_INTENT,而且在上一次服務被殺死前會去調用stopSelf方法停止服務。其中START_REDELIVER_INTENT意味着當Service因內存不足而被系統kill後,則會重建服務,並通過傳遞給服務的最後一個 Intent 調用 onStartCommand(),此時Intent時有值的。
     *              START_FLAG_RETRY 該flag代表當onStartCommand調用後一直沒有返回值時,會嘗試重新去調用onStartCommand()。
     * @param startId 指明當前服務的唯一ID,與stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根據ID停止服務。
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("服務啓動:onStartCommand被調用,flags:"+flags+"  startId:"+startId);
        return super.onStartCommand(intent, flags, startId);
    }
    /**
     * 服務銷燬時的回調
     */
    @Override
    public void onDestroy() {
        System.out.println("服務銷燬:onDestroy被調用");
        super.onDestroy();
    }
}

然後在AndroidManifest.xml裏增加service節點,用於註冊,如果是使用AS創建會自動在AndroidManifest.xml裏增加service節點,如果是創建類繼承service,則需手動添加。

 <service
            android:name=".services.MyService"
            android:enabled="true"
            android:exported="true" />

服務創建後,對服務進行調試。

我們在androidTest下的com.kiba.framework.ExampleInstrumentedTest裏編寫單元測試。

單元測試的方法使用JUnit4的註解。

注:JUnit4的J指java,unit指單元,瞭解這個含義,我們在調試遇到問題時,方便精確百度。

PS:JUnit4有很多問題,比如調試斷點時會自動Disconnected斷開連接。

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        assertEquals("com.kiba.framework", appContext.getPackageName());
    }
    @Test
    public void servicesTest(){
        //不同實例服務調用,先start,後stop
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        Intent it=new Intent(appContext, MyService.class);
        appContext.startService(it);
        appContext.stopService(it);
        Intent it2=new Intent(appContext, MyService.class);
        appContext.startService(it2);
        appContext.stopService(it2);
        assertEquals("com.kiba.framework", appContext.getPackageName());
    }
    @Test
    public void servicesTest2(){
        //同一實例服務調用,先start,後stop
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        Intent it=new Intent(appContext, MyService.class);
        appContext.startService(it);
        appContext.stopService(it);
        appContext.startService(it);
        appContext.stopService(it);
        assertEquals("com.kiba.framework", appContext.getPackageName());
    }
    @Test
    public void servicesTest3(){
        //不同實例,不調用銷燬服務方法,只調用start
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        Intent it=new Intent(appContext, MyService.class);
        appContext.startService(it);
        Intent it2=new Intent(appContext, MyService.class);
        appContext.startService(it2);
​
        assertEquals("com.kiba.framework", appContext.getPackageName());
    }
}

調試可以點擊綠色三角,然後debug。

也可以點擊調試項目的按鈕,鼠標放上去,會有提示,如下圖。

調試時,會彈出新界面,在界面裏找到Console,可以查看我們的輸出。

 

測試結果:

不同實例服務調用,先start,後stop,結果如下:

 

service重建創建了。

同一實例服務調用,先start,後stop,結果如下:

 

service重建創建了。

不同實例,不調用銷燬服務方法,只調用start,結果如下:

 

service未創建。

雖然定義了兩個實例,但onCreate沒有被重複調用,即,同一類型的service,只有顯示調用了stopService纔會銷燬

拓展知識(進程和聲明週期)

Android操作系統嘗試儘可能長時間的保持應用的進程,但當可用內存很低時最終要移走一部分進程。怎樣確定那些程序可以運行,那些要被銷燬,Android讓每一個進程在一個重要級的基礎上運行,重要級低的進程最有可能被淘汰,一共有5級,下面這個列表就是按照重要性排列的:

1 一個前臺進程顯示的是用戶此時需要處理和顯示的。下列的條件有任何一個成立,這個進程都被認爲是在前臺運行的。 a 與用戶正發生交互的。 b 它控制一個與用戶交互的必須的基本的服務。 c 有一個正在調用生命週期的回調函數的service(如onCreate()、onStar()、onDestroy()) d 它有一個正在運行onReceive()方法的廣播接收對象。 只有少數的前臺進程可以在任何給定的時間內運行,銷燬他們是系統萬不得已的、最後的選擇——當內存不夠系統繼續運行下去時。通常,在這一點上,設備已經達到了內存分頁狀態,所以殺掉一些前臺進程來保證能夠響應用戶的需求。

2 一個可用進程沒有任何前臺組件,但它仍然可以影響到用戶的界面。下面兩種情況發生時,可以稱該進程爲可用進程。 它是一個非前臺的activity,但對用戶仍然可用(onPause()方法已經被調用)這是可能發生的,例如:前臺的activity是一個允許上一個activity可見的對話框,即當前activity半透明,能看到前一個activity的界面,它是一個服務於可用activity的服務。

3 一個服務進程是一個通過調用startService()方法啓動的服務,並且不屬於前兩種情況。儘管服務進程沒有直接被用戶看到,但他們確實是用戶所關心的,比如後臺播放音樂或網絡下載數據。所以系統保證他們的運行,直到不能保證所有的前臺可見程序都正常運行時纔會終止他們。

4 一個後臺進程就是一個非當前正在運行的activity(activity的onStop()方法已經被調用),他們不會對用戶體驗造成直接的影響,當沒有足夠內存來運行前臺可見程序時,他們將會被終止。通常,後臺進程會有很多個在運行,所以他們維護一個LRU最近使用程序列表來保證經常運行的activity能最後一個被終止。如果一個activity正確的實現了生命週期的方法,並且保存它當前狀態,殺死這些進程將不會影響到用戶體驗。

5 一個空線程沒有運行任何可用應用程序組,保留他們的唯一原因是爲了設立一個緩存機制,來加快組件啓動的時間。系統經常殺死這些內存來平衡系統的整個系統的資源,進程緩存和基本核心緩存之間的資源。 Android把進程裏優先級最高的activity或服務,作爲這個進程的優先級。例如,一個進程擁有一個服務和一個可見的activity,那麼這個進程將會被定義爲可見進程,而不是服務進程。

此外,如果別的進程依賴某一個進程的話,那麼被依賴的進程會提高優先級。一個進程服務於另一個進程,那麼提供服務的進程會獲得不低於被服務的進程的優先級。例如,如果進程A的一個內容提供商服務於進程B的一個客戶端,或者進程A的一個service被進程B的一個組件綁定,那麼進程A至少擁有和進程B一樣的優先級,或者更高。

PS1:運行服務的進程的優先級高於運行後臺activity的進程。

PS2:activity啓動一個服務,服務在onStartCommand裏執行一個長時間運行的操作可能會拖垮這個activity,可以理解爲在activity裏調用了一個函數,該函數長時間執行操作,則應用anr了。

----------------------------------------------------------------------------------------------------

注:此文章爲原創,任何形式的轉載都請聯繫作者獲得授權並註明出處!
若您覺得這篇文章還不錯,請點擊下方的推薦】,非常感謝!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章