關於Handler的理解,子線程不能更新UI的糾正和回調的思考

  開發Android這麼久了,總會聽到有人說:主線程不能訪問網絡,子線程不能更新UI。Android的主線程的確不能長時間阻塞,可是子線程爲何不能更新UI呢?今天把這些東西整理,順便在子線程更新UI。java

  首先寫了一個handler在子線程更新主線程UI,在子線程作了一個耗時操做:從網絡下載了一個圖片並利用handler發送到handleMessage()的回調中,並更新到主線程的bitmap。圖片顯示成功,沒有問題。接下來在子線程中更新onCreate()中實例化的textview,報錯:android

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()代碼以下:windows

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纔剛剛初始化完成,但這樣是不安全的,你們都不推薦這種方式。併發

媽的,覺得下午換辦公室,直接電源斷了,一上午寫的全沒了,日。app

咱們都很是熟悉的生命週期,爲何按照onCreate,onStart這樣的順序執行的呢,多是在源碼中某個方法肯定了調用順序,也可能跟堆棧,硬件交互,全局變量有關。less

關於Java中的接口回調,在Android的點擊事件和網絡請求等有大量的應用。好比項目中有大量重複的工做須要抽取到工具類中,但具體的業務邏輯靈活多變,後者業務後期的維護升級這些都要求代碼沒法在工具類中一步到位。這時就能夠用回調解決,封裝的時候調用接口的抽象方法,在業務實現的地方添加相應的代碼。那麼關於接口的理解,就再也不僅限於初學時的定義了。ide

  接口的出現,一是解決了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。

  水往低處流,人往高處走。

相關文章
相關標籤/搜索