Android-Service詳解

kiba518發表於2023-02-11

前言

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了。

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

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