從源碼角度看Handler原理

在Android中,有一個規定就是除了主線程,其他線程不能操作UI視圖,因爲不這樣做的話會出現線程不安全問題。但還有一個規定UI線程在執行一個操作如果5秒內沒有響應就會包ANR錯誤。所以UI線程中不允許訪問網絡這樣的耗時操作,那麼問題來了,子線程執行耗時操作,比如從網絡獲取圖片,但子線程不能更新UI,而主線程能更新UI,但不能去下載圖片。這樣handler消息處理機制就出現了。

1. Handler是什麼?

handler是Android給我們提供用來更新UI的一套機制,也是一套消息處理的機制,我們可以發送消息,也可以通過它處理消息。

2. 爲什麼要使用Handler?

Android在設計時,就封裝了一套消息消息創建、傳遞、處理機制,如果不遵循這樣的機制就沒辦法更新UI,就會拋出異常。

3. Android中爲什麼要設計只能通過Handler機制更新UI?

解決多線程併發問題。如果允許多線程更新UI會導致界面錯亂,如果非要使用多線程並使用加鎖機制更新UI又會導致性能下降,所以更新UI的操作全部交給主線程。

4.那麼handler機制是怎樣的機制呢?

瞭解handler機制,首先需要清楚四個核心類:
Message:對發送的消息的封裝
MessageQueue:消息隊列,存放所有的消息
Looper:可以循環讀取消息(從MessageQueue中讀取)
Handler:處理消息,同時也是發送消息的

具體來看看handler是怎麼實現的。

4.1使用1:主線程接收子線程發來的消息(下載圖片爲例)
1)在主線程中初始化handler對象:

private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //處理子線程發送過來的message
            Bitmap bitmap = (Bitmap)msg.obj;
            imageView.setImageBitmap(bitmap);
        }
    };

2)然後子線程中下載好圖片後發送圖片給主線程:

new Thread(new Runnable() {
            @Override
            public void run() {
                HttpGet get = new HttpGet(path);
                HttpClient client = new DefaultHttpClient();
                HttpResponse response = null;
                try {
                    response = client.execute(get);
                    if (response.getStatusLine().getStatusCode() == 200) {
                        byte[] arr = EntityUtils.toByteArray(response
                                .getEntity());
                        Bitmap bitmap = BitmapFactory.decodeByteArray(arr, 0,
                                arr.length);
                        // 下載完成時把圖片發送給主線程
                        // 從MessageQueue中獲取可用的Message對象,如果沒有可用的Message對象則會創建一個新的Message對象
                        Message msg = Message.obtain();
                        // 把發送的圖片封裝到msg中
                        msg.obj = bitmap;
                        // 使用Handler發送msg
                        handler.sendMessage(msg);// 把msg發送給實例化handler的線程
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

消息的攔截:

/**
 * 攔截消息測試
 * @author Administrator
 *
 */
public class ThirdActivity extends Activity {
    Handler handler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Log.i("--", "消息都要經過我這裏");
            /**返回false,消息會繼續向下分發,返回true則攔截*/
            return true;
        }
    }) { 
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i("--", "我纔是處理消息的");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendEmptyMessage(1);
            }
        }).start();
    }
}

3)使用1有關handler機制的操作有:
Handler handler = new Handler();
handleMessage(Message msg);

Message msg = Message.obtain();
handler.sendMessage(msg);

那麼現在來看一看它內部到底是怎樣執行的。

5.從源碼一步一步分析handler原理

5.1Handler handler = new Handler();

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

這個無參構造方法會調用兩個參數的構造方法,注意上面的參數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());
            }
        }
        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;
    }

那來看看這個兩個參數的構造方法都做了些什麼。
FIND_POTENTIAL_LEAKS這個變量初始化爲false
private static final boolean FIND_POTENTIAL_LEAKS = false;
所以直接執行後面的,也就是這些:

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對象和一個MessageQueue對象,mLooper = Looper.myLooper();中Looper.myLooper()方法中是得到當前線程的Looper對象。那麼當前線程是什麼?在這裏,因爲我們是在主線程中實例化Handler對象,所以當前線程就是主線程,值得注意的是,主線程在創建時就會維護一個Looper對象和MessageQueue對象,所以這裏得到的Looper對象和消息隊列都是主線程的。

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

mQueue = mLooper.mQueue;這句說明handler內部的MessageQueue和Looper的MessageQueue是一個。
好,那麼handler的初始化階段先到這裏。
5.2 Message msg = Message.obtain();
再來看看這句做了哪些事,
注意:有時候我們會直接這麼寫Message msg = new Message();

 public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

這裏的意思就是如果sPool不爲空就返回sPool,否則就new一個新的Message對象。(sPool是回收放在消息池中的對象)

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

5.3 handler.sendMessage(msg);
關鍵的一句終於到了,來看看這個又做了什麼。
第一步:

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

到第四步算是找到能辦事的了,看這裏都辦了哪些事。
1) msg.target = this;可以查看Message源碼target就是Handler類型的,這裏把msg的target指向this,也就是這個發送消息的handler。

2)queue.enqueueMessage(msg, uptimeMillis);調用queue的enqueueMessage方法,去MessageQueue類中看看。

final boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }
        boolean needWake;
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }
            msg.when = when;
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }

很簡單,就是把handler發送的msg加入到消息隊列中。

5.4handleMessage(Message msg);
好了,最後只用處理msg了。這是個回調方法,所以必須要等到Looper去從消息隊列中讀取消息時纔會執行。
那就去看看Looper.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();
        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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            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);
            }
            msg.recycle();
        }
    }

開頭幾句是得到當前線程的Looper對象,然後同構Looper對象得到queue對象,很明顯,5.1已經說了,那麼這裏得到的就是主線程那個Looper和MessageQueue。然後開始了死循環,一直從消息隊列中讀取消息,讀不到就返回,否則執行後面操作。注意看後面操作,真正處理消息的時候到了。

msg.target.dispatchMessage(msg);關鍵就是這句。 它調用了msg的target的dispatchMessage方法,之前說了,target就是發送消息的那個handler,好,再會Handler類看dispatchMessage方法。

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

很簡單,這就是一個消息分發處理嘛。記得handler發送消息有兩種方式,一種就是普通的handler.sendMessage(msg);方法,還有一種就是
handler.post(new Runnable(){
@Override
public void run() {
//主線程應該執行的操作
}
});
先來看第一種:直接回調hanleMessage(msg);方法,這樣主程序中就執行該操作啦。
再來看第二種:

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

調用message.callback的run方法。
從源碼可以一步一步看出,如下,message的callback指向handler調用post方法傳入的Runnable對象。

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

再回頭看dispatchMessage方法,會調用下面這個方法:

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

內部最後調用run方法。
handler原理基本就這樣了。

總結:handler對象定義在需要接收本線程或其他線程發送消息的線程中,該線程會維護一個Looper對象和MessageQueue對象,在其他線程或本線程通過handler發送的消息會加入到handler所在線程中的消息隊列中,同時Looper的loop()方法會一直讀取消息隊列,讀到消息後,又讓發送消息的handler處理這個消息。

源碼下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章