美文网首页
Android中消息机制的几个问题解释

Android中消息机制的几个问题解释

作者: dashingqi | 来源:发表于2020-09-09 00:56 被阅读0次
Android_Banner.jpg

Handler的运行机制

  • Handler:发送消息
  • Message:信息的载体
  • Looper:一个线程中有一个Looper,用于从MessageQueue中取出数据
  • MessageQueue:消息队列,存储及Handler发送的消息(这个MessageQueue是属于Looper中的)
  • ThreadLocal:线程本地缓存,线程范围内保证变量的唯一性,线程中会维护一个类似HashMap的东西,然后用ThreadLocal对象作为key,value就是要存储的值,就保证了存储数据的唯一性了。

Handler防止内存泄露

Handler导致Activity内存泄露的原因

  • 当Handler发送消息到队列中时,此时Activity finish掉了,队列中的消息依然会由handler进行处理,由于消息队列中的Message持有Handler的引用,而Handler持有Activity的引用(非静态内部类持有外部类的引用,静态的内部类不会持有外部类的引用),这样就会导致Activity不能被回收了,进而就会导致内存泄露了。

Handler定义为static的同时,为何还要用WeakReference包裹外部类的对象?

  • 因为我们需要使用外部类的成员,可以通过 "activity."获取到变量方法,如果直接使用强引用,显然会导致activity泄露。
    使用单独定义的Handler类
  • 不会造成内存泄露的,因为Handler不会持有Activity的引用。
  • 单独定义Handler,同样可以弱引用activity

防止泄露

  • 定义为静态的内部类,这样就不会持有外部类的引用(Activity)
  • 弱引用activity:通过Handler的构造方法传递Activity,在构造方法中将Activity弱引用
  • 在onDestroy()方法中 调用handler.removeCallbacksAndMessages(null):移除当前Handler的所有消息和删除回调函数。
为什么我们能在主线程中直接使用Handler,而不需要创建Looper
  • 通常认为ActivityThread就是我们得主线程,在ActivityThread的main的入口方法中 调用了Looper的prepareMainLooper()方法创建了主线程的Looper,并且调用了loop方法,所以我们在主线程就可以直接使用Handler了。
  • 在子线程单独声明一个Handler对象,如果不调用Looper.prepare()和Looper.loop()方法,前者会报错,后者不会取到消息的
Handler里面的Callback能干什么
  • Callback是一个接口里面有个抽象方法也叫做 handleMessage
  • 在dispatchMessage方法中 可以用来拦截Handler的handleMessage方法的执行,当Callback中的handleMessage返回true的时候就会拦截Handler的handleMessage方法的执行。
创建Message的最佳方式

Android 给Message设计了回收的机制,在我们使用的时候尽量复用Message,减少内存消耗。

  • Message.obtain();
  • handler.obtainMessage()
Looper死循环为什么不会导致应用卡死,会消耗系统资源吗
  • 对于Handler来说,想要在当前线程中正常工作,该线程中必须得提前准备好looper对象(唯一的)
  • 对于UI'线程来说,它的管理类ActivityThread的main方法中就提前在UI线程中为我们准备好了一个Looper对象,并且调用了looper.loop()方法,开启无限循环去取handler发送的消息。
  • 回到本问题中,对于一个线程其实就是一段可执行的代码,当线程执行完毕后,线程终止了,对于Android的UI线程(主线程)来说当然不能就这么退出了,通常就是通过一个死循环来保证UI线程不能退出。当然当没有消息的时候就会睡眠。
  • 真正会导致主线程卡死的是在生命周期方法中执行耗时的方法导致应用无响应就卡死,通常这样的耗时操作我们都是在子线程中操作。
  • 所谓的卡死无非就是UI刷新不及时,输入的事件没有及时相应,二这些都是通过消息机制来驱动的,而我们的Looper.loop虽然是一个死循环,但是真正导致卡死是上一次消息事件处理时间长,导致下一次事件没有能及时处理,实则是没有阻塞主线程而是阻塞了loop方法内部的操作,从而会发生ANR这些东西,随意耗时的操作不建议在主线程中操作,在子线程中操作这样事件能得到处理,从而不会导致阻塞取事件的操作。
