Android消息機(jī)制

Android的消息機(jī)制主要是指的Handler的運(yùn)行機(jī)制以及Handler所附帶的MessageQueueLooper的工作過程,這三者實(shí)際上是一個(gè)整體赶袄。

從開發(fā)的角度來看嚼鹉,Handler是消息機(jī)制的上層接口昆咽,通過它我們可以輕松的將一個(gè)任務(wù)切換到它所在的線程中去執(zhí)行实辑。以消息傳遞為例,在一個(gè)線程創(chuàng)建Handler惜姐,另外一個(gè)線程通過持有該Handler的引用調(diào)用sendMessage實(shí)現(xiàn)消息在線程之間的傳遞。MessageQueue的中文翻譯是消息隊(duì)列椿息,內(nèi)部采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)消息列表歹袁。Looper的中文翻譯是循環(huán)坷衍,這里可以理解為消息循環(huán)。由于MessageQueue只是一個(gè)消息的存儲(chǔ)單元条舔,它不能去處理消息枫耳,而Looper就填補(bǔ)了這個(gè)功能,Looper會(huì)以無限循環(huán)的形式去查找是否有新消息逞刷,如果有的話就處理消息嘉涌,否則就一直等待著。Looper中還有一個(gè)特殊的概念:ThreadLocal夸浅,當(dāng)我們調(diào)用Looper.prepare()方法創(chuàng)建Looper時(shí)使用到它仑最。在Handler內(nèi)部就是通過ThreadLocal來獲取每個(gè)線程的Looper的。

Handler的內(nèi)部實(shí)現(xiàn)主要涉及到如下幾個(gè)類: Thread帆喇、MessageQueue和Looper警医。這幾類之間的關(guān)系可以用如下的圖來簡(jiǎn)單說明:


Thread是最基礎(chǔ)的,Looper和MessageQueue都構(gòu)建在Thread之上坯钦,Handler又構(gòu)建在Looper和MessageQueue之上预皇,我們通過Handler間接地與下面這幾個(gè)相對(duì)底層一點(diǎn)的類打交道。

下面我們來先分別介紹ThreadLocal婉刀,MessageQueue吟温,Looper, 和Handler的工作機(jī)制,再將它們的工作流程進(jìn)行一個(gè)大致的串講突颊。

1. ThreadLocal

1. 概述

ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類鲁豪,通過它可以在指定的線程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后律秃,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù)爬橡,對(duì)于其他線程來說則無法獲取到數(shù)據(jù)。

上面的話可以理解為棒动,ThreadLocal是一個(gè)全局變量糙申,用來存儲(chǔ)對(duì)應(yīng)Thread的本地變量。當(dāng)使用ThreadLocal維護(hù)變量時(shí)船惨,ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本柜裸,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本粱锐。 舉一個(gè)簡(jiǎn)單的例子:

public class MainActivity extends AppCompatActivity {
   private static final String TAG = "MainActivity";
   //定義ThreadLocal對(duì)象
   private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
?
?
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
?
       mBooleanThreadLocal.set(true);
       Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
?
       new Thread("Thread#1") {
           @Override
           public void run() {
               mBooleanThreadLocal.set(false);
               Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
          }
      }.start();
?
       new Thread("Thread#2") {
           @Override
           public void run() {
               Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
          }
      }.start();
  }
}

在上面的代碼中粘室,在主線程中設(shè)置mBooleanThreadLocal的值為true,在子線程1中設(shè)置mBooleanThreadLocal值為false卜范,子線程2中不設(shè)置mBooleanThreadLocal的值衔统。然后在3個(gè)線程中分別通過get方法獲取值并打印出來。根據(jù)前面的描述,期望的打印結(jié)果應(yīng)該是:主線程為true锦爵,子線程1為false舱殿,子線程2位null,因?yàn)樽泳€程2中沒有設(shè)置值险掀。實(shí)際打印結(jié)果如下:


可以看到沪袭,盡管在三個(gè)線程中訪問的為同一個(gè)對(duì)象,但是ThreadLocal為他們各自維護(hù)了一個(gè)該對(duì)象的副本樟氢,所以訪問到的結(jié)果不同冈绊。

