Android系统分析之带着问题看Handler
1 Handler问题三连:是什么?有什么用?为什么要用,不用行不行?
1.1 Handler是什么?
答:Handler是Android FrameWork架构中的一个基础组件;
1.2 Handler有什么用?
答:把子线程中的UI更新信息传递给主线程(UI线程),以此完成UI更新操作;
1.3 Handler为什么要用,不用行不行?
答:不行,Handler是Android在设计之初就封装的一套消息创建、传递、处理机制。Android要求在主线程(UI线程)中更新UI。
2 真的只能在主(UI)线程中更新UI吗?
答:Android要求在主线程(UI线程)中更新UI,是要求,不是规定,硬要在子线程中更新UI也是可以的。
2.1 为什么可在一个子线程中创建一个对话框,并可更新对话框的UI,而不能更新主线程的UI?
答:Android的UI更新(GUI)被设计成了单线程,子线程可更新子线程创建的UI、不能更新主线程创建的UI。案例如下:
private lateinit var textView: TextViewthread {Looper.prepare() val dialog = AlertDialog.Builder(this).apply { setIcon(R.drawable.ic_launcher) setTitle("子线程创建的对话框") setCancelable(true) setNegativeButton("子线程更新 主线程创建的UI", object : DialogInterface.OnClickListener { override fun onClick(dialog: DialogInterface?, which: Int) { // 抛出异常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. // 翻译后是:只有创建这个view的线程才能操作这个view; textView.text = "子线程更新 主线程创建的UI ${Thread.currentThread().name}" } }) setPositiveButton("子线程更新 子线程创建的UI", object : DialogInterface.OnClickListener { override fun onClick(dialog: DialogInterface?, which: Int) { setTitle("子线程更新 子线程创建的UI ${Thread.currentThread().name}") } })}.create() dialog.show() Looper.loop()}
2.2 为什么直接在子线程中更新主线程创建的UI,没有报错;而如果延迟300毫秒后就报错了?
答:Android系统在onResume()时会检查:只有创建这个view的线程才能操作这个view,否则抛出异常。①ViewRootImp在onCreate()时还没创建,所以子线程中更新了 主线程创建的UI。②在onResume()时,ActivityThread的handleResumeActivity()执行后才创建ViewRootImp,然后调用requestLayout(),走到checkThread()检查时抛出异常。案例如下:
fun onCreate(savedInstanceState: Bundle?) {thread {// 直接在子线程中更新了 主线程创建的UI,却没有报错: textView.text = "子线程更新UI ${Thread.currentThread().name}"}thread {// 2、加上休眠300毫秒,程序就崩溃了 Thread.sleep(300) textView.text = "子线程更新UI ${Thread.currentThread().name}" }}
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException ("Only the original thread that created a view hierarchy can touch its views."); }}
3 Android UI更新机制(GUI) 为何设计成了单线程的?
答:因为多线程对同一个UI控件操作,容易造成不可控的错误。而必须解决这种多线程安全问题,简单的做法是加锁,不是加一个,而是每层加锁(用户代码–GUI顶层–GUI底层),但是这意味着更多耗时以及UI更新效率低下,而如果每层共用一把锁的话,其实就是单线程。因此,Android没有采用线程锁,而是采用单线程消息队列机制,实现了一个伪锁。
4 真的不能在主(UI)线程中执行网络操作吗?
答:能。通常,网络请求在主线程进行,会抛出异常NetworkOnMainThreadException,因为Android 2.3引入用于检测两大问题:ThreadPolicy(线程策略) 和 VmPolicy(VM策略)。在onCreate()的setContentView()后加上permitNetWork(),把严苟模式的网络监测关了,就可以在主线程执行网络请求了。
5 Handler怎么用?
答:有两个方式,一是sendMessage() + handleMessage();二是post(runnable)。其中,post(Runnable r)调用的是sendMessageDelayed(getPostMessage®, 0)发送消息,只不过延迟秒数为0。案例如下:
// 方式-:sendMessage() + handleMessage()// 步骤1:在主线程中 通过匿名内部类 创建Handler类对象val mhandler = Handler() {// 通过复写handlerMessage()从而确定更新UI的操作 override fun handleMessage(msg: Message) { super.handleMessage(msg) // 需执行的UI操作 }}// 步骤2:创建消息对象val msg = Message.obtain() // 实例化消息对象msg.what = 1 // 消息标识msg.obj = "AA" // 消息内容存放// 步骤3:在工作线程中 通过Handler发送消息到消息队列中mHandler.sendMessage(msg)// 步骤4:开启工作线程(同时启动了Handler)
// 方式二是post(runnable)// 步骤1:在主线程中创建Handler实例val mhandler = Handler()// 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容,需传入1个Runnable对象mHandler.post(Runnable { // 需执行的UI操作 }})// 步骤3:开启工作线程(同时启动了Handler)
5.1 那Runnable是怎么变成Message的呢?
答:在getPostMessage()方法中,通过Message.obtain()获取一个新的Mesage对象,把Runnable变量的值赋值给callback属性。
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
5.2 其他两个种在子线程中更新UI的方法?
答:activity.runOnUiThread(),和view.post()与view.postDelay();
6 为什么建议使用Message.obtain()来创建Message实例?
答:因为随着事件不断发送,会频繁大量创建消息对象,带来内存消耗;因此使用Message.obtain()通过消息池复用消息对象,可以避免重复创建对象,节约内存。
6.1 obtain()是怎么复用消息对象的?
答:分析obtain()的逻辑(如下),加锁判断消息池是否为空?不为空,取消息池的链表表头消息对象返回,正在使用标记为0,吃容量-1;为空,创建一个新的消息对象返回。Message池其实是一个单链表。获取消息池逻辑如下图:
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
6.2 消息对象时什么时候加到池中?
答:当消息对象被Looper分发完后,在loop()最后会调用msg.recycleUnchecked()函数,回收没有被使用的消息对象。具体逻辑是:标记设置为FLAG_IN_USE,表示正在使用,相关属性重置;加锁判断消息池是否满50,未满,采用单链表头插法把消息插入到链表表头。回收消息逻辑如下图:
void recycleUnchecked() { // ...... flags = FLAG_IN_USE; // 表示正在使用 when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { // MAX_POOL_SIZE = 50 next = sPool; sPool = this; sPoolSize++; } } }
7 Handler涉及到的类有哪几个?
答:主要有6个,分别是如下:
图片来源于:1、换个姿势,带着问题看Handler
7 为什么子线程中不可以直接new Handler(),而主线程中可以?
答:在new Handler()时,调用Looper.myLooper()获取当前线程的Looper对象,若线程无Looper对象则抛出异常,异常和逻辑如下图所示。主线程在启动时在ActivityThread的main函数中,调用Looper.prepareMainLooper()创建了Looper和MessageQueue对象,完成了初始化。而子线程还需要额外调用Looper.prepare()和Looper.loop()开启轮询,否则会报错。
public Handler(Callback callback, boolean async) { // 1. 指定Looper对象mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()"); } // 2. handler对象绑定Looper的消息队列对象(MessageQueue) mQueue = mLooper.mQueue;}
7.1 具体的Looper.prepareMainLooper()的初始化过程是?
定位到:ActivityThread.main()
public static void main(String[] args) { //... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } //... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");}
定位到:Looper.prepareMainLooper();
public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}
定位到:Looper → prepare函数
public static final void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); // 创建Looper对象,并存放在ThreadLocal变量中}
定位到:Looper → Looper构造函数
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed); // 创建1个Looper对象时,创建一个与之绑定的消息队列对象 mRun = true; mThread = Thread.currentThread(); // Looper与线程绑定}
7.2 mQuitAllowed变量,直译「退出允许」,具体作用是?
答:用来防止开发者手动终止消息队列,停止Looper循环。定位到:MessageQueue → quit函数:
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
7.3 一个线程中初始化多个handler,会产生多少个Looper?主线程和子线程Looper是同一个么?
答:不会,一个线程只能对应一个Looper,主线程和子线程Looper不是同一个。在Looper.prepare()方法中会调用sThreadLocal.get()获取当前线程的Looper,当多次调用不为空时会抛出异常。逻辑如下:
public static final void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); // 创建Looper对象,并存放在ThreadLocal变量中}
8 ThreadLocal是如何将Looper对象存放在Thread线程里,并解决并发访问的冲突问题的?
答:ThreadLocal是线程局部变量,为每个线程提供一个独立变量副本。每个线程内部都维护了一个ThreadLocalMap,变量名是threadLocals,这个map的key是ThreadLocal。在存值时,通过当前线程取出线程的变量threadLocals,以ThreadLocal为key,存储一个值;在获取值时,过当前线程取出线程的变量threadLocals,以ThreadLocal为key,获取一个值。因为存和取都在线程自己的变量中操作,所以不存在线程安全问题。以下3 个源码辅助理解:
(1)定位到:ThreadLocal → set函数:
public void set(T value) { // 取出当前线程 Thread t = Thread.currentThread(); // 通过当前线程取出线程的成员(ThreadLocalMap)threadLocals(2) ThreadLocalMap map = getMap(t); if (map != null) { // 向ThreadLocalMap,以ThreadLocal为key,存储一个值 (---->源码看:2.5 再深入分析) map.set(this, value); } else { // 创建Thread成员threadLocals(3) createMap(t, value); } }
(2)定位到:ThreadLocal → getMap函数:
ThreadLocalMap getMap(Thread t) { // 取出Thread的成员return t.threadLocals; }
(3)定位到:ThreadLocal → get函数:
public T get() { // 取出当前线程 Thread t = Thread.currentThread(); // 不同线程有不同ThreadLocalMap,就是有不同的副本 ThreadLocalMap map = getMap(t); if (map != null) { // 根据key获取table中的Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { return (T)e.value; } } return setInitialValue();}
10 主线程给子线程的Handler发送消息怎么写?
答:普通实现方法中,子线程初始化Handler,存在报空指针风险,因为:多线程并发的问题,当主线程执行到sendMessage时,子线程的Handler还没有初始化。因此,最优方法是:通过HandlerThread获取子线程的Looper,再在主线程初始化Handler,并传入子线程的Looper。
// 在主线程中给子线程的Handler发送信息 fun mainSendMessageToThread(view: View) { val thread = LooperThread() thread.start() // 1.报空指针,因为:多线程并发的问题,当主线程执行到sendEmptyMessage时,子线程的Handler还没有初始化// thread.mHandler!!.sendEmptyMessage(0x123) // 2.解决方法是:主线程延时给子线程发消息,等待子线程的Handler完成初始化 Handler().postDelayed(Runnable { thread.mHandler!!.sendMessage(obtainSyncMessage(0x123, "同步消息")) }, 1000) // 3.更优解决方法是:通过HandlerThread获取子线程的Looper,再在主线程初始化Handler,并传入子线程的Looper initHandlerThread() } // 通过HandlerThread获取子线程的Looper,再在主线程初始化Handler,并传入子线程的Looper private fun initHandlerThread() { val handlerThread = HandlerThread("Thread-1") handlerThread.start() val handler = MyHandler(handlerThread.looper) handler.sendMessage(obtainSyncMessage(0x123, "同步消息")) } /** * 子线程的Handler接收信息 */ private inner class LooperThread : Thread() { var mHandler: Handler? = null override fun run() { Looper.prepare() // 存在报空指针风险,因为:多线程并发的问题,当主线程执行到sendEmptyMessage时,子线程的Handler还没有初始化 mHandler = MyHandler(Looper.myLooper()) Looper.loop() } }
11 HandlerThread实现的核心原理?
答:①HandlerThread = 继承线程 + 封装Looper;②getLooper()加锁死循环wait()等待,而堵塞线程;③线程的run方法中,加锁等待当前线程的Looper对象创建成功,再notifyAll()通知getLooper()中的wait()等待,说Looper对象已经创建成功了;④等待唤醒后,getLooper()返回在run方法中创建的Looper对象。
图片来源于:1、换个姿势,带着问题看Handler
11 Looper是怎么循环分拣队列里的消息的?
答:ActivityThread在main函数中调用Looper.prepareMainLooper完成主线程的Loper初始化,然后调用Looper.loop()开启消息循环等待接收(分拣)消息。消息循环如下UML图,这个过程分为7个步骤:
(1)第一步定位到:Looper → loop函数,首先获得当前线程的Looper对象和消息队列,然后循环不断地检查消息队列中是否有新消息需要处理,有就取出消息判空后分发给Handerl处理,没有就在消息队列的next()中进入睡眠状态,等待新消息。
public static void loop() {Looper.loop()final Looper me = myLooper(); // 获得当前线程的Looper实例final MessageQueue queue = me.mQueue; // 获取消息队列for (;;) { // 死循环 Message msg = queue.next(); // 取出队列中的消息 if (msg == null) { return; } msg.target.dispatchMessage(msg); // 将消息分发给Handler}}
(2)第二步定位到:MessageQueue -> next函数:,
(3)第三步定位到:Looper → loop函数,
(4)第四步定位到:Looper → loop函数,
(5)第五步定位到:Looper → loop函数,
(6)第六步定位到:Looper → loop函数,
(7)第七步定位到:Looper → loop函数,
10 当我们用Handler发送一个消息,发生了什么?
答:
12 分发给Handler的消息是怎么处理的?
答:
9 如果只有一个looper,looper如何区分handler,handler发送了消息会不会导致Looper错乱,最终不知道谁处理。
答:
13 IdleHandler是什么?有什么作用?应用场景是什么?
答:
14 Looper在主线程中死循环,为啥不会ANR?
答:
15 Handler泄露的原因及正确写法?
答:
16 Handler中的同步屏障机制是什么?同步屏障使用场景?
答:
17 Android 11 Handler相关变更?
答:
20 学习链接
1、换个姿势,带着问题看Handler
2、Android Handler:手把手带你深入分析 Handler机制源码
更多相关文章
- Handler实现多线程
- Android(安卓):动画的使用(1)
- Android(安卓)开关机动画显示源码分析
- Android(安卓)消息通知栏用法详解(一)
- SignalR+HTML5实现消息推送及Android通知栏消息
- Android(安卓)Framework系列之IPC(一)
- 程序员挑战高薪,你必须会的十大面试题《一》
- android 笔记 --- Android中Handler,Looper,HandlerThread
- android-async-http开源请求库