HandlerThread和IntentService的使用詳解

在Android開發(fā)中,我們知道可以在主線程中直接使用Handler,這是因為在APP的入口,系統(tǒng)就已經(jīng)調(diào)用Looper.prepareMainLooper(),和Looper.loop(),但是如果我們想在子線程使用handler颗味,也很簡單浦旱,只要先使用Looper.prepare();然后調(diào)用Looper.loop()后痢掠,就可使用handler了产还,但是可能有些小伙伴覺得也麻煩厘熟,是不是應(yīng)該有一種對這種進行封裝的框架呢屯蹦?沒錯,系統(tǒng)里有個HandlerThread就是干這件事的绳姨。

我們來看看HandlerThread的使用:

        HandlerThread thread = new HandlerThread("MyHandlerThread");
        thread.start();

        Looper looper = thread.getLooper();
        MyHandler myHandler = new MyHandler(this,looper);

首先登澜,創(chuàng)建一個HandlerThread對象,然后必須先調(diào)用start()方法飘庄,因為HandlerThread是繼承Thread的脑蠕,調(diào)用start()開啟線程的執(zhí)行,可以看一下其run()方法的源碼:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

可以看到跪削,run方法里面做了對Looper的初始化操作谴仙,初始化完成后回調(diào)了onLooperPrepared()方法,當(dāng)我們需要做一些在Looper初始化完成并在開啟輪詢之前的操作的時候碾盐,我們就可以繼承該類然后復(fù)寫自己的onLooperPrepared方法做我們需要的工作狞甚,這樣設(shè)計增加了其擴展性。

下面我們回到主線程的使用當(dāng)中廓旬,接著調(diào)用thread.getLooper()哼审,看一下該方法源碼:

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

首先,判斷Looper的線程是否存活的孕豹,然后進入同步代碼塊中涩盾,此代碼塊是個while循環(huán),如果線程是存活的并且mLoope這時還沒有被初始化好励背,這時該線程(主線程)就會等待春霍,等到mLoope初始化好之后,子線程會調(diào)用notifyAll()來喚醒叶眉,然后返回mLooper址儒。

接下來我們就可以使用返回的Looper創(chuàng)建Handler了:
MyHandler myHandler = new MyHandler(this,looper);
這樣通過此handler發(fā)送的消息都會被創(chuàng)建此Looper的子線程拿去處理了芹枷。

我們再來看一下HandlerThread的一個典型的應(yīng)用:系統(tǒng)為我們提供的IntentService ,先貼出其源碼:


package android.app;

import android.annotation.WorkerThread;
import android.annotation.Nullable;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;


/**
 * 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.
 */