2. 實(shí)現(xiàn)原理

ThreadLocal是一個(gè)泛型類public class ThreadLocal<T>埠啃,要理解它的工作原理死宣,可以從getset方法入手,先來看set方法:

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

可見碴开,首先通過getMap方法來獲取當(dāng)前線程中的ThreadLocal數(shù)據(jù)毅该,如果返回的ThreadLocalMap對(duì)象不為空,則調(diào)用set方法添加數(shù)據(jù)潦牛,否則就創(chuàng)建一個(gè)新對(duì)象并把值添加進(jìn)去眶掌。ThreadLocalMap中定義了一個(gè)private Entry[] table;來存儲(chǔ)數(shù)據(jù),我們可以通過set方法將數(shù)據(jù)添加到table數(shù)組中巴碗。

再來看看get方法:

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

可以發(fā)現(xiàn)朴爬,ThreadLocalget方法同樣是先取出當(dāng)前線程的ThreadLocalMap 對(duì)象,如果這個(gè)對(duì)象為null就返回初始值橡淆,這個(gè)初始值由ThreadLocalinitialValue()方法來確定寝殴,默認(rèn)情況下為null,默認(rèn)實(shí)現(xiàn)如下所示:

protected T initialValue() {
    return null;
}

如果ThreadLocalMap 對(duì)象不為null明垢,那么就取出table數(shù)組并找到相應(yīng)的值返回回去。

ThreadLocalsetget方法可以看出市咽,它們所操作的對(duì)象都是當(dāng)前線程的ThreadLocalMap對(duì)象的table數(shù)組痊银,因此在不同線程中訪問同一個(gè)ThreadLocalsetget方法,它們對(duì)ThreadLocal所做的讀/寫操作僅限于各線程的內(nèi)部施绎,所以ThreadLocal可以在多個(gè)線程中互不干擾地存儲(chǔ)和修改數(shù)據(jù)溯革。

3. 與Android消息機(jī)制的聯(lián)系

當(dāng)我們?cè)谝粋€(gè)線程中創(chuàng)建一個(gè)Handler,調(diào)用Looper.prepare()時(shí)通過ThreadLocal保存當(dāng)前線程下的Looper對(duì)象谷醉,而所有線程的Looper都由一個(gè)ThreadLocal來維護(hù)致稀,也就是在所有線程中創(chuàng)建的Looper都存放在了同一個(gè)ThreadLocal中。而Handler又與Looper協(xié)同工作俱尼,大致關(guān)系如下圖:


2. MessageQueue

最基礎(chǔ)最底層的是Thread抖单,每個(gè)線程內(nèi)部都維護(hù)了一個(gè)消息隊(duì)列——MessageQueue。消息隊(duì)列MessageQueue,顧名思義矛绘,就是存放消息的隊(duì)列(好像是廢話…)耍休。那隊(duì)列中存儲(chǔ)的消息是什么呢?假設(shè)我們?cè)赨I界面上單擊了某個(gè)按鈕货矮,而此時(shí)程序又恰好收到了某個(gè)廣播事件羊精,那我們?nèi)绾翁幚磉@兩件事呢? 因?yàn)橐粋€(gè)線程在某一時(shí)刻只能處理一件事情囚玫,不能同時(shí)處理多件事情喧锦,所以我們不能同時(shí)處理按鈕的單擊事件和廣播事件,我們只能挨個(gè)對(duì)其進(jìn)行處理抓督,只要挨個(gè)處理就要有處理的先后順序燃少。 為此Android把UI界面上單擊按鈕的事件封裝成了一個(gè)Message,將其放入到MessageQueue里面去本昏,即將單擊按鈕事件的Message入棧到消息隊(duì)列中供汛,然后再將廣播事件的封裝成以Message,也將其入棧到消息隊(duì)列中涌穆。也就是說一個(gè)Message對(duì)象表示的是線程需要處理的一件事情怔昨,消息隊(duì)列就是一堆需要處理的Message的池。線程Thread會(huì)依次取出消息隊(duì)列中的消息宿稀,依次對(duì)其進(jìn)行處理趁舀。MessageQueue中有兩個(gè)比較重要的方法,一個(gè)是enqueueMessage方法祝沸,一個(gè)是next方法矮烹。enqueueMessage方法用于將一個(gè)Message放入到消息隊(duì)列MessageQueue中,next方法是從消息隊(duì)列MessageQueue中阻塞式地取出一個(gè)Message罩锐。

