Android多執行緒之IntentService

_小馬快跑_發表於2017-12-15

@author:小馬快跑 @email:mqcoder90@gmail.com @github:https://github.com/crazyqiang


####IntentService是什麼? IntentService繼承自Service,所以IntentService也是四大元件之一,IntentService內部封裝了HandlerThread執行緒 (只有一個執行緒) 來按順序處理非同步任務,通過startService(Intent) 來啟動IntentService並通過Intent來傳遞非同步任務,當任務結束後IntentService通過*stopSelf(int startId)來自己停止服務。IntentService是一個抽象類,如果想使用IntentService,首先建立一個類繼承IntentService,然後重寫onHandleIntent(Intent)*在子執行緒中處理Intent傳過來的任務。

IntentService特點:

  • onHandleIntent(Intent)發生在子執行緒,不能直接更新UI,需要先把結果發到Activity中
  • 提交的任務順序執行,如果一個任務A正在IntentService中執行,此時傳送另一個非同步任務B到IntentService中,那麼必須等到任務A執行完之後任務B才會開始執行
  • 已經在IntentService中執行的任務是不會被打斷的

####IntentService使用例子

先上效果圖:

IntentService.gif
可以看到,我們先啟動了第1個任務,當第1個任務還沒有執行完時,此時又啟動了第2個任務,第2個任務不會立即執行,而是等到第1個任務下載到100%完成之後才會開始第2個下載任務,這就驗證了IntentService會順序執行非同步任務,來看具體實現,首先繼承一個IntentService並覆寫onHandleIntent():

public class MyIntentService extends IntentService {
    public static final String ACTION_ONE = "action_one";
    public static final String ACTION_TWO = "action_two";
    private int progressOne, progressTwo;

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (intent == null) return;
        String action = intent.getAction();
        switch (action) {
            case ACTION_ONE:
                while (progressOne < 100) {
                    progressOne++;
                    sendBroadcast(getUpdateIntent(0, progressOne));
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case ACTION_TWO:
                while (progressTwo < 100) {
                    progressTwo++;
                    sendBroadcast(getUpdateIntent(1, progressTwo));
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                break;
        }
    }
 }
複製程式碼

onHandleIntent()是在子執行緒中執行,通過Intent接收任務然後執行任務,並通過BroadCastReceiver把運算結果不斷髮送到Activity中來更新UI,當所有任務執行完成以後,IntentService自動關閉。我們看到在IntentService中處理了任務,那麼這裡的任務是哪裡傳過來的呢?看下面程式碼:

 Intent intent = new Intent(IntentServiceActivity.this, MyIntentService.class);
 intent.setAction(MyIntentService.ACTION_ONE);
 startService(intent);
複製程式碼

我們看到通過startService(Intent)直接啟動並把任務傳遞到IntentService,最後別忘了在AndroidManifest.xml中定義IntentService:

 <service
     android:name=".multiThread.intentService.MyIntentService"
     android:screenOrientation="portrait" />
複製程式碼

完整原始碼地址:Android多執行緒之IntentService

####IntentService原始碼解析

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    public IntentService(String name) {
        super();
        mName = name;
    }
複製程式碼

首先定義變數,並在構造方法中傳入工作執行緒的名字。

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

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

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
複製程式碼

IntentService中上面三個方法的執行順序:onCreate>onStartCommand>onStart

1、在onCreate()中初始化一個HandlerThread執行緒並啟動,接著初始化一個ServiceHandler並把HandlerThread中的Looper作為引數傳入ServiceHandler,這樣就可以在主執行緒中通過ServiceHandler把Message傳送到HandlerThread子執行緒中處理了; 2、在onStartCommand()中又呼叫了onStart()並根據mRedelivery 返回START_REDELIVER_INTENT 或者是START_NOT_STICKY,這兩個有什麼區別呢?我們來複習一下在onStartCommand()中返回值:

  • START_STICKY:如果service程式被kill掉,保留service的狀態為開始狀態,但不保留遞送的intent物件。隨後系統會嘗試重新建立service,由於服務狀態為開始狀態,所以建立服務後一定會呼叫onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到service,那麼引數Intent將為null。

  • START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啟該服務

  • START_STICKY_COMPATIBILITY:START_STICKY的相容版本,但不保證服務被kill後一定能重啟。

  • START_FLAG_REDELIVERY:如果你實現onStartCommand()來安排非同步工作或者在另一個執行緒中工作, 那麼你可能需要使用START_FLAG_REDELIVERY來讓系統重新傳送一個intent。這樣如果你的服務在處理它的時候被Kill掉, Intent不會丟失.

所以當返回START_FLAG_REDELIVERY時,如果Service被異常Kill掉,在Service重啟以後會重新傳送Intent;如果返回START_NOT_STICKY,當Service被異常Kill掉時不會重新啟動。

3、在onStart()中把Intent封裝到Message中並通過ServiceHandler傳送到HandlerThread中了,經過HandlerThread中的Looper.loop()迴圈取訊息,最終還是還是ServiceHandler去處理訊息,所以我們來看ServiceHandler:

   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);
        }
    }

    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
複製程式碼

在handleMessage()中,我們發現回撥了onHandleIntent()方法,而這個方法是個抽象方法,也是在子類中我們必須要實現的,所以最終訊息的處理需要我們仔細去處理,注意這個回撥方法是在子執行緒中執行的,在執行完onHandleIntent()後,呼叫了stopSelf來關閉自己,關閉時IntentService回撥onDestroy():

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

我們看到在IntentService結束時呼叫了mServiceLooper.quit()來停止HandlerThread中Looper的迴圈,即HandlerThread執行緒沒有任務時不會再阻塞而是退出了。

相關文章