Handler機制實現(xiàn)原理(四)handler的源碼分析

Handler本身可在多線程之間調(diào)用辟灰,不管它在哪個線程發(fā)送消息端礼,都會回到它被初始化的哪個線程中接收到消息掰茶。

初始化

Handler有7個構(gòu)造方法,分別對應(yīng)不同的參數(shù)來初始化不同的Handler屬性目代,但是真正完成初始化操作的只有兩個構(gòu)造方法:

    // 是否需要查找潛在的漏洞
    private static final boolean FIND_POTENTIAL_LEAKS = false;

    /**
     * 將Callback接口作為構(gòu)造方法參數(shù),可以用作接收消息的回調(diào)
     * 這樣就可以省去自己重寫Handler自身的handleMessage方法
     *
     * @param msg 接收到的消息
     * @return 是否需要進一步處理,即調(diào)用Handler自身的handleMessage方法
     */
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;

    // 發(fā)送的消息是否為異步的榛了,默認是false
    final boolean mAsynchronous;

    /**
     * @hide 隱藏的構(gòu)造方法在讶,外部不可見
     */
    public Handler(Callback callback, boolean async) {

        // 檢查是否存在內(nèi)存泄漏的可能
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        // 得到當(dāng)前線程的Looper
        mLooper = Looper.myLooper();

        // 如果Looper還沒初始化拋出異常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

        // 得到當(dāng)前線程的MessageQueue
        mQueue = mLooper.mQueue;

        
        mCallback = callback;
        mAsynchronous = async;
    }

 
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

從上面代碼的得知,構(gòu)造方法初始化的工作就是給mLooper,mQueue,mCallbackmAsynchronous這幾個關(guān)鍵的屬性賦值.mLoopermQueue自然就是當(dāng)前線程下的Looper和MessageQueue了霜大,如果傳遞了Looper參數(shù)就直接賦值构哺,如果沒傳遞就調(diào)用Looper.myLooper();得到當(dāng)前線程的Looper。

mCallback是Handler內(nèi)部定義的一個簡單接口战坤,其目的是為了替代傳統(tǒng)的接收消息方法曙强。當(dāng)然使用mCallback的同時并不會影響正常的Handler消息分發(fā)。此處解釋從后面接收消息時的邏輯就可以看到途茫。

mAsynchronous的意思是該Handler發(fā)送的消息是否是異步的碟嘴,從前面Message源碼的文章中我們知道Message中有一個設(shè)置消息是否為異步消息的方法,MessageQueue對異步消息的處理也與同步消息不同囊卜。此處如果設(shè)置了mAsynchronoustrue娜扇,那么這個Handler發(fā)送的所有消息就都是異步消息。

在有兩個參數(shù)的構(gòu)造方法中我們會發(fā)現(xiàn)有一段檢查是否存在內(nèi)存泄漏的代碼栅组,為什么會這樣呢雀瓢?在分析完發(fā)送消息和接收消息后再說這個。

當(dāng)然玉掸,除了上面兩個構(gòu)造方法外還有其它幾個構(gòu)造方法致燥,但均是調(diào)用上面兩個方法:

   public Handler() {
        this(null, false);
    }

    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    /**
     * @hide 隱藏的構(gòu)造方法,外部不可見
     */
    public Handler(boolean async) {
        this(null, async);
    }

發(fā)送消息

使用Handler發(fā)送消息時我們知道它分為兩類:

  • postXXX()方法切換回原線程排截。
  • sendMessageXXX()方法發(fā)送消息到原線程嫌蚤。

其實這兩種方法本質(zhì)都是發(fā)送一個Message對象到原線程,只不過PostXXX()方法是發(fā)送了一個只有Runnable callback屬性的Message對象断傲。

先來看一下sendMessageXXX()類的方法:

    // 發(fā)送一條普通消息
    public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
    }

    // 發(fā)送一條空消息
    public final boolean sendEmptyMessage(int what){
        return sendEmptyMessageDelayed(what, 0);
    }

    // 發(fā)送一條空的延時消息
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    // 發(fā)送一條空的定時消息
    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, uptimeMillis);
    }

    // 發(fā)送一個普通的延時消息
    public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    // 發(fā)送一個普通的定時消息
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //得到消息隊列
        MessageQueue queue = mQueue;
        // 如果消息隊列是空的記錄日志然后結(jié)束方法
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        // 執(zhí)行消息入隊操作
        return enqueueMessage(queue, msg, uptimeMillis);
    }

