提出问题
现在这里列出几个问题:
- Handler的作用
- 为什么Android中要设计为只能在UI线程中去更新UI呢?
- Handler、Looper MessageQueue之间的关系
- 主线程如何让往子线程发消息
- 子线程中可以使用Handler吗?
- 可以在非主线程中更新UI吗?
- 使用Handle要注意什么(会引发什么问题)?
问题分析
以上几个问题如果都能回答的上来,说明关于Handle你已经基本掌握了,如果还有点模糊的话可以先看下以下两篇文章:
《Android消息处理机制》
《View的绘制流程》
ok,现在先看第一个问题:
Handler的作用
- 接收消息:在非UI线程中完成耗时操作,在UI线程中去更新UI。
- 发送消息:可以在主线程中发送延时消息。
为什么Android中要设计为只能在UI线程中去更新UI呢?
从源码的角度来回答:
我们知道既然是要更新UI,那么一定会涉及到View的绘制,既然要绘制View,那么一定会调用ViewRootImpl. ()方法,requestLayout()方法,里面都会调用checkThread();里面就对当前是否是主线程进行判断。如果不是主线程,就会抛异常。1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void requestLayout() {//我们在自定义ViewGroup的时候,如果要更新布局就会调用这个方法刷新界面,其实就是调用了View的重新绘制流程。
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {//非主线程抛异常
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}从设计思想的角度来回答:
- 解决多线程并发问题(根本原因)在子线程中加锁也可以解决并发的问题,可是会造成UI卡顿的新问题,性能低下,所以不能加锁。
- 提高界面更新的性能问题
- 架构设计的简单
说说Handler、Looper MessageQueue之间的关系
这里的话我举个例子来回答:在一个流水线生产车间中,整个大的机器就是Looper(一直在工作状态),而传送带就是MessageQueue,有货物(Massage)时,就会一直传送(Loop循环),机器处理这些货物(Massage)。没有货物(Massage)时就机器就关闭(主线程进入休眠状态),因为节省成本要省电(释放CPU资源),而工人(Handle)负责把货物(Massage)放到传送带上(Handler.sendMessage(Message))。
这个例子不知道贴不贴切。。。主线程往子线程发消息
我们用的最多的就是子线程向主线程发送消息,那如何让主线程给子线程发送消息呢?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47public class MainActivity extends Activity implements OnClickListener {
public static final int UPDATE_TEXT = 1;
private TextView tv;
private Button btn;
private Handler handler;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
//固定写法
new Thread(new Runnable() {
public void run() {
//1、准备Looper对象
Looper.prepare();
//2、在子线程中创建Handler
handler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("当前线程2", Thread.currentThread().getName());
}
};
//3、调用Looper的loop()方法,取出消息对象
Looper.loop();
}
}).start();
}
public void onClick(View v) {
Log.i("当前线程1", Thread.currentThread().getName());
switch (v.getId()) {
case R.id.btn:
Message msg = handler.obtainMessage();
handler.sendMessage(msg);
break;
default:
break;
}
}
}
由此可见可以在子线程中创建使用Handle的,但是以下写法是不对的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
//尝试在子线程中去创建Handler
new Thread(new Runnable() {
public void run() {
new Handler();
}
}).start();
}
Handle.class:1
2
3
4
5mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
以上会有 “Can’t create handler inside thread that has not called Looper.prepare()”的错误。
所以要在子线程中创建Handle的话必须要执行Looper的prepare()方法,生成Looper对象,把Looper对象和当前线程对象形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler对象,调用Looper的myLooper()方法,mLooper就不会为null了,得到与Handler所对应的Looper对象。handler、looper 、消息队列就形成了一一对应的关系
可以在非主线程中更新UI吗?
可以吗?答案是可以的。要从源码的角度来分析就很好理解。
还是说回View的绘制吧,刚刚也说了View的绘制肯定会调用CheckThread()方法,不是主线程的话肯定会执行不下去的。那好,我们想想,当我们开启一个Activiy的时候,在哪个生命周期方法中开始View的绘制的,是在onCreate()吗?我们看了源码就会知道是在onRsume()中才开始绘制View的:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// H();
case RESUME_ACTIVITY://Acticity生命周期Resume
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// 调用onResume方法
r = performResumeActivity(token, clearHide, reason);
// 将decorView添加到屏幕中
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//wm其实就是WindowManagerImpl,而在WindowManagerImpl中又调用了WindowManagerGlobal的addView方法。
//将DecorView添加到Window中
wm.addView(decor, l);//调用AddView,开始View的绘制
}
所以,我们只要在onResume方法之前,比如说在onCreate方法中:1
2
3
4
5
6
7
8
9
10
11
12
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) this.findViewById(R.id.startBtn);
new Thread(new Runnable() {
public void run() {
tv.setText("子线程更细UI");
}
}).start();
}
以上代码可直接运行。