标签:
本文包括与Handler有关的以下问题:
(1)Handler的作用
(2)为什么Android中要设计为只能在UI线程中去更新UI呢?
(3)Handler的两个异常
(4)Handler、Looper MessageQueue之间的关系(源码角度)
(5)跟线程相关的Handler,即HandlerThread(源码角度分析)
(6)主线程往子线程发消息
一、Handler的作用:
(1)在非UI线程中完成耗时操作,在UI线程中去更新UI。
(2)可以在主线程中发送延时消息。
二、为什么Android中要设计为只能在UI线程中去更新UI呢?
(1)解决多线程并发问题(根本原因)
(2)提高界面更新的性能问题
(3)架构设计的简单
你可能会说,既然是担心多线程并发问题,那我在子线程中加锁进行更新UI行不行呢?你这样想的话,会容易造成UI卡顿的。
注1:大部分面试者很难去说出一个令面试官满意的答案。
注2:关于多线程,这里举一个例子,比如说银行取款的问题。正常情况下,银行卡余额不能少于取款金额,如果多线程进行取款的话,就会造成线程不安全。
注3:Android中之所以说架构简单,是因为帮我们封装了很多更新UI的操作。
三、Handler的两个异常:
在使用Handler时,经常会出现以下两个异常:
(1)CalledFromWrongThreadException:这种异常是因为尝试在子线程中去更新UI,进而产生异常。
(2)Can‘t create handle inside thread that ha not called Looper.prepared:是因为我们在子线程中去创建Handler,而产生的异常。
我们接下来通过代码来把这两个异常演示一下。
1、子线程中更新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。
运行程序后,点击按钮,效果如下:
此时,我们来看一下后台的log日志:
上图中报的错误日志就是因为我们在子线程中去更新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();
2、在子线程中创建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 }
运行程序后, 报错如下:
四、Handler、Looper MessageQueue之间的关系:(源码角度)
如果要问到Handler,这个问题基本是面试必问的。
原理分析:
Handler是Android类库提供的用于发送、处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。整个异步消息处理流程的示意图如下图所示:
根据上面的图片,我们现在来解析一下异步消息处理机制:
现在可以做出如下总结:
(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操作了
生活中的例子:
上图中,可以这么理解:开会时,我(Handler)想中途离开去做别的事情,通过sendMessage发消息给领导,领导思考了一会儿,同意之后,通过Looper.looep()方法将消息回传给我,说我可以离开,然后我就调用handleMessage方法去做别的事情去了。
注:面试的时候,如果只是从字面的角度来解释Handler、Looper MessageQueue之间的关系,并不能够真正打动面试官,倒不如再举一个生动的例子,让面试官觉得你是懂面向对象的思维的。
五、跟线程相关的Handler,即HandlerThread(源码角度分析)
这个问题可以看一下这篇博客:
http://blog.csdn.net/lmj623565791/article/details/47079737
六、主线程往子线程发消息:(考察你是否真的理解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 //1、准备Looper对象
30 Looper.prepare();
31 //2、在子线程中创建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 //3、调用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中发送消息,在子线程中接收消息的固定写法。上面的三个步骤再重复一下:
注意,此时handleMessage()方法是在子线程中运行的。
后台运行效果:
小小地总结一下:
首先执行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对象。
六、为什么在有些时候子线程中是可以直接更新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
标签:
原文地址:http://www.cnblogs.com/laughingQing/p/4800396.html