0x2、Handler怎么用
1.sendMessage() + handleMessage()
代码示例如下:
黄色部分会有如下警告
Handler不是静态类可能引起**内存泄露
**,原因以及正确写法等下再讲。
另外,建议调用 Message.obtain() 函数来获取一个Message实例,为啥?点进源码:
从源码,可以看到obtain()的逻辑:加锁 → 判断Message池是否为空
- ① 不为空,取一枚Message对象,正在使用标记置为0,池容量-1,返回此对象;
- ② 为空,新建一个Message对象返回;
此处复用Message,可避免**避免重复创建实例对象
,达到节约内存的目的,而且不难看出Message实际上是无头结点的单链表
**。
上述获取消息池的逻辑:
定位到下述代码,还可以知道:池的容量为50
然后问题来了,Message信息什么时候加到池中?
答:当Message 被Looper分发完后,会调用 recycleUnchecked()函数,回收没有在使用的Message对象。
标志设置为**FLAG_IN_USE
**,表示正在使用,相关属性重置,加锁,判断消息池是否满,未满,单链表头插法 将消息插入到表头。
2.post(runnable)
代码示例如下:
跟下post():
实际上调用了 sendMessageDelayed() 发送消息,只不过延迟秒数为0,那Runnable是怎么变成Message的呢?跟下getPostMessage()
噢,获取一个新的Message示例后,把 Runnable 变量的值赋值给 callback属性
3.附:其他两个种在子线程中更新UI的方法
activity.runOnUiThread()
view.post() 与 view.postDelay()
0x3、Handler底层原理解析
终于来到稍微有点技术含量的环节,在观摩源码了解原理前,先说下几个涉及到的类。
1.涉及到的几个类
2.前戏
在我们使用Handler前,Android系统已为我们做了一系列的工作,其中就包括了
创建「Looper」和「MessageQueue」对象
上图中有写:ActivityThread 的 main函数是APP进程的入口,定位到 ActivityThread → main函数
定位到:Looper → prepareMainLooper函数
定位到:Looper → prepare函数
定位到:Looper → Looper构造函数
另外这里的 mQuitAllowed 变量,直译「退出允许」,具体作用是?跟下 MessageQueue:
em...用来 防止开发者手动终止消息队列,停止Looper循环。
3.消息队列的运行
前戏过后,创建了Looper与MessageQueue对象,接着调用Looper.loop()开启轮询。 定位到:Looper → loop函数
接着有几个问题,先是这个 myLooper() 函数:
这里的 ThreadLocal → 线程局部变量 → JDK提供的用于解决线程安全的工具类。作用:为每个线程提供一个独立的变量副本 → 以解决并发访问的冲突问题。本质:
每个Thread内部都维护了一个ThreadLocalMap,这个map的key是ThreadLocal,
value是set的那个值。get的时候,线程都是从自己的变量中取值,所以不存在线程安全问题。
主线程和子线程的Looper对象实例相互隔离的!!!
另外,线程为key也保证了每个线程只有一个Looper,而创建Looper对象时又会创建MessageQueue对象,所以间接保证每个线程最多只能有一个MessageQueue。
知道这个以后,有个问题就解惑了:
为什么子线程中不能直接 new Handler(),而主线程可以?
答:主线程与子线程不共享同一个Looper实例,主线程的Looper在启动时就通过 prepareMainLooper() 完成了初始化,而子线程还需要调用 Looper.prepare() 和 Looper.loop()开启轮询,否则会报错,不信,可以试试:
直接就奔溃了~
加上试试?
可以,没有报错,程序正常运行。
对了,既然说Handler用于子线程和主线程通信,试试在主线程中给子线程的Handler发送信息,修改一波代码:
运行,直接报错:
原因:多线程并发的问题,当主线程执行到sendEnptyMessage时,子线程的Handler还没有创建。 一个简单的解决方法是:主线程延时给子线程发消息,修改后的代码示例如下:
运行结果如下:
可以,不过其实Android已经给我们封装好了一个轻量级的异步类 HandlerThread
4.HandlerThread
HandlerThread = 继承Thread + 封装Looper
使用方法很简单,改造下我们上面的代码:
用法挺简单的,源码其实也很简单,跟一跟:
剩下一个quit()和quitSafely()停止线程,就不用说了,所以HandlerThread的核心原理就是:
- 继承Thread,getLooper()加锁死循环wait()堵塞线程;
- run()加锁等待Looper对象创建成功,notifyAll()唤醒线程;
- 唤醒后,getLooper返回由run()中生成的Looper对象;
是吧,HandlerThread的实现原理竟简单如斯,另外,顺带提个醒!!!
Java中所有类的父类是 Object 类,里面提供了wait、notify、notifyAll三个方法;Kotlin 中所有类的父类是 Any 类,里面可没有上述三个方法!!! 所以你不能在kotlin类中直接调用,但你可以创建一个java.lang.Object的实例作为lock, 去调用相关的方法。
代码示例如下:
private val lock = java.lang.Object() fun produce() = synchronized(lock) { while(items>=maxItems) { lock.wait() } Thread.sleep(rand.nextInt(100).toLong()) items++ println("Produced, count is$items:${Thread.currentThread()}") lock.notifyAll() } fun consume() = synchronized(lock) { while(items<=0) { lock.wait() } Thread.sleep(rand.nextInt(100).toLong()) items-- println("Consumed, count is$items:${Thread.currentThread()}") lock.notifyAll() }