編輯:關於Android編程
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } textView.setText("after changed"); } });
開頭我們就看到了如上的一段簡單的偽碼。因為這裡我試圖去還原一種場景,一種可能我們不少人最初接觸Android時可能都會遇到的錯誤場景。
這種場景的邏輯很簡單:在程序運行中,某個控件的值會被動態的改變。這個值通過某種途徑獲取,但該途徑是耗時的(例如訪問網絡,文件讀寫等)。
上面的偽碼中的邏輯是:點擊按鈕將會改變文本框的值,且這個值是通過某種耗時的操作獲取到,於是我們通過將線程休眠5秒來模擬這個耗時操作。
好的,現在我們通過編譯正式開始運行類似的代碼。那麼,我們首先會收到熟悉的一個錯誤,即“Application No Response(ANR)”。
接著,通過查閱相關的資料,我們明白了:原來我們像上面這樣做時,耗時操作直接就是存在於主線程,即所謂的UI線程當中的。
那麼這就代表著:這個時候的UI線程會因為執行我們的耗時操作而被堵塞,也自然就無法響應用戶其它的UI操作。於是,就會引起ANR這個錯誤了。
現在我們了解了ANR出現的原因,所以我們自然就會通過一些手段來避開這種錯誤。我們決定將耗時的操作從UI線程拿走,放到一個新開的子線程中:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5 * 1000); textView.setText("after changed"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } });
好的,現在我們模擬的耗時操作已經被我們放到了UI線程之外的線程。當我們信心十足的再次運行程序,確得到了如下的另一個異常信息:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
從異常信息中,我們看到系統似乎是在告訴我們一個信息,那就是:只有創建一個視圖層次結構的原始線程才能觸摸到它的視圖。
那麼,我們似乎就能夠理解這種異常出現的原因了:我們將耗時操作放在了我們自己創建的分線程中,顯然它並非原始線程,自然就不能夠去訪問View。
這樣設計的初衷實際上是不難猜想的,如果任何線程都能去訪問UI,請聯想一下並發編程中各種不可預知且操蛋的問題,可能我們的界面最終就熱鬧了。
但是,現在我們針對於這一異常的解決方案似乎也不難給出了。既然只有主線程能夠訪問View,那麼我們只需要將更新UI的操作放到主線程就OK了。
那麼,這裡就順帶一提了。不知道有沒有人和我曾經一樣,想當然的寫出過類似下面一樣的代碼:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); // 這是在主線程裡執行的 textView.setText("after changed"); } });
是的,如上的代碼十分的,非常的“想當然”。這當然是因為對Java多線程機制理解不夠所造成的。更新UI的操作確實是放到了主線程,但是!!!:
這並不代表著,UI更新一定會在分線程的耗時操作全部完成後才會執行,這自然是因為線程執行權是隨機切換的。也就是說,很可能出現的情況是:
分線程中的耗時操作現在並沒有執行完成,即我們還沒有得到一個正確的結果,便切換到了主線程執行UI的更新,這個時候自然就會出現錯誤。
這個時候,作為菜鳥的我們有點不知所措。於是,趕緊上網查查資料,看看有沒有現成的解決方案吧。這時,通常“Handler”就會進入我們的視線了:
private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 0x0001: textView.setText("after changed"); } } }; //=============================================== new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5 * 1000); mHandler.sendEmptyMessage(0x0001); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
我們發現關於Handler的使用似乎十分容易不過,容易到當我們認為自己掌握了它的時候似乎都沒有成就感:
首先,我們只需要建立一個Handler對象。 接著,我們會在需要的地方,通過該Handler對象發送指定的Message。 最後,該Handler對象通過handleMessage方法處理接收到的Message。
但我們沉下心來想一想:Handler為什麼能夠解決我們之前碰到的非原始線程不能更新UI的錯誤呢?它的實現原理如何?它能做的就只是更新UI嗎?
掰扯了這麼多,帶著這些疑問,我們終於來到了我們這篇blog最終的目的,那就是搞清楚Android的消息機制(主要就是指Handler的運行機制)。
就像醫生如果要弄清楚人體構造,方式當然是通過解剖來進行研究。而我們要研究一個對象的實現原理,最好的方式就是通過分析它的源碼。
個人的習慣是,當我們沒有一個十分明確的切入點的時候,選擇構造函數切入通常是比較合適的,那我們現在就打開Handler的構造函數來看一下:
// 1. public Handler() { this(null, false); } // 2. public Handler(Callback callback) { this(callback, false); } // 3. public Handler(Looper looper) { this(looper, null, false); } // 4. public Handler(Looper looper, Callback callback) { this(looper, callback, false); } // 5. public Handler(boolean async) { this(null, async); } // 6. public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class 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; } // 7. public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
好的,分析一下我們目前所看的,我覺得我們至少可以很容易的分析並掌握兩點:
Handler自身提供了7種構造器,但實際上只有最後兩種提供了具體實現。 我們發現各種構造器最終圍繞了另外兩個類,即Callback與Looper。我們推測它們肯定不是做擺設的。
現在我們來分別看一下唯一兩個提供了具體實現的構造器,我們發現:
除了 ”if (FIND_POTENTIAL_LEAKS) “這一段看上去和反射有關的if代碼塊之外,這兩個構造器剩下的實現其實基本上是完全一致的,即:
mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async;
唯一的不同在於mLooper這一實例變量的賦值方式:
mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //========================================== mLooper = looper;
“mLooper = Looper.myLooper();”這種方式究竟有何神奇,我們這裡暫且不提。我們的注意力聚焦在以上看到的幾個實例變量,打開源碼看看:
final MessageQueue mQueue; final Looper mLooper; final Callback mCallback; final boolean mAsynchronous;
mAsynchronous是一個布爾型的變量,並且我們看到默認情況它的構造值是false,從命名我們就不難推測到,它多半與異步有關。除此之外:
其余類型分別是”MessageQueue,Looper,Callback“。一定記住它們!!!正是它們配合Handler及Message完成了整個消息傳遞的架構。
OK,首先我們來看Callback這個東西,從命名來看絕逼與回調有關系,打開源碼,果不其然正是定義在Handler內部的一個接口:
public interface Callback { public boolean handleMessage(Message msg); }
我們看到了其中唯一聲明的一個方法接口,看上去似乎有點眼熟。是的,那麼它與Handler自身的handleMessage有何聯系?我們暫且提不提。
現在,我們再接著看Looper和MessageQueue兩個類型。很遺憾的是,這裡我們發現:這是另外單獨定義的兩個全新的類。也就是說:
目前我們似乎無法在邏輯上將其與Handler聯系起來。我們現在只知道從命名上來說,它們似乎分別代表著“循環”與“消息隊列”的意思。
那麼,到了這一步似乎情況有點糟糕,因為似乎失去了下一步的切入點。沒關系,這個時候我們回憶一下我們通常怎麼樣使用Handler:
mHandler.post(); mHandler.sendMessage();
沒錯,我們基本上就是通過以上兩種方式去使用Handler。所以現在我們打開這兩個方法相關的源碼來看看:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
由此我們發現的是:post函數最終調用的仍是send系列的函數;而sendMessage底部也依然是通過sendMessageDelayed調用的。
並且!查看一系列的send方法源碼發現:它們最終都將通過sendMessageAtTime來完成整個調用。所以顯然這將是我們下一個關注點。
先分析一下post方法的實現,我們看到其實Handler內部是通過getPostMessage對我們傳入的Runnable對象進行了一次封裝。
當我們看到getPostMessage方法的實現,我們會發現沒什麼大不了的,只是將傳入Runnable對象賦值給了一個Message對象而已。
但我們也可能會觀察到另外一點。就是我們可能會在使用Message時會通過構造器得到消息對象,而這裡是通過靜態方法obtain。
這二者有什麼不同呢?我們先打開Message的構造器的方法來看一下:
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}). */ public Message() { }
好的,我們發現構造器實際上沒有任何實現內容。而注釋告訴我們:更推薦使用obtain系列的方法來獲取一個Message對象。
那麼我們就好奇了?為什麼更推薦使用obtain呢?我們以無參的obtain方法為例,打開源碼瞧一瞧:
/** * 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最終的本質仍是產生Message對象。關鍵在於一個叫sPool的東西,這兄弟到底是個什麼鬼呢?
實際上是Handler內部會通過這個叫做sPool的靜態全局變量構建一個類似“池”的東西,而通過next屬性我們不難推斷”池”應該是以單鏈表來實現的。
再查看方法的注釋:從全局池中返回一個新的消息實例。使我們能夠避免在許多情況下分配新的對象。由此我們好像已經知道為何推薦obtain了。
包括網上很多打著類似“new message與obtainMessage之間區別”的資料裡,一大段的文字之後,我們會發現最終實際有用的就類似一句話:
obtainMessage可以從池中獲取Message對象,從而避免新的對象創建,達到節約內存的效果。但這樣當然還是熄滅不了一顆好奇的心:
究竟為什麼這個“池”能夠避免新的對象創建呢?要解開這個疑問,我們還需要關注Handler類中的另一個方法“recycleUnchecked”的如下代碼:
void recycleUnchecked() { // 第一部分 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++; } } }
該方法顧名思義,主要的工作通常主要就是回收一個完成使命的Message對象。而這個回收動作發生的時機是什麼呢?
通常來說,我們可以通過人為調用msg.recycle()來完成回收;另一種更常見的回收時機發生在MessageQuene當中,我們稍後會看到。
接下來我們該方法中的回收工作都做了什麼,代碼中注釋為第一部分的代碼做的工作很易懂,就是將回收的message對象的各個屬性清空。
第二部分其實就是將回收的對象向“池”內添加的過程,而之前說到的obtain方法,其一旦判斷sPoll不為null,就直接從池內獲取閒置對象,不再創建。
到此實際上我們就已經分析了,為什麼obtain能夠節約內存開銷的原理了。但如果你的數據結構和我一樣渣,可能還會有點暈。沒關系,看如下代碼:
Message msg1 = Message.obtain(); msg1.recycle(); Message msg2 = Message.obtain();
我們對應於這三行簡單的代碼,來有代入感的分析一下它們運行的過程,相信就會有個比較清晰的理解了。
首先獲取msg1的時候,這個時候sPool肯定是為null的。所以它的工作實際與直接通過構造器創建對象沒有區別。 通過msg1對象調用recycle方法,最終進入之前所說的回收工作的第二部分執行。此時的結果為:msg1.next = sPoll(即null,沒有next節點);sPoll = msg1; 這時我們再通過obtain去獲取對象msg2,進入方法後,判斷sPoll不為null。於是, Message m = msg1;注意:
這代表我們已經從池中取出了msg1,於是執行sPool = m.next時,我們說到msg1.next是null,所以sPool再次等於null,邏輯完全正確。
與此同時,我們也可以想得到,假設m.next不等於null時:sPool = m.next的邏輯實際上就轉換成了,將sPool指向next節點,即代表我們已經取走一個對象了,池將指向下一個節點,即為我們下次要獲取的消息對象。
好了,現在相信我們都清楚以上的概念了。我們的關注點將回到我們之前提到的關鍵位置,即sendMessageAtTime方法,打開其源碼:
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); }
我們發現該方法的實現很簡單,但最終會調用另一個方法“enqueueMessage”,趕緊打開這個方法看一看:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
我們發現該方法顯然使用了委托設計模式,將最終的方法實現委托了給了quene對象,即MessageQuene來實現。
對於MessageQuene中的enqueueMessage方法,該方法的源碼個人覺得沒必要全部關注。我們先看下面這小段代碼:
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.recycle方法,記得我們之前說過的回收工作嗎?這裡正是另一種發生時機,這個時機的標准如上所示,正是:
“mQuitting”為true,而在什麼時候mQuitting會被設置為true,我們稍後將會看到,這裡先暫且一提。接著看另一端更為關鍵的代碼:
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;
有了之前的基礎,我們發現該方法所做的工作實際上很簡單,它仍然是以單鏈表的形式,通過不斷追加next節點達到向隊列中添加Message的效果。
由此,我們發現:當我們通過handler對象post或send了一條消息,其實最終的工作很簡單,就是向MessageQuene即消息隊列中追加一條消息而已。
那麼,接下來呢?自然的,消息追加到了隊列當中。我們則需要從隊列中依次取出消息對象,才能對其作出處理。苦苦尋覓一番之後:
我們發現了next()方法,該方法的實現歸根結底是通過循環來不斷的從隊列中拉取消息,考慮到篇幅,我們不再貼出源碼。唯一注意:
if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); }
當沒有新的消息來臨之前,如上的代碼將能夠確保隊列“阻塞”而一直等待新的消息對象來臨。好了,我們總結一下:
MessageQuene將通過enqueueMessage方法向隊列中插入消息,而通過next方法取出消息。但現在的關鍵點在:
關於enqueueMessage方法我們已經知道它在Handler當中被調用,而next方法目前我們只看到聲明,還沒看到調用的產生。
以next()方法的調用為關鍵字按圖索骥,我們最終發現它在我們之前提到的另一個關鍵的東西”Lopper”中產生了調用,具體是Lopper中的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; ... for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ... msg.target.dispatchMessage(msg); ... ... msg.recycleUnchecked(); } }
我們只保留了關於loop方法最為關鍵的部分,我們依次來分析一下,首先我們注意到的,肯定是一個名為“myLooper()”的方法調用:
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
我們從代碼中看到邏輯十分簡單清晰,就是通過myLooper()來獲取looper對象,而最終的方式則是通過sThreadLocal來獲取。
這裡,就不得不提到一個功能強大的東西ThreadLocal。我們來看一下Looper的源碼當中關於sThreadLocal的定義:
static final ThreadLocalsThreadLocal = new ThreadLocal ();
這個東西的作用究竟如何?簡單來說,我們知道普通的定義一個實例變量,它將創建在“堆”上。
而“堆”並非線程私有的,所以實例變量也將被線程共享。而ThreadLocal則是將變量的作用域限制為線程私有。舉例來說:
static final ThreadLocalsThreadLocal = new ThreadLocal (); sThreadLocal.set("1"); new Thread(new Runnable() { @Override public void run() { sThreadLocal.set("2"); } }).start();
上面的代碼通過sThreadLocal.get()來獲取string,在主線程中和我們new的線程當中獲取的值是獨立的,分別是“1”和“2”。
接下來,我們看到的就是將會在一個無限循環中一直通過調用MessageQuene的next()方法來獲取消息對象。
假設此次獲取到了msg對象,則會通過msg.target調用dispatchMessage方法來分發消息。問題在於target是個什麼東西?
在Message類中查看源碼,我們可以知道target自身是一個Handler類型的對象。但通常我們都沒有人為的去為這個變量賦值。
那麼這個變量通常默認是什麼呢?回到之前Handler類的enqueneMessage方法當中,看到如下代碼:
msg.target = this;
也就是說,如果我們沒有明確的去為Message對象的target域賦值,它將被默認賦值為發送這條Message的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); } }
這個方法的實現邏輯也很清晰,它的具體分發過程如下:
如果msg.callback不為null,那麼將通過handleCallback方法來處理消息(實際上就是msg.callback.run()); 否則進一步判斷mCallback是否為null,不為null則通過mCallback.handleMessage來處理消息。 最後如果mCallback也為null,則會調用Handler自身的handleMessage方法來處理消息。
邏輯很簡單,我們唯一注意的就是msg.callback和mCallback這兩兄弟是指什麼東西?很簡單:
msg.callback是我們通過Message.obtain(Handler h, Runnable callback)或者通過Handler.post(Runnable r)傳入的Runnable對象。
而mCallback就更熟悉了,回想我們之前查看Handler的構造器時看到的東西。
mCallback的本質就是Handler內部定義的接口Callback,所以通過它實際就是通過回調接口處理消息。
而這裡,我覺得值得一說的是msg.callback這個東西。我們知道當它不為null,最終實際將通過message.callback.run()來處理消息。
也就是說最終實際上是調用了Runnable對象的run方法,但有Java的基礎就會知道這樣的調用實際與線程無關,只相當於普通的調用一個實例方法而已。
對於這點,我們一定要有清楚的認識,否則可能會因為使用不當造成一些想不到的錯誤。具體的例子我們暫且不說,放在最後的總結部分來看。
實際上到了這裡,我們就已經把構建Android消息機制的四個關鍵,即Handler,Message,MessageQuene及Looper給聯系起來了。簡單總結一下:
通過調用Handler的post或者send系列的方法來發送一條Message。 這一條Message最終會加入到鏈表結構的MessageQuene當中存放。 Looper會通過內部的loop方法不斷調用MessageQuene的next()方法獲取下一條Message 當Looper獲取到Message方法後,又會通過Handler的dispatchMessage來分發並處理消息。
我相信到了這裡,我們或多或少都會有些收獲。但對於剛接觸Andoid消息機制的朋友來說,還可能存在一個疑問,那就是:
通過之前我們的分析與理解,我們知道了對於Handler處理消息的機制來說,Lopper的參與是至關重要的。
但與此同時,我們發現之前我們似乎並沒有創建Looper。我們不免會考慮,是系統幫助我們創建了嗎?答案是肯定的。
回憶一下之前的代碼,我們是通過無參的構造器來創建Handler對象的。我們也可以看到,該構造器最終會調用我們之前說到的第6個構造器。
然後我們發現在第6種構造器當中,是通過“mLooper = Looper.myLooper();”的方式來獲取looper對象的。
這時我們就想起了之前的ThreadLocal,但即使是使用ThreadLocal,也起碼得有一個ThreadLocal.set(Looper)的過程吧。
這個過程是在什麼時候完成的呢?正常來說,我們推斷這個過程很可能發生在Looper的構造器中。但一番查找我們發現Looper壓根沒提供公有的構造器。
經過苦苦的尋覓之後,最終我們會發現在Looper的靜態方法“prepare”中,終於找到了我們想要看見的代碼:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
好了,現在我們回想一下,我們之前的代碼中創建的Handler,是一個屬於當前Activity的實例域。這代表它的創建是在主線程中完成的。
而關於Looper的創建的確也是在Android的主線程,即ActivityThread中完成創建的。具體的調用位於主線程的入口main方法中。
並且,主線程裡的Looper,是通過調用Looper類專門為主線程創建Looper對象封裝的方法“prepareMainLooper()”來創建的。
現在我們再看關於”Can’t create handler inside thread that has not called Looper.prepare()”的這個異常,我們就很容易找到原因了。
因為通過源碼我們知道這個異常就是在第6個構造器中,當通過Looper.myLooper()獲取結果為null時報告的。
同時,我們知道我們在主線程創建Handler的時候,沒有問題是因為主線程默認就創建了Looper對象。那麼:
當我們要在主線程以外的線程中創建Handler對象的時候,只要我們也為它創建對應的Looper對象就行了。示例如下:
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Handler handler = new Handler(); Looper.loop(); } }).start();
是的,在創建完對象後,別忘了調用loop()方法,因為這才是開啟循環從消息隊列中獲取消息的關鍵。
好了,現在我們正式接著看loop()方法中接下來的代碼msg.recycleUnchecked();。是的,我們很熟悉的”回收”,由此我們可以知道:
回收Message的另一個時機實際就是,當Message對象從隊列中取出處理完成之後,就會進行回收,放入池內。
如果具體閱讀MessageQuene的源碼,我們會發現還有多種不同的回收時機,我們簡單總結幾種常見的時機:
人為調用message.recycle()來回收對象。 message從隊列中取出被處理完成後會自動回收。 調用Lopper.quit()/quitSafely(),該方法最終會調用MessageQuene的quit()方法。 MessageQuene調用quit在合適的時機將自身隊列中的Message對象進行回收。 MessageQuene的quit方法還會將我們之前談到的mQuitting設置為true,這代表著當調用了quit之後,再通過handler來send任何message,都將被直接回收。
到這裡,對於Android的消息機制我們也就研究的差不多了。雖然我總覺得在寫的過程中,我本來還有個別想總結的點,但最後似乎忘了,也想不起了。
我們最後總結一個問題,那就是Handler為什麼能夠執行更新UI的操作!現在我們就來分析一下這個過程,回想一下:
通常我們在另開的線程中執行耗時操作,當耗時操作執行完畢後,則調用sendMessage()最終讓handler更新UI。
現在我們知道了,這時的Handler我們一定會是定義在主線程,即UI線程當中的。當我們在分線程中sendMessage的時候:
經過我們之前說到的一系列調用,最終會通過Handler來進行最終處理。而Handler本身是在主線程中運行的,自然也就能夠操作UI。
所以說,如果說Handler能夠讓我們更新UI,不如說其本質是將操作切換到該Handler所在的線程來執行。
我們可以試著發送消息給在分線程中創建的Handler對象,然後在handleMessage仍然試圖去訪問UI。會發現結果當然是行不通的。
這裡就說到我們之前說到的handler.post()這樣的使用方式了,因為參數類型是Runnable,所以我們很容易認為是通過handler執行一個線程任務。
但實際情況是,假設Handler對象是在主線程中創建的。那麼,通過post()方法,我們仍然可以去更新UI。這是為什麼?
這就回到了我們之前說的,當handler去dispatchMessage後,如果判斷msg.callback不等於null,就會通過msg.callback.run()來處理消息。
這個時候實際本質上就是在切換到主線程去執行了Runnable對象的實例方法run()而已。所以當然能夠更新UI。
而我們可能會有這樣一種需求,那就是想要在指定的時間之後去執行一個耗時的線程任務,這個時候可能會想到Handler的postDelayed方法。
我想說的使用誤區就是,這個時候的Handler一定不要是創建在主線程當中的,因為這樣耗時操作最終還是在主線程執行,自然也就會引發ANR。
如果我們一定想要通過Handler實現我們這樣的需求,其實很簡單,當然就是要把Handler創建在分線程當中,就像下面這樣:
new Thread(new Runnable() { @Override public void run() { Looper.prepare();; final Handler handler = new Handler(); Looper.loop(); handler.post(new Runnable() { @Override public void run() { try { Thread.sleep(5000); handler.sendEmptyMessage(0); } catch (InterruptedException e) { e.printStackTrace(); } } }); } }).start();
關於Ditscc分布式編譯環境的搭建,網上也有不少文章,但是基本上都過時了。所以看了很多文章,走了不少彎路,最後總算梳理清楚了一條正確的環境搭建的步驟,而且可以實現zer
一、概述隨著Android L版本的發布,RecyclerView已經逐漸地取代了ListView,用來顯示較多的數據集,RecyclerView相比ListView在性
Android的消息機制幾乎是面試必問的話題,當然也並不是因為面試,而去學習,更重要的是它在Android的開發中是必不可少的,占著舉足輕重的地位,所以弄懂它是很有必要的
這幾天開始了View工作原理的學習,當然最初肯定是從View的繪制過程開始的,至於其中的源碼分析網上挺多的,我只會在隨後的博客中做些總結,並不從代碼層面進行分析,畢竟網上