理解 IntentService 原理

我愛宋慧喬發表於2019-02-27

本人只是 Android小菜一個,寫技術文件只是為了總結自己在最近學習到的知識,從來不敢為人師,如果裡面有些不正確的地方請大家盡情指出,謝謝!

1.概述

service的作用相信大家都是非常熟悉的,主要用來在後臺進行任務處理,例如後臺播放音樂、下載檔案、上傳檔案等等。由於service是執行在主執行緒中的,也有一定的時間限制,如果在主執行緒中對一個任務的處理時間超過了限制,程式就會出現“應用不響應”,即ANR, Application Not Responding。為了避免這樣情況,都會在service裡用新的thread處理一些可能需要更多處理時間的任務。

其實Android早就替我們設計了一種更方便的service + thread模式,就是本文要講的IntentService,通過它可以很方便地實現在service中使用thread進行耗時任務的處理。

本文將首先給大家演示下它的基本使用方式,再講解下IntentService的內部原理。

2. IntentService 的使用

在知道如何使用前,先看看IntentService到底是什麼東西,它的宣告如下:

/**
 * IntentService is a base class for {@link Service}s that handle asynchronous
 * requests (expressed as {@link Intent}s) on demand.  Clients send requests
 * through {@link android.content.Context#startService(Intent)} calls; the
 * service is started as needed, handles each Intent in turn using a worker
 * thread, and stops itself when it runs out of work.
 *
 * <p>This "work queue processor" pattern is commonly used to offload tasks
 * from an application's main thread.  The IntentService class exists to
 * simplify this pattern and take care of the mechanics.  To use it, extend
 * IntentService and implement {@link #onHandleIntent(Intent)}.  IntentService
 * will receive the Intents, launch a worker thread, and stop the service as
 * appropriate.
 *
 * <p>All requests are handled on a single worker thread -- they may take as
 * long as necessary (and will not block the application's main loop), but
 * only one request will be processed at a time.
 */
public abstract class IntentService extends Service { ... }
複製程式碼

相信大家都能很容易看懂這段宣告的意思,小菜在這裡簡單為大家總結下,這麼一大段文字主要是說明了兩個問題:

  1. IntentService是什麼:用來進行處理非同步請求的服務,其內部有一個工作執行緒,所有傳送給服務的請求都會在這個工作執行緒中按序執行,在處理完所有請求後服務會自動停止。
  2. IntentService如何使用:擴充IntentService並在其擴充類或者叫子類中實現onHandleIntent(Intent)介面,在這個介面中進行實際的請求處理,這些請求通過Context.startService(Intent)來進行傳送。

Android SDK真的可以作為所有SDK的典範,它會清楚地告訴你“是什麼”和“怎麼用”的問題,針對相對複雜的情況,還會直接在宣告裡給出範例。

既然我們已經知道要如何使用IntentService了,就讓我們用一個小例子來演示一下:

2.1 服務端

服務端指的是IntentService端,其作用是接收客戶端傳送過來的請求並處理。

public class TestIntentService extends IntentService {
    private static final String TAG = "TestIntentService";
    
    private static final String DEFAULT_NAME = "default_name";
    
    // 為了區分不同的請求和方便呼叫端使用,直接定義了不同的 ACTION.
    public static final String DOWNLOAD_ACTION = "com.test.intent.action.DOWNLOAD";
    public static final String UPLOAD_ACTION = "com.test.intent.action.UOLOAD";

    // 要在 AndroidManifest.xml 裡宣告 servcie,必須提供一個無參建構函式.
    public TestIntentService() {
        // IntentService 的建構函式需要提供一個工作執行緒的名字資訊.
        super(DEFAULT_NAME);
    }

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

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

