開發Android這么久了,總會聽到有人說:主線程不能訪問網絡,子線程不能更新UI。Android的主線程的確不能長時間阻塞,但是子線程為什么不能更新UI呢?今天把這些東西整理,順便在子線程更新UI。
首先寫了一個handler在子線程更新主線程UI,在子線程做了一個耗時操作:從網絡下載了一個圖片並利用handler發送到handleMessage()的回調中,並更新到主線程的bitmap。圖片顯示成功,沒有問題。接下來在子線程中更新onCreate()中實例化的textview,報錯:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.只有創建了這個view的thread才能操縱這個view。Android加載view有兩種方式,一是setContentView,二是inflater.inflate()。所以第三步,在子線程中用WindowManager.add()展示了view,run()代碼如下:
LayoutInflater layoutInflater = LayoutInflater.from(getApplicationContext()); View view = layoutInflater.inflate(R.layout.test, null); TextView textView = ((TextView) view.findViewById(R.id.test_tv)); textView.setText("十年一劍"); textView.setTextColor(getResources().getColor(R.color.colorAccent)); WindowManager windowManager = getWindowManager(); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.height = 600; layoutParams.width = 400; layoutParams.flags = 2; layoutParams.format = 1; windowManager.addView(view, layoutParams);
報錯: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare().看他這意思,還需要looper。加上后,果然子線程內view顯示出來了。尷尬的是,這個WindowManager.LayoutParams的flags和format等屬性還沒掌握。而且在代碼前加上sleep方法view仍可以顯示。那么,在onCreate()中的子線程中更新主線程UI呢?
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); new Thread() { @Override public void run() { super.run(); textview.setText("heun3540"); } }.start(); }
沒有問題,子線程更新了主線程的UI,沒有報錯。但在加了一句Thread.sleep(1000)后,出現了android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.此外在點擊事件中new Thread()更新主線程UI,點擊后可以看到textview的文本已經改變,隨后應用崩掉。
對以上幾個例子總結一下:利用handler可以從子線程發送消息到主線程達到更新UI的目的;在子線程里可以更新在子線程中加載的view(需要looper);在主線程的onCreate()中創建的子線程也可以更新主線程的UI,前提是不做其他耗時操作;除外,在onCreate()的子線程做耗時后更新UI報錯;子線程沒有looper想更新自己的UI也報錯;點擊事件(可以看做耗時事件)中的子線程更新主線程UI報錯。由此可以看出,持有view的線程都可以更改自己的view,主線程默認looper不需要手動添加。一般的更新其他線程的UI需要handler即線程間的通信但handler只是線程間傳遞數據,更新操作還是要rootview來完成。那為什么在onCreate()的子線程更新主線程UI沒有報錯呢?而稍一耗時就報錯了呢?必然是因為更新UI快於異常線程檢測以至UI更新已經完了可能ViewRootImpl才剛剛初始化完成,但這樣是不安全的,大家都不推薦這種方式。
媽的,以為下午換辦公室,直接電源斷了,一上午寫的全沒了,日。
我們都非常熟悉的生命周期,為什么按照onCreate,onStart這樣的順序執行的呢,可能是在源碼中某個方法確定了調用順序,也可能跟堆棧,硬件交互,全局變量有關。
(此處更新一下,生命周期的執行順序我剛做安卓的時候並不懂為什么這樣。實際上,Activity生命周期的執行順序是在ActivityThread中規定好了的,對以前的猜想做個總結。)
關於Java中的接口回調,在Android的點擊事件和網絡請求等有大量的應用。比如項目中有大量重復的工作需要抽取到工具類中,但具體的業務邏輯靈活多變,后者業務后期的維護升級這些都要求代碼無法在工具類中一步到位。這時就可以用回調解決,封裝的時候調用接口的抽象方法,在業務實現的地方添加相應的代碼。那么關於接口的理解,就不再僅限於初學時的定義了。
接口的出現,一是解決了Java類只能繼承一個父類而導致父類過於龐雜,抽象程度不高的問題,二是對於日后代碼維護十分的方便。比如說一個項目里有動物,鳥,人,猴子,海豚,飛機這些東西,動物作為抽象類,具體的動物作為實現類,動物有吃,睡覺,走抽象方法,鳥是啄,人是用碗吃。至於鳥和飛機,他們都可以飛,飛就可以設計成接口。
再舉個例子,鳥,喜鵲,鴕鳥,孔雀,飛機,戰斗機。在這個例子中,鳥是需要抽象的,那么到底抽象成接口還是抽象類呢?假如是接口的話,走,叫,吃這些可以有,可是鴕鳥不會飛,只有孔雀可以開屏,那就需要再單獨設計兩個接口,一個包含fly(),一個包含開屏(),三個接口分別選擇實現,而飛機只要實現飛這個接口就可以了。假如鳥抽象成抽象類,同樣走吃叫設計為抽象方法,剩下的一樣,這樣看起來接口和抽象類對於鳥來說沒太大區別。這個倒很好解釋,因為鳥的幾個方法都設計成了抽象方法,假如吃這個方法已經定下來了,顯然抽象類更合適。
此外,按照java的規則,一切皆對象,而類是對象的抽象。那么孔雀是鳥,所以鳥也應該設計成類更合適。套用一句大神的話:類定義了是不是;接口定義了有沒有。孔雀繼承了鳥,那他就是鳥的一種,而飛機實現了飛,則是具備了飛的能力。需要提到一點是:飛設計成接口,是因為這個行為與吃,叫不是同一類行為,假若飛和吃放在一起,那飛機豈不是得會吃才行。所以也可以看出來,兩種不同屬性的行為是不能寫到一起的。
而Handler更新主線程的UI也是在主線程中進行的,只不過通過handler對象將子線程等耗時操作中得到的數據利用message傳到了主線程。關於handler的原理,老生常談。溫故而知新。今天試着解釋一下相關的源碼,6.0以上的。
Looper是final修飾,不可繼承。
Class used to run a message loop for a thread.
Threads by default do not have a message loop associated with them; to create one ,call #prepare in the thread that is to run the loop,and then #loop to have it process messages until the loop is stopped.
Most interaction with a message loop is through the #Handler class.
Looper類的解釋告訴我們兩個主要方法,prepare和loop。主線程不需要顯示調用Looper的兩個方法。但在子線程中,則需要顯式調用。幾個全局變量:
private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue; final Thread mThread;
ThreadLocal在這里理解為將Looper對象與當前線程綁定,在同一個線程作用域內可見,是一個Java工具類。一個靜態的looper引用,一個messageQueue引用,一個線程引用。
/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static void prepare() { prepare(true); }
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));
}
prepare方法中傳入的布爾值最終傳給了MessageQueue的構造方法中,它代表了 True if the message queue can be quit。prepare方法得到了looper對象並且looper在實例化的時候同時獲取到當前線程的引用,還會實例化一個成員變量MessageQueue。
Looper中的構造方法是私有的:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
Looper的loop方法,首先會拿到looper和消息隊列實例,接着在無限循環中調用queue.next()取出隊列的消息,交給msg.target.dispatchMessage(msg)處理。這其中消息的發送正是由handler.sendMessageAtTime()來做。
1 /** 2 * Run the message queue in this thread. Be sure to call 3 * {@link #quit()} to end the loop. 4 */ 5 public static void loop() { 6 final Looper me = myLooper(); 7 if (me == null) { 8 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 9 } 10 final MessageQueue queue = me.mQueue; 11 12 // Make sure the identity of this thread is that of the local process, 13 // and keep track of what that identity token actually is. 14 Binder.clearCallingIdentity(); 15 final long ident = Binder.clearCallingIdentity(); 16 17 for (;;) { 18 Message msg = queue.next(); // might block 19 if (msg == null) { 20 // No message indicates that the message queue is quitting. 21 return; 22 } 23 24 // This must be in a local variable, in case a UI event sets the logger 25 final Printer logging = me.mLogging; 26 if (logging != null) { 27 logging.println(">>>>> Dispatching to " + msg.target + " " + 28 msg.callback + ": " + msg.what); 29 } 30 31 final long traceTag = me.mTraceTag; 32 if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { 33 Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); 34 } 35 try { 36 msg.target.dispatchMessage(msg); 37 } finally { 38 if (traceTag != 0) { 39 Trace.traceEnd(traceTag); 40 } 41 } 42 43 if (logging != null) { 44 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 45 } 46 47 // Make sure that during the course of dispatching the 48 // identity of the thread wasn't corrupted. 49 final long newIdent = Binder.clearCallingIdentity(); 50 if (ident != newIdent) { 51 Log.wtf(TAG, "Thread identity changed from 0x" 52 + Long.toHexString(ident) + " to 0x" 53 + Long.toHexString(newIdent) + " while dispatching to " 54 + msg.target.getClass().getName() + " " 55 + msg.callback + " what=" + msg.what); 56 } 57 58 msg.recycleUnchecked(); 59 } 60 }
在第6行,調用myLooper方法返回了ThreadLocal保存的looper對象:
/** * 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實例化時的messageQueue傳給了新的引用MessageQueue,十七到二十行是無限循環,queue.next()取出消息。msg.target.dispatchMessage(msg)處理消息,msg.recycleUnchecked()回收資源。到此,消息隊列和輪詢已經建立,下面應該是發送消息了。那就來看一下Handler的代碼:
1 /** 2 * A Handler allows you to send and process {@link Message} and Runnable Handler對象允許發送處理和一個線程的消息隊列相關聯的message和runnable對象。 3 * objects associated with a thread's {@link MessageQueue}. Each Handler 每一個Handler實例都與一個單獨的線程和它的消息隊列關聯。 4 * instance is associated with a single thread and that thread's message 當創建一個Handler對象時,它就與創建它的線程和線程的消息隊列綁定了。 5 * queue. When you create a new Handler, it is bound to the thread / 6 * message queue of the thread that is creating it -- from that point on, 至此,它就會分發消息和runnable對象到綁定的消息隊列,並且在它們從消息隊列取出時執行。 7 * it will deliver messages and runnables to that message queue and execute 8 * them as they come out of the message queue. 9 * 10 * <p>There are two main uses for a Handler: (1) to schedule messages and handler主要有兩個用處:一是安排messages和runnable對象在未來的某一刻執行; 11 * runnables to be executed as some point in the future; and (2) to enqueue 二是為在其他線程執行的動作排隊(此處翻譯不當) 12 * an action to be performed on a different thread than your own. 13 * 14 * <p>Scheduling messages is accomplished with the 借助post和send系列方法,調度消息得到完成。 15 * {@link #post}, {@link #postAtTime(Runnable, long)}, 16 * {@link #postDelayed}, {@link #sendEmptyMessage}, 17 * {@link #sendMessage}, {@link #sendMessageAtTime}, and 18 * {@link #sendMessageDelayed} methods. The <em>post</em> versions allow post允許當Runnable對象被接收且將要被messagequeue調用時為它們排隊; 19 * you to enqueue Runnable objects to be called by the message queue when sendMessage允許為一個包含了數據集且將會被handler的handleMessage方法(需要自己重寫)處理的消息對象排隊。 20 * they are received; the <em>sendMessage</em> versions allow you to enqueue 21 * a {@link Message} object containing a bundle of data that will be 22 * processed by the Handler's {@link #handleMessage} method (requiring that 23 * you implement a subclass of Handler). 24 * 25 * <p>When posting or sending to a Handler, you can either 當用post或者send向handler發消息時,可以在消息隊列就緒時立即處理也可以指定延遲做延時處理。后者需要實現超時等時間行為。 26 * allow the item to be processed as soon as the message queue is ready 27 * to do so, or specify a delay before it gets processed or absolute time for 28 * it to be processed. The latter two allow you to implement timeouts, 29 * ticks, and other timing-based behavior. 30 * 31 * <p>When a 32 * process is created for your application, its main thread is dedicated to 當應用中的進程創建時,主線程致力於運行消息隊列。隊列着重於頂層的應用組件如活動,廣播接收者等和任何這些組件創建的window。 33 * running a message queue that takes care of managing the top-level 可以創建子線程並且通過handler與主線程通信。和以前一樣,是靠調用post或者sendMessage來實現,當然,是在子線程中調用。 34 * application objects (activities, broadcast receivers, etc) and any windows 發出的Runnable對象或者消息就會調度到handler的消息隊列中並在恰當時處理。 35 * they create. You can create your own threads, and communicate back with 36 * the main application thread through a Handler. This is done by calling 37 * the same <em>post</em> or <em>sendMessage</em> methods as before, but from 38 * your new thread. The given Runnable or Message will then be scheduled 39 * in the Handler's message queue and processed when appropriate. 40 */
說來慚愧,這一段類注釋翻譯花了好長時間,好歹六級也過了好多年了。從類的注釋中得知,handler主要用send和post發送消息,在重寫的handleMessage方法處理消息。
所有的send方法底層都是通過sendMessageAtTime實現的,其中在sendMessageDelayed方法中調用sendMessageAtTime時傳入了SystemClock.uptimeMills():
1 /** 2 * Enqueue a message into the message queue after all pending messages 3 * before (current time + delayMillis). You will receive it in 4 * {@link #handleMessage}, in the thread attached to this handler. 5 * 6 * @return Returns true if the message was successfully placed in to the 7 * message queue. Returns false on failure, usually because the 8 * looper processing the message queue is exiting. Note that a 9 * result of true does not mean the message will be processed -- if 10 * the looper is quit before the delivery time of the message 11 * occurs then the message will be dropped. 12 */ 13 public final boolean sendMessageDelayed(Message msg, long delayMillis) 14 { 15 if (delayMillis < 0) { 16 delayMillis = 0; 17 } 18 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); 19 } 20 21 /** 22 * Enqueue a message into the message queue after all pending messages 23 * before the absolute time (in milliseconds) <var>uptimeMillis</var>. 24 * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> 25 * Time spent in deep sleep will add an additional delay to execution. 26 * You will receive it in {@link #handleMessage}, in the thread attached 27 * to this handler. 28 * 29 * @param uptimeMillis The absolute time at which the message should be 30 * delivered, using the 31 * {@link android.os.SystemClock#uptimeMillis} time-base. 32 * 33 * @return Returns true if the message was successfully placed in to the 34 * message queue. Returns false on failure, usually because the 35 * looper processing the message queue is exiting. Note that a 36 * result of true does not mean the message will be processed -- if 37 * the looper is quit before the delivery time of the message 38 * occurs then the message will be dropped. 39 */ 40 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 41 MessageQueue queue = mQueue; 42 if (queue == null) { 43 RuntimeException e = new RuntimeException( 44 this + " sendMessageAtTime() called with no mQueue"); 45 Log.w("Looper", e.getMessage(), e); 46 return false; 47 } 48 return enqueueMessage(queue, msg, uptimeMillis); 49 } 50 51 52 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { 53 msg.target = this; 54 if (mAsynchronous) { 55 msg.setAsynchronous(true); 56 } 57 return queue.enqueueMessage(msg, uptimeMillis); 58 }
handler的enqueueMessage方法中將handler對象賦給了msg的target屬性,接着調用了MessageQueue的enqueueMessage方法。MessageQueue也是final類,算是這幾個類中比較native的,很多都是與底層交互的方法。在它的enqueueMessage方法中將message壓入消息隊列,接着loop()方法中msg.target.dispatchMessage(msg),上文已經提到了。Message也是final類。所以最后是調用handler的dispatchMessage方法:
1 /** 2 * Subclasses must implement this to receive messages. 3 */ 4 public void handleMessage(Message msg) { 5 } 6 7 /** 8 * Handle system messages here. 9 */ 10 public void dispatchMessage(Message msg) { 11 if (msg.callback != null) { 12 handleCallback(msg); 13 } else { 14 if (mCallback != null) { 15 if (mCallback.handleMessage(msg)) { 16 return; 17 } 18 } 19 handleMessage(msg); 20 } 21 }
所以看到消息最后的處理正是在我們實例化handler時覆寫的HandlerMessage方法,至此,消息發送處理機制走完了。
那么總結一下:消息機制的過程是,Looper.prepare()實例化looper對象和消息隊列,handler實例化獲得上一步的looper對象和消息隊列的引用,handler.sendMessageAtTime()發送消息到消息隊列(這其中包括了給message的target賦值,將message壓入到消息隊列),Looper.loop()輪詢隊列取出消息交給message.target.dispatchMessage()處理,實質上是調用了我們自己重寫的handleMessage()。而Android為我們做了大量的封裝工作。開發人員只需要構造message並發送,自定義消息處理邏輯就可以了。
在研究源碼時,首先看類注釋,接着明確自己的需求,再去找關鍵方法,千萬莫要在龐雜的代碼中迷失。
在探尋源碼的過程中,發現了下一次博客的內容,就是WindowManager.LayoutParams,SystemClock,ThreadLocal,AtomicInteger。
水往低處流,人往高處走。