Looper死循环会消耗系统资源吗
  • 针对这个问题,我们知道当消息队列中没有消息的时候,UI线程会处于睡眠的状态。
  • 深究起来就是,looper.loop()方法也是调用MessageQueue的next方法来去消息,这个next方法是一个阻塞方法,当系统没有消息的时候,会阻塞在next方法中的nativePollOnce()方法中,此时UI线程会释放掉资源。
    当再次有消息到来的时候,通过网pipe管道的写端写数据来唤醒主线程工作,
  • 所以Looper的死循环不会消耗系统资源的。
主线程的消息循环机制是什么?
  • 看见这个问题,我个人可能更偏向于去解释 在子线程通过Handler发送一个Message到主线程中去更新UI,实则当看完Activity的启动流程后,我觉得主线程的消息循环机制不是这么简单的。
  • 看下ActivityThread的main方法中又如下一行代码
public static void main(String[] args) {
        //初始化Looper
        Looper.prepareMainLooper();
        
    
        ActivityThread thread = new ActivityThread();
        // 重点分析
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        //运行loop
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  • 在上述代码中,有thread.attach(),调用该方法实则是创建Binder通道(创建了一个新的线程),该Binder是ApplicationThread,用来与AMS之间进行通信的,我们知道一个Activity启动涉及到两次跨进程通信
    • 第一次是从App进程通过Binder告诉AMS我要开启一个Activity的请求,当AMS收到这个请求后,就在系统进程中,先处理栈顶的Activity,然后在创建新的Activity,在处理栈顶的Activity的时候,需要向ApplicationThread中进行又一次跨进程通信,
    • 第二次是从系统进程到App进程,此时App进程收到这个处理请求后,会通过Handler发送一个消息到handleMessage()中(子线程到UI线程中),从而执行栈顶Activity的onPause()方法,对于创建新的Activity会发送不同Mesasge,进而回调不同生命周期方法。
  • 所以总结来说就是 当AMS接收到ActivityThread(UI线程)的请求后,会调用ApplicationThread中的Binder的方法,当收到AMS发来的请求,ApplicationThread会通过Handler来发送消息到主线程中,进而完成了一次消息循环。这也就是主线程的消息循环的基本操作。
  • 额外:当从AMS收到的请求,在App进程中进行消息的发送的时候,当一个消息的处理时间过程会影响UI的刷新率,从而可能会导致卡顿。
ActivityThread并没有继承Thread,那么在main方法中创建的Looper是存储在那个线程中呢?
  • 我们知道在当前线程中创建的Looper对象,都是放置到ThreadLocal中,来保证线程中的唯一性
  • 在启动一个APP进程的时候,实则都是从zygote进程中fork一份出来的(这样的好处就是,效率高,可以直接拿zygote进程中的一些信息,省去了初始化的一些操作)二我们ActivityThread管理的UI线程就是从zygote进程中fork出来进程的主线程的。
当发送一个延迟消息,会怎么样
  • 当发送一个延迟消息,会将它放入到MessageQueue中,而不是等到时间到了再将它放到MessageQueue中在从中取出来
  • 只不过在放的过程中,有讲究,当消息队列为空的时候会把当前消息放入到消息队列头部,在next方法内部调用了nativePollOnce阻塞当前线程,使得其休眠,当时间到了之后会 nativeWake()去唤醒线程从而取得这个消息,如果不为空的话,会拿队列中的消息的时间进行比较,找准一个位置放入。

相关文章

网友评论

      本文标题:Android中消息机制的几个问题解释

      本文链接:https://www.haomeiwen.com/subject/qrmzectx.html