5.1 Service使用大全

點此進入:從零快速構建APP系列目錄導圖
點此進入:UI編程系列目錄導圖
點此進入:四大組件系列目錄導圖
點此進入:數據網絡和線程系列目錄導圖

本節代碼下載地址:WillFlowService

一、服務是什麼?

服務(Service)是一種在後臺運行的組件,是 Android 中實現程序後臺運行的解決方案,它非常適合用於去執行那些不需要和用戶交互而且還要求長期運行的或爲遠程進程執行作業的任務。服務的運行不依賴於任何用戶界面,例如,當用戶位於其他應用中時,服務可能在後臺播放音樂或者通過網絡獲取數據,但不會阻斷用戶與 Activity 的交互。所以即使當程序被切換到後臺,或者用戶打開了另外一個應用程序,服務仍然能夠保持正常運行。

不過需要注意的是,服務並不是運行在一個獨立的進程當中的,而是依賴於創建服務時所在的應用程序進程。當某個應用程序進程被殺掉時,所有依賴於該進程的服務也會停止運行。另外,也不要被服務的後臺概念所迷惑, 實際上服務並不會自動開啓線程,默認情況,如果沒有顯示的指 Service 所運行的進程,Service 和 Activity 是運行在當前 App 所在進程的 Mainthread(UI 主線程)裏面的。也就是說,我們需要在服務的內部手動創建子線程,並在這裏執行具體的任務,尤其是網絡請求、拷貝數據庫、大文件等類似的耗時操作,否則就有可能出現主線程被阻塞住的情況。

不過也有特殊情況,就是說我們可以在清單文件配置 Service 執行所在的進程,讓 Service 在另外的進程中執行:

        <service
            android:name=".MyService"
            android:enabled="true"
            android:process=":remote" >
        </service>

二、服務的生命週期

之前我們學習過了Activity以及Fragment的生命週期,與之相類似地,服務也有自己的生命週期,前面我們使用到的 onCreate()、onStartCommand()、onBind() 和 onDestroy() 等方法都是在服務的生命週期內可以回調的方法。

(1)服務的生命週期回調方法

要創建服務,我們必須創建 Service 的子類(或使用它的一個現有子類)。在實現中,我們需要重寫一些回調方法,以處理服務生命週期的某些關鍵方面並提供一種機制將組件綁定到服務,應重寫的最重要的回調方法包括:

1、onStartCommand()

當另一個組件(如 Activity)通過調用 startService() 請求啓動服務時,系統將調用此方法。一旦執行此方法,服務即會啓動並可在後臺無限期運行。 如果我們實現此方法,則在服務工作完成後,需要由我們通過調用 stopSelf() 或 stopService() 來停止服務。(如果我們只想提供綁定,則無需實現此方法。)

2、onBind()

當另一個組件想通過調用 bindService() 與服務綁定(例如執行 RPC)時,系統將調用此方法。在此方法的實現中,我們必須通過返回 IBinder 提供一個接口,供客戶端用來與服務進行通信。還是要務必實現此方法,但如果我們並不希望允許綁定,則應返回 null。

3、onCreate()

首次創建服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或 onBind() 之前)。如果服務已在運行,則不會調用此方法。

4、onDestroy()

當服務不再使用且將被銷燬時,系統將調用此方法。服務應該實現此方法來清理所有資源,如線程、註冊的偵聽器、接收器等,這是服務接收的最後一個調用。

(2)控制服務的開啓和停止方法

1、startService() 方法

一旦在項目的任何位置調用了 Context 的 startService() 方法,相應的服務就會啓動起來,並回調 onStartCommand() 方法。如果這個服務之前還沒有創建過, onCreate() 方法會先於 onStartCommand()方法執行。服務啓動了之後會一直保持運行狀態,直到 stopService() 或 stopSelf() 方法被調用。注意雖然每調用一次 startService() 方法, onStartCommand() 就會執行一次,但實際上每個服務都只會存在一個實例。所以不管你調用了多少次 startService( )方法,只需調用一次 stopService() 或 stopSelf() 方法,服務就會停止下來了。

2、bindService() 方法

另外,還可以調用 Context 的 bindService() 來獲取一個服務的持久連接,這時就會回調服務中的 onBind() 方法。類似地,如果這個服務之前還沒有創建過, onCreate() 方法會先於 onBind() 方法執行。之後,調用方可以獲取到 onBind() 方法裏返回的 IBinder 對象的實例,這樣就能自由地和服務進行通信了。只要調用方和服務之間的連接沒有斷開,服務就會一直保持運行狀態。

3、stopService() 方法

