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();
開啟一個執行緒,通常有兩種方式:
繼承Thread類,覆蓋run方法
實現runnable介面,實現run方法
對於第一種方法繼承Thread類,覆蓋run方法,我們檢視原始碼就可以知道,最終還是實現runnable介面,所以沒有多大的區別。
publicclass 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如果我們設定了延時時間,那麼會計算相應的傳送時間,當前時間加上延時就是最終的訊息傳送時間。
//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當Handler將訊息插入到訊息佇列後,那麼重要的問題來了,子執行緒是如何和主執行緒通訊的呢?按道理講,既然可以將插入到佇列中,那麼肯定有一個東西可以從訊息佇列中去訊息然後進行處理,對於這個東西,就是有Looper來承擔了。
我們首先來看下Looper這個類:
public final class Looper { // sThreadLocal.get() will return null unless you've called prepare(). //用於存放當前執行緒的looper物件 static final ThreadLocalsThreadLocal = new ThreadLocal (); //主執行緒的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繼承自Threadprivate 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
同理,我們也是進入到原始碼中進行分析一下:/** *Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.
* * @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中都有使用。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1868/viewspace-2814517/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android訊息機制HandlerAndroid
- android訊息機制—HandlerAndroid
- Android的Handler訊息機制 解析Android
- Android訊息機制Handler用法Android
- Android 原始碼分析(二)handler 機制Android原始碼
- Android Handler 訊息機制詳述Android
- Android Handler MessageQueue Looper 訊息機制原理AndroidOOP
- Android 訊息機制:Handler、MessageQueue 和 LooperAndroidOOP
- Android Handler機制之Handler 、MessageQueue 、LooperAndroidOOP
- Android Handler機制之訊息池的擴充套件 SimplePool與SynchronizedPoolAndroid套件synchronized
- Android Handler機制之Message的傳送與取出Android
- Android-Handler訊息機制實現原理Android
- Android進階;Handler訊息機制詳解Android
- Android Handler訊息機制原始碼解讀Android原始碼
- Android非同步訊息機制Android非同步
- Android Handler機制之ThreadLocalAndroidthread
- Android訊息機制全面解析(Handler,MessageQueue,Looper,Threadlocal)AndroidOOPthread
- Android之Handler訊息傳遞機制詳解Android
- Android Handler機制之迴圈訊息佇列的退出Android佇列
- Handler訊息機制完全解析Handler解析
- Android Handler機制之Message及Message回收機制Android
- Android Handler機制之總目錄Android
- Android中的非同步訊息處理機制Android非同步
- Android全面解析之由淺及深Handler訊息機制Android
- Android Handler訊息傳遞機制:圖文解析工作原理Android
- Handler訊息傳遞機制
- Binder通訊機制與IPC通訊.md
- 使用 Frida 逆向分析 Android 應用與 BLE 裝置的通訊Android
- 學習 Android Handler 訊息機制需要注意這些問題!Android
- Android 9.0 原始碼_機制篇 -- 全面解析 HandlerAndroid原始碼
- Android Handler機制理解和AsyncTask使用小記Android
- Android 之 “只是想來談談 Handler 機制”Android
- Android--Handler機制及原始碼詳解Android原始碼
- Android Handler機制之記憶體洩漏Android記憶體
- Handler機制解析
- 深入理解Android非同步訊息處理機制Android非同步
- Android原始碼解析之一 非同步訊息機制Android原始碼非同步
- 原始碼分析:Android訊息處理機制原始碼Android