Android中關於Handler的若干思考

在以前的博文中,講過一些和Handler有關的知識,例如:html

Android 多線程----AsyncTask異步任務詳解 java

Android多線程----異步消息處理機制之Handler詳解android

今天再把Handler的知識回顧總結一下。面試

 

本文包括與Handler有關的如下問題:安全

  (1)Handler的做用微信

  (2)爲何Android中要設計爲只能在UI線程中去更新UI呢?多線程

  (3)Handler的兩個異常架構

  (4)Handler、Looper MessageQueue之間的關係(源碼角度)併發

  (5)跟線程相關的Handler,即HandlerThread(源碼角度分析)app

  (6)主線程往子線程發消息

 

1、Handler的做用:

(1)在非UI線程中完成耗時操做,在UI線程中去更新UI。

(2)能夠在主線程中發送延時消息。

 

2、爲何Android中要設計爲只能在UI線程中去更新UI呢?

(1)解決多線程併發問題(根本緣由)

(2)提升界面更新的性能問題

(3)架構設計的簡單

你可能會說,既然是擔憂多線程併發問題,那我在子線程中加鎖進行更新UI行不行呢?你這樣想的話,會容易形成UI卡頓的,並且性能也很差。

注1:大部分面試者很難去說出一個令面試官滿意的答案。

注2:關於多線程,這裏舉一個例子,好比說銀行取款的問題。正常狀況下,銀行卡餘額不能少於取款金額,若是多線程進行取款的話,就會形成線程不安全。

注3:Android中之因此說架構簡單,是由於幫咱們封裝了不少更新UI的操做。

 

3、Handler的兩個異常:

在使用Handler時,常常會出現如下兩個異常:

(1)CalledFromWrongThreadException:這種異常是由於嘗試在子線程中去更新UI,進而產生異常。

(2)Can't create handle inside thread that ha not called Looper.prepared:是由於咱們在子線程中去建立Handler,而產生的異常。

咱們接下來經過代碼來把這兩個異常演示一下。

一、子線程中更新UI的異常:

(1)activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="按鈕"/>

</RelativeLayout>

 上方代碼中,一個文本,一個按鈕,代碼比較簡單。

(2)MainActivity.java:

 1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.view.View;
 4 import android.widget.Button;
 5 import android.widget.TextView;
 6 
 7 public class MainActivity extends Activity {
 8 
 9     private TextView tv;
10     private Button btn;
11 
12     @Override
13     protected void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         setContentView(R.layout.activity_main);
16         tv = (TextView) findViewById(R.id.tv);
17         btn = (Button) findViewById(R.id.btn);
18 
19         //點擊按鈕後,嘗試在子線程中更新UI
20         btn.setOnClickListener(new View.OnClickListener() {
21             @Override
22             public void onClick(View v) {
23 
24                 new Thread(new Runnable() {
25                     @Override
26                     public void run() {
27                         tv.setText("smyhvae"); //子線程中更新UI 28                     }
29                 }).start();
30 
31             }
32         });
33     }
34 }

上方代碼中,核心代碼是第27行:點擊按鈕後,在子線程中更新UI。

運行程序後,點擊按鈕,效果以下:

b40c8233-1367-4c80-b3c1-42e03ab1345f

此時,咱們來看一下後臺的log日誌:

678ff542-e443-4276-92ab-9a810a38ff88

上圖中報的錯誤日誌就是由於咱們在子線程中去更新UI。

 

解決方案:

  在子線程中建立Message消息,經過Handler發給主線程,以後在Handler的handleMessage方法中得到Message消息,進而處理更新UI界面。代碼以下:

 1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.os.Handler;
 4 import android.os.Message;
 5 import android.view.View;
 6 import android.view.View.OnClickListener;
 7 import android.widget.Button;
 8 import android.widget.TextView;
 9 
