天天看點

handler 的消息機制

在日常開發中,我們經常要用到消息的通信機制,比如網絡請求,在子線程中請求到資料後,切換到主線程(也叫ui線程-activityThread)去更新資料。

在這一過程中,有幾個比較重要的類是我們要熟悉或者了解的,分别是–handler Looper Message Messagequeue ThreadLocal。

這裡我們就不詳細的去分析每個類的底層邏輯了,隻會在要用到的相關方法時再去分析下。

那我們就直接切到正題吧,我們平時需要用到handler的時候絕大部分都是直接在主線程中建立handler并初始化的,然後在子線程中去把資料消息發送到主線程去處理,比如這樣。。

Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 0:
                        //todo
                        break;
                }
            }
        };
           

發送一條消息

new Thread(new Runnable() {
            @Override
            public void run() {
                //這裡請求網絡資料,然後把資料通過message發送

                Message message=Message.obtain();
                message.obj="如果不是基本資料類型的可以通過此字段來發送";
                message.what=0;
                handler.sendMessage(message);

            }
        }).start();
           

好了,這是一條很基本的消息發送,很容易了解,接下來,我們去分析為什麼在子線程通過 handler.sendMessage(message);就能把消息給發送到了主線程呢?我們跟着他的方法一步步的下去看,點進handler的sendMessage(message)去看

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

           

ok,調用了 this.sendMessageDelayed(msg, 0L),這裡的第二個參數是延遲發送的參數,如果我們想延遲發送消息的話,可以一開始直接就調用handler.sendMessageDelayed(),這樣就可以延遲發送消息了。

我們接着點進去sendMessageDelayed()這個方法裡面看看,

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if(delayMillis < 0L) {
            delayMillis = 0L;
        }

        return this.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
           

好吧,判斷了一下延遲的時間,然後調用了this.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);,接着點進去look look

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

嗯,主角之一MessageQueue粉墨登場了,讓我們我們來會會它,可以看到這裡直接就把 this.mQueue指派給了新建立的queue了,那 this.mQueue是在哪建立的呢,這是我想寫這篇部落格的原因之一。其實 this.mQueue是主線程的MessageQueue,而非這個子線程自己的MessageQueue,因為主線程在調用main函數之後就已經建立好了MessageQueue,具體我們看下代碼

public static void main(String[] args) {
        Trace.traceBegin(64L, "ActivityThreadMain");
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new ActivityThread.EventLoggingReporter(null));
        File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        Process.setArgV0("<pre-initialized>");
        

> Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if(sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Trace.traceEnd(64L);
    

>     Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
           

這裡先說下,其實MessageQueue是Looper的一個成員變量,它在Looper的構造函數中初始化,當調用Looper.prepare時

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");
        } else {
            sThreadLocal.set(new Looper(quitAllowed));
        }
    }

           

這裡可以看到Looper是在 sThreadLocal.set(new Looper(quitAllowed));中被建立,這個sThreadLocal是ThreadLocal,也是我們的主角之一,它主要是用來存儲線程内變量的,接下來我們進去Looper的構造函數中看。

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

嗯哼,終于看到,原來this.mQueue是在這裡被建立的,到這裡我們先捋一下思路,調用了Looper.prepare之後才建立MessageQueue,但是我們好像從來沒有調用過Looper.prepare()函數呀,那MessageQueue是哪來的呀,别急,慢慢來,喝口水消消性子,等貧僧念念經。。。

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

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

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

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

    public Handler(boolean async) {
        this((Handler.Callback)null, async);
    }

    public Handler(Handler.Callback callback, boolean async) {
        this.mLooper = Looper.myLooper();
        if(this.mLooper == null) {
            throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
        } else {
            this.mQueue = this.mLooper.mQueue;
            this.mCallback = callback;
            this.mAsynchronous = async;
        }
    }
           

這裡看到handler有幾個構造函數,我們建立handler的時候并沒有傳入任何參數,是以調用了handler的無參構造函數,然後無參構造函數又調用有兩個參數的那個構造函數 public Handler(Handler.Callback callback, boolean async),然後看到這行 this.mLooper = Looper.myLooper();

看到沒,Looper是在這裡被指派的,我們進去Looper.myLooper()這個方法看

public static Looper myLooper() {
        return (Looper)sThreadLocal.get();
    }
           

哦,原來Looper是在這裡獲得的,那為什麼通過(Looper)sThreadLocal.get()就能獲得呢,因為我們說過ThreadLocal是專門存儲線程内資訊的,而Looper也屬于線程内的變量,當然也就被存儲在ThreadLocal裡面咯,而在每個線程内調用(Looper)sThreadLocal.get()所獲得的值是不同的,因為在(Looper)sThreadLocal.get()的内部會通過Thread.currentThread去擷取每個線程對應的值,這裡我們就不貼代碼了,有需要了解的爺請自行檢視源碼哈,這裡請允許小弟偷下懶呗。

