android 異步通信機制 Handler 的分析與運用

當我們應用程序啟動時,Android系統就會創建一個主線程即UI線程,在這個UI線程中進行對UI控件的管理,如頁面的刷新或者事件的響應等過程。同時Android規定在UI主線程不能進行耗時操作,否則會出現ANR現象,對此,我們一般是通過開啟子線程來進行耗時操作,在子線程中通常會涉及到頁面的刷新問題,這就是如何在子線程進行UI更新,對于這個問題,我們一般通過異步線程通信機制中的Handler來解決,接下來我們就來分析一下Handler機制。

常規用法

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.execute)
    Button execute;

    @BindView(R.id.text)
    TextView text;

    //處理子線程發過來的消息
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        //子線程發送消息
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = handler.obtainMessage();
                message.what = 1;
                message.obj = "子線程更新UI操作";
                handler.sendMessage(message);
            }
        }).start();

    }

}

以上代碼就是我們一般會使用到的,子線程通過Message,給主線程發送消息進行UI操作,接下來我們就一步一步進行深究,看看android是如何實現子線程和主線程如何交互的。

首先,我們在主線程中開啟一個子線程,我們用了以下方式:

new Thread(new Runnable() {

            @Override
            public void run() {
               //處理事件
            }
        }).start();

開啟一個線程,通常有兩種方式:

  1. 繼承Thread類,覆蓋run方法
  2. 實現runnable接口,實現run方法

對于第一種方法 繼承Thread類,覆蓋run方法 ,我們查看源碼就可以知道,最終還是實現runnable接口,所以沒有多大的區別。

public
class Thread implements Runnable {
    //...
}

回歸正題:

Message message = handler.obtainMessage();
message.what = 1;
message.obj = "子線程更新UI操作";
handler.sendMessage(message);

我們在run方法中進行發送消息,對于第一行我們獲得一個消息是通過

handler.obtainMessage();

而不是通過

Message message =new Message();

這兩者有什么區別呢?還是來進入到源碼中一窺究竟吧!我們首先進入Handler類中,進行查看

public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

繼續進入

public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }

最終來到了Message類中

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

我們仔細觀察一下sPool ,這個sPool 是什么東西呢?pool是池的意思,線程有線程池,那么我們也可以認為Message也有一個對象池,我們分析一下源碼可以得知:

如果Message對象池中還存在message的話,我們直接使用message,而不是創建一個新的Message

接下來就是對message進行一些常規的設置,如要傳遞的消息內容之類的,最后進行消息 的發送。

我們進入到消息的最后一步源碼中進行查看:

handler.sendMessage(message);

會調用sendMessageDelayed方法

//Handler類

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

繼續深入查看

//Handler.java 
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        //定時發送消息
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

如果我們設置了延時時間,那么會計算相應的發送時間,當前時間加上延時就是最終的消息發送時間。

//Handler.java 定時發送消息
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實際上是Handler
        msg.target = this;

        //異步
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }

        //消息入隊
        return queue.enqueueMessage(msg, uptimeMillis);
    }

我們來看看大頭,消息是如何入隊的。

//MessageQueue.java 消息入隊,隊列的實現其實單鏈表的插入和刪除操作
boolean enqueueMessage(Message msg, long when) {

        //指的是Handler
        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) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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;
    }

當Handler將消息插入到消息隊列后,那么重要的問題來了,子線程是如何和主線程通信的呢?按道理講,既然可以將插入到隊列中,那么肯定有一個東西可以從消息隊列中去消息然后進行處理,對于這個東西,就是有Looper來承擔了。

我們首先來看下Looper這個類:

public final class Looper {


    // sThreadLocal.get() will return null unless you've called prepare().
    //用于存放當前線程的looper對象
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    //主線程的Looper也就是UI線程Looper
    private static Looper sMainLooper;  // guarded by Looper.class

    //當前線程的消息隊列
    final MessageQueue mQueue;

    //當前線程
    final Thread mThread;

    //...
}

我們對Looper這個類進行了簡單的介紹,對于消息的獲取并處理我們得進入到主線程中即ActivityThread.java類中去