enqueueMessage主要操作其實(shí)就是單鏈表的插入操作奉狈,這里就不再過多解釋了。而next方法是一個(gè)無限循環(huán)的方法涩惑,如果消息隊(duì)列中沒有消息仁期,那么next方法會(huì)一直阻塞在這里。當(dāng)有新消息到來時(shí)竭恬,next方法會(huì)返回這條消息并將其從單鏈表中移除跛蛋。

3. Looper

1. Looper的創(chuàng)建(使用ThreadLocal存儲(chǔ))

LooperAndroid的消息機(jī)制中扮演著消息循環(huán)的角色,具體來說就是它會(huì)不停地從MessageQueue中查看是否有新消息痊硕,如果有新消息就會(huì)立刻處理赊级,否則就一直阻塞在哪里。首先來看一下它的構(gòu)造方法岔绸,在構(gòu)造方法中它會(huì)創(chuàng)建一個(gè)MessageQueue即消息隊(duì)列理逊,然后將當(dāng)前線程的對(duì)象保存起來橡伞,如下所示:

@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;

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

創(chuàng)建looper時(shí)調(diào)用looper.prepare,具體操作:

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即為存儲(chǔ)各線程Looper的ThreadLocal對(duì)象挡鞍,調(diào)用其get方法判斷該線程是否已經(jīng)持有了Looper骑歹,如果已經(jīng)持有則拋出異常,這是為了保證對(duì)于每個(gè)線程Looper的唯一性墨微。如果沒有Looper道媚,則為該線程創(chuàng)建一個(gè)Looper。

2. Looper的循環(huán)

Looper最重要的一個(gè)方法是loop方法翘县,只有調(diào)用了loop后最域,消息循環(huán)系統(tǒng)才會(huì)真正地起作用,它的實(shí)現(xiàn)如下:

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

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

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

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

選取其中比較關(guān)鍵的部分做一下講解:

  1. final MessageQueue queue = me.mQueue;

變量me是通過靜態(tài)方法myLooper()獲得的當(dāng)前線程所綁定的Looper锈麸,me.mQueue是當(dāng)前線程所關(guān)聯(lián)的消息

隊(duì)列镀脂。

  1. for (;;)

我們發(fā)現(xiàn)for循環(huán)沒有設(shè)置循環(huán)終止的條件,所以這個(gè)for循環(huán)是個(gè)死循環(huán)忘伞。

  1. Message msg = queue.next(); // might block

我們通過消息隊(duì)列MessageQueue的next方法從消息隊(duì)列中取出一條消息薄翅,如果此時(shí)消息隊(duì)列中有Message,

那么next方法會(huì)立即返回該Message氓奈,如果此時(shí)消息隊(duì)列中沒有Message翘魄,那么next方法就會(huì)阻塞式地等待獲

取Message。

  1. msg.target.dispatchMessage(msg);

msg的target屬性是Handler舀奶,該代碼的意思是讓Message所關(guān)聯(lián)的Handler通過dispatchMessage方法讓

Handler處理該Message暑竟,關(guān)于Handler的dispatchMessage方法將會(huì)在下面詳細(xì)介紹。

loop方法是一個(gè)死循環(huán)育勺,唯一跳出循環(huán)的方式是MessageQueuenext方法返回了null但荤。

