Android更新Ui進階精解(二)
《代碼裡的世界》
1.回顧
第一篇講了對Ui線程更新的方法和見解,然後接著講了線程檢查機制,這裡來詳細分析下更新Ui的核心——Android中訊息系統模型。當然,這裡要講的其實也已經不再簡簡單單地是更新Ui的範疇了。不過還是很值得學習和分析一下。另外,其實網上關於這方面的講解也有很多了,本篇也是綜合整理並用自己的理解加以描述和概括。同時也感謝有更高造詣的大大能予以批評指正。
提煉
Android中的訊息機制主要有如下幾個要點,這裡也著重圍繞這些內容來講解:
1. Handler 調度訊息和runnable對象在不同線程中執行相應動作。
2. Looper訊息泵,用來為一個線程開啟一個訊息迴圈
3. MessageQueue 遵循FIFO先進先出規則的訊息佇列,以鏈表形式存取Message,供looper提取
2.分析
為了方便分析,借用一下找到的模型圖綜合看一下:
首先在一個線程中初始化一個looper並prepare(準備),然後建立該looper對象的處理對象Handler,接著當需要互動變更時,可以在其他線程(或自身線程)中使用handler發訊息至該訊息佇列MessageQueue,最後looper會自動有序抽取訊息(沒有訊息則掛起),交給handler執行訊息處理邏輯。
Orz,我的概念描述還是一塌糊塗,還是轉代碼說明吧:
比如我們有個線程專門負責一類處理邏輯,並且只允許該線程來處理這類邏輯,那麼我們怎麼做到呢?
1. 在一個線程裡邊定義一個Looper
Looper.prepare(); //稍微有點兒多,詳細見下文
2.定義一個處理訊息的Handler
handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //處理邏輯 } };
3.啟動looper,並開始工作,輪詢訊息
Looper.loop(); //詳細見下文 //要停止的話,使用Looper.quit();
4.在其他線程將要處理的資料data或回調對象callback以訊息Message模式通過Handler發至該訊息佇列MessageQueue
handler.sendMessage(msg)
5.Looper的loop()方法會從訊息佇列中取到該訊息並喚醒處理邏輯
//即loop()方法中的代碼 for (;;) { //顯然這個死迴圈一直在等待訊息到來並處理 Message msg = queue.next(); // 取一條訊息 if (msg == null) { return; } msg.target.dispatchMessage(msg); //調用訊息綁定的handler執行處理邏輯 //other code.... }
6.handler跳轉到執行處理邏輯的過程
public void dispatchMessage(Message msg) { if (msg.callback != null) { //如果有回調,則調用 handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } //調用回調方法 private static void handleCallback(Message message) { message.callback.run(); }
以上便是整個訊息系統的過程,後邊我們會逐個分析精講。
3.回到我們更新UI講解
在ActivityManagerService為Android應用程式建立新的進程並啟動activity時候,主線程ActivityThread首先被建立。該進程 Process.java@start(“android.app.ActivityThread”,…)會載入執行ActivityThread的靜態成員函數main,開啟該方法:
public static void main(String[] args) { //other code.. 我們只看有用的部分,其他暫略過 Looper.prepareMainLooper(); //準備looper,注,綁定的為當前主線程 ActivityThread thread = new ActivityThread();//開啟一個新ActivityThread線程 thread.attach(false);//最後執行到activity //other code.. Looper.loop(); //啟動looper
這個靜態函數做了兩件事情,一是在主線程中建立了一個ActivityThread執行個體,二是通過Looper類使主線程進入訊息迴圈。
然後,代碼經過一系列邏輯( ActivityThread.attach->IActivityManager. attachApplication -> attachApplicationApplicationThread.scheduleLaunchActivity ->… ->ActivityThread.performLaunchActivity ),最終會調用activity的attach方法。
我們開啟activity類。可以看到,它定義了uiThread和Handler參數
ActivityThread mMainThread;//對應上邊的ActivityThread線程 private Thread mUiThread;//主Ui線程 final Handler mHandler = new Handler();//這個handler就是activity用來處理Ui的了。我們自己定義的handler其實等於重新定義了這個mHandler;
我們來看activity的attach方法:
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, IVoiceInteractor voiceInteractor) { mUiThread = Thread.currentThread();//當前主線程Ui線程 mMainThread = aThread; //對應上邊的ActivityThread線程 }
所以,當我們要更新UI的時候,都會用到sendMessage,比如使用runOnUiThread,來看下這個方法
public final void runOnUiThread(Runnable action) { /** *如果當前線程不為Ui主線程則使用定義好的mHandler */ if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); }}
開啟post方法:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
還是熟悉的配方,還是熟悉的味道。。封裝成訊息,然後發送出去。好,我們再回頭看看最初第一篇文章裡的四種方法:
1.new 一個handler來 sendMessage();
2.利用new handler來post
不過是把上邊已經定義好Activity的mHandler重新定義了一遍,然後封裝成訊息發送出去;
3.runOnUiThread
同樣不過是用了Activity的mHandler發送訊息;
4.view.post
稍微看一下代碼:
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }
對於AttachInfo應該不算陌生吧,附加view到Activity的時候會把activity的一些屬性附加給AttachInfo,這裡同樣調用取得mHandler然後再post。。繞了一圈又回來了。
綜上看來,整個UI更新機制其實就是Android訊息系統模型的一個簡單實現。至此,我們的更新UI部分也算講完了,那麼作為補充部分,還是從源碼上完整細緻的過一下整個訊息系統模型吧。
4.精解
這裡通過剖析源碼來理解各部分的具體實現,再結合前面講的內容加以融會貫通,便於深入理解最終達到在不同情境的熟練使用。
我們將按照順序來逐個查看。
首先說說訊息對象,畢竟其他類操作的最基本元素也都是它。
4.1 Message
public final class Message implements Parcelable { //繼承Parcelable 用於資料傳遞 /**幾種資料類型**/ public int arg1; public int arg2; public Object obj; Bundle data; public int what;//供handler處理的訊息識別標識身份 long when;//什麼時候處理該訊息 Handler target;//處理該訊息的目標handler Runnable callback; //回調方法 int flags;//標籤標識 static final int FLAG_IN_USE = 1 << 0;//是否可用(回收利用) static final int FLAG_ASYNCHRONOUS = 1 << 1; static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; public Messenger replyTo;//可選對象,可以用來記錄發送方或接收者 Message next;//這條訊息的下一條訊息 /** *開一個訊息池,便於迴圈利用訊息,避免產生新對象並分配記憶體 */ private static final Object sPoolSync = new Object(); private static Message sPool; private static int sPoolSize = 0; private static final int MAX_POOL_SIZE = 50;}
既然提到訊息池又在前面講了利用obtain()節省記憶體資源,那麼我們就看下這個obtain()
/** *從訊息池中返回一個新的訊息執行個體,避免我們通常情況下分配新對象。 */ public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; sPoolSize--; return m; } } return new Message(); }
然後就是基於此方法的一系列運用:先調用obtain()方法,然後把擷取的Message執行個體的 各種參數賦值傳參。
//取一個訊息對象,把已存在的訊息內容賦值過去 public static Message obtain(Message orig) { Message m = obtain(); m.what = orig.what; m.arg1 = orig.arg1; m.arg2 = orig.arg2; m.obj = orig.obj; m.replyTo = orig.replyTo; if (orig.data != null) { m.data = new Bundle(orig.data); } m.target = orig.target; m.callback = orig.callback; return m; } public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj) { Message m = obtain(); m.target = h; m.what = what; m.arg1 = arg1; m.arg2 = arg2; m.obj = obj; return m; } //調用obtain並賦值,不再一一列出 public static Message obtain(Handler h) {//..} public static Message obtain(Handler h, Runnable callback) {//..} public static Message obtain(Handler h, int what) {//...} public static Message obtain(Handler h, int what, Object obj) {//...} public static Message obtain(Handler h, int what, int arg1, int arg2) {//...}
然後就是回收釋放recycle,它返回一個訊息池執行個體。釋放之後將不能再使用此方法。
public void recycle() { clearForRecycle(); synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } } //清空所有資料 void clearForRecycle() { flags = 0; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; when = 0; target = null; callback = null; data = null; } //拷貝訊息內容 public void copyFrom(Message o) { this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM; this.what = o.what; this.arg1 = o.arg1; this.arg2 = o.arg2; this.obj = o.obj; this.replyTo = o.replyTo; if (o.data != null) { this.data = (Bundle) o.data.clone(); } else { this.data = null; } }
後邊就是get和set方法以及Parcelable 的讀寫。
4.2 Looper
先看他所定義的屬性:
public class Looper { private static final String TAG = Looper; static final ThreadLocal sThreadLocal = new ThreadLocal(); private static Looper sMainLooper;//唯一對應一個主線程的looper靜態執行個體 final MessageQueue mQueue;//訊息佇列 final Thread mThread; //當前綁定線程 volatile boolean mRun; //是否允許退出 private Printer mLogging;//日誌列印 //....各種方法體....}
通常作業系統都為線程提供了內部儲存空間,一個線程對應一個記憶體空間,因此這裡很方便的為一個線程定義唯一對應的looper執行個體:ThreadLocal< Looper > 這個有點類似C中申請記憶體大小 *malloc(sizeof Looper),或者我們可以簡單理解為只作用於當前線程的new Looper.
而sMainLooper是當前應用程式的主線程looper,區別是適用於主線程。
我們再看他的構造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mRun = true; mThread = Thread.currentThread(); }
此構造方法是私人的,即不允許在外部執行個體化,這樣通過單例模式保證外部從該線程取得looper唯一。另外它主要初始化了mQueue 、mRun 和 mThread 幾個屬性,並綁定了當前線程。找一下它調用執行個體化的部分:
//重載,主要看下邊的prepare(boolean quitAllowed)方法 public static void prepare() { prepare(true); } /** *初始化當前線程作為Looper並存為本地變數, *並由此來建立handler和處理常式 * *@quitAllowed 是否允許退出(迴圈取訊息) *通過調用loop()和quit()來開啟和結束迴圈 */ private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { //保證一個線程唯一對應一個Looper throw new RuntimeException(Only one Looper may be created per thread); } sThreadLocal.set(new Looper(quitAllowed));//線上程中初始化一個執行個體 }
sThreadLocal的get和set,負責在記憶體中存取與線程唯一對應的looper。
同時我們會注意到有兩個方法prepareMainLooper和getMainLooper:
/** *初始化當前線程作為Looper並作為android應用的取訊息邏輯, *是由當前運行環境來建立,不需要手動調用 */ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { //加鎖,保證執行個體化唯一一個looper if (sMainLooper != null) { throw new IllegalStateException(The main Looper has already been prepared.); } sMainLooper = myLooper(); } } /** * 返回當前應用程式主線程的looper執行個體 */ public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } }
這部分是共應用程式初始化的時候調用的,我們一般用不到,不過也可以看出只是初始化了主線程的looper。
好的,基本的初始化工作也已經完成了,來看該類中的核心部分,迴圈取訊息的loop()
/** * 啟動隊列的迴圈取訊息作業,直到調用quit()退出 */ 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; // 確保當前線程是本地進程的唯一標示 Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); //開始迴圈取訊息作業 for (;;) { 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); } //調用訊息處理邏輯(回調和執行handler處理) msg.target.dispatchMessage(msg); if (logging != null) { logging.println(<<<<< Finished to + msg.target + + msg.callback); } // 確保在處理訊息邏輯時當前線程並沒有被打斷 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(); } }
基本都有備忘,不用過多解釋了。msg.target.dispatchMessage前面已經講過,後便可能會在拉出來遛遛。
其他再就是基本的get和列印和異常捕獲相關的了,有興趣的可以自己去看一下。
4.3 MessageQueue
4.4 Handler