理解 IntentService 原理

本人只是 Android小菜一個,寫技術文檔只是爲了總結本身在最近學習到的知識,歷來不敢爲人師,若是裏面有些不正確的地方請你們盡情指出,謝謝!java

1.概述

service的做用相信你們都是很是熟悉的,主要用來在後臺進行任務處理,例如後臺播放音樂、下載文件、上傳文件等等。因爲service是運行在主線程中的,也有必定的時間限制,若是在主線程中對一個任務的處理時間超過了限制,進程就會出現「應用不響應」,即ANR, Application Not Responding。爲了不這樣狀況,都會在service裏用新的thread處理一些可能須要更多處理時間的任務。android

其實Android早就替咱們設計了一種更方便的service + thread模式,就是本文要講的IntentService,經過它能夠很方便地實如今service中使用thread進行耗時任務的處理。express

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

2. IntentService 的使用

在知道如何使用前,先看看IntentService究竟是什麼東西,它的聲明以下:app

/** * 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的典範,它會清楚地告訴你「是什麼」和「怎麼用」的問題,針對相對複雜的狀況,還會直接在聲明裏給出範例。async

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

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秒模擬上傳過程。oop

在寫好了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,感興趣的同窗能夠本身去研究。

相關文章
相關標籤/搜索