當(dāng)Looper的quit方法被調(diào)用時(shí),Looper就會(huì)調(diào)用MessageQueue的quit方法或quitSafely方法來通知消息隊(duì)列退出涧至,當(dāng)消息隊(duì)列被標(biāo)記為退出狀態(tài)時(shí)腹躁,它的next方法就會(huì)返回null。如果MessageQueuenext方法返回了新消息南蓬,Looper就會(huì)處理這條消息: msg.target.dispatchMessage(msg)纺非,這里的msg.target是發(fā)送這條消息的Handler對(duì)象,這樣Handler發(fā)送的消息最終又交給它的dispatchMessage方法來處理了蓖康。但是這里不同的是,HandlerdispatchMessage方法是在創(chuàng)建Handler時(shí)所使用的Looper中執(zhí)行的垒手,這樣就成功的將代碼邏輯切換到指定的線程中去執(zhí)行了蒜焊。

4. Handler

1. 消息發(fā)送

Handler的工作主要包含消息的發(fā)送和接收過程。消息的發(fā)送最終是通過send的一系列方法來實(shí)現(xiàn)的科贬。發(fā)送一條消息的典型過程如下所示:

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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

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

在enqueueMessage中有兩件事需要注意:

  1. msg.target = this
    該代碼將Message的target綁定為當(dāng)前的Handler
  2. queue.enqueueMessage
    變量queue表示的是Handler所綁定的消息隊(duì)列MessageQueue泳梆,通過調(diào)用queue.enqueueMessage(msg, uptimeMillis)我們將Message放入到消息隊(duì)列中鳖悠。

其他的一些調(diào)用最終也都?xì)w結(jié)到上面這個(gè)流程中:


2. 消息處理

通過上文可以發(fā)現(xiàn),Handler發(fā)送消息的過程僅僅是向消息隊(duì)列插入了一條消息优妙,MessageQueuenext方法就會(huì)返回這條消息給Looper乘综,Looper收到消息后就開始處理了,最終消息由Looper交由Handler處理套硼,即HandlerdispatchMessage方法會(huì)被調(diào)用卡辰,這時(shí)Handler就進(jìn)入了處理消息的階段。dispatchMessage的實(shí)現(xiàn)如下所示:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

可以看到邪意,如果我們?cè)O(shè)置了callback(Runnable對(duì)象)的話九妈,則會(huì)直接調(diào)用handleCallback方法

private static void handleCallback(Message message) {
        message.callback.run();
}

即,如果我們?cè)诔跏蓟疕andler的時(shí)候設(shè)置了callback(Runnable)對(duì)象雾鬼,則直接調(diào)用run方法萌朱。比如我們經(jīng)常寫的runOnUiThread方法:

runOnUiThread(new Runnable() {
            @Override
            public void run() {
                
            }
        });

public final void runOnUiThread(Runnable action) {
      if (Thread.currentThread() != mUiThread) {
          mHandler.post(action);
      } else {
          action.run();
      }
}

而如果msg.callback為空的話,會(huì)直接調(diào)用我們的mCallback.handleMessage(msg)策菜,即handler的handlerMessage方法晶疼。handlerMessage方法的執(zhí)行也會(huì)在創(chuàng)建handler的線程中。

綜上又憨,我們可以看到Handler提供了三種途徑處理Message翠霍,而且處理有前后優(yōu)先級(jí)之分:首先嘗試讓postXXX中傳遞的Runnable執(zhí)行,其次嘗試讓Handler構(gòu)造函數(shù)中傳入的Callback的handleMessage方法處理竟块,最后才是讓Handler自身的handleMessage方法處理Message壶运。


3. post與send的比較

上述例子都是使用send方法,這里加一個(gè)post的例子幫助理解handler對(duì)于不同類型信息的處理

首先浪秘,調(diào)用post方法時(shí)依然調(diào)用了sendMessageDelayed蒋情,但是值得注意的是這里的參數(shù)有所不同,使用了getPostMessage(r)作為參數(shù)耸携。

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);//getPostMessage方法是兩種發(fā)送消息的不同之處
    }

那么我們來看一看??這個(gè)getPostMessage方法:

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

可以看到棵癣,依然是把Runnable對(duì)象封裝成了一個(gè)Message進(jìn)行發(fā)送,不過這里設(shè)置了m.callback = r 夺衍,這也呼應(yīng)了上文中提到的狈谊,如果我們?cè)诔跏蓟疕andler的時(shí)候設(shè)置了callback(Runnable)對(duì)象,則直接調(diào)用run方法沟沙。