從上面的代碼中發(fā)現(xiàn)脱吱,不管調(diào)用何種發(fā)送消息的方法,最后真正調(diào)用的都是sendMessageAtTime()方法认罩。而真正發(fā)送的核心方法也就是入隊方法是Handler的enqueueMessage()方法箱蝠。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // 將消息的宿主設(shè)置為當(dāng)前Handler自身
        msg.target = this;

        //如果Handler被設(shè)置成了異步就把消息也設(shè)置成異步的
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }

        // 執(zhí)行消息隊列的入隊操作
        return queue.enqueueMessage(msg, uptimeMillis);
    }

看到這里終于明白了為啥Looper和MessageQueue一直在使用Message的target屬性而我們卻從來沒有給它賦值過,是Handler在發(fā)送消息前自己賦值上去的垦垂。

看完了發(fā)送消息類的方法在看看切換線程類的方法干了什么:


    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    private static Message getPostMessage(Runnable r, Object token) {
        Message m = Message.obtain();
        m.obj = token;
        m.callback = r;
        return m;
    }

    public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    public final boolean postAtTime(Runnable r, long uptimeMillis) {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
    
    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){
        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    }
    
    public final boolean postDelayed(Runnable r, long delayMillis){
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    
    public final boolean postAtFrontOfQueue(Runnable r){
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

上本節(jié)開頭講的一樣宦搬,postXXX()類方法就是構(gòu)造了一個只有Runnable callback的Message對象,然后走正常發(fā)送消息的方法劫拗。唯一有一個特例就是postAtFrontOfQueue()方法间校,它調(diào)用了sendMessageAtFrontOfQueue()方法是之前發(fā)送消息沒有用到過的:

    public final boolean sendMessageAtFrontOfQueue(Message msg) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, 0);
    }

原來這個方法特殊的地方就是在入隊的時候時間參數(shù)為0,我們在MessageQueue源碼知道如果入隊消息的時間參數(shù)為0那么這個消息會被直接放在隊列頭页慷。所以憔足,postAtFrontOfQueue()方法就是直接在消息隊列頭部插入了一個消息胁附。

接收消息

在Looper 的源碼中我們知道每當(dāng)從MessageQueue中取出一個消息時就會調(diào)用這個消息的宿主target中分發(fā)消息的方法:

// Looper分發(fā)消息
msg.target.dispatchMessage(msg);

而這個宿主target也就是我們的Handler,所有Handler接收消息就是在這個dispatchMessage()方法中了:

    public void dispatchMessage(Message msg) {
        // 如果Message的callback不為空滓彰,說明它是一個通過postXXX()方法發(fā)送的消息
        if (msg.callback != null) {
            // 直接運行這個callback
            handleCallback(msg);
        } else {
            //如果mCallback 不為空說明Handler設(shè)置了Callback接口
            // 先執(zhí)行接口處理消息的方法
            if (mCallback != null) {
                // 如果callback接口處理完消息返回true說明它將消息攔截
                // 不再執(zhí)行Handler自身的處理消息方法控妻,直接結(jié)束方法
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 調(diào)用Handler自身處理消息的方法
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        // 運行callback,也就是這個Runnable接口
        message.callback.run();
    }

    // Handler自身處理消息的方法揭绑,開發(fā)者需要重新該方法來實現(xiàn)接收消息
    public void handleMessage(Message msg) {
    }

由此可見弓候,如果是單純的PostXXX()方法發(fā)送的消息,Handler接收到了之后直接運行Message對象的Runnable接口他匪,不會將它當(dāng)做一個消息進行處理菇存。

而我們的mCallback接口是完全可以替代Handler自身接收消息的方法,因為其高優(yōu)先處理等級诚纸,它甚至可以選擇攔截掉Handler自身的接收消息方法撰筷。

內(nèi)存泄漏的可能

我們在使用Handler的時候?qū)懛ㄒ话闳缦拢?/p>

    private final Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {

        }
    };

由于重寫了handleMessage()方法相當(dāng)于生成了一個匿名內(nèi)部類,也就相當(dāng)于如下代碼:

    private final Handler handler = new MyHandler ();

    class MyHandler extends Handler{

        @Override
        public void handleMessage(Message msg) {
            
        }
    }