public static void main(String[] args) {
        //省略部分代碼...
        Looper.prepareMainLooper(); ------------------(1)

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        //省略部分代碼...
        Looper.loop(); ------------------------(2)

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

對于 Looper.prepareMainLooper() 我們進行分析看看,到底是什么?

public static void prepareMainLooper() {
        //不允許退出
        prepare(false);
        synchronized (Looper.class) {
            //一個線程只能有一個Looper對象
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }

            //主線程的looper
            sMainLooper = myLooper();
        }
    }

對于myLoop()是什么東東?

/**
     * 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類中的相關變量,也就是存儲Looper對象的東西,類似于一個容器。這里是取的Looper對象,那么我們在哪里進行存呢?我們進入到prepare方法中:

//保存當前線程的Looper對象
 private static void prepare(boolean quitAllowed) {

        //一個線程只允許一個Looper對象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }

        //存入Looper對象
        sThreadLocal.set(new Looper(quitAllowed));
    }

接下來我們看一下 Looper.loop() 的方法:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {

        //獲取當前線程的Looper對象
        final Looper me = myLooper();

        //主線程中不需要手動調用Looper.prepare()方法,
        //當我們使用子線程時需要手動調用Looper.prepare()方法,否則會報異常。
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        //當前線程中的消息隊列
        final MessageQueue queue = me.mQueue;

        //省略部分代碼...

        //死循環,不斷處理消息
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

             //省略部分代碼...
            try {
                //msg.target就是Handler,Handler處理消息
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            //省略部分代碼...

            //消息回收
            msg.recycleUnchecked();
        }
    }

Looper.loop()其實就是不斷的從隊列中獲取消息,然后進行處理。

在上面方法中,有一行代碼:msg.target.dispatchMessage(msg);我們進入內部去詳細查看一下實現:

//Handler.java 分發消息
/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {

        // msg.callback== Runnable callback;
        if (msg.callback != null) {
            //如果有runnable,那么則實現它的run方法里面的內容
            handleCallback(msg);
        } else {
            //否則處理handleMessage方法
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

handleCallback(msg);方法實現

private static void handleCallback(Message message) {
        //msg.callback== Runnable callback;
        message.callback.run();
    }

handleMessage方法實現

public interface Callback {
        public boolean handleMessage(Message msg);
    }

對于上面那個方法,其實就是我們在主線程中實現的方法:

//消息處理
Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                text.setText("obj:" + msg.obj.toString() + "\nwhat: " + msg.what + "\narg1: " + msg.arg1);
            }
        }
    };

到此,基本上就已經分析了大概,不過,我們在實際的開發過程中有時候會碰到這個問題:

Can't create handler inside thread that has not called Looper.prepare()

而我們的代碼是如何寫的呢?

//自定義一個Thread繼承自Thread
private class MyThread extends Thread {
        private Handler myThreadHandler;

        @Override
        public void run() {

            myThreadHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == 1) {
                      //todo...
                    }
                }
            };


            Message message = new Message();
            message.what = 1;
            message.obj = "MyThread Message Handler";
            myThreadHandler.sendMessage(message);

        }
    }

上述代碼很簡單,就是自定義一個Thread,然后申明一個Handler來使用,然后通過下面代碼進行線程通信:

myThreadBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new MyThread().start();
            }
        });

為什么上面簡單的代碼會出現這個問題呢?而我們在Activity中申明的Handler就可以直接用,而不會出現上述的error?其實,我們在UI主線程中使用Handler時已經調用過了Looper.prepare()和Looper.loop(),我們返回到上面的 ActivityThread.java 類中的main方法處,我們可以發現,其實UI主線程已經調用過了,

Looper.prepareMainLooper();
 //...
 Looper.loop();

而在我們子線程卻需要我們自己手動調用一下,知道了原因所在,我們來修改一下,再次運行,即可得出正確的答案。

private class MyThread extends Thread {
        private Handler myThreadHandler;

        @Override
        public void run() {

            //注意此處的prepare方法
            Looper.prepare();

            myThreadHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == 1) {
                     //todo...
                    }
                }
            };


            Message message = new Message();
            message.what = 1;
            message.obj = "MyThread Message Handler";
            myThreadHandler.sendMessage(message);

            //注意此處的loop方法
            Looper.loop();

        }
    }

以上修改內容即可得出正確的答案,接下來我們來總結一下:

為什么使用異步消息處理的方式就可以對UI進行操作了呢?這是由于Handler總是依附于創建時所在的線程,比如我們的Handler是在主線程中創建的,而在子線程中又無法直接對UI進行操作,于是我們就通過一系列的發送消息、入隊、出隊等環節,最后調用到了Handler的handleMessage()方法中,這時的handleMessage()方法已經是在主線程中運行的,因而我們當然可以在這里進行UI操作了。

除了通過Handler的sendMessage方法來進行子線程和主線程進行通信外,我們還可以通過以下的方法來達到相同的效果。

1、handler.post

我們來仔細分析一下源代碼進行說明。

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

調用handler.post方法,將runnable參數轉化為一條message進行發送的。接著我們進入getPostMessage(r)中進行分析看看。

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

將傳遞進來的runnable參數賦值給Message的callback變量,賦值給它有什么用呢?我們還記不記得在Handler的dispatchMessage時會做一個判斷???

public void dispatchMessage(Message msg) {
        //判斷Message的callback是否為null,這里的callback就是runnable
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

上面的callback是否為null的判斷決定著整個流程,如果callback不等于null的話我們進入handleCallback(msg)方法中一窺究竟。

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

看到沒?直接調用了run方法,其中的message.callback就是Runnable,所以它會直接執行run方法。所以對于在子線程中更新UI時使用handler.post 方法時,直接在run方法中進行UI更新如:

mHandler.post(new Runnable() {
            @Override
            public void run() {
                handlerText.setText("result: this is post method");
            }
        });

2、view.post

同理,我們也是進入到源碼中進行分析一下:

/**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @see #postDelayed
     * @see #removeCallbacks
     */
