Android消息機制

所謂的Android消息機制其實就是Handler機制肮帐,其主要的作用是將一個任務放到另外一個線程中去執(zhí)行咖驮。一般來說用于網(wǎng)絡請求之后更新UI的情況較多,但是這并不意味著Handler只能用于這種場景训枢,為什么更新UI的時候要使用到Handler呢托修?因為Android規(guī)定只能在UI線程中訪問UI,否則會報錯恒界!這個線程檢查的操作是在ViewRootImpl的checkThread方法中去做的

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

其中這個mThread就是UI線程睦刃。如果說沒有Handler的話哪我們該怎么去刷新UI呢?

基本使用

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 0:
            {
                textView.setText("change");
            }
        }
    }
};
...
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        handler.sendEmptyMessage(0);
    }
}).start();

以上就是我們最常見的Handler最常見的寫法十酣,但是這樣寫存在一個很大的問題涩拙,那就是內(nèi)存泄漏。在Java語言中耸采,非靜態(tài)內(nèi)部類會持有外部類的一個隱試引用兴泥,這樣就可能造成外部類無法被垃圾回收。而導致內(nèi)存泄漏虾宇。很顯然在這里Handler就是一個非靜態(tài)內(nèi)部類搓彻,它會持有Activity的應用導致Activity無法正常釋放。

內(nèi)存泄漏問題

上面說到Handler默認的使用方式歲會造成內(nèi)存泄露嘱朽,那么該如何去寫呢旭贬?正確的寫法應該是使用靜態(tài)內(nèi)部類的形式,但是如果只使用靜態(tài)內(nèi)部類的話handler調(diào)用activity中的方法又成了一個問題,因此使用弱引用來持有外部activity對象成為了很好的解決方案搪泳。代碼如下:

final MyHandler handler=new MyHandler(this);
…
private static class MyHandler extends Handler {
    //創(chuàng)建一個弱引用持有外部類的對象
    private final WeakReference<MainActivity> content;

    private MyHandler(MainActivity content) {
        this.content = new WeakReference<MainActivity>(content);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        MainActivity activity= content.get();
        if (activity != null) {
            switch (msg.what) {
                case 0: {
                    activity.notifyUI();
                }
            }
        }
    }
}
…
@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(this);

}

消息機制中的成員

Hander機制當中的主要成員有Handler稀轨、Looper、MessageQueue森书、Message這四個成員,當然Threadlocal也會存在一些蹤跡,但是個人認為它并不屬于Handler機制中的成員凛膏!

Handler

從名字的英文含義上你就能大概知道它是消息處理者杨名,負責發(fā)送消息和處理消息。

Looper

是一個查詢消息的循環(huán)結(jié)構(gòu)猖毫,負責查詢MessageQueue當中的消息

Message

這就是我們的消息台谍,它能攜帶一個int數(shù)據(jù)和一個Object數(shù)據(jù)

MessageQueue

它是Message的一個集合

源碼分析Handler機制的工作流程


image.png

我們先從Handler發(fā)送消息開始,上面Demo中我們使用的是sendEmptyMessage方法吁断,但其實我們還有一些了其他的send方法和post方法趁蕊,但是這些方法最終都是要調(diào)用sendMessageAtTime具體代碼如下

/**
 * Enqueue a message into the message queue after all pending messages
 * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
 * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
 * Time spent in deep sleep will add an additional delay to execution.
 * You will receive it in {@link #handleMessage}, in the thread attached
 * to this handler.
 * 
 * @param uptimeMillis The absolute time at which the message should be
 *         delivered, using the
 *         {@link android.os.SystemClock#uptimeMillis} time-base.
 *         
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    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, uptimeMillis);
}

在sendMessageAtTime有一個點就是mQueue這個變量,它是一個MessageQueue的對象仔役。最終我們調(diào)用了enqueueMessage方法

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

首先msg要綁定Handler掷伙,msg.target = this;這個好理解,一個Message對象只能由一個Handler來處理又兵。然后

if (mAsynchronous) {
    msg.setAsynchronous(true);
}

如果mAsynchronous為true表示該消息是異步的任柜。最后一步是將消息交給我們的MessageQueue的enqueueMessage處理,代碼如下:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

到這我們的Message對象就添加到MessageQueue當中了沛厨!到這里我們理解了Handler的send和post方法的實際作用就是將Message消息添加到MessageQueue之中宙地,但是這一系列的操作之中我們并沒有看見創(chuàng)建MessageQueue對象的過程,似乎在這之前它已經(jīng)創(chuàng)建好了逆皮,于是我們想起了之前的一個變量叫mQueue宅粥,它是一個MessageQueue的對象

final MessageQueue mQueue;

那他是在哪得到的呢?我們看一下Handler的構(gòu)造方法电谣,以我們最常用的來看

/**
 * Default constructor associates this handler with the {@link Looper} for the
 * current thread.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 */
public Handler() {
    this(null, false);
}

該方法調(diào)用了

/**
 * Use the {@link Looper} for the current thread with the specified callback interface
 * and set whether the handler should be asynchronous.
 *
 * Handlers are synchronous by default unless this constructor is used to make
 * one that is strictly asynchronous.
 *
 * Asynchronous messages represent interrupts or events that do not require global ordering
 * with respect to synchronous messages.  Asynchronous messages are not subject to
 * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
 *
 * @param callback The callback interface in which to handle messages, or null.
 * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
 * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
 *
 * @hide
 */
public Handler(Callback callback, boolean async) {
    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());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

好了我們看到了mQueue實際上是通過mLooper.mQueue這獲取到的秽梅。而mLooper又是通過