當調用了 startService() 方法後,又去調用 stopService() 方法,這時服務中的 onDestroy() 方法就會執行,表示服務已經銷燬了。類似地,當調用了 bindService() 方法後,又去調用 unbindService() 方法, onDestroy() 方法也會執行,這兩種情況都很好理解。但是需要注意,我們是完全有可能對一個服務既調用了 startService()方法,又調用了 bindService()方法的,這種情況下該如何才能讓服務銷燬掉呢?根據 Android 系統的機制,一個服務只要被啓動或者被綁定了之後,就會一直處於運行狀態,必須要讓以上兩種條件同時不滿足,服務才能被銷燬。所以,這種情況下要同時調用 stopService() 和 unbindService() 方法, onDestroy() 方法纔會執行。

(3)創建啓動服務

服務啓動之後,其生命週期即獨立於啓動它的組件,並且可以在後臺無限期地運行,即使啓動服務的組件已被銷燬也不受影響。 因此,服務應通過調用 stopSelf() 結束工作來自行停止運行,或者由另一個組件通過調用 stopService() 來停止它。應用組件(如 Activity)可以通過調用 startService() 方法並傳遞 Intent 對象(指定服務幷包含待使用服務的所有數據)來啓動服務,服務通過 onStartCommand() 方法接收此 Intent。

例如,假設某 Activity 需要將一些數據保存到在線數據庫中,該 Activity 可以啓動一個協同服務,並通過向 startService() 傳遞一個 Intent,爲該服務提供要保存的數據。服務通過 onStartCommand() 接收 Intent,連接到互聯網並執行數據庫事務,事務完成之後,服務會自行停止運行並隨即被銷燬。

注意:默認情況下,服務與服務聲明所在的應用運行於同一進程,而且運行於該應用的主線程中。 因此,如果服務在用戶與來自同一應用的 Activity 進行交互時執行密集型或阻止性操作,則會降低 Activity 性能。所以爲了避免影響應用性能,我們一般在服務內啓動新線程。

一般來說,我們可以擴展兩個類來創建啓動服務:
1、Service

這是適用於所有服務的基類,擴展此類時,必須創建一個用於執行所有服務工作的新線程,因爲默認情況下,服務將使用應用的主線程,也就是我們說的UI線程,但這會降低應用正在運行的所有 Activity 的性能。

2、IntentService

這是 Service 的子類,它使用工作線程逐一處理所有啓動請求。如果我們不要求服務同時處理多個請求,那麼這是最好的選擇。我們只需實現 onHandleIntent() 方法即可,該方法會接收每個啓動請求的 Intent,使我們能夠執行後臺工作。

三、服務的使用

瞭解了服務的生命週期方法和控制方法後,下面就讓我們開始對服務的相關內容進行學習。作爲 Android 四大組件之一,服務也少不了有很多非常重要的知識點,那我們自然要從最基本的用法開始學習了。

(1)定義一個服務

首先看一下如何在項目中定義一個服務。

新建 MyService 的類,代碼如下所示:
/**
 * Created by   : WGH.
 */
public class MyService extends Service{
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

目前 MyService 中可以算是空空如也,但有一個 onBind() 方法特別醒目,這個方法是 Service 中唯一的一個抽象方法,所以必須要在子類裏實現。我們會在後面使用到 onBind() 方法,目前可以暫時將它忽略掉。

既然是定義一個服務,自然應該在服務中去處理一些事情,那處理事情的邏輯應該寫在哪裏呢?這時就可以重寫 Service 中的另外一些方法了,如下所示:

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate()");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy()");
        super.onDestroy();
    }

可以看到,這裏我們又重寫了 onCreate()、 onStartCommand() 和 onDestroy()這三個方法,它們是每個服務中最常用到的三個方法。其中 onCreate() 方法會在服務創建的時候調用,onStartCommand() 方法會在每次服務啓動的時候調用, onDestroy() 方法會在服務銷燬的時候調用。通常情況下,如果我們希望服務一旦啓動就立刻去執行某個動作,就可以將邏輯寫在 onStartCommand() 方法裏。而當服務銷燬時,我們又應該在 onDestroy()方法中去回收那些不再使用的資源。

另外需要注意,每一個服務都需要在 AndroidManifest.xml 文件中進行註冊才能生效,不知道你有沒有發現,這是 Android四大組件共有的特點。於是我們還應該修改 AndroidManifest.xml 文件,代碼如下所示:

    <service android:name=".MyService">

    </service>

(2)啓動和停止服務

