Android Handler機(jī)制詳解

前言

Handler是Android的消息機(jī)制,他能夠很輕松的在線程間傳遞數(shù)據(jù)。由于Android開發(fā)規(guī)范的限制,我們不能在主線程執(zhí)行耗時(shí)操作(如網(wǎng)絡(luò),IO操作等),不能在子線程更新UI,所以Handler大部分用來在耗時(shí)操作與更新UI之間切換。這讓很多人誤以為Handler就是用來更新UI的,其實(shí)這只是它的一小部分應(yīng)用。

開始

我相信大多數(shù)人對(duì)Handler的用法已經(jīng)爛熟于心了,這篇文章不會(huì)去探討Handler的使用,而是著重從源碼上分析Handler的運(yùn)行機(jī)制。

想要了解Handler的運(yùn)行機(jī)制,我們需要了解 MessageQueueMessage,Looper 這幾個(gè)類。

  • MessageQueue 的意思就是消息隊(duì)列,它存儲(chǔ)了我們需要用來處理的消息Message
  • Message是消息類,內(nèi)部存在一個(gè)Bundle對(duì)象和幾個(gè)public字段存儲(chǔ)數(shù)據(jù),MessageQueue作為一個(gè)消息隊(duì)列不能自己處理消息,所以需要用到Looper
  • Looper是一個(gè)循環(huán)裝置,他負(fù)責(zé)從不斷從MessageQueue里取出Message,然后回調(diào)給HandlerhandleMessage來執(zhí)行具體操作。
  • Handler在這里面充當(dāng)?shù)慕巧袷且粋€(gè)輔助類,它讓我們不用關(guān)系MessageQueueLooper的具體細(xì)節(jié),只需要關(guān)系如何發(fā)送消息和回調(diào)的處理就行了。

上面講了幾個(gè)關(guān)鍵類在Handler運(yùn)行機(jī)制中的職責(zé),相對(duì)大家對(duì)Handler機(jī)制有個(gè)粗略的了解。

我相信各位看官在閱讀這篇文章前都是帶著問題的,我們將通過問題來解答大家的疑惑。

分析

Looper

在分析Looper之前,我們還需要知道ThreadLocal這個(gè)類,如果對(duì)ThreadLocal還不太了解,可以去看我的另一篇文章《ThreadLocal詳解》

Looper是如何創(chuàng)建?

Handler執(zhí)行的線程和它持有的Looper有關(guān)。每個(gè)Thread都可以創(chuàng)建唯一的Looper對(duì)象。

 //為當(dāng)前線程創(chuàng)建Looper對(duì)象的方法。
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
    //使用ThreadLocal來存儲(chǔ)當(dāng)前線程的Looper對(duì)象,這保證了每個(gè)線程有且僅有一個(gè)Looper對(duì)象。
    //這里做了非空判斷,所以在同一個(gè)線程prepare方法是不允許被調(diào)用兩次的
    //第一次創(chuàng)建好的Looper對(duì)象不會(huì)被覆蓋,它是唯一的。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    

那么主線程的Looper對(duì)象是怎么創(chuàng)建的呢?

public static void prepareMainLooper() {
//其實(shí)主線程創(chuàng)建Looper和其他線程沒有區(qū)別,也是調(diào)用prepare()。
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //但是Looper用sMainLooper這個(gè)靜態(tài)變量將主線程的Looper對(duì)象存儲(chǔ)了起來
            //可以通過getMainLooper()獲取,存儲(chǔ)MainLooper其實(shí)非常有作用,下面會(huì)講到。
            sMainLooper = myLooper();
        }
    }

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
Looper是如何從MessageQueue取出消息并分發(fā)的?