10 public class MainActivity extends Activity implements OnClickListener {
11     public static final int UPDATE_TEXT = 1;
12     private TextView text;
13     private Button changeText;
14 
15     //程序一加載,直接在主線程中建立Handler
16     private Handler handler = new Handler() {
17         public void handleMessage(Message msg) {
18             switch (msg.what) {
19             case UPDATE_TEXT:
20                 text.setText("Nice to meet you");
21                 break;
22             default:
23                 break;
24             }
25         }
26     };
27 
28     @Override
29     protected void onCreate(Bundle savedInstanceState) {
30         super.onCreate(savedInstanceState);
31         setContentView(R.layout.activity_main);
32         text = (TextView) findViewById(R.id.text);
33         changeText = (Button) findViewById(R.id.change_text);
34         changeText.setOnClickListener(this);
35     }
36 
37     @Override
38     public void onClick(View v) {
39         switch (v.getId()) {
40         case R.id.change_text:
41             new Thread(new Runnable() {
42                 @Override
43                 public void run() {
44                     Message message = new Message(); 45                     message.what = UPDATE_TEXT;
46                     handler.sendMessage(message);
47                 }
48             }).start();
49             break;
50         default:
51             break;
52         }
53     }
54 }

 上方第44行代碼也能夠換成:

Message message = handler.obtainMessage();

  

二、在子線程中建立Handler的異常:

MainActivity.java:

 1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.os.Handler;
 4 import android.widget.TextView;
 5 
 6 public class MainActivity extends Activity {
 7 
 8     private TextView tv;
 9 
10     @Override
11     protected void onCreate(Bundle savedInstanceState) {
12         super.onCreate(savedInstanceState);
13         setContentView(R.layout.activity_main);
14         tv = (TextView) findViewById(R.id.tv);
15 
16         //嘗試在子線程中去建立Handler
17         new Thread(new Runnable() {
18             @Override
19             public void run() {
20                 new Handler();
21             }
22         }).start();
23     }
24 }

 運行程序後, 報錯以下:

4b57cbb1-9c58-49e2-b3b8-84e97e34b859

  

4、Handler、Looper MessageQueue之間的關係:(源碼角度)

    若是要問到Handler,這個問題基本是面試必問的。

原理分析:

Handler是Android類庫提供的用於發送、處理消息或Runnable對象的處理類,它結合Message、MessageQueue和Looper類以及當前線程實現了一個消息循環機制,用於實現任務的異步加載和處理。整個異步消息處理流程的示意圖以下圖所示:

根據上面的圖片,咱們如今來解析一下異步消息處理機制

  • Message消息體,用於裝載須要發送的對象。
  • Handler:它直接繼承自Object。做用是:在子線程中發送Message或者Runnable對象到MessageQueue中;在UI線程中接收、處理從MessageQueue分發出來的Message或者Runnable對象。發送消息通常使用Handler的sendMessage()方法,而發出去的消息通過處理後最終會傳遞到Handler的handlerMessage()方法中。
  • MessageQueue用於存放Message或Runnable對象的消息隊列。它由對應的Looper對象建立,並由Looper對象管理。每一個線程中都只會有一個MessageQueue對象。
  • Looper是每一個線程中的MessageQueue的管家負責接收和分發Message或Runnable的工做。調用Looper.loop()方法,就是一個死循環,不斷地從MessageQueue中取消息:若是有消息,就取出,並調用Handler的handlerMessage()方法;若是沒有消息阻塞。

如今能夠作出以下總結:

(1)Handler負責發送消息,Looper負責接收Handler發送的消息放到MessageQueue,Looper又將消息回傳給Handler本身。

(2)一個Handler對應一個Looper對象,一個Looper對應一個MessageQueue對象(Looper內部包含一個MessageQueue),一個Handler能夠生成多個Message。

(3)Handler就是公開給外部線程的接口,用於線程間的通訊。Looper是由系統支持的用於建立和管理MessageQueue的依附於一個線程的循環處理對象,而Handler是用於操做線程內部的消息隊列的,因此            Handler也必須依附一個線程,並且只能是一個線程。