定義好了服務之後,接下來就應該考慮如何去啓動以及停止這個服務。啓動和停止的方法當然我們也不會陌生,主要是藉助 Intent 來實現的,下面就讓我們嘗試去啓動以及停止 MyService 這個服務。

修改 activity_main.xml 中的代碼,如下所示:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wgh.willflowservice.MainActivity">

    <Button
        android:id="@+id/start_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開啓服務"
        android:textColor="#0d73f9"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.501"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.218" />

    <Button
        android:id="@+id/stop_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止服務"
        android:textColor="#ff3f3f"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.501"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.382" />

</android.support.constraint.ConstraintLayout>

這裏我們在佈局文件中加入了兩個按鈕,分別是用於啓動服務和停止服務的。

然後修改 MainActivity 中的代碼,如下所示:
/**
 * Created by   : WGH.
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = MainActivity.class.getSimpleName();
    private Button mButtonStart;
    private Button mButtonStop;

    /**
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        mButtonStart = (Button) findViewById(R.id.start_service);
        mButtonStop = (Button) findViewById(R.id.stop_service);

        mButtonStart.setOnClickListener(this);
        mButtonStop.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                Log.i(TAG, "啓動服務");
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                Log.i(TAG, "停止服務");
                break;
        }
    }
}

可以看到,這裏在 onCreate() 方法中分別獲取到了 Start Service 按鈕和 Stop Service 按鈕的實例,並給它們註冊了點擊事件。然後在 Start Service 按鈕的點擊事件裏, 我們構建出了一個 Intent 對象,並調用 startService() 方法來啓動 MyService 這個服務。 在 Stop Serivce 按鈕的點擊事件裏,我們同樣構建出了一個 Intent 對象,並調用 stopService() 方法來停止 MyService 這個服務。

startService() 和 stopService() 方法都是定義在 Context 類中的,所以我們在 Activity 裏可以直接調用這兩個方法。注意,這裏完全是由Activity來決定服務何時停止的,如果沒有點擊 Stop Service 按鈕,服務就會一直處於運行狀態。那服務有沒有什麼辦法讓自已停止下來呢?當然可以,就像我們之前所說的,只需要在 MyService 的任何一個位置調用 stopSelf() 方法就能讓這個服務停止下來了。

編譯運行看效果:

**再次提醒:**onCreate()方法是在服務第一次創建的時候調用的,而 onStartCommand()方法則在每次啓動服務的時候都會調用,由於剛開始我們是第一次點擊”開啓服務”按鈕,服務此時還未創建過,所以兩個方法都會執行,之後再連續多點擊幾次”開啓服務”按鈕,我們發現就只有 onStartCommand()方法可以得到執行了。

(3)Service和Activity進行通信

前面的篇幅中我們學習了啓動和停止Service的方法,不知道你有沒有發現,雖然Service是在Activity裏啓動的,但在啓動了Service之後,Activity與Service基本就沒有什麼關係了。確實如此,我們在Activity裏調用了 startService() 方法來啓動 MyService 這個 Service,然後 MyService 的 onCreate() 和 onStartCommand() 方法就會得到執行。之後 Service 會一直處於運行狀態,但具體運行的是什麼邏輯, Activity 就控制不了了。這就類似於 Activity 通知了Service一下:“你可以啓動了!”然後Service就去忙自己的事情了,但 Activity 並不知道 Service 到底去做了什麼事情,以及完成的如何。

那麼有沒有什麼辦法能讓 Activity 和 Service 的關係更緊密一些呢?例如在 Activity 中指揮 Service 去幹什麼,Service 就去幹什麼。當然可以,這就需要藉助我們剛剛忽略的 onBind() 方法了。比如說目前我們希望在 MyService 裏提供一個下載功能,然後在 Activity 中可以決定何時開始下載,以及隨時查看下載進度。實現這個功能的思路是創建一個專門的 Binder 對象來對下載功能進行管理,修改 MyService 中的代碼,如下所示:

    private DownloadBinder mBinder = new DownloadBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind()");
        return mBinder;
    }

    class DownloadBinder extends Binder {
        public void startDownload() {
            Log.i(TAG, "開始下載!");
            Toast.makeText(getApplicationContext(), "開始下載!", Toast.LENGTH_SHORT).show();
        }
        public int getProgress() {
            Log.i(TAG, "獲取進度!");
            Toast.makeText(getApplicationContext(), "獲取進度!", Toast.LENGTH_SHORT).show();
            return 0;
        }
    }

首先,這裏我們新建了一個 DownloadBinder 類,並讓它繼承自 Binder,然後在它的內部提供了開始下載以及查看下載進度的方法。當然這只是兩個模擬方法,並沒有實現真正的功能,我們在這兩個方法中分別打印了一行日誌和彈出吐司。

接着,在 MyService 中創建了 DownloadBinder 的實例,然後在 onBind()方法裏返回了這個實例,這樣 MyService 中的工作就全部完成了。下面就要看一看,在Activity中如何去調用服務裏的這些方法了。

修改 activity_main.xml 中的代碼,如下所示:
    <Button
        android:id="@+id/bind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="綁定服務"
        android:textColor="#63b400"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.454" />

    <Button
        android:id="@+id/unbind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="解綁服務"
        android:textColor="#823bed"
        android:textSize="26dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.58" />

這兩個按鈕分別是用於綁定服務和取消綁定服務的,那到底誰需要去和服務綁定呢?當然就是Activity了,當一個Activity和服務綁定了之後,就可以調用該服務裏的 Binder 提供的方法了。

修改 MainActivity 中的代碼,如下所示:
    private static boolean mBindFlag = false;
    private MyService.DownloadBinder mDownloadBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        mButtonBind = (Button) findViewById(R.id.bind_service);
        mButtonUnbind = (Button) findViewById(R.id.unbind_service);
        mButtonBind.setOnClickListener(this);
        mButtonUnbind.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.bind_service:
                Intent bindIntent = new Intent(this, MyService.class);
                mBindFlag = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE); // 綁定服務
                Log.i(TAG, "綁定服務");
                break;
            case R.id.unbind_service:
                if (mBindFlag) {
                    unbindService(serviceConnection);
                    Log.i(TAG, "解綁服務");
                    mBindFlag = false;
                }
                break;
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mDownloadBinder = (MyService.DownloadBinder) service;
            mDownloadBinder.startDownload();
            mDownloadBinder.getProgress();
        }
    };

首先,這裏我們創建了一個 ServiceConnection 的匿名類,在裏面重寫了 onServiceConnected() 方法和 onServiceDisconnected() 方法,這兩個方法分別會在Activity與服務成功綁定以及解除綁定的時候調用。在 onServiceConnected() 方法中,我們又通過向下轉型得到了 DownloadBinder 的實例,有了這個實例, Activity和服務之間的關係就變得非常緊密了。

現在我們可以在Activity中根據具體的場景來調用 DownloadBinder 中的任何 public 方法,即實現了指揮服務幹什麼,服務就去幹什麼的功能。這裏仍然只是做了個簡單的測試, 在 onServiceConnected() 方法中調用了 DownloadBinder 的 startDownload() 和 getProgress() 方法。

當然,現在Activity和服務其實還沒進行綁定呢,這個功能是在 Bind Service 按鈕的點擊事件裏完成的。可以看到,這裏我們仍然是構建出了一個 Intent 對象,然後調用 bindService() 方法將 MainActivity 和 MyService 進行綁定。bindService() 方法接收三個參數,第一個參數就是剛剛構建出的 Intent 對象,第二個參數是前面創建出的 ServiceConnection 的實例,第三個參數則是一個標誌位,這裏傳入 BIND_AUTO_CREATE 表示在活動和服務進行綁定後自動創建服務,這會使得 MyService 中的 onCreate()方法得到執行,但 onStartCommand()方法不會執行。bindService() 方法返回一個boolean類型,這就讓我們得知綁定的成功與否。然後如果我們想解除活動和服務之間的綁定該怎麼辦呢?調用一下 unbindService() 方法就可以了,這也是 Unbind Service 按鈕的點擊事件裏實現的功能。

不過在解除綁定之前我們還是要判斷一下 mBindFlag 這個標識位,否則可能出現這樣的情況:在綁定服務成功後連續第兩次點擊“解綁服務”按鈕或者沒有綁定服務而直接點擊“解綁服務”按鈕,那麼此時就會導致應用崩潰。所以我們用此標識位來判斷服務綁定的成功與否或者是否解綁過,以此避免應用崩潰的情況發生。

編譯運行看效果:

我們可以看到,首先是 MyService 的 onCreate() 方法得到了執行,然後是 onBind() 方法,最後 startDownload() 和 getProgress() 方法都得到了執行,說明我們確實已經在Activity裏成功調用了服務裏提供的方法了。

注意:任何一個服務在整個應用程序範圍內都是通用的,即 MyService 不僅可以和 MainActivity 綁定,還可以和任何一個其他的活動進行綁定,而且在綁定完成後它們都可以獲取到相同的 ownloadBinder 實例。

點此進入:GitHub開源項目“愛閱”,下面是“愛閱”的效果圖:


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