Looper.myLooper();

這個方法來獲取到的辰企,分析到這終于又出現(xiàn)了一個關(guān)鍵字Looper,那我們看一下myLooper這個方法

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

哈!一臉懵逼潜索。但是大概可以知道Looper對象被存在了一個對象里面誊抛,看到了get方法我很容易想到它也許還有set方法拗窃,我們先來看一下這個sThreadLocal是什么随夸?

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

好了我們知道ThreadLocal這東西了,但是到這線索似乎斷了set方法在哪里驼修?這時候我忽然想到如果在子線程中使用Handler是一個什么樣的場景乙各!如果不先調(diào)用Looper.prepare()方法是會報錯吧耳峦!問題的關(guān)鍵就在于這妇萄,我們看一下代碼

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

sThreadLocal的set方法被我們找到了,并且new了一個Looper懦底。由此可見我們的Looper也是存在ThreadLocal中的聚唐。Looper的構(gòu)造方法如下所示

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

好了mQueue這個對象的由來算是講清楚了杆查,他就是一個MessageQueue對象亲桦!可以說Looper和MessageQueue是一一對應的客峭,一個Looper對象中含有一個MessageQueue舔琅。ThreadLocal通過set方法將Looper存在其中备蚓,那么我們具體看一下set方法實現(xiàn)

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

可以看到Looper對象最終被存儲在了一個叫ThreadLocalMap的數(shù)據(jù)結(jié)構(gòu)里面购笆,createMap方法用于創(chuàng)建ThreadLcalMap對象,createMap方法中會new一個ThreadLocalMap對象并將這個對象賦給t的threadlocals屬性

/**
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap個人認為是一個hash表横缔,同樣是<key,value>的形式有些和HashMap類似茎刚,它同樣存在自己的擴容機制膛锭,同樣存在自己的hash函數(shù)初狰。到這里我們明白了MessageQueue是存在Looper里面的奢入,而Looper又是存在ThreadLocal里面的腥光,Thread當中有且只有一個ThreadLocal.ThreadLocalMap對象武福,因此Thread捉片、Looper和MessageQueue三者形成了一一對應的關(guān)系界睁,然而Handler于他們沒有一點關(guān)系翻斟,Handler只和Message對象成對應的關(guān)系访惜,所以Thread债热、Looper窒篱、MessageQueue墙杯、Handler四者的關(guān)系是一個線程中只能有一個Looper和一個MessageQueue但是可以存在一個或者多個Handler溉旋。到這里他們之間的關(guān)系我們搞清楚了观腊!另外我們也知道在調(diào)用了MessageQueue的enqueueMessage方法之后我們就把Message對象添加到了MessageQueue當中了,剩下的事情就是Message是如何被處理的婶溯,在子線程當中使用Handler的時候除了要先寫Looper.prepare()之外迄委,還要寫Looper.loop()方法

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    ...
    boolean slowDeliveryDetected = false;

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

      ...

        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
       
       ...

        msg.recycleUnchecked();
    }
}

可以看到loop方法中有一個無限的for循環(huán)硫狞,在循環(huán)中通過queue.next()來便利Message财忽,然后我們看到了這句代碼
msg.target.dispatchMessage(msg);
這個target就是我們之前綁定的Handler即彪,也就是說我們在這里調(diào)用了Handler的dispatchMessage()方法并且將msg作為參數(shù)傳遞了過去漏益,我們看看dispatchMessage的代碼

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

如果說msg的callback不為空調(diào)用handleCallback將消息交給子線程去處理,這種處理方式主要是對應了post(runable)這種形式發(fā)送消息的情況轻庆。另外就是調(diào)用handleMessage方法了榨了,好了這個方法我們再熟悉不過了呐粘,到這里消息從MessageQueue中取出并交由Handler處理的過程也完成了作岖。最后在loop方法中調(diào)用msg.recycleUnchecked()辕万,到這Handler消息機制我們就算是分析完成了渐尿。那么Handler是這么實現(xiàn)跨線程通訊的呢?就是通過方法回調(diào)凉夯,和接口回調(diào)休傍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拐邪,隨后出現(xiàn)的幾起案子扎阶,更是在濱河造成了極大的恐慌犀农,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件击你,死亡現(xiàn)場離奇詭異,居然都是意外死亡绒障,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門捍歪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來户辱,“玉大人,你說我怎么就攤上這事糙臼÷洌” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵变逃,是天一觀的道長必逆。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么名眉? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任粟矿,我火速辦了婚禮,結(jié)果婚禮上损拢,老公的妹妹穿的比我還像新娘陌粹。我一直安慰自己,他們只是感情好福压,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布掏秩。 她就那樣靜靜地躺著,像睡著了一般荆姆。 火紅的嫁衣襯著肌膚如雪蒙幻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天胆筒,我揣著相機與錄音邮破,去河邊找鬼。 笑死仆救,一個胖子當著我的面吹牛抒和,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播派桩,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蚌斩!你這毒婦竟也來了铆惑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤送膳,失蹤者是張志新(化名)和其女友劉穎员魏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叠聋,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡撕阎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碌补。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虏束。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖厦章,靈堂內(nèi)的尸體忽然破棺而出镇匀,到底是詐尸還是另有隱情,我是刑警寧澤袜啃,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布汗侵,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏晰韵。R本人自食惡果不足惜发乔,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雪猪。 院中可真熱鬧栏尚,春花似錦、人聲如沸浪蹂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坤次。三九已至古劲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缰猴,已是汗流浹背产艾。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留滑绒,地道東北人闷堡。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像疑故,于是被迫代替她去往敵國和親杠览。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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