Android - Handler

Android - Handler_第1张图片

一、概览

类似于过安检,人(Handler)将行李(Message)放在安检机(MessageQueue)中,传送带(Looper)通过不断循环将行李从安检机中取出,最后由同一个人(Handler)取走处理。

二、Looper

  •  轮询器。每个线程中只有一个Looper,切换线程和消息分发,唤醒与挂起靠的是 Linux 中的 epoll 机制来实现。
  • handler构造时,可以选择是否传入looper对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为handler没有和looper建立起关联。

2.1 prepareMainLooper( )

AMS 调用 ActivityThread.main() 进而调用 Looper.prepareMianLooper() 进而调用 parper(),将主线程和 Looper 绑定,然后调用 loop() 跑起来。

2.2 prepare( )

用来将当前线程和Looper绑定。先通过 sThreadLocal.get() 看有没有 value(这里是主线程是否绑定过Looper),value不为 null 就报错不让再次赋值保证了 Looper 的唯一性,value为 null 就调用 sThreadLocal.set() 新建一个 Looper 对象(构造函数中会创建MessageQueue对象)传入赋值实现了 Looper 和主线程绑定

Android - Handler_第2张图片

2.3 sThreadLocal

Looper 中创建了一个 static final 的 ThreadLocal 成员变量。

 

2.3.1  Thread

该类中有一个成员变量 threadLocals 它是一个空的 ThreadLocalMap。即每个线程都有自己的ThreadLocalMap,当不同线程访问代码被 ThreadLocal 操作的是它们各自持有的数据。

2.3.2 ThreadLocal

在一个线程中可以创建多个 ThreadLocal 对象(作用是当作key存入value),不管是调用 ThreadLocal 对象的 get() 还是 set() 内部都会先获取当前线程Thread(这里是主线程),然后获取 Thread 所持有的 ThreadLocalMap(即Thread的成员变量threadLocals),以该 ThreadLocal 对象(这里是sThreadLocal)为 key 往里面存取 value(这里是Looper)。由此通过 sThreadLocal 将 Looper 和主线程的绑定。

Android - Handler_第3张图片

 Android - Handler_第4张图片

2.3.3 ThreadLocalMap

是 ThreadLocal 的静态内部类。Entry 继承于 WeakReference,key 是 ThreadLocal 类型,value是 Object 类型。

2.4 myLooper( )

返回当前线程绑定的 Looper 对象,如果未绑定过则返回null。

 

2.5 loop( )

拿到当前线程的 Looper 对象(此处为主线程),通过无限 for 循环保证了不会因为代码执行完而退出 main() 函数导致程序(进程)结束。loopOnce()中调用 MessageQueue.next() 从队列中取出 Message,再调用 Message.target.dispatchMessage() 分发给发送这个 Message 的 Handler 处理(即发送和处理该 Message 的是同一个 Handler)

Android - Handler_第5张图片

 Android - Handler_第6张图片

四、Handler

负责发送和接收(处理)Message。Handler 在构造时可以选择是否传入 Looper 对象,若不传则默认从当前线程获取,若取不到则会抛出异常。这就是为什么在子线程里不能简单用消息循环的原因,因为需要 Handler 和 Looper 绑定。

sendMessageAtTime( ) 发送消息的方法非常多  (post/send) 最终调用的都是这个,uptimeMillis即系统开机时间uptimeMillis和传入的延迟时间 delayMillis 相加。
handlerMessage( ) 由外部来重写该方法,以此来处理收到的Message。
dispatchMessage( ) 用于分发消息,判断Message的callback,不为null调用Runable(),为null调用handleMessage()。

Android - Handler_第7张图片

4.1 Handler( )

Handler构造时需要传入一个 Looper 实现了 Handler 和 Looper 绑定,通过唯一的 Looper 拿到唯一的MessageQueue,使得 Handler 和 MessageQueue 绑定(即同一个 Handler 在不同线程中发送 Message 是发到同一个 MessageQueue 中),由于该 MessageQueue 所属的 Looper 所绑定的是主线程,因此子线程中发送的 Message 最终是在主线程中处理

Android - Handler_第8张图片

4.2 sendMessageAtTime( )