接着回到話題,因為我們是在主線程建立的handler,是以理所當然的最後擷取到的Looper就可以一口咬定是屬于主線程的啦,而更理所當然的MessageQueue也是屬于主線程的啦,因為它是在Looper的構造函數中被建立的嘛!那麼,通過handler.sendMessage()發送的message自然就是存進主線的MessageQueue中咯,而在

Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 0:
                        //todo
                        break;
                }
            }
        };
           

中取出的message也是從主線程的MessageQueue取出的,接下來我們回到上面的步伐接着看,

剛剛我們看到

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

看到調用了this.enqueueMessage(queue, msg, uptimeMillis)這個方法,點進去看看

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

        return queue.enqueueMessage(msg, uptimeMillis);
    }
           

可以看到,把handler本身指派給了message的target字段,因為這個方法是在handler類裡面調用的,是以this就代表了handler本身。然後看到queue.enqueueMessage(msg, uptimeMillis);這裡的queue就是從主線程所擷取來的MessageQueue,就是在這個方法中插入一條消息到消息隊列中的。好了,消息存進去了,那麼如何取出消息的呢?

取出消息的操作是在Looper.loop中,我們去看看

public static void loop() {
        Looper me = myLooper();
        if(me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        } else {
            MessageQueue queue = me.mQueue;
            Binder.clearCallingIdentity();
            long ident = Binder.clearCallingIdentity();

            while(true) {
                Message msg = queue.next();
                if(msg == null) {
                    return;
                }

                Printer logging = me.mLogging;
                if(logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
                }

                long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
                long traceTag = me.mTraceTag;
                if(traceTag != 0L && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }

                long start = slowDispatchThresholdMs == 0L?0L:SystemClock.uptimeMillis();

                long end;
                try {
                    msg.target.dispatchMessage(msg);
                    end = slowDispatchThresholdMs == 0L?0L:SystemClock.uptimeMillis();
                } finally {
                    if(traceTag != 0L) {
                        Trace.traceEnd(traceTag);
                    }

                }

                long newIdent;
                if(slowDispatchThresholdMs > 0L) {
                    newIdent = end - start;
                    if(newIdent > slowDispatchThresholdMs) {
                        Slog.w("Looper", "Dispatch took " + newIdent + "ms on " + Thread.currentThread().getName() + ", h=" + msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                    }
                }

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

                newIdent = Binder.clearCallingIdentity();
                if(ident != newIdent) {
                    Log.wtf("Looper", "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();
            }
        }
    }
           

這段代碼有點長,我們挑重點來看就行了,其實在看源碼的時候,我們沒有必要把每行代碼都看懂,隻要看明白大概流程就行了,因為有些源碼不是一個人寫的,你很難去揣摩别人的每一行代碼的作用,那樣你會走火入魔的(不恰當),不扯了,回到主題

MessageQueue queue = me.mQueue;
        
        
           

這裡首先擷取到了我們存進去消息的那個MessageQueue,然後就進入到了阻塞循環體,我們看重點

Message msg = queue.next();
                if(msg == null) {
                    return;
                }
           

在循環體中通過queue.next();取出消息(同時把該message從消息隊列中移除),這裡可以看到,當msg ==null時,就不會往下走了,在這裡就阻塞住了,接着往下看

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

                }
           

看這句 msg.target.dispatchMessage(msg);這裡的 msg.target就是我們先前發送消息的時候傳入的handler對象,點進去dispatchMessage(msg)方法看看

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

            this.handleMessage(msg);
        }

    }
           

看這句msg.callback != null,這裡的callback其實是Runnable對象,當我們通過handler.post系列函數發送消息的時候,這裡的callback就不會為null,但我們是通過send系列函數發送的,是以這裡它為空,接着進入到

else {
            if(this.mCallback != null && this.mCallback.handleMessage(msg)) {
                return;
            }

            this.handleMessage(msg);
        }
           

this.mCallback != null && this.mCallback.handleMessage(msg)這裡的mCallback是Handler.Callback mCallback;如果我們建立handler時傳入Callback,它就不會為空,還記得handler有這樣一個構造函數吧

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

是以它也是空,接着肯定的要調用 this.handleMessage(msg);方法,我們進去看

public void handleMessage(Message msg) {
    }
           

看到沒,空空如也。。。

這個方法就是我們建立handler時重寫的

Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 0:
                        //todo
                        break;
                }
            }
        };
           

終于,經過了九九六十一,回到了主線程的

public void handleMessage(Message msg) {
                switch (msg.what){
                    case 0:
                        //todo
                        break;
                }
           

handleMessage(Message msg)方法中,這裡就可以就行ui重新整理的工作啦,咧咧咧,我不聽,我不聽,我就要在子線程重新整理

額。陛下,冷靜點,到飯點了,我們吃飯去吧。。。。。。。。

最後說明一點,本文純屬個人了解,可能不可避免有錯誤,各位看官如果發現有錯誤,請給指出啊,别有誤人子弟我就要跳黃河洗不清啦,哈哈哈。。。。