public abstract class IntentService extends Service {
    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((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    /**
     * 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;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

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

    /**
     * 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 onDestroy() {
        mServiceLooper.quit();
    }

    /**
     * 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
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 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)}.
     *               This may be null if the service is being restarted after
     *               its process has gone away; see
     *               {@link android.app.Service#onStartCommand}
     *               for details.
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);


}

先介紹一下IntentService莲趣,這是一個Service的子類鸳慈,專門用來在子線程處理異步任務(wù)的,處理完成之后會自動的結(jié)束服務(wù)喧伞。
我們可以看到走芋,在onCreate()方法中使用了HandlerThread,和我們之前那段代碼幾乎差不多潘鲫,在onStart方法中通過Handler將intent信息發(fā)送到消息隊列中翁逞,然后在子線程中調(diào)用handleMessage方法去處理 ,這里可以看到handleMessage方法中溉仑,先調(diào)用了onHandleIntent((Intent)msg.obj)挖函,這是個抽象方法,需要我們自己去實現(xiàn)浊竟,我們可以在其中做一些耗時的任務(wù)挪圾,任務(wù)完成之后會自動調(diào)用stopSelf(msg.arg1)結(jié)束服務(wù)。在onDestroy中調(diào)用mServiceLooper.quit()結(jié)束消息隊列逐沙,防止此Service出現(xiàn)內(nèi)存泄漏哲思。當(dāng)我們多次調(diào)用startService開啟服務(wù)時,會走onStartCommand方法吩案,從源碼中看到棚赔,它也會調(diào)用onStart(intent, startId)方法,然后將intent信息發(fā)送過去徘郭,由此靠益,我們能得出,當(dāng)有多個任務(wù)時残揉,這些任務(wù)都被依次的存入子線程的消息隊列中胧后,待子線程一個一個取出去處理,如果前面的任務(wù)耗時抱环,則會阻塞后面的任務(wù)壳快。
這里有一點需要了解一下,前面講了在任務(wù)處理完成之后會調(diào)用stopSelf(msg.arg1)結(jié)束服務(wù)镇草,那么你也許會問:如果消息隊列中有多個任務(wù)眶痰,既然第一個任務(wù)完成之后就把服務(wù)給結(jié)束了,那之后的任務(wù)怎么辦梯啤?
這里我們就需要來看看stopSelf(msg.arg1)的源碼了:

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

可以看到是通過AIDL調(diào)用ActivityManagerService來停止服務(wù)的竖伯,關(guān)于怎么關(guān)閉的,這里就不介紹了,有興趣的可以去看看源碼七婴,這里我們看其官方注釋祟偷,提示此方法是stopSelfResult的老版本,我們來看看stopSelfResult的源碼:

/**
     * 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}. 
     * 
     * <p><em>Be careful about ordering of your calls to this function.</em>.
     * If you call this function with the most-recently received ID before
     * you have called it for previously received IDs, the service will be
     * immediately stopped anyway.  If you may end up processing IDs out
     * of order (such as by dispatching them on separate threads), then you
     * are responsible for stopping them in the same order you received them.</p>
     * 
     * @param startId The most recent start identifier received in {@link 
     *                #onStart}.
     * @return Returns true if the startId matches the last start request
     * and the service will be stopped, else false.
     *  
     * @see #stopSelf()
     */
    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幾乎差不多打厘,就是返回值從void改變?yōu)閎oolean修肠,意味著這個并方法不一定能真正的停掉服務(wù),我們再來看看方法參數(shù)和返回值的注釋:

@param startId The most recent start identifier received in {@link #onStart}.
@return Returns true if the startId matches the last start request and the service will be stopped, else false.

startId是通過onStart方法接收的ID婚惫。
如果需要關(guān)閉的startId和最后一次請求服務(wù)的時候的startId相匹配,就停止服務(wù)魂爪,如果不匹配先舷,則不停止。什么意思滓侍?就是說如果你多次調(diào)用startService啟動了服務(wù)蒋川,此時最后“最后一次請求服務(wù)的時候的startId”就是你最后請求的那個startID,也就是說只有最后的那個startID才能停止掉服務(wù)撩笆,之前的都不行捺球,明白了吧。其實夕冲,這才符合實際氮兵,如果你的Service要同時處理多個請求,你就不能在當(dāng)前一個請求處理完成之后立刻停止Service歹鱼,因為很可能現(xiàn)在你已經(jīng)收到了一個新的啟動Service請求(如果立刻停止泣栈,那么新來的請求就會跟著終止)。為了避免這種情況發(fā)生弥姻,你可以用stopSelf(int)來保證你當(dāng)前停止Service的請求是基于上一個請求的南片。也就是說,當(dāng)你調(diào)用stopSelf(int)庭敦,你把startID傳給了對應(yīng)的要停止的Service疼进,這個startID是上一個請求的StartID!!如果沒有第二個請求來秧廉,那么這個Service就會死掉伞广,但是如果這個Service已經(jīng)又接受到一個新的啟動請求之后,你才調(diào)用stopSelf(int)疼电,那么你傳遞給stopSelf()的ID是上一個請求的ID赔癌,而當(dāng)前Service的startID已經(jīng)更新為新的請求的ID,造成兩個ID不對應(yīng)澜沟,stopSelf()失效灾票,那么Service就不會停止。這樣就避免了將后面的請求終止茫虽。

最后總結(jié)一下:

  1. 對于HandlerThread的使用刊苍,要先調(diào)用其start方法做Looper的初始化操作既们,然后調(diào)用HandlerThread的getLooper方法來獲取looper,此方法是個阻塞的方法正什,直到Looper初始化完成才能被喚醒啥纸,最后將looper傳入Handle中使用。
  2. IntentService是系統(tǒng)為我們封裝的一個Service婴氮,其原理是通過HandlerThread來實現(xiàn)的斯棒,我們在使用時只需基礎(chǔ)IntentService并實現(xiàn)onHandleIntent方法,在方法中具體處理我們的耗時任務(wù)主经,如果有多個任務(wù)荣暮,則這些任務(wù)會在子線程的消息隊列中排隊等待被處理,并且處理完最后一個任務(wù)時罩驻,才會停止掉該服務(wù)穗酥。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市惠遏,隨后出現(xiàn)的幾起案子砾跃,更是在濱河造成了極大的恐慌,老刑警劉巖节吮,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抽高,死亡現(xiàn)場離奇詭異,居然都是意外死亡透绩,警方通過查閱死者的電腦和手機厨内,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渺贤,“玉大人雏胃,你說我怎么就攤上這事≈景埃” “怎么了瞭亮?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長固棚。 經(jīng)常有香客問我统翩,道長,這世上最難降的妖魔是什么此洲? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任厂汗,我火速辦了婚禮,結(jié)果婚禮上呜师,老公的妹妹穿的比我還像新娘娶桦。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布衷畦。 她就那樣靜靜地躺著栗涂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪祈争。 梳的紋絲不亂的頭發(fā)上斤程,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音菩混,去河邊找鬼忿墅。 笑死,一個胖子當(dāng)著我的面吹牛沮峡,可吹牛的內(nèi)容都是我干的疚脐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼帖烘,長吁一口氣:“原來是場噩夢啊……” “哼亮曹!你這毒婦竟也來了橄杨?” 一聲冷哼從身側(cè)響起秘症,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎式矫,沒想到半個月后乡摹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡采转,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年聪廉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片故慈。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡板熊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出察绷,到底是詐尸還是另有隱情干签,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布拆撼,位于F島的核電站容劳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闸度。R本人自食惡果不足惜竭贩,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望莺禁。 院中可真熱鬧留量,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孝赫,卻和暖如春较木,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背青柄。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工伐债, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人致开。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓峰锁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親双戳。 傳聞我的和親對象是個殘疾皇子虹蒋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容