发送消息的方法非常多(各种post/send)最终调用的都是这个,在确保存在 MessageQueue 后就调用 enqueueMessage() 将 Message 入列。

Android - Handler_第9张图片

4.3 enqueueMessage( )

Handler先将自己赋值给 Messgae 的 target 实现了 Handler 和 Message 绑定(即发送和处理该 Message 的是同一个 Handler),然后调用 MessageQueue.enqueueMessage() 将 Message 存入队列中

Android - Handler_第10张图片

4.4 dispatchMessage( )

判断 Message 携带的是 callback 还是对象,是 callback 就直接运行,是对象就回调到 handleMessage() 中让我们在创建 Handler 对象的地方重写该方法处理。

Android - Handler_第11张图片

五、MessageQueue

消息队列。每个线程中只有一个MessageQueue,存放 Handler 发送来的Message。是单链表结构(非线性非顺序的物理结构。采用见缝插针的存储方式不要求内存连续,也不需要随机访问(只需要取头值),靠 Message中 的 next 指向下一个,随机存储顺序访问)。

5.1 Looper.mQueue

Looper构造是私有的,其中会创建 MessageQueue 并赋值给 final 修饰的成员变量mQueue,因此 MessageQueue 也是唯一。 

 

5.2 enqueueMessage( )

enqueueMessage()里的同步锁让 Handler 在不同线程中发送过来的 Message 同步入列,并通过形参 when 来对队列中的 Message 进行排序。形参 when 给 Message 加了时间戳排序。

Android - Handler_第12张图片

5.3 next( )

next()里的同步锁与 enqueueMessage() 里的同步锁相互协作,即便是在子线程发消息在主线程取消息,实现了消息入列和出列的互斥性(不能同时进行),保证了有序性和线程安全

  • 什么消息都没有的时候,执行 nativePollOnce() 处于休眠状态节省CPU给其它地方。(早期使用的是 wait/notify,Android2.3后来使用 epoll 机制为了可以同时处理 native 消息。)
  •  if(msg != null && msg.target == null) 说明是消息屏障,立马轮询优先执行异步消息。
  • 当 if(msg != null) 为普通消息,根据时间戳取头部执行。
  • 当非延时的 Message 都执行完的时候(主线程没有重要的事情做的时候),通过 for 循环处理 idleMessage。

Android - Handler_第13张图片

Android - Handler_第14张图片

 六、Message

可以携带需要的数据在线程间传递。根据优先级分为:异步消息 > 普通消息 > idleMessage。

消息屏障 判断依据 msg.target == null。发送屏障消息后会发送异步消息(判断依据msg.isAsynchronous),获取的是当前时间戳,会加入到队列头部,由于优先刷新UI。
idleMessage(不重要消息) 当所有延时为0的消息都处理完后,主线程没有重要的事情做的时候(即不影响主线程的时候),才会去执行idleMessage。例如GC、AMS管理Activity的 stop destroy。
Message的字段 说明
what: Int 唯一标识。
target: Handler 绑定Handler(该 Message 是由同一个 Handler 发送和处理的)。
data: Bundle 携带 Bundle 数据。

arg1: Int

arg2: Int

携带 Int 数据。
obj: Object 携带可序列化数据。
callback: Runnable 携带 Runnable 数据(Handler的post()就是将一个Runnable对象复制过来封装为Message对象)。
when:Long 时间戳。
next: Message 指向下一个节点(Message 通过 next 字段指向下一个Message,从而串联成队列。enqueueMessage()、recycleUnchecked()、obtain() 操作的就是 next 的指向)。

6.1 Message.sPool

回收后用来复用的 Message,它是 static 的。MAX_POOL_SIZE = 50 缓存池最多存50个,超过就会 new 了。sPoolSize = 0 用来记录池中数量,缓存一个就+1取出一个就-1。spoolSync用来做同步锁。

Android - Handler_第15张图片

6.2 obtain( )

构建 Message 实例,可以通过 new 关键字,推荐通过 obtain() 方法。APP中会有大量管理事件需要通过 Handler 来发送消息(60Hz屏幕光UI刷新每秒就有60个Message更别其它提系统服务了),使用缓存池为了避免频繁创建销毁 Message 对象内存抖动造成卡顿。

Android - Handler_第16张图片

