android艺术开发探索之消息机制
handler的工作主要包含发送消息和接受消息,通过send方法发送,发送的消息被插入到MessageQueue中,MessageQueu
e的next方法会将消息返回给looper,looper接收到消息就处理,最终looper将消息交给handler的handleMessage处理。
handler的定义:
handler真的是我们所说的那样更新ui的吗?
个人认为它是具有更新ui的功能但是不是说就是刷新ui的,handler的主要作用是将一个任务切换到某个指定的线程中去执
行。为何规定更新ui要在UI线程ActivityThread中?
ViewRootImpl对ui操作做了验证,因此只能在主线程中访问ui。但是主线程做了耗时操作会卡死出现ANR,因此将耗时操作
放在子线程。android的ui控件线程不安全,多线程并发可能导致ui处于不可预期状态,如果加锁的话会让ui访问的逻辑
变得复杂,其次锁机制会降低UI访问的效率(锁机制会阻塞某些线程的执行),因次采用单线程模型处理ui操作,也就是
使用handler切换ui访问的执行线程。主线程的消息循环模型是啥?
ActivityThread的入口方法为main,在main方法中调用Looper.prepareMainLooper()创建主线程的looper以及Message
Queue,并通过Looper.loop()开启主线程消息循环,ActivityThread.H为主线程的handler用来和消息队列进行交互,A
ctivityThread通过ApplicationThread和AMS进行进程通信,AMS以进程通信的方式回调ApplicationThread中的binder
方法,之后ApplicationThread向ActivityThread.H发送消息,ActivityThread.H收到消息将ApplicationThread的逻辑
切换到ActivityThread中执行,即切换到主线程执行。
(我不明白为何是进程通信,比如请求网络耗时操作是在子线程完成,执行过程中要刷新ui,就要切换到主线程,这怎么也
是线程之间干的事,师父说app自己不会刷,只有系统才会刷,系统和app是进程间的通信,因此其实系统默默的干了很多
事,它很累,我们在写代码的时候不用的资源要及时释放@-@!!!)
MessageQueue、Looper、ThreadLocal
MessageQueue的工作原理
内部采用单链表的数据结构存储消息队列,里面存储了一组消息,可以插入删除消息但不能处理消息,采用looper处理消息
enqueueMessage方法是往消息队列中插入一条消息,而next是取出消息并且移除,next方法是一个无限循环方法,有消息
就返回消息并从MessageQueue中移除消息,没有就一直阻塞等待。
Looper的工作原理
Looper:处理MessageQueue中的消息,有消息就处理,无消息就阻塞等待,Looper的构造器中会创建MessageQueue,并
且当前线程对象保存,利用ThreadLocal在每个线程中存储数据,handler利用ThreadLocal获取每个线程的Looper,线程默
认无Looper,我们的ActivityThread被创建时就会初始化Looper,因此主线程可以默认使用handler.
handler在创建时会使用当前线程的Looper构建内部循环系统,通过send方法发送消息到MessageQueue,Looper发现有新
消息来时就会处理,Looper是运行在handler所在的线程中的,因此handler的作用就是将一个任务切换到某个指定的线程
中去执行。
handler工作需要Looper,Looper创建如下:
new Thread(){
@Override
public void run() {
Looper.prepare();
Handler handler=new Handler();
Looper.loop();//只有调用此方法消息循环系统才会真正起作用,该方法死循环,跳出条件为next方法
//返回null,也就是在looper推出的时候,next方法阻塞致使looper一直阻塞
// getMainLooper();//获取主线程的Looper
}
}.start();
ThreadLocal的工作原理
1.ThreadLocal:它是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的
线程中获取存储的数据,对其他的线程来说则获取不到,Looper的作用域是线程,并且不同的线程拥有不同的Looper。
2.当某些数据以线程为作用域并且不同的线程具有不同的数据副本时,就可以采用ThreadLocal,否则得写一个LooperMan
ger类了,但是系统并未这样做,这就体现了ThreadLocal的好处。
3.ThreadLocal的另一使用场景是复杂逻辑下的对象传递,比如监听器的传递,采用ThreadLocal可以让监听器作为线程
内的全局对象而存在,线程内部通过get获取监听器。
demo如下:
private ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<>();
mBooleanThreadLocal.set(true);
Log.e("结果", "主线程" + mBooleanThreadLocal.get());
new Thread("子线程1"){
@Override
public void run() {
Log.e("结果","子线程1"+mBooleanThreadLocal.get());
}
}.start();
new Thread("子线程2"){
@Override
public void run() {
mBooleanThreadLocal.set(true);
Log.e("结果","子线程2"+mBooleanThreadLocal.get());
}
}.start();
打印日志如下:
08-08 11:49:25.423 2306-2306/com.efrobot.robot.autonomic E/结果: 主线程true
08-08 11:49:25.431 2306-2334/com.efrobot.robot.autonomic E/结果: 子线程1null
08-08 11:49:25.459 2306-2335/com.efrobot.robot.autonomic E/结果: 子线程2true
如果ThreadLocal数据不是分开的,那么子线程1的结果应该是true,但是这里却输出null,这说明了不同的线程访问同一个ThreadLocal,他们取出的是各自
线程中存储的数据,不同线程的数据互不干扰,读写操作也仅限于各自线程内部。ThreadLocal.values是专门用来存储ThreadLocal值得,底层有一个数组table,它的存储位置总是不一样的,因此取出的
值不一样。