可是你有沒有想過內(nèi)部類憑什么能夠調(diào)用外部類的屬性和方法呢畦徘?答案就是內(nèi)部類隱式的持有著外部類的引用毕籽,編譯器在創(chuàng)建內(nèi)部類時把外部類的引用傳入了其中,只不過是你看不到而已井辆。

既然Handler作為內(nèi)部類持有著外部類(多數(shù)情況為Activity)的引用关筒,而Handler對應(yīng)的一般都是耗時操作。當(dāng)我們在子線程執(zhí)行一項耗時操作時杯缺,用戶退出程序蒸播,Activity需要被銷毀,而Handler還在持有Activity的引用導(dǎo)致無法回收萍肆,就會引發(fā)內(nèi)存泄漏袍榆。

解決方法分為兩步

  1. 生成內(nèi)部類時把內(nèi)部類聲明為靜態(tài)的。
  2. 使用弱引用來持有外部類引用塘揣。

靜態(tài)內(nèi)部類不會持有外部類的引用包雀,且弱引用不會阻止JVM回收對象。

    static class MyHandler extends Handler{

        WeakReference<Activity> mActivity ;

        public MyHandler(Activity activity){
            mActivity = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = mActivity.get();

            if (activity == null){
                return;
            }

            // do something

        }
    }

所以亲铡,在文章剛開始初始化方法中檢查漏洞的代碼其實就是檢查這個內(nèi)存泄漏的可能性:

        // 檢查是否存在內(nèi)存泄漏的可能
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            // 是否為匿名類才写,內(nèi)部類以及是否為靜態(tài)類
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

大功告成,完美奖蔓!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赞草,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吆鹤,更是在濱河造成了極大的恐慌厨疙,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,657評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件檀头,死亡現(xiàn)場離奇詭異轰异,居然都是意外死亡岖沛,警方通過查閱死者的電腦和手機暑始,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,662評論 3 385
  • 文/潘曉璐 我一進店門搭独,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人廊镜,你說我怎么就攤上這事牙肝。” “怎么了嗤朴?”我有些...
    開封第一講書人閱讀 158,143評論 0 348
  • 文/不壞的土叔 我叫張陵配椭,是天一觀的道長。 經(jīng)常有香客問我雹姊,道長股缸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,732評論 1 284
  • 正文 為了忘掉前任吱雏,我火速辦了婚禮敦姻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘歧杏。我一直安慰自己镰惦,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,837評論 6 386
  • 文/花漫 我一把揭開白布犬绒。 她就那樣靜靜地躺著旺入,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凯力。 梳的紋絲不亂的頭發(fā)上茵瘾,一...
    開封第一講書人閱讀 50,036評論 1 291
  • 那天,我揣著相機與錄音咐鹤,去河邊找鬼拗秘。 笑死,一個胖子當(dāng)著我的面吹牛慷暂,可吹牛的內(nèi)容都是我干的聘殖。 我是一名探鬼主播,決...
    沈念sama閱讀 39,126評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼行瑞,長吁一口氣:“原來是場噩夢啊……” “哼奸腺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起血久,我...
    開封第一講書人閱讀 37,868評論 0 268
  • 序言:老撾萬榮一對情侶失蹤突照,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后氧吐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讹蘑,經(jīng)...
    沈念sama閱讀 44,315評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡末盔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,641評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了座慰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陨舱。...
    茶點故事閱讀 38,773評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖版仔,靈堂內(nèi)的尸體忽然破棺而出游盲,到底是詐尸還是另有隱情,我是刑警寧澤蛮粮,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布益缎,位于F島的核電站,受9級特大地震影響然想,放射性物質(zhì)發(fā)生泄漏莺奔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一变泄、第九天 我趴在偏房一處隱蔽的房頂上張望令哟。 院中可真熱鬧,春花似錦杖刷、人聲如沸励饵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,859評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽役听。三九已至,卻和暖如春表窘,著一層夾襖步出監(jiān)牢的瞬間典予,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工乐严, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瘤袖,地道東北人。 一個月前我還...
    沈念sama閱讀 46,584評論 2 362
  • 正文 我出身青樓昂验,卻偏偏與公主長得像捂敌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子既琴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,676評論 2 351

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