6.3 recycleUnchecked( )

使用完的 Message 会被回收,将字段携带的数据都清除掉后,通过 next 将下一个节点指向当前 sPool,再将自己赋值给sPool,这样就像该 Message 插入到了缓存池队列头部。

Android - Handler_第17张图片

七、使用步骤

  • 在主线程里构造 handler 并重写 handleMessage( ) 方法;
  • 在子线程里构造 message,并使用 handler 将 message 发送出去;
  • 在主线程里的 handleMessage( ) 方法中判断是哪个 message 然后做相应处理。
Activity {
    private val updateText = 123    //定义Message唯一值
    //【第一步】创建一个全局的Handler并重写handleMessage()
    private val handler = object : Handler(Looper.getMainLooper()) {
        //【第三步】处理消息
        override fun handleMessage(msg: Message) {
            when (msg.what) {    //判断是哪个Message
                updateText -> {
                    msg.obj    //获取携带的数据
                }
            }
        }
    }
    //【第二步】创建Messgae并发送
    fun show() {
        val str = "一条消息"
        //方式一:直接新建
        val msg1 = Message()
        msg1.what = updateText
        msg1.obj = str
        handler.sendMessage(msg1)
        //方式二:避免频繁创建销毁Message推荐使用obtain()
        val msg2 = Message.obtain()
        val msg3 = handler.obtainMessage()  //相对于上一个,会自动将target字段设置为这个handler
        val msg4 = handler.obtainMessage(updateText, str)   //相对于上一个,构造里就能携带what和obj字段
        handler.sendMessage(msg4)
        //方式三:利用post在子线程中更新UI,利用postDelayed延时
        handler.postDelayed({
            //此处执行在Runnable中
        }, 2000)    //延迟2秒
    }
}

八、内存泄漏风险(Java)

Kotlin无影响的原因详见闭包讲解

8.1 原因

  • 非static的内部类或匿名内部类会持有外部类的引用,我们经常在一些内部类中显示跳转activity的时候,给Intent赋值的时候,第一个参数会写外部类名.this ,这就是持有外部类的引用的很好表现。 同样,其他地方需要用到这个内部类的时候,也不能是直接new出来,因为为非static的,必须先通过new出外部类才能。
  • 那么现在的情况就是,这个非static的handler内部类,无论是否是匿名的,便会持有外部的activity的引用。在Activity finish之后,若此时消息队列中有未处理完的Message(特别是带有延时的),那么Handler也仍然存在(在enqueueMessage()的时候Handler被赋值给了Message.target,所以Message持有Handler),由于Handler中持有Context的引用,那么Activity就无法正常回收(GC可达性分析),Activity中所有占内存的东西就成了内存泄露。

8.2 解决

  • 方式一:被static修饰后调用便不需要外部类的实例,但是总不能把Activity中的控件全static才能操作,我们通过使Handler持有Activity的一个弱引用来解决这个问题,直接持有Activity的话,我们便与之前的匿名内部类直接持有外部类的引用没区别了,而持有了弱引用,在Activity有用的情况下,其会被AMS持有强引用,GC不会回收,而当其finish了,便没有强引用对象持有了,此时GC时便会回收该Activity。
  • 方式二:在 onDestroy() 中调用 handler.removeCallbackAndMessage() 也可以避免内存泄漏
public class MainActivity extends Activity {
    private MyHandler mMyHandler;
    //自定义一个Handler
    private static class MyHandler extends Handler {
        private WeakReference mActivityWeakReference;
        //构造中传入当前Activity并使用弱引用包装
        public void MyHandler(MainActivity activity) {
            mActivityWeakReference = new WeakReference<>(activity);
        }
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        MainActivity mainActivity = mActivityWeakReference.get();
        if (mainActivity != null) {
            switch (msg.what) {
                case 0:
                    //TODO...
                    break;
            }
        }
    }
}