4. Callback

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}

Handler.Callback是用來處理Message的一種手段河劝,如果沒有傳遞該參數(shù),那么就應(yīng)該重寫Handler的handleMessage方法矛紫,也就是說為了使得Handler能夠處理Message赎瞎,我們有兩種辦法:

  1. 向Hanlder的構(gòu)造函數(shù)傳入一個(gè)Handler.Callback對(duì)象,并實(shí)現(xiàn)Handler.Callback的handleMessage方法
  2. 無需向Hanlder的構(gòu)造函數(shù)傳入Handler.Callback對(duì)象颊咬,但是需要重寫Handler本身的handleMessage方法

也就是說無論哪種方式务甥,我們都得通過某種方式實(shí)現(xiàn)handleMessage方法牡辽,這點(diǎn)與Java中對(duì)Thread的設(shè)計(jì)有異曲同工之處。

在Java中敞临,如果我們想使用多線程态辛,有兩種辦法:

  1. 向Thread的構(gòu)造函數(shù)傳入一個(gè)Runnable對(duì)象,并實(shí)現(xiàn)Runnable的run方法

  2. 無需向Thread的構(gòu)造函數(shù)傳入Runnable對(duì)象挺尿,但是要重寫Thread本身的run方法

5. 總結(jié)

  • 在使用handler的時(shí)候奏黑,在handler所創(chuàng)建的線程需要維護(hù)一個(gè)唯一的Looper對(duì)象, 每個(gè)線程對(duì)應(yīng)一個(gè)Looper票髓,每個(gè)線程的Looper通過ThreadLocal來保證

  • Looper對(duì)象的內(nèi)部又維護(hù)有唯一的一個(gè)MessageQueue攀涵,所以一個(gè)線程可以有多個(gè)handler,
    但是只能有一個(gè)Looper和一個(gè)MessageQueue洽沟。

  • Message在MessageQueue不是通過一個(gè)列表來存儲(chǔ)的以故,而是將傳入的Message存入到了上一個(gè)
    Message的next中,在取出的時(shí)候通過頂部的Message就能按放入的順序依次取出Message裆操。

  • Looper對(duì)象通過loop()方法開啟了一個(gè)死循環(huán)怒详,不斷地從looper內(nèi)的MessageQueue中取出Message,
    然后通過handler將消息分發(fā)傳回handler所在的線程踪区。

  • handler收到消息以后昆烁,通過handleMessage進(jìn)行消息處理


6. 補(bǔ)充:資源管理與內(nèi)存泄漏

1. Handler的內(nèi)存泄漏問題

Handler使用是用來進(jìn)行線程間通信的,所以新開啟的線程會(huì)持有Handler引用缎岗,如果在Activity等中創(chuàng)建Handler静尼,并且是非靜態(tài)內(nèi)部類的形式,就有可能造成內(nèi)存泄漏传泊。

首先鼠渺,非靜態(tài)內(nèi)部類是會(huì)隱式持有外部類的引用,所以當(dāng)其他線程持有了該Handler眷细,線程沒有被銷毀拦盹,則意味著Activity會(huì)一直被Handler持有引用而無法導(dǎo)致回收。

同時(shí)溪椎,MessageQueue中如果存在未處理完的Message普舆,Message的target也是對(duì)Activity等的持有引用,也會(huì)

造成內(nèi)存泄漏校读。

解決的辦法:

  • 使用靜態(tài)內(nèi)部類+弱引用的方式:

    靜態(tài)內(nèi)部類不會(huì)持有外部類的的引用沼侣,當(dāng)需要引用外部類相關(guān)操作時(shí),可以通過弱引用還獲取到外部類相關(guān)操作歉秫,弱引用不會(huì)造成對(duì)象該回收回收不掉的問題蛾洛。

    private Handler sHandler = new TestHandler(this);
    
    static class TestHandler extends Handler {
        private WeakReference<Activity> mActivity;
        TestHandler(Activity activity) {
            mActivity = new WeakReference<>(activity);
        }
      
          @Override
          public void handleMessage(Message msg) {
              super.handleMessage(msg);
              Activity activity = mActivity.get();
              if (activity != null) {
                  //TODO:
              }   
          }
    }
    
  • 在外部類對(duì)象被銷毀時(shí),將MessageQueue中的消息清空端考。例如雅潭,在Activity的onDestroy時(shí)將消息清空。

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
    

