Android多線程開發之IntentService的使用

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/isee361820238/article/details/52643136

一、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.
 */

總結下來就是:
- IntentService是Service類的子類,用來處理異步請求
- 客戶端可以通過startService(Intent)方法傳遞請求給IntentService
- IntentService單獨開啓了一個線程來處理所有的Intent請求所對應的任務,以免事務處理阻塞主線程,而且任務是按先後順序逐個進行處理的
- 當IntentService處理完所有的任務後,它會在適當的時候自動結束服務

二、IntentService使用步驟

  1. 繼承IntentService,實現構造方法和onHandleIntent()方法;
  2. 在Manifest文件中註冊自己的IntentService類;
  3. 創建任務請求併發送到IntentService進行處理;
  4. 通過IntentService向其它組件發送任務處理的結果;
  5. 在接收的組件中進行後續處理。

三、IntentService使用實例

下面是一個模擬圖片上傳的demo。
ImgUploadService.java類:

public class ImgUploadService extends IntentService {
    public static final String TAG = "ImgUploadService";

    private static final String ACTION_UPLOAD_IMG = "com.demo.service.action.UPLOAD_IMAGE";
    public static final String EXTRA_IMG_PATH = "com.demo.service.extra.IMG_PATH";

    public ImgUploadService(String name) {
        super(name);
        Log.d(TAG, "ImgUploadService[" + " ThreadName: " + name + " ]");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d(TAG, "onHandleIntent");
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_UPLOAD_IMG.equals(action)) {
                final String path = intent.getStringExtra(EXTRA_IMG_PATH);
                handleUploadImg(path);
            }
        }
    }

    private void handleUploadImg(String path) {
        try {
            Thread.sleep(3000); //模擬上傳耗時
            Intent intent = new Intent(IntentServiceActivity.UPLOAD_RESULT);
            intent.putExtra(EXTRA_IMG_PATH, path);
            LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void startUploadImg(Context context, String path) {
        Intent intent = new Intent(context, ImgUploadService.class);
        intent.setAction(ACTION_UPLOAD_IMG);
        intent.putExtra(EXTRA_IMG_PATH, path);
        context.startService(intent);
    }

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

IntentServiceActivity.java類:

public class IntentServiceActivity extends Activity {
    public static final String UPLOAD_RESULT = "com.demo.service.UPLOAD_RESULT";

    private LinearLayout mLlContainer;
    private TextView mBtnUpload;
    int i = 0;

    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        setContentView(R.layout.activity_main_handlerthread);
        mLlContainer = (LinearLayout) findViewById(R.id.ll_container);
        mBtnUpload = (TextView) findViewById(R.id.btn_upload);
        registerReceiver();

        mBtnUpload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addTask(); //模擬上傳
            }
        });
    }

    private void registerReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(UPLOAD_RESULT);
        LocalBroadcastManager.getInstance(this).registerReceiver(mUploadImgReceiver, filter);
    }

    public void addTask() {
        String path = "圖片" + i++ + ".png";
        ImgUploadService.startUploadImg(this, path);

        TextView tv = new TextView(this);
        mLlContainer.addView(tv);
        tv.setText(path + " ....正在上傳中....");
        tv.setTag(path);
    }

    private void handleResult(String path) {
        TextView tv = (TextView) mLlContainer.findViewWithTag(path);
        tv.setText(path + "  ----上傳成功---- ");
    }

    private BroadcastReceiver mUploadImgReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(UPLOAD_RESULT)) {
                String path = intent.getStringExtra(ImgUploadService.EXTRA_IMG_PATH);
                handleResult(path);
            }
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mUploadImgReceiver);
    }
}

activity_main.xml文件:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_upload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:padding="10dp"
        android:text="發送上傳請求"/>
</LinearLayout>

四、IntentService源碼解析

IntentService本身就是一個Service,它擁有Service的所有生命週期方法,但內部是通過HandlerThread實現異步執行任務的,HandlerThread是一個內部維護了一個消息隊列的線程,對於這一點,可查看博客:Android多線程開發之HandlerThread的使用

既然IntentService有生命週期,那麼就從它的構造函數看起:

/**
 * Creates an IntentService.  Invoked by your subclass's constructor.
 *
 * @param name Used to name the worker thread, important only for debugging.
 */
public IntentService(String name) {
    super();
    mName = name;
}

構造方法很簡單,用於創建一個IntentService對象,參數namne用於定義工作線程的名字,僅用於調試作用。接下來看下IntentService的onCreate()方法:

@Override
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對象,並傳入工作線程的名字
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    // 開啓後臺工作線程
    thread.start();

    // 獲取後臺工作線程的Looper對象
    mServiceLooper = thread.getLooper();
    // 創建一個ServiceHandler對象,用來處理異步消息。
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

在onCreate()方法裏,首先利用HandlerThread類創建了一個循環的工作線程,接着獲取工作線程中的Looper對象並將其作爲參數創建了一個叫ServiceHandler的類,該類是IntentService的內部類,該類繼承了Handler,我們看它的定義:

public abstract class IntentService extends Service {
    //volatile關鍵字保證變量每次在使用的時候,都從主存中取。而不是從各個線程的“工作內存”中讀取
    private volatile Looper mServiceLooper;//
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

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