九、在子线程中创建Handler(HandlerThread)

        要在子线程中创建 Handler 就在构造的时候传入子线程中的Looper,进而需要拿到子线程的 ThreadLocal,进而需要拿到子线程对象,因此子线程不能写成匿名内部类,需要定义成一个类。在类中重写 run() 方法调用 Looper.prepare() 创建 Looper 再调用 looper.loop() 将循环跑起来,然后提供一个 getLooper() 方法供外部获取。外部先创建线程对象,调用 start() 跑起来就初始化了Looper,然后通过获取 getLooper() 的在创建 Handler 的时候传入。注意:子线程在 run() 中创建Looper,外部创建 Handler 时是在主线程中 getLooper() 传入的,可能为null,主线程拿子线程结果存在多线程并发问题。使用 sleep() 存在性能问题,要使用同步锁机制。

        实际开发中不会手写,而是使用HandlerThread。Looper在 run( ) 中创建在 getLooper( ) 中获取,两个方法中的同步代码块使得创建和获取互斥,保证了获取前 Looper 已经被初始化。

9.1 getLooper( )

获取 Looper。同步代码块中的判断用来确保获取前 Looper 已经被初始化,否则释放锁让线程进到 run( ) 方法的同步代码块中创建Looper。

Android - Handler_第18张图片

9.2 run( )

创建 Looper 并循环起来。同步代码块保证了在 getLooper( ) 中获取的安全性。可能有很多地方等着获取该 Looper 来创建 Handler,因此要 notifyAll() 全部唤醒。

Android - Handler_第19张图片

9.3 使用代码

val handlerThread = HandlerThread("demo")   //创建HandlerThread子线程
handlerThread.start()   //启动线程
val handler = Handler(handlerThread.looper) { msg ->    //创建Handler
    //此处为handleMessage
    when (msg.what) {
        123 -> {}
        else -> {}
    }
    false
}
handler.sendEmptyMessage(123)   //发送消息

面试

.1 Handler、Loop、MessageQuee关系?

一个线程对应一个 Looper 对应一个 MessageQueue 对应无数个 Handler。

.2 非UI线程真的不能操作View吗?

可以,报错的意思是“只有创建视图层次结构的原始线程才能访问其视图”,界面一般是主线程进行绘制的,所以界面的更新也就一般都限制在主线程中。Android系统只是限制了必须在同个线程内进行 ViewRootImpl 的创建和更新这两个操作。ViewRootImpl 在初始化的时候会将当前线程保存到 mThread,在后续进行 UI 更新的时候就会调用 checkThread() 方法进行线程检查,如果发现存在多线程调用则直接抛出以上的异常信息(哪个线程创建的view哪个线程才能更改)。

public void click(View view) {
    new Thread(new Runnable() {
        public void run() {
            Looper.prepare();
            boolean b1 = Looper.myLooper() == Looper.getMainLooper();   //fale,当前线程不是主线程
            Toast.makeText(Drawer99_Test_Activity.this,b1+","+b2,Toast.LENGTH_LONG).show();
            Looper.loop();
        }
    }).start();
}

.3 为什么不用wait/notify而用epoll?

早期使用的是 wait/notify,Android2.3后来使用 epoll 机制为了可以同时处理 native 消息。

.4 View.post() 和 Handler.post()的区别?

View.post() 最终调用的是 Handler.post()。

.5 如何进行线程切换的?

子线程持有的 Handler 如果绑定到的是主线程的 Looper (Handler创建的时候需要传入Looper) 的话,那么子线程发送的 Message 就可以由主线程来消费,以此来实现线程切换,执行 UI 更新操作等目的。

.6 主线程 Looper.loop() 为什么不会导致 ANR?

loop() 循环本身不会导致 ANR,会出现 ANR 是因为在 loop 循环之内 Message 处理时间过长,无法处理其他事件导致。让程序不退出的话,写一个死循环,那么main方法中的代码永远不会执行完,这样程序就不会自己退出了。正是因为Looper.loop方法这个死循环,它阻塞了主线程,所以我们的app才不会退出。本质上Android就是事件驱动的程序,界面刷新也好,交互也好,本质上都是事件,这些事件最后通通被作为了Message发送到了MessageQueue中。由Looper来进行分发,然后在进行处理。也就是我们的Android程序就是运行在这个死循环中的,一旦这个死循环结束,app也就结束了。

.7 为什么 UI 体系要采用单线程模型?

UI控件不是线程安全的,并发访问会造成不可预期的状态,上锁会变得复杂低效,还会阻塞某些进程的执行。

你可能感兴趣的:(Framework,android)