Android源碼解析Handler系列第(一)篇 --- Message全局池

轉(zhuǎn)載請(qǐng)注明文章出處LooperJing

1、UI不能在子線程中更新是個(gè)偽命題

我們常說(shuō)UI需要在主線程中進(jìn)行更新,子線程就不能更新UI嗎?不是,我們并不是說(shuō)不能在子線程中更新UI,而是說(shuō)UI必須要在它的創(chuàng)建線程中進(jìn)行更新,比如下面一段代碼在子線程更新UI就不會(huì)報(bào)錯(cuò)。

    new Thread(new Runnable() {
            @Override
            public void run() {
                TextView  textView=new TextView(MainActivity.this);
                textView.setText("Hello");
            }
     }).start();

OK,那我偏偏不在創(chuàng)建線程中進(jìn)行更新,如下!view在主線程中創(chuàng)建,在子線程中add一個(gè)Button。

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final LinearLayout view = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.activity_main, null, false);
        setContentView(view);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Button btn=new Button(MainActivity.this);
                btn.setText("Hello");
                view.addView(btn);
            }
        }).start();
    }

運(yùn)行后,Button被順利的加到了根布局中。尼瑪,“UI必須要在它的創(chuàng)建線程中進(jìn)行更新”這種說(shuō)法也不對(duì)啊!!!不是不對(duì),而是欠妥,我修改一下代碼!

     new Thread(new Runnable() {
            @Override
            public void run() {
                Button btn=new Button(MainActivity.this);
                btn.setText("Hello");
                SystemClock.sleep(2000);
                view.addView(btn);
            }
        }).start();

就是在addView之前,睡眠了一會(huì),結(jié)果還是拋出了 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.這個(gè)錯(cuò)誤!通常這種錯(cuò)誤,就要用Handler向UI線程發(fā)送消息來(lái)更新!這個(gè)后面會(huì)對(duì)源碼進(jìn)行分析。說(shuō)了半天,進(jìn)入正題,我今天要討論的是Message。要掌握Handler,先對(duì)Handler發(fā)送的Message有所了解。

2、Message的初步認(rèn)識(shí)

Message就是一個(gè)實(shí)現(xiàn)了Parcelable的java類(lèi),我的第一感覺(jué)是它可以可以用在進(jìn)程間通信(IPC),可以用在網(wǎng)絡(luò)中傳輸,看一下它比較重要的幾個(gè)字段。先來(lái)幾個(gè)熟悉的。

    //當(dāng)有多個(gè)Handler發(fā)消息的時(shí)候,或者一個(gè)Handler發(fā)多個(gè)消息的時(shí)候,可以用what標(biāo)識(shí)一個(gè)唯一的消息
   public int what;

    //當(dāng)傳遞整形數(shù)據(jù)的時(shí)候,不需要使用setData(Bundle)去設(shè)置數(shù)據(jù),使用arg1,arg2開(kāi)銷(xiāo)更小。
    public int arg1; 

    public int arg2;

    //Message所攜帶的數(shù)據(jù)對(duì)象
    public Object obj;

這幾個(gè)都是很常用的,要深入了解Message,不得不了解下面幾個(gè)。

    //flags表示這個(gè)Message有沒(méi)有在使用,1表示在池中,等待復(fù)用,0表示正在被使用,
     int flags;

    //Message發(fā)送之后,何時(shí)才能被處理
     long when;
    
   // Message所攜帶的數(shù)據(jù) 
     Bundle data;

    //表示這個(gè)消息被哪個(gè)Handler處理
     Handler target;
    
    //我們用Handler.post一個(gè)Runnable,這個(gè)Runnable也是被包裝成Message對(duì)象的
     Runnable callback;
    
    // 作為消息鏈表所用的一個(gè)成員
    Message next;

    //sPoolSync是對(duì)象鎖,因?yàn)镸essage.obtain方法會(huì)在任意線程調(diào)用
    private static final Object sPoolSync = new Object();

    //sPool代表接下來(lái)要被重用的Message對(duì)象
    private static Message sPool;

    //sPoolSize表示有多少個(gè)可以被重用的對(duì)象
    private static int sPoolSize = 0;

    //MAX_POOL_SIZE是pool的上限,這里hardcode是50
    private static final int MAX_POOL_SIZE = 50;

對(duì)于上面列舉的,挑幾個(gè)重點(diǎn)分析一下。

Runable是怎么被封裝到Message中的?我們常為會(huì)延遲做某個(gè)操作寫(xiě)出下面的代碼

handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                
            }
        },1000);
跟蹤進(jìn)去發(fā)現(xiàn)在postDelayed中調(diào)用了getPostMessage
   private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

所以這個(gè)run方法只是一個(gè)普通的回調(diào)而已,千萬(wàn)不要認(rèn)為他是在另外一個(gè)線程中。剛說(shuō)了Message中的target表示了這個(gè)消息要被哪一個(gè)Handler所處理,對(duì)于Message是怎么被處理的呢?盡管后面要分析Handler源碼,這里還是提一下。

Message的處理順序
Looper.loop()方法中Message被從MessageQueue取出來(lái)后會(huì)調(diào)用msg.target.dispatchMessage(msg)

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

在dispatchMessage方法中,檢查是否有由Runnable封裝的消息,如果有,首先處理;其次處理的是mCallback,mCallback是什么?mCallback是Callback的對(duì)象,Callback是Handler中的一個(gè)內(nèi)部接口,這個(gè)接口的作用是,當(dāng)你實(shí)例化一個(gè)Handler對(duì)象的時(shí)候,可以避免不去實(shí)現(xiàn)Handler的handleMessage方法。

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