Looper分發(fā)消息的主要邏輯在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();
        //保證當(dāng)前線程必須有Looper對(duì)象,如果沒有則拋出異常,調(diào)用Looper.loop()之前應(yīng)該先調(diào)用Looper.prepare().
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //Looper需要不斷從MessageQueue中取出消息,所以它持有MessageQueue對(duì)象
        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();

        for (;;) {
           //這里開始執(zhí)行死循環(huán),queue通過調(diào)用next方法來取出下一個(gè)消息。
           //很多人很疑惑死循環(huán)不會(huì)相當(dāng)耗費(fèi)性能嗎,如果沒有那么多消息怎么辦?
           //其實(shí)當(dāng)沒有消息的時(shí)候,next方法會(huì)阻塞在這里,不會(huì)往下執(zhí)行了,性能問題不存在。
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                //這里滿足了死循環(huán)跳出的條件,即取出的消息為null
                //沒有消息next不是會(huì)阻塞嗎,怎么會(huì)返回null呢?
                //其實(shí)只有MessageQueue停止的時(shí)候(調(diào)用quit方法),才會(huì)返回null
                //MessageQueue停止后,調(diào)用next返回null,且不再接受新消息,下面還有詳細(xì)介紹。
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            //這里的msg.target是Handler對(duì)象,分發(fā)消息到Handler去執(zhí)行。
            //有人問主線程可以創(chuàng)建這么多Handler,怎么保證這個(gè)Handler發(fā)送的消息不會(huì)跑到其它Handler去執(zhí)行呢?
            //那是因?yàn)樵诎l(fā)送Message時(shí),他會(huì)綁定發(fā)送的Handler,在此處分發(fā)消息時(shí),也只會(huì)回調(diào)發(fā)送該條消息的Handler。
            //那么分發(fā)消息具體在哪個(gè)線程執(zhí)行呢?
            //我覺得這個(gè)不該問,那當(dāng)然是當(dāng)前方法在哪個(gè)線程調(diào)用就在哪個(gè)線程執(zhí)行啦。
            msg.target.dispatchMessage(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);
            }

        //這里對(duì)Message對(duì)象進(jìn)行回收,會(huì)清空所有之前Message設(shè)置的數(shù)據(jù)。
        //正是因?yàn)镸essage有回收機(jī)制,我們?cè)趧?chuàng)建消息的時(shí)候應(yīng)該優(yōu)先選擇Message.obtain(). 
        //如果發(fā)送的消息足夠多,Message緩存的Message對(duì)象不夠了,obtain內(nèi)部會(huì)調(diào)用new Message()創(chuàng)建一個(gè)新的對(duì)象。
            msg.recycleUnchecked();
        }
    }
Looper 分發(fā)的消息在哪個(gè)線程執(zhí)行?

先給大家展示一段Looper文檔上的示例代碼

class LooperThread extends Thread {
   public Handler mHandler;
  
   public void run() {
        Looper.prepare(); //創(chuàng)建LooperThread的Looper對(duì)象
  
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
              //處理發(fā)送過來的消息
            }
        };
    
        Looper.loop(); //開始循環(huán)消息隊(duì)列
   }
}

上面這段代碼相信很多人都寫過,這是一段在子線程創(chuàng)建Handler的案例,其中handleMessage所執(zhí)行的線程為LooperThread,因?yàn)?code>Looper.loop()執(zhí)行在LooperThreadrun方法里??梢栽谄渌€程通過mHandler發(fā)送消息到LooperThread

如果不調(diào)用Looper.prepare()直接new Handler()會(huì)怎么樣呢?

我們可以查看Handler的源碼看看無參構(gòu)造是如何運(yùn)行的

public Handler() {
//調(diào)用兩參構(gòu)造
    this(null, false);
}
    
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());
        }
    }
//獲取當(dāng)前線程的Looper,如果不創(chuàng)建Looper會(huì)拋出異常。
//主線程我也沒看到有調(diào)用Looper.prepare()啊,怎么在主線程不會(huì)拋異常呢?這個(gè)看下一個(gè)問題。
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
主線程的Looper對(duì)象在哪里創(chuàng)建的?

從上一個(gè)問題可以看出如果不調(diào)用Looper.prepare()直接new Handler()就會(huì)拋出異常`

throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");`

那么主線程的Looper在哪里創(chuàng)建的呢?首先它是創(chuàng)建了的,因?yàn)?code>Looper.getMainLooper() != null,其實(shí)MainLooper創(chuàng)建的時(shí)間比我們想象的早,它在ActivityThread類里面,ActivityThreadAndroid的啟動(dòng)類,main方法就在里面(如果有人問你Android有沒有main方法,你應(yīng)該知道怎么回答了吧),而MainLooper就是在main方法里面創(chuàng)建的。