public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

通過該方法的注釋我們就知道,首先會將runnable放進到Message隊列中去,然后在UI主線程中運行,調用handler的post方法,本質的原理都是一樣的。

3、runOnUiThread

對于runOnUiThread方法,我們從字面上也可以了解到是在主線程中運行的,我們詳細分析一下:

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

代碼很簡單,就是先判斷一下當前線程是否是UI主線程,是的話,直接運行run方法,不是的話通過Handler來實現。

通過以上四種在子線程中更新UI的方法,其內在的本質都是一樣的,都是借助于Handler來實現的,本篇分析了異步通信機制中的Handler,通過本篇的學習與了解,對于實際項目中如果遇到相似的問題的話,我想應該可以迎刃而解了。 知其然而知所以然!

最后我們來總結一下本篇文章中涉及到的各個對象的意思:

1、MessageQueue

消息隊列,它的內部存儲了一組數據,以隊列的形式向外提供了插入和刪除的工作。但是它的內部實現并不是隊列,而是 單鏈表

2、Looper

會不停檢查是否有新的消息,如果有就調用最終消息中的Runnable或者Handler的handleMessage方法。對應提取并處理消息。

3、Handler

Handler的工作主要包含消息的發送和接收過程。消息的發送可以通過post的一系列方法以及send的一系列方法來實現,不過最后都是通過send的一系列方法實現的。對應添加消息和處理線程。

4、Message

封裝了需要傳遞的消息,并且本身可以作為鏈表的一個節點,方便MessageQueue的存儲。

5、ThreadLocal

一個線程內部的數據存儲類,通過它可以在指定的線程中儲存數據,而其它線程無法獲取到。在Looper、AMS中都有使用。

參考

1、http://www.jianshu.com/p/94ec12462e4e)

2、http://blog.csdn.net/woshiwxw765/article/details/38146185

關于作者

1. 簡書 http://www.jianshu.com/users/18281bdb07ce/latest_articles

2. 博客 http://crazyandcoder.github.io/

3. github https://github.com/crazyandcoder

 

 本文由用戶 r4047xgv 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!