        @Override
        public void handleMessage(Message msg) {
            //一收到消息就回調到onHandleIntent()中進行處理
            onHandleIntent((Intent)msg.obj);
            //處理完消息就調用stopSelf()方法,並傳入消息的索引值
            stopSelf(msg.arg1);
        }
    }
}

從源碼中可以看到,mServiceLooper和mServiceHandler都加了volatile關鍵字修飾,這是爲了保證變量在每次使用的時候都從主內存中讀取,而不是從各個線程的“工作內存”中讀取,也就是說保證內存的可見性。

接着看onHandleIntent()方法,該方法是在ServiceHandler收到消息後回調的,也即onHandleIntent()方法是在HandlerThread工作線程中執行的,它是一個抽象方法,留給調用者去實現耗時任務的。

/**
* This method is invoked on the worker thread with a request to process.
 * Only one Intent is processed at a time, but the processing happens on a
 * worker thread that runs independently from other application logic.
 * So, if this code takes a long time, it will hold up other requests to
 * the same IntentService, but it will not hold up anything else.
 * When all requests have been handled, the IntentService stops itself,
 * so you should not call {@link #stopSelf}.
 *
 * @param intent The value passed to {@link
 *               android.content.Context#startService(Intent)}.
 */
protected abstract void onHandleIntent(Intent intent);

此外,因爲任務是通過ServiceHandler發送給異步線程的消息隊列來處理的,而消息隊列裏的消息是依次取出並執行的,所以從這裏可以瞭解到任務必定是串行執行的。而執行完一個消息後,調用了一個帶有startId參數的stopSelf()方法:

** * 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) {
    }
}

該方法實際上是通過調用ActivityManager的stopServiceToken()方法來停止當前服務的,不過服務不會馬上停止,而是等待繼續完成剩下的任務後才自動結束,因爲方法參數裏攜帶了一個startId,它可以看做是一個請求的唯一標識,只有當所有的請求都結束,我們的Service才自行銷燬,而startId是從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(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

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

對於Service,每次調用startServcie方法啓動一個服務,都會調用一次onStartCommand()方法,每次也會帶一個startId過去,如果執行多個任務,那麼就會存在多個startId,然後在onStart()方法中通過mServiceHandler獲得一個消息對象msg,然後將startId作爲該消息的消息碼,將異步任務請求intent作爲消息內容封裝成一個消息msg發送到mServiceHandler中進行異步處理,最後回調到onHandleIntent()中,子類通過實現onHandleIntent()即可進行異步耗時任務的執行。

這裏注意,我們不需要自己去重寫onStartCommand()和onStart()方法,但onHandleIntent()必須重寫。

當所有的startId都處理完後,IntentService會調用onDestroy()自行銷燬,消息隊列也會自行退出。

@Override
public void onDestroy() {
    mServiceLooper.quit();
}

這裏還有兩個方法,留給子類必要的時候去實現它們,分別是setIntentRedelivery()和onBind()方法:

/**
 * Sets intent redelivery preferences.  Usually called from the constructor
 * with your preferred semantics.
 *
 * <p>If enabled is true,
 * {@link #onStartCommand(Intent, int, int)} will return
 * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
 * {@link #onHandleIntent(Intent)} returns, the process will be restarted
 * and the intent redelivered.  If multiple Intents have been sent, only
 * the most recent one is guaranteed to be redelivered.
 *
 * <p>If enabled is false (the default),
 * {@link #onStartCommand(Intent, int, int)} will return
 * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
 * dies along with it.
 */
public void setIntentRedelivery(boolean enabled) {
    mRedelivery = enabled;
}
/**
 * Unless you provide binding for your service, you don't need to implement this
 * method, because the default implementation returns null. 
 * @see android.app.Service#onBind
 */
@Override
public IBinder onBind(Intent intent) {
    return null;
}

setIntentRedelivery()表示設置是否重新派發,通常在構造方法中進行設置。如果設置爲true,onStartCommand()將會返回START_REDELIVER_INTENT,如果在onHandleIntent()返回之前進程已經死掉了,那麼進程將會重新啓動,intent會重新發送,如果有大量的intent發送了,那麼只會保證最近的intent會被重新派發。如果設置爲false(也即默認的值),那麼onStartCommand()將會返回START_NOT_STICKY,這時如果進程死了,Intent也會停止傳遞。

對於onBind()方法,默認返回了null,除非我們是通過bindService()啓動的服務,否則我們也不需要實現它。

五、IntentService應用場景

從前面的分析可以知道IntentService有以下特點:
- IntentService繼承自Service,任務的執行都是以隊列的形式進行的,而且不受UI生命週期的影響
- 創建了一個獨立的工作線程來處理onStartCommand()發送過來的任務,並逐個排隊進行處理
- 不需要自己主動啓動和關閉線程
- 不需要主動調用stopSelf()來結束服務,任務處理完後會自動進行關閉

但也有以下侷限性:
- 不能直接和UI進行交互,需自行處理線程之間的通信
- 工作任務是順序執行的,一旦隊列中有某個任務執行時間過長,那麼就會導致後續的任務都會被延遲處理
- 正在執行的任務無法被打斷

所以IntentService只適合處理簡單的順序執行的後臺耗時任務,不適合處理高併發的後臺耗時任務。比如多文件下載,多圖片上傳等都可用IntentService進行處理,處理完後若需要進行通知,可通過廣播或者其它一些線程切換工具進行異步通知,比如EventBus等。

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