上代碼:

//android.app.ActivityThread
public final class ActivityThread {
    ...
    public static void main(String[] args) {
         
        SamplingProfilerIntegration.start();
    
        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);
    
        Environment.initForCurrentUser();
    
        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
    
        Security.addProvider(new AndroidKeyStoreProvider());
    
        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
    
        Process.setArgV0("<pre-initialized>");
    
        //注意這里,這里創(chuàng)建了主線程的Looper
        Looper.prepareMainLooper();
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
    
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
    
        AsyncTask.init();
    
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        //開啟消息循環(huán)
        Looper.loop();
    
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
}
MainLooper可以用來做什么
判斷當(dāng)前線程是否為主線程

因?yàn)長(zhǎng)ooper是在某一線程唯一的,那么可以在么做。如果

 public static boolean isMainThread() {
        //如果當(dāng)前線程的Looper和MainLooper是同一個(gè)對(duì)象,那么可以認(rèn)為當(dāng)前線程是主線程
        return Looper.myLooper() == Looper.getMainLooper() ;
}

但是也有人說下面這樣也可以

public static boolean isMainThread() {
    //這個(gè)方法其實(shí)是不準(zhǔn)確的,線程的名稱是可以隨便更改的。
    return Thread.currentThread().getName().equals("main");
}

所以用Looper來判斷主線程是很好的做法

創(chuàng)建運(yùn)行在主線程的Handler

Handler除了有無參構(gòu)造,還有一個(gè)可以傳入Looper的構(gòu)造。通過指定Looper,可以在任意地方創(chuàng)建運(yùn)行在主線程的Handler

    class WorkThread extends Thread{
        private Handler mHandler;

        @Override
        public void run() {
            super.run();
            mHandler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    //運(yùn)行在主線程
                }
            };
            mHandler.sendEmptyMessage(0);
        }
    }
Looper的quit方法和quitSafely方法有什么區(qū)別

下面是Looper兩個(gè)方法的源碼

public void quit() {
    mQueue.quit(false);
}
public void quitSafely() {
    mQueue.quit(true);
}

可以看出實(shí)際上是調(diào)用的MessageQueuequit方法
下面是MessageQueue的源碼

//android.os.MessageQueue
void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
            //如果調(diào)用的是quitSafely運(yùn)行removeAllFutureMessagesLocked,否則removeAllMessagesLocked。
            if (safe) {
            //該方法只會(huì)清空MessageQueue消息池中所有的延遲消息,
            //并將消息池中所有的非延遲消息派發(fā)出去讓Handler去處理,
            //quitSafely相比于quit方法安全之處在于清空消息之前會(huì)派發(fā)所有的非延遲消息。
                removeAllFutureMessagesLocked();
            } else {
            //該方法的作用是把MessageQueue消息池中所有的消息全部清空,
            //無論是延遲消息(延遲消息是指通過sendMessageDelayed或通過postDelayed等方法發(fā)送的需要延遲執(zhí)行的消息)還是非延遲消息。
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

無論是調(diào)用了quit方法還是quitSafely方法,MessageQueue將不再接收新的Message,此時(shí)消息循環(huán)就結(jié)束,MessageQueuednext方法將返回null,結(jié)束loop()的死循環(huán).這時(shí)候再通過Handler調(diào)用sendMessagepost等方法發(fā)送消息時(shí)均返回false,表示消息沒有成功放入消息隊(duì)列MessageQueue中,因?yàn)橄㈥?duì)列已經(jīng)退出了。

Message

Message.obtain()和new Message()如何選擇

Message提供了obtain等多個(gè)重載的方法來創(chuàng)建Message對(duì)象,那么這種方式和直接new該如何選擇。下面看看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(); //只有當(dāng)從對(duì)象池里取不出Message才去new
}

void recycleUnchecked() {
        //清除所有使用過的痕跡
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

    //回收到對(duì)象池
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

從上面代碼可以看出,通過obtain方法是從對(duì)象池取,而new是創(chuàng)建了一個(gè)新的對(duì)象。我們應(yīng)該使用obtain來創(chuàng)建Message對(duì)象,每次使用完后都會(huì)自動(dòng)進(jìn)行回收,節(jié)省內(nèi)存。

......

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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