從消息的分發(fā)可以看到,如果返回了true,那么handler中的handleMessge方法是不會(huì)被執(zhí)行的,如果返回false或者mCallback沒(méi)有被賦值,那么就會(huì)回調(diào)Handlerd的handleMessge,這就是一個(gè)Message的分發(fā)流程。了解了Message的分發(fā)之后,那么Message是怎么被創(chuàng)建的呢。

3、Message全局池

ANDROID系統(tǒng)中很多操作都是靠發(fā)消息完成的,比如按下返回鍵就是一個(gè)消息,這樣系統(tǒng)會(huì)不會(huì)構(gòu)建大量的Message對(duì)象呢?上面看到getPostMessage方法中,以 Message.obtain()的方式獲取一個(gè)消息,而不是通過(guò)它的構(gòu)造函數(shù),獲取消息的代碼很簡(jiǎn)單。

 /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }

有一個(gè)關(guān)鍵詞是sPool,初步感覺(jué)是池的意思,別忘記了,在第一節(jié)Message的初步認(rèn)識(shí)中,sPool是一個(gè)Message對(duì)象 ,這里有點(diǎn)蒙逼,一個(gè)對(duì)象起個(gè)名字跟池有什么關(guān)系。回顧一下,Message還有一個(gè)成員是 next。

// sometimes we store linked lists of these things
   Message next;

這樣每一個(gè)Message對(duì)象都有一個(gè)next指針指向下一個(gè)可用的Message,這不就是大學(xué)數(shù)據(jù)結(jié)構(gòu)中的鏈表嘛!所以一個(gè)消息池的結(jié)構(gòu)是這樣的。

Message鏈表

既然知道Message有一個(gè)消息池(我們通常稱(chēng)這個(gè)消息池為全局池)的機(jī)制,那么它設(shè)計(jì)初衷肯定是要做到復(fù)用的,那么還有一個(gè)問(wèn)題,Message何時(shí)被入池,何時(shí)出池?(MessageQueue雖然是存儲(chǔ)消息的,但要弄清楚,這里說(shuō)的消息池跟MessageQueue并沒(méi)有什么關(guān)系。)

消息要入池,也就是這個(gè)消息被回收到池中,等待復(fù)用,所以我們大膽猜測(cè)這個(gè)消息肯定不在使用之中,如果這個(gè)消息正在使用之中,是肯定不會(huì)把它放到全局池里面的,也就是說(shuō)只有這個(gè)消息完成了它的使命,系統(tǒng)才能把它回收到全局池中。通過(guò)這樣的分析,消息入池不是在它的創(chuàng)建階段,而是在回收階段。直接看代碼吧。Message中有一個(gè)recycle的方法。

 /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

首先判斷消息是否在使用之中

    boolean isInUse() {
        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
    }

如果在使用之中,繼續(xù)判斷gCheckRecycle,gCheckRecycle的默認(rèn)值是true,這個(gè)一個(gè)與版本相關(guān)的常量,在5.0之后的版本這個(gè)值是false,不知道作什么使用,有誰(shuí)知道可以告訴我一下。在此不糾結(jié)了。如果不在使用之中,最后會(huì)走進(jìn)recycleUnchecked。

/**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    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++;
            }
        }
    }

這段代碼也不是很長(zhǎng),很好理解,一開(kāi)始,把這個(gè)消息所有成員賦值成最初的狀態(tài),F(xiàn)LAG_IN_USE的值是1一開(kāi)始說(shuō)了Message的flags表示這個(gè)Message有沒(méi)有在使用,1表示在池中,等待復(fù)用,0表示正在被使用。重點(diǎn)看同步鎖中的代碼。
假設(shè)全局池沒(méi)有元素時(shí),我們將第一個(gè)消息放到池中,sPool一開(kāi)始是NULL,next指向了sPool,所以此時(shí)的消息的sPool和next都是NULL,然后sPool指向當(dāng)前的Message對(duì)象,最后池的數(shù)量加1。大致如下圖。


全局池中只有一個(gè)消息等待被復(fù)用

假設(shè)有來(lái)個(gè)消息m2,在走一遍同步鎖中的代碼,此時(shí)全局池的狀態(tài)如下圖所示。

全局池中兩個(gè)消息等待被復(fù)用

同理,三個(gè)消息的時(shí)候,是這樣

全局池中三個(gè)消息等待被復(fù)用

看到這,相信你已經(jīng)知道了消息是怎么加到全局池中的,那么何時(shí)出池呢?再看消息的獲取代碼,嘗試找出消息何時(shí)出池的。

 /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    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();
    }

obtain經(jīng)常使用在多線程之中,所以用sPoolSync作為同步鎖,第一次sPool為空,會(huì)new一個(gè)消息返回,這new的消息會(huì)在回收的時(shí)候,會(huì)被加到全局池中。如果sPool不為空,sPool是什么?sPool是指向全局池的頭指針,sPool不為空,說(shuō)明了全局池中有元素。把sPool賦值給一個(gè)Message對(duì)象m,同時(shí)全局池的頭指針向后移,指向下一個(gè)被復(fù)用的消息,然后把m的flags賦值為0,表示這個(gè)消息被復(fù)用了,池中元素?cái)?shù)量減1。經(jīng)過(guò)這個(gè)邏輯,上圖中的消息m3是不是就被出池了呢?

消息m3離開(kāi)全局池

OK,本文分析到這里,一個(gè)Message大部分內(nèi)容都被分析了,我們知道了Message內(nèi)部有一個(gè)全局池,保證了開(kāi)發(fā)者可以不構(gòu)建大量的消息,提高性能。我們也知道了一個(gè)消息何時(shí)入池,何時(shí)出池。OK,Message復(fù)用機(jī)制到此結(jié)束。

Please accept mybest wishes for your happiness andsuccess

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

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