(4)因爲Handler是在主線程中建立的,因此此時handleMessage()方法中的代碼也會在主線程中運行,因而咱們在這裏就能夠安心地進行UI操做了

 

生活中的例子:

97e2d20a-ec78-4f28-81b5-83b2ee41f797

上圖中,能夠這麼理解:開會時,我(Handler)想中途離開去作別的事情,經過sendMessage發消息給領導,領導思考了一下子,贊成以後,經過Looper.looep()方法將消息回傳給我,說我能夠離開,而後我就調用handleMessage方法去作別的事情去了。

注:面試的時候,若是隻是從字面的角度來解釋Handler、Looper MessageQueue之間的關係,並不可以真正打動面試官,倒不如再舉一個生動的例子,讓面試官以爲你是懂面向對象的思惟的。

 

5、跟線程相關的Handler,即HandlerThread(源碼角度分析)

這個問題能夠看一下這篇博客:

http://blog.csdn.net/lmj623565791/article/details/47079737

 

6、主線程往子線程發消息:(考察你是否真的理解Handler機制)

  咱們在平時開發的過程當中,常常是子線程往主線程中發消息,讓主線程更新UI。可是根據具體的項目需求,也可能會要求讓你在主線程中往子線程中發消息。

 1 import android.app.Activity;
 2 import android.os.Bundle;
 3 import android.os.Handler;
 4 import android.os.Looper;
 5 import android.os.Message;
 6 import android.util.Log;
 7 import android.view.View;
 8 import android.view.View.OnClickListener;
 9 import android.widget.Button;
10 import android.widget.TextView;
11 
12 public class MainActivity extends Activity implements OnClickListener {
13     public static final int UPDATE_TEXT = 1;
14     private TextView tv;
15     private Button btn;
16     private Handler handler;
17 
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22         tv = (TextView) findViewById(R.id.tv);
23         btn = (Button) findViewById(R.id.btn);
24         btn.setOnClickListener(this);
25         //疑問:爲何這段代碼若是寫在onClick方法裏面會報空指針?
26         new Thread(new Runnable() {
27             @Override
28             public void run() {
29                 //一、準備Looper對象
30  Looper.prepare(); 31                 //二、在子線程中建立Handler
32                 handler = new Handler() { 33  @Override 34                     public void handleMessage(Message msg) { 35                         super.handleMessage(msg); 36                         Log.i("handleMessage:", Thread.currentThread().getName()); 37                         Log.i("後臺輸出", "收到了消息對象"); 38  } 39  }; 40                 //三、調用Looper的loop()方法,取出消息對象
41  Looper.loop(); 42             }
43         }).start();
44 
45     }
46     @Override
47     public void onClick(View v) {
48         Log.i("onClick:", Thread.currentThread().getName());
49         switch (v.getId()) {
50             case R.id.btn:
51                 Message msg = handler.obtainMessage();
52                 handler.sendMessage(msg);
53                 break;
54 
55             default:
56                 break;
57         }
58     }
59 }

上方的第29行至41行代碼:這是MainThread中發送消息,在子線程中接收消息的固定寫法。上面的三個步驟再重複一下:

  • 準備Looper對象
  • 在WorkerThread當中生成一個Handler對象
  • 調用Looper的loop()方法以後,Looper對象將不斷地從消息隊列當中取出對象,而後調用handler的handleMessage()方法,處理該消息對象;若是消息隊列中沒有對象,則該線程阻塞

注意,此時handleMessage()方法是在子線程中運行的

後臺運行效果:

60e0e63e-2c82-45de-a787-ca7dd881141b

小小地總結一下:

  首先執行Looper的prepare()方法,這個方法有兩個做用:一是生成Looper對象,而是把Looper對象和當前線程對象造成鍵值對(線程爲鍵),存放在ThreadLocal當中,而後生成handler對象,調用Looper的myLooper()方法,獲得與Handler所對應的Looper對象,這樣的話,handler、looper 、消息隊列就造成了一一對應的關係,而後執行上面的第三個步驟,即Looper在消息隊列當中循環的取數據。

