多线程开发-Thread Looper与Handler关系解密
Posted 楠来风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程开发-Thread Looper与Handler关系解密相关的知识,希望对你有一定的参考价值。
第3节 Handler
多个线程之间除了有“执行的同步”关系,还有“数据的共享”关系,以及“功能的委托”关系。
例如之前提到的视频信息显示到列表上,
- 委托数据查询功能:主线程启动一个工作线程thread-查询视频信息;
- 委托数据的界面更新功能:工作线程查询完成后,因为不能够修改界面元素,所以必须将结果通知到主线程,委托主线程将结果的结果显示到界面上。
为此,android SDK提供了Handler
帮助各个不同的线程之间传递数据或委托功能处理。
3.1 Thread、Looper与Handler的关系
一个线程创建以后,就会开始执行它Runnable
中的run()
方法。如果要这个线程一直运行而不退出的话,就要在里面设置一个循环,
Runnable runnable = new Runnable()
@Override
public void run()
//开始循环
while(xxx)
;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Looper
可以为Thread
创建这样一个循环的环境,
@Override
public void run()
......
Looper.prepare();
......
//相当于while()循环
Looper.loop();
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Looper
具有一个消息队列,可以存放任何线程(包括它自己)给自己布置的任务。这些任务被一条一条的放在队列当中,在loop()
函数中被取出来-执行,然后又取出来-执行,周而复始,永不停止。
public static void loop()
......
for (;;)
//从消息队列queue中取出任务
Message msg = queue.next();
//用消息提供的方法来处理每个消息蕴含的任务
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Handler
是和Looper
相关联的,通过Handler
,任何线程可以把需要完成的任务放到Looper
的消息队列里面。
Thread
就好比一条产线,Looper
中的消息队列就是这个流水线上的传送带,带子上分成了很多格,每一格放要处理的原料和处理这些原料的工人(原料和工人打包成了一个Message
)。等轮到格子上的工人时,工人才能开始处理格子里放的原料。Handler
就像是一个转运工具,提供给别的模块使用。这个工具可以把原料和工人放到产线的传送带上。
注意,一条产线只有一条传送带(Looper
);但可以有多个为同一条产线提供转运服务的转运工具(Handler
)。
所以使用这种产线的流程是,
- 创建一条产线A;
- 在这条产线A上创建一条传送带;
- 当别的模块B要向这条产线布置任务的时候,就要创建在产线A上工作的工人;
- B要告诉工人携带哪些原料,怎么处理这些原料;
- 等轮到产线A上对应的格子被处理的时候,上面的工人就开始操作了;
![](http://pic.w2bc.com/upload/201606/08/201606081548012722.png)
3.2 Handler的使用
Handler
最常见的使用场景是:
- 为了进行耗时操作,主线程创建一个工作线程完成耗时操作;
- 工作线程开始耗时工作,时不时向主线程发送消息,告知当前完成的状态;
- 主线程根据工作线程的报告,更新界面元素,展示工作进度;
![](http://pic.w2bc.com/upload/201606/08/201606081548044403.png)
为了实现这样的功能,我们首先需要知道,
- 每个
Activity
都是运行在程序的主线程当中的; - 只有主线程能修改界面元素,其他线程修改的话,应用会崩溃;
- 主线程在创建之后,系统已经为它设置了
Looper
,主线程已经有了消息处理的队列(生产流水线和流水线上的格子);
用Handler
处理这种场景时,我们有2种方式。
3.2.1 使用sendMessage()
- 创建一个能向主线程消息队列中布置任务的
Handler
;如果创建时直接new Handler()
,说明这个Handler
是放在主线程的消息队列中的“工人”; - 重写
Handler
的handleMessage()
函数; - 在
handleMessage()
函数中,根据msg.what
的不同,进行对应的处理;
//创建的Handler是挂在主线程上的
private Handler mHandler = new Handler()
@Override
public void handleMessage(Message msg)
switch(msg.what)
case MSG_XXX:
//这里是在主线程中执行的,可以修改界面元素了
break;
case ......
break;
default:
super.handleMessage(msg);
;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
任何想要布置任务的线程只需要利用Handler
的sendMessage()
函数,就能把任务放到主线程的消息队列中,
Runnable runnable = new Runnable()
@Override
public void run()
//耗时的操作
while(!stop)
Message msg = mHandler.obtainMessage(MSG_XXX);
//用主线程的Handler向主线程布置任务
mHandler.sendMessage(msg);
;
Thread work = new Thread(runnable);
work.start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
这样主线程的消息队列中就有了这个新任务。等到这个消息被处理的时候,主线就可以根据参数修改界面元素了。
除了使用
Message msg = mHandler.obtainMessage(MSG_XXX);
mHandler.sendMessage(msg);
- 1
- 2
也可以使用
mHandler.obtainMessage(MSG_XXX).sendToTarget();
- 1
这里使用的Message
就携带了“工人”和“原料”,Message
通过Handler
对象获取,
//只设置Message的what数值
Message msg = mHandler.obtainMessage(what);
//设置Message的what数值和Object值
Message msg = mHandler.obtainMessage(what, object);
//设置Message的what数值,arg1、arg2和Object值
Message msg = mHandler.obtainMessage(what, arg1, arg2, object);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
发送Message
可以直接发送,也可以延时发送,
//直接发送消息,将任务布置到消息队列中
mHandler.sendMessage(msg);
//延迟1000毫秒发送消息
mHandler.sendDelayedMessage(msg,1000)
- 1
- 2
- 3
- 4
- 5
3.2.2 使用post()
-
创建一个能向主线程消息队列中布置任务的
Handler
;如果创建时直接new Handler()
,说明这个Handler
是放在主线程的消息队列中的工人
;Handler mHandler = new Handler();
- 1
-
在工作线程中使用
Handler
的post()
方法,里面的Runnable
就是在Handler所服务线程中运行;Runnable mHandlerRunnable; Runnable runnable = new Runnable() @Override public void run() //耗时的操作 while(!stop) mHandlerRunnable = new Runnable() @Override public void run() //这里是在主线程中执行的,可以修改界面元素了 ; //用主线程的Handler向主线程发送任务 mHandler.post(mHandlerRunnable); ; Thread work = new Thread(runnable); work.start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
还可以使用
//延迟1000毫秒执行runnable
mHandler.postDelayed(mHandlerRunnable, 1000);
- 1
- 2
3.3 Handler任务的移除
大多数情况下,当Activity退出以后,需要将它布置给主线程的任务给移除掉。如果不移除,可能会遇到大麻烦。可以想象一下,
- 工作线程通过
Handler
给主线程布置了一个任务-根据一个参数修改界面显示,此时这个任务已经放到了主线程的任务队列里面; - 用户突然退出了这个Activity,Activity被销毁了,但是主线程是不会退出的(Activity只是主线程上长的一个果子,果子被摘了,但是不会影响树的存在);
- 主线程的任务队列依次执行到了工作线程布置的任务,任务要求更新Activity上的界面元素,但是这个Activity已经被销毁了;
![](http://pic.w2bc.com/upload/201606/08/201606081548062499.png)
这时,程序就会出错。所以当一个Activity退出销毁的时候,一定要把相关的任务移除。
3.3.1 使用removeMessages()
通过它移除任务队列中所有特定Message
,
mHandler.removeMessages(MSG_XXX);
- 1
3.3.2 使用removeCallbacks()
通过它移除任务队列中所有特定Runnable
,
mHandler.removeCallbacks(mHandlerRunnable);
- 1
这里的mHandlerRunnable
就是之前post()
方式使用的mHandlerRunnable
,所以在使用post()
方式的时候,我们要把Runnable
的引用保存起来,以便以后的移除操作。
第4节 HandlerThread
3.1章节介绍了Thread、Looper与Handler的关系。为了让开发者能简单的使用具备Looper
的Thread
,而不需要开发者写代码组合它们,Android SDK提供了HandlerThread
。
HandlerThread
的原理很简单,
- 创建一个线程A;
- 在A上创建一个
Looper
;
这些这个新创建的线程就可以不停的接收和处理任务了。
4.1 HandlerThread的使用
-
创建并启动
HandlerThread
;HandlerThread mHandlerThread = new HandlerThread("创建的线程名字"); mHThread.start();
- 1
- 2
-
获取可以访问
HandlerThread
对象任务队列的Handler
;把Thread的Looper
指定给Handler
;Handler mHandler = new Handler(mHandlerThread.getLooper());
- 1
-
使用
Handler
向HandlerThread
对象布置任务;mHandler.sendMessage(MSG_XXX);
- 1
-
让
HandlerThread
对象退出;//马上退出线程 mHandlerThread.quit(); //执行完队列中的任务后退出线程 mHandlerThread.quitSafely();
- 1
- 2
- 3
- 4
- 5
4.2 HandlerThread与Handler、Thread名称区分
HandlerThread与Handler、Thread名字相似,这里再做一下澄清。
- Thread:线程,单纯的一个线程;
- HandlerThread:具备任务队列的线程,它就是一个线程;
- Handler:可以访问指定线程任务队列的一个工具;
第3节 Handler
多个线程之间除了有“执行的同步”关系,还有“数据的共享”关系,以及“功能的委托”关系。
例如之前提到的视频信息显示到列表上,
- 委托数据查询功能:主线程启动一个工作线程thread-查询视频信息;
- 委托数据的界面更新功能:工作线程查询完成后,因为不能够修改界面元素,所以必须将结果通知到主线程,委托主线程将结果的结果显示到界面上。
为此,Android SDK提供了Handler
帮助各个不同的线程之间传递数据或委托功能处理。
3.1 Thread、Looper与Handler的关系
一个线程创建以后,就会开始执行它Runnable
中的run()
方法。如果要这个线程一直运行而不退出的话,就要在里面设置一个循环,
Runnable runnable = new Runnable()
@Override
public void Android多线程分析之三:Handler,Looper的实现
Can't create handler inside thread that has not called Looper.prepare()
解决使用Handler时Can't create handler inside thread that has not called Looper.prepare()
android消息处理机制之2handler与looper,MessageQueue:的关系
无法在未调用 Looper.prepare() 的线程 Thread[Thread-5,5,main] 中创建处理程序 [重复]