美文网首页安卓
handler源码解析一

handler源码解析一

作者: crossroads | 来源:发表于2020-09-15 13:17 被阅读0次

    前提

    看的时候,要自己跟源码哦,这样比较容易理解。这里是android-30的源码

    一. 首先明白什么是内存泄漏?为什么handler会引起内存泄漏?什么是ThreadLocal?

    内存泄漏:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
    为什么: 匿名内部类有实现接口的匿名类,和继承父类的匿名类,handler就是一个匿名内部类,我们都知道匿名内部类持有外部类的引用。因此handler持有activity的引用,当Activity被finish掉了,GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,就不会去回收这个Activity,导致内存泄漏。

    // 内存泄漏的使用方式示例
            val handler = object : Handler() {
                override fun handleMessage(msg: Message) {
                    super.handleMessage(msg)
                }
            }
    

    因此,需要预防内存泄漏,
    将handler改为静态,因为静态类和方法只属于类本身,并不属于该类的对象和其他外部类的对象,所以静态内部类不会持有外部类对象。但由于要使用activity的成员变量,因此需要引用activity,若使用强引用,则会导致activity无法被回收,而使用软引用,只有内存不足才会被回收,只有弱引用,GC机制发现只有弱引用的对象或者没有引用的对象回收掉,所以使用弱引用+static就可以了。
    ThreadLocal: 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,对其他线程是隔离的。

    二. 初始化准备

    Handler()构造函数可以传递(Looper looper, Callback callback, boolean async),callback是一个接口,需要实现handleMessage方法,async默认为false,这里我们不讲这个。咱们跟踪最常用的构造函数,不传looper的那个。


    可以看到,在构造函数中,使用Looper.myLooper()获取looper,并获得looper的messageQueue赋值给handler的mQueue变量。那么关键点就是looper是怎么来的?

    那threadLocal又是什么时候赋值的呢?

    找到了,在Looper.prepare方法中,这里我们可以看到有个判断throw exception,这里说明Looper.prepare只能调用一次,因为每一个线程只能有一个looper。可以看到,Looper.prepare()初始化Looper,new Looper()初始化messageQueue。
    那主线程什么时候调用的Looper.prepare呢?追溯到Looper.prepareMainLooper(),这个方法在ActivityThread的main方法中调用,ActivityThread的main方法是整个APP的入口,至于app的启动过程这就是另一个话题了。
    捋一下逻辑,
    1》 首先app启动的时候,ActivityThread.main()调用Looper.prepare(),初始化Looper和Looper持有的messageQueue对象,通过threadLocal保存当前线程的Looper对象。2》 new handler()会从threadLocal中获取该线程的Looper对象,通过looper对象获得messageQueue

    三. 发送消息

    handler.sendMessage(Message.obtain())
    

    在发送消息前,我们需要知道MessageQueue其实是一个单链表,Message是单链表中的一个节点。具体可看他们的类结构,这里不再赘述。
    2. Message.obtain()


    从全局的池子中获得一个Message对象,在很多情况下不需要创建新的对象。可以看到返回的message对象是sPool,查找sPool,得知这是一个被回收掉的message链表的一个节点,最大长度是50。这便是享元模式的应用吧。
    3. sendMessage 和sendMessageDelayed
    直接看sendMessage(),发现它调用了sendMessageDelayed,继续
    sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
    

    注意这里的时间,使用的是 SystemClock.uptimeMillis()。
    继续

    queue.enqueueMessage(msg, uptimeMillis)
    

    我们进去这个方法,会看到有个判断语句,咱们先看第一个判断。

      Message p = mMessages;
      if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.
          msg.next = p;
          mMessages = msg;
         needWake = mBlocked;
      } 
    

    咱们假设p!=null,当前msg延迟时间是0或者延迟时间小于p的时间,可以看出mMessages就是链表的第一个节点。每次新来的msg,插入到最前面。



    接着看另一个判断,如果msg的等待时间大于消息队列中的第一个节点的时间,猜猜会怎么做?是不是要排序,保证messageQueue按照时间顺序排列。

         Message prev;
         for (;;) { // 死循环,可以理解为while(true)
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                 break;
              }
             if (needWake && p.isAsynchronous()) {
                  needWake = false;
              }
        }
         msg.next = p;
         prev.next = msg; 
    

    这样就保证了messageQueue中的message按时间顺序排列。
    我们再看一下这个send方法是不是有返回值,那这个返回值的意思是什么呢

     Returns true if the message was successfully placed in to the 
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.  Note that a
         *         result of true does not mean the message will be processed -- if
         *         the looper is quit before the delivery time of the message
         *         occurs then the message will be dropped.
    

    翻译一下就是,如果这个消息被成功放入队列就返回true。如果失败就返回false,通常失败是因为looper处理的这个消息队列退出了。注意,返回true不代表这个消息被处理了,如果looper在msg传递之前退出会导致msg被删除。

    至此,handler发送消息机制源码就分析完啦!接下来,我们将分析处理消息机制的源码。

    后记

    为什么一个线程只有一个looper?(这个是和Looper.loop有关的,可以看完源码分析二文章之后,再看这个哦!)
    https://blog.csdn.net/qq_40207976/article/details/105723076
    threadLocal简介
    https://zhuanlan.zhihu.com/p/215454357
    system.uptimeMillis
    https://blog.csdn.net/u012810020/article/details/52223976

    相关文章

      网友评论

        本文标题:handler源码解析一

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