另外,在本文最開頭的第一段中,咱們在主線程中建立Handler也沒有調用Looper.prepare()方法,爲何就沒有崩潰呢?,這是因爲在程序啓動的時候,系統已經幫咱們自動調用了Looper.prepare()方法。查看ActivityThread中的main()方法,代碼以下所示:

 1 public static void main(String[] args) {  
 2     SamplingProfilerIntegration.start();  
 3     CloseGuard.setEnabled(false);  
 4     Environment.initForCurrentUser();  
 5     EventLogger.setReporter(new EventLoggingReporter());  
 6     Process.setArgV0("<pre-initialized>");  
 7  Looper.prepareMainLooper();  
 8     ActivityThread thread = new ActivityThread();  
 9     thread.attach(false);  
10     if (sMainThreadHandler == null) {  
11         sMainThreadHandler = thread.getHandler();  
12     }  
13     AsyncTask.init();  
14     if (false) {  
15         Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));  
16     }  
17     Looper.loop();  
18     throw new RuntimeException("Main thread loop unexpectedly exited");  
19 }  

 上方代碼中,能夠看到,在第7行調用了Looper.prepareMainLooper()方法,而這個方法又會再去調用Looper.prepare()方法,代碼以下所示:

1 public static final void prepareMainLooper() {  
2     prepare();  
3     setMainLooper(myLooper());  
4     if (Process.supportsProcesses()) {  
5         myLooper().mQueue.mQuitAllowed = false;  
6     }  
7 } 

 總結:這樣基本就將Handler的建立過程徹底搞明白了,總結一下就是在主線程中能夠直接建立Handler對象,而在子線程中須要先調用Looper.prepare()才能建立Handler對象。

 

6、爲何在有些時候子線程中是能夠直接更新UI的:

這道面試題應該是本文中最難的一個面試題了,須要好好理解。爲了回答這個問題,咱們須要先經過看源碼去了解下面這三個問題:

(1)Android是如何檢測非UI線程去更新UI的

(2)ViewRootImp是什麼?

(3)ViewRootImp是在哪裏建立的?

源碼我就不貼出來了,這裏我只是總結一下。

答案:

  非UI線程真的不能更新UI嗎? 是能夠的

解釋:

  在線程中更新UI時會調用ViewParent.invalidateChild()方法檢查當前的thread是不是Mainthread

具體源碼以下:

1 final ViewParent p = mParent;
2     if (p != null && ai != null && l < r && t < b) {
3     final Rect damage = ai.mTmpInvalRect;
4     damage.set(l, t, r, b);
5     p.invalidateChild(this, damage); 6 }

 而ViewParent是一個接口類,其實現類是ViewRootImpl,經過查看invalidateChild()方法裏面的代碼就能夠看到會他調用checkThread()方法。checkThread()方法以下:

1 void checkThread() {
2     if (mThread != Thread.currentThread()) {   //檢查更新UI的線程是不是MainThread
3         throw new CalledFromWrongThreadException(
4         "Only the original thread that created a view hierarchy can touch its views.");
5     }
6 }

 上面的第02行就是檢查:在線程中更新UI時當前線程是不是MainThread。

可是,ViewRootImpl這個類是在activity的onResume()方法中建立。就算在子線程中更新UI,只要在ViewRootImpl建立以前更新UI(好比,程序在執行onCreate方法時,我就去執行setText方法區更新UI),就能夠逃避掉checkThread()的檢查。

關於本題,給出如下連接你們去細讀一下源碼吧:

Android更新Ui進階精解(一):

http://www.jianshu.com/p/6de0a42a44d6

爲何咱們能夠在非UI線程中更新UI:

http://blog.csdn.net/aigestudio/article/details/43449123

 

個人公衆號

下圖是個人微信公衆號(生命團隊id:vitateam),歡迎有心人關注。博客園分享技術,公衆號分享心智

我會很感激第一批關注個人人。此時,年輕的我和你,一無全部;然後,富裕的你和我,滿載而歸。

相關文章
相關標籤/搜索