2. 創(chuàng)建Message時(shí)的資源管理

使用Handler.obtainMessage()來獲取Message對(duì)象的却特,和直接new一個(gè)Message有什么差別呢扶供?

Message message = handler.obtainMessage();
Message message = new Message();

看一看obtain的代碼:

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

在Message中有一個(gè)static Message變量sPool,這個(gè)變量是用于緩存Message對(duì)象的裂明,在obtain中可以看到當(dāng)需要一個(gè)Message對(duì)象時(shí)椿浓,如果sPool不為空則會(huì)返回當(dāng)前sPool(Message),而將sPool指向了之前sPool的next對(duì)象闽晦,(之前講MessageQueue時(shí)講過Message的存儲(chǔ)是以鏈?zhǔn)降男问酱鎯?chǔ)的扳碍,通過Message的next指向下一個(gè)Message,這里就是返回了sPool當(dāng)前這個(gè)Message仙蛉,然后sPool重新指向了其下一個(gè)Message)笋敞,然后將返回的Message的next指向置為空(斷開鏈表),sPoolSize記錄了當(dāng)前緩存的Message的數(shù)量荠瘪,如果sPool為空夯巷,則沒有緩存的Message,則需要?jiǎng)?chuàng)建一個(gè)新的Message(new Message)哀墓。

那么趁餐,緩存中的sPool是哪里來的呢

public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

recycle()是回收Message的方法,在Message處理完或者清空Message等時(shí)會(huì)調(diào)用。recycleUnchecked()方法中可以看到篮绰,將what后雷、arg1、arg2吠各、object等都重置了值臀突,如果當(dāng)前sPool(Message緩存池)的大小小于允許緩存的Message最大數(shù)量時(shí),將要回收的Message的next指向sPool走孽,將sPool指向了回收的Message對(duì)象(即將Message放到了sPool緩存池的頭部)

7. 參考文章

這些是我在準(zhǔn)備和學(xué)習(xí)過程中參考的一些博客惧辈,大家有興趣可以自己再康康??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末磕瓷,一起剝皮案震驚了整個(gè)濱河市盒齿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌困食,老刑警劉巖边翁,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異硕盹,居然都是意外死亡符匾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門瘩例,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啊胶,“玉大人甸各,你說我怎么就攤上這事⊙嫫海” “怎么了趣倾?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)某饰。 經(jīng)常有香客問我儒恋,道長(zhǎng),這世上最難降的妖魔是什么黔漂? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任诫尽,我火速辦了婚禮,結(jié)果婚禮上炬守,老公的妹妹穿的比我還像新娘牧嫉。我一直安慰自己,他們只是感情好减途,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布驹止。 她就那樣靜靜地躺著,像睡著了一般观蜗。 火紅的嫁衣襯著肌膚如雪臊恋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天墓捻,我揣著相機(jī)與錄音抖仅,去河邊找鬼。 笑死砖第,一個(gè)胖子當(dāng)著我的面吹牛撤卢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梧兼,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼放吩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了羽杰?” 一聲冷哼從身側(cè)響起渡紫,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎考赛,沒想到半個(gè)月后惕澎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颜骤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年唧喉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡八孝,死狀恐怖董朝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情干跛,我是刑警寧澤益涧,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站驯鳖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏久免。R本人自食惡果不足惜浅辙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阎姥。 院中可真熱鬧记舆,春花似錦、人聲如沸呼巴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衣赶。三九已至诊赊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間府瞄,已是汗流浹背碧磅。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遵馆,地道東北人鲸郊。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像货邓,于是被迫代替她去往敵國和親秆撮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

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