    @Override
    public void onHandleIntent(Intent intent) {
        String action = intent.getAction();
        // 根據不同的請求型別進行不同的處理,這裡只是休眠一段時間,並沒有進行實際的處理。
        if (DOWNLOAD_ACTION.equals(action)) {
            try {
                Log.i(TAG, "onHandleIntent, start to download");
                Thread.sleep(30 * 1000);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        } else if (UPLOAD_ACTION.equals(action)) {
            try {
                Log.i(TAG, "onHandleIntent, start to upload");
                Thread.sleep(40 * 1000);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
    }
}
複製程式碼

在這段程式碼裡,請求處理函式onHandleIntent(Intent)會根據接收到的請求進行不同的處理,如果收到的是“下載”請求就休眠30秒模擬下載過程,如果收到的是“上傳”請求就休眠40秒模擬上傳過程。

在寫好了service邏輯後一定不要忘記在AndroidManifest.xml對其進行註冊,否則是無法使用的,註冊程式碼如下:

<service android:name=".TestIntentService" />
複製程式碼

這裡只是簡單地對其進行註冊,並沒有設定其他相關屬性,例如intent-filter,因為這些和本文所講內容並無直接關係。

2.2 客戶端

客戶端主要是用來向服務端傳送請求。

// 傳送“下載”請求
Intent downloadIntent = new Intent(this, TestIntentService.class);
downloadIntent.setAction(TestIntentService.DOWNLOAD_ACTION);
startService(downloadIntent);

// 傳送“上傳”請求
Intent upIntent = new Intent(this, TestIntentService.class);
upIntent.setAction(TestIntentService.UPLOAD_ACTION);
startService(upIntent);
複製程式碼

現在看當傳送這“下載”和“上傳”請求後,IntentService是如何響應的:

02-27 12:58:23.100 24190 24190 I TestIntentService: onCreate
02-27 12:58:23.102 24190 24240 I TestIntentService: onHandleIntent, start to download
02-27 12:58:53.107 24190 24240 I TestIntentService: onHandleIntent, start to upload
02-27 12:59:33.115 24190 24190 I TestIntentService: onDestroy
複製程式碼

可以看到:在傳送“下載”請求的時候,service首先被建立,然後開始處理這個“下載請求”,僅接著“上傳”請求也被接收並在處理完第一個請求後開始處理,在處理完所有請求後service被自動銷燬。

3. IntentService 的原理

前面已經講了如何通過IntentService實現在工作執行緒中處理較耗時任務,那麼IntentService內部又是如何實現的呢?本節我們通過分析它的原始碼來一探究竟。

3.1 建立工作執行緒

既然IntentService的功能是在工作執行緒中處理任務,首先來看看這個工作執行緒是如何建立出來的。

public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.

    super.onCreate();
    // 建立工作執行緒
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
    
    // 和工作執行緒內部的訊息迴圈關聯
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}
複製程式碼

IntentService第一次啟動的時候會呼叫其onCreate來完成一些初始化操作:

  1. 首先建立了一個HandlerThread物件,這就是前面一直提到的“工作執行緒”。大家對HandlerThread都很瞭解,那這個HandlerThread是什麼呢?簡單來說,它就是內部有一個訊息迴圈佇列的執行緒,我們知道預設的執行緒內部是沒有訊息迴圈佇列的,這就導致我們無法直接在其內部使用HandlerAndroid為了方便使用,直接提供了一個含有訊息迴圈佇列的HandlerThread
  2. 利用已建立的HandlerThread內部的訊息迴圈建立一個 ServiceHandler物件,這樣它的訊息處理函式handleMessage就會在對應的執行緒中執行了。

3.2 接收和處理請求

既然工作執行緒已經建立完成,這時就要考慮如何接收和處理客戶端傳送過來的請求了,已經瞭解到客戶端是通過startService來傳送請求的,結合service的生命週期,會執行onStartCommand回撥:

/**
 * You should not override this method for your IntentService. Instead,
 * override {@link #onHandleIntent}, which the system calls when the IntentService
 * receives a start request.
 * @see android.app.Service#onStartCommand
 */
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}
複製程式碼

從這段程式碼看到,onStartCommand會直接呼叫onStart,在這裡對傳送過來的請求接收並通過mServiceHandler進行處理。

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}
複製程式碼

handleMessage中對接收到的請求用onHandleIntent進行實際處理,而onHandleIntent就是我們在使用過程中必須實現的處理邏輯。

3.3 銷燬工作執行緒

前面提到:當所有請求都被處理完成後,service就會被銷燬,這是如何實現的呢?在上面看到handleMessage方法裡在處理完當前請求時會呼叫stopSelf(msg.arg1)來嘗試停止當前服務,之所以說“嘗試”,是因為它不一定能真正停止服務。還是來看下stopSelf(int)的實現程式碼:

/**
 * Old version of {@link #stopSelfResult} that doesn't return a result.
 *  
 * @see #stopSelfResult
 */
public final void stopSelf(int startId) {
    if (mActivityManager == null) {
        return;
    }
    try {
        mActivityManager.stopServiceToken(
                new ComponentName(this, mClassName), mToken, startId);
    } catch (RemoteException ex) {
    }
}

/**
 * Stop the service if the most recent time it was started was 
 * <var>startId</var>.  This is the same as calling {@link 
 * android.content.Context#stopService} for this particular service but allows you to 
 * safely avoid stopping if there is a start request from a client that you 
 * haven't yet seen in {@link #onStart}. 
 */
public final boolean stopSelfResult(int startId) {
    if (mActivityManager == null) {
        return false;
    }
    try {
        return mActivityManager.stopServiceToken(
                new ComponentName(this, mClassName), mToken, startId);
    } catch (RemoteException ex) {
    }
    return false;
}
複製程式碼

stopSelf(int)的宣告裡提到它是stopSelfResult(int)的老版本,唯一的區別就是沒有返回值。那我們直接看stopSelfResult(int)的宣告,其中提到只有在當前的service的最近一次啟動是startId發起的才會被停止。我們把這句話放在IntentService的場景裡去理解,如果說當前接收到3個請求,在處理第一個請求後打算去停止服務,但是呼叫stopSelf(int)的時候發現最後一次啟動是第三個請求發生的,並不會停止服務;處理完第二個請求後是類似的,只有在處理完第三個請求後,去嘗試停止服務,這時發現最近一次啟動就是它發起的,可以去停止服務了。

停止服務時,其onDestroy會得到呼叫:

@Override
public void onDestroy() {
    mServiceLooper.quit();
}
複製程式碼

在這裡會停止工作執行緒的訊息迴圈,等待執行緒退出。

4. 總結

IntentService能夠接受使用者傳送的請求並在工作執行緒中順序處理,處理完成後自動退出,但是由於從 Android O開始對後臺服務增加了更嚴格的控制,導致當前程式在後臺時其含有的後臺服務也無法長期存活,IntentService的使用也有了一定的限制,推薦使用更好的JobIntentService,感興趣的同學可以自己去研究。

相關文章