摘要
1.Android提供Handler的原因?
答:Android规定访问UI只能通过主线程,如果子线程访问UI,程序会抛出异常;ViewRootImpl在checkThread方法中做了判断。
2.系统为什么不允许在子线程中去访问UI?
答:因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。
ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程存储数据,数据存储后,只能在指定的线程可以获取到存储的数据,对于其他线程则无法获取到数据。
ThreadLocal使用场景
1.当数据是以线程作为作用域并且不同线程有不同副本的时候,就可以考虑使用;
2.复杂逻辑下的对象传递。
ThreadLocal和锁的比较
ThreadLocal解决的是同一个线程内的资源共享问题,而synchronized解决的是多个线程间的资源共享问题。
ThreadLocal原理
不同线程访问同一个ThreadLoacl的get方法,ThreadLocal的get方法会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的Value值。
1.ThreadLocal set方法
JDK中与Android SDK的ThreadLocal有些许区别,我们以SDK中的为例:
|
|
Values是Thread类内部专门用来存储线程的ThreadLocal数据的,它内部有一个数组private Object[] table,ThreadLocal的值就存在这个table数组中。如果values的值为null,那么就需要对其进行初始化然后再将ThreadLocal的值进行存储。
查看Values#put()方法可以总结出ThreadLocal数据的存储规则:ThreadLocal的值在table数组中的存储位置总是ThreadLocal的索引+1的位置。
2.ThreadLocal get方法
|
|
计算索引index位置,value=table[index+1]。
MessageQueue的工作原理
MessageQueue通过单链表的数据结构来维护消息队列;
MessageQueue#enqueueMessage(Message msg, long when):往消息队列插入一条消息;
MessageQueue#next()是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里;当有新消息到来时,next方法会返回这条消息并将其从单链表中移除;
MessageQueue#quit(boolean safe)设置清空消息。
Looper的工作原理
Looper#prepare()方法给Thread设置Looper;
Looper#prepareMainLooper()方法给主线程创建Looper,本质还是调用Looper#prepare()方法;
Looper#myLooper()方法得到当前Thread的Looper;
Looper#getMainLooper()方法得到主线程的Looper;
Looper#loop()是一个无限循环方法,除非退出Looper会让MessageQueue#next()返回的消息为空,否则调用MessageQueue#next()方法获取新消息是一个阻塞操作,当没有消息时,next方法会一直阻塞,这也导致了loop方法一直阻塞。如果MessageQueue的next方法返回了新消息,Looper就会调用msg.target.dispatchMessage(msg)处理这条消息;
Looper#quit()和Looper#quitSafely()方法最终调用MessageQueue#quit(boolean safe)方法来退出,二者的区别是:前者会直接退出Looper,后者只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。Looper退出之后,通过Handler发送的消息就会失败,这个时候Handler的send方法会返回false。在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议在不需要的时候终止Looper。
Handler的工作原理
可以指定一个特殊的Looper来构造Handler;
Handler#enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)方法将此Handler赋值给msg.target;
Looper#loop()方法最终将消息交给msg.target处理,即调用Handler#dispatchMessage(Message msg)方法;
|
|
主线程消息循环
1.Android的入口方法为ActivityThread#main(),在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。
2.Looper.loop()是一个无限循环方法,如果主线程的Looper终止,则应用程序会抛出异常。那么既然主线程卡在这里了,Activity为什么还能启动?点击一个按钮仍然可以响应?
答:startActivity的时候,会向AMS(ActivityManagerService)发一个跨进程请求(AMS运行在系统进程中),之后AMS启动对应的Activity;AMS也需要调用App中Activity的生命周期方法(不同进程不可直接调用),AMS会发送跨进程请求,然后由App的ActivityThread中的ApplicationThread会来处理,ApplicationThread会通过主线程线程的Handler将执行逻辑切换到主线程。四大组件的生命周期都是以消息的形式通过UI线程的Handler发送,由UI线程的Looper执行的。
Android提供的在子线程中进行UI操作的几种方式
Handler#post()
|
|
Activity#runOnUiThread(Runnable action)
|
|
如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。
View#post()
|
|
先看下面这张图,图画的是Android中Activity启动的部分流程图:
很明显可以看出:
1.在onResume()之后调用(不包含onResume()方法的调用),attachInfo不为null,使用attachInfo.mHandler,attachInfo的mHandler是在Activity调用onResume()时创建的Handler对象,属于主线程Handler。
2.在onResume()之前调用(包含onResume()方法本身),attachInfo为null,调用getRunQueue().post(action);会将Runnable加入View自己维护的HandlerActionQueue中;HandlerActionQueue内部维护一个HandlerAction[]数组。
|
|
那么executeActions()方法什么时候被执行?参数handler又哪个Handler?
搜索发现在ViewRootImpl#performTraversals()方法中调用了executeActions()方法。传入的参数Handler是mAttachInfo.mHandler,同1(attachInfo的mHandler是在Activity调用onResume()时创建的Handler对象,属于主线程Handler。)
所以假设我们在Activity#onCreate()中通过View.post一个Runnable对象,最终还是在第一个performTraversals()方法执行的时候,加入到了MainLooper的MessageQueue里。