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),欢迎有心人关注。博客园分享技术,公众号分享心智

我会很感激第一批关注个人人。此时,年轻的我和你,一无全部;然后,富裕的你和我,满载而归。

相关文章
相关标签/搜索