当前位置: 移动技术网 > IT编程>移动开发>Android > Android 消息机制以及handler的内存泄露

Android 消息机制以及handler的内存泄露

2019年07月24日  | 移动技术网IT编程  | 我要评论

狂澜迷情,islandfever3,大营

handler

每个初学android开发的都绕不开handler这个“坎”,为什么说是个坎呢,首先这是android架构的精髓之一,其次大部分人都是知其然却不知其所以然。今天看到handler.post这个方法之后决定再去翻翻源代码梳理一下handler的实现机制。

异步更新ui

先来一个必背口诀“主线程不做耗时操作,子线程不更新ui”,这个规定应该是初学必知的,那要怎么来解决口诀里的问题呢,这时候handler就出现在我们面前了(asynctask也行,不过本质上还是对handler的封装),来一段经典常用代码(这里忽略内存泄露问题,我们后面再说):

首先在activity中新建一个handler:

private handler mhandler = new handler() {
    @override
    public void handlemessage(message msg) {
      super.handlemessage(msg);
      switch (msg.what) {
        case 0:
          mtesttv.settext("this is handlemessage");//更新ui
          break;
      }
    }
  };

然后在子线程里发送消息:

new thread(new runnable() {
      @override
      public void run() {
        try {
          thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
          mhandler.sendemptymessage(0);
        } catch (interruptedexception e) {
          e.printstacktrace();
        }
      }
    }).start();

至此完成了在子线程的耗时操作完成后在主线程异步更新ui,可是并没有用上标题的post,我们再来看post的版本:

new thread(new runnable() {
      @override
      public void run() {
        try {
          thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
          handler handler = new handler();
          handler.post(new runnable() {
            @override
            public void run() {
              mtesttv.settext("this is post");//更新ui
            }
          });
        } catch (interruptedexception e) {
          e.printstacktrace();
        }
      }
    }).start();

从表面上来看,给post方法传了个runnable,像是开了个子线程,可是在子线程里并不能更新ui啊,那么问题来了,这是怎么个情况呢?带着这个疑惑,来翻翻handler的源码:

先来看看普通的sendemptymessage是什么样子:

public final boolean sendemptymessage(int what)
  {
    return sendemptymessagedelayed(what, 0);
  }
public final boolean sendemptymessagedelayed(int what, long delaymillis) {
    message msg = message.obtain();
    msg.what = what;
    return sendmessagedelayed(msg, delaymillis);
  }

将我们传入的参数封装成了一个消息,然后调用sendmessagedelayed:

public final boolean sendmessagedelayed(message msg, long delaymillis)
  {
    if (delaymillis < 0) {
      delaymillis = 0;
    }
    return sendmessageattime(msg, systemclock.uptimemillis() + delaymillis);
  }

再调用sendmessageattime:

public boolean sendmessageattime(message msg, long uptimemillis) {
    messagequeue queue = mqueue;
    if (queue == null) {
      runtimeexception e = new runtimeexception(
          this + " sendmessageattime() called with no mqueue");
      log.w("looper", e.getmessage(), e);
      return false;
    }
    return enqueuemessage(queue, msg, uptimemillis);
  }

好了,我们再来看post():

public final boolean post(runnable r)
  {
    return sendmessagedelayed(getpostmessage(r), 0);//getpostmessage方法是两种发送消息的不同之处
  }

方法只有一句,内部实现和普通的sendmessage是一样的,但是只有一点不同,那就是 getpostmessage(r) 这个方法:

private static message getpostmessage(runnable r) {
    message m = message.obtain();
    m.callback = r;
    return m;
  }

这个方法我们发现也是将我们传入的参数封装成了一个消息,只是这次是m.callback = r,刚才是msg.what=what,至于message的这些属性就不看了

android消息机制

看到这里,我们只是知道了post和sendmessage原理都是封装成message,但是还是不清楚handler的整个机制是什么样子,继续探究下去。

刚才看到那两个方法到最终都调用了sendmessageattime

public boolean sendmessageattime(message msg, long uptimemillis) {
    messagequeue queue = mqueue;
    if (queue == null) {
      runtimeexception e = new runtimeexception(
          this + " sendmessageattime() called with no mqueue");
      log.w("looper", e.getmessage(), e);
      return false;
    }
    return enqueuemessage(queue, msg, uptimemillis);
  }

这个方法又调用了 enqueuemessage,看名字应该是把消息加入队列的意思,点进去看下:

private boolean enqueuemessage(messagequeue queue, message msg, long uptimemillis) {
    msg.target = this;
    if (masynchronous) {
      msg.setasynchronous(true);
    }
    return queue.enqueuemessage(msg, uptimemillis);
  }

masynchronous这个异步有关的先不管,继续将参数传给了queue的enqueuemessage方法,至于那个msg的target的赋值我们后面再看,现在继续进入messagequeue类的enqueuemessage方法,方法较长,我们看看关键的几行:

message prev;
for (;;) {
  prev = p;
  p = p.next;
  if (p == null || when < p.when) {
    break;
  }
  if (needwake && p.isasynchronous()) {
    needwake = false;
  }
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;

果然像方法名说的一样,一个无限循环将消息加入到消息队列中(链表的形式),但是有放就有拿,这个消息怎样把它取出来呢?

翻看messagequeue的方法,我们找到了next(),代码太长,不赘述,我们知道它是用来把消息取出来的就行了。不过这个方法是在什么地方调用的呢,不是在handler中,我们找到了looper这个关键人物,我叫他环形使者,专门负责从消息队列中拿消息,关键代码如下:

for (;;) {
   message msg = queue.next(); // might block
   ...
   msg.target.dispatchmessage(msg);
   ...
   msg.recycleunchecked();
}

简单明了,我们看到了我们刚才说的msg.target,刚才在handler中赋值了msg.target=this,所以我们来看handler中的dispatchmessage:

public void dispatchmessage(message msg) {
    if (msg.callback != null) {
      handlecallback(msg);
    } else {
      if (mcallback != null) {
        if (mcallback.handlemessage(msg)) {
          return;
        }
      }
      handlemessage(msg);
    }
  }

1.msg的callback不为空,调用handlecallback方法(message.callback.run())
2.mcallback不为空,调用mcallback.handlemessage(msg)
3.最后如果其他都为空,执行handler自身的 handlemessage(msg) 方法
msg的callback应该已经想到是什么了,就是我们通过handler.post(runnable r)传入的runnable的run方法,这里就要提提java基础了,直接调用线程的run方法相当于是在一个普通的类调用方法,还是在当前线程执行,并不会开启新的线程。

所以到了这里,我们解决了开始的疑惑,为什么在post中传了个runnable还是在主线程中可以更新ui。

继续看如果msg.callback为空的情况下的mcallback,这个要看看构造方法:

1.
public handler() {
    this(null, false);
  }
2.  
public handler(callback callback) {
    this(callback, false);
  }
3.
public handler(looper looper) {
    this(looper, null, false);
  }
4.
public handler(looper looper, callback callback) {
    this(looper, callback, false);
  }
5.
public handler(boolean async) {
    this(null, async);
  }
6.
public handler(callback callback, boolean async) {
    if (find_potential_leaks) {
      final class<? extends handler> klass = getclass();
      if ((klass.isanonymousclass() || klass.ismemberclass() || klass.islocalclass()) &&
          (klass.getmodifiers() & modifier.static) == 0) {
        log.w(tag, "the following handler class should be static or leaks might occur: " +
          klass.getcanonicalname());
      }
    }

    mlooper = looper.mylooper();
    if (mlooper == null) {
      throw new runtimeexception(
        "can't create handler inside thread that has not called looper.prepare()");
    }
    mqueue = mlooper.mqueue;
    mcallback = callback;
    masynchronous = async;
  }
7.
public handler(looper looper, callback callback, boolean async) {
    mlooper = looper;
    mqueue = looper.mqueue;
    mcallback = callback;
    masynchronous = async;
  }

具体的实现就只有最后两个,已经知道mcallback是怎么来的了,在构造方法中传入就行。

最后如果这两个回调都为空的话就执行handler自身的handlemessage(msg)方法,也就是我们熟知的新建handler重写的那个handlemessage方法。

looper

看到了这里有一个疑惑,那就是我们在新建handler的时候并没有传入任何参数,也没有哪里显示调用了looper有关方法,那looper的创建以及方法调用在哪里呢?其实这些东西android本身已经帮我们做了,在程序入口activitythread的main方法里面我们可以找到:

 public static void main(string[] args) {
  ...
  looper.preparemainlooper();
  ...
  looper.loop();
  ...

总结

已经大概梳理了一下handler的消息机制,以及post方法和我们常用的sendmessage方法的区别。来总结一下,主要涉及四个类handler、message、messagequeue、looper:

新建handler,通过sendmessage或者post发送消息,handler调用sendmessageattime将message交给messagequeue

messagequeue.enqueuemessage方法将message以链表的形式放入队列中

looper的loop方法循环调用messagequeue.next()取出消息,并且调用handler的dispatchmessage来处理消息

在dispatchmessage中,分别判断msg.callback、mcallback也就是post方法或者构造方法传入的不为空就执行他们的回调,如果都为空就执行我们最常用重写的handlemessage。

最后谈谈handler的内存泄露问题
再来看看我们的新建handler的代码:

private handler mhandler = new handler() {
    @override
    public void handlemessage(message msg) {
      ...
    }
  };

当使用内部类(包括匿名类)来创建handler的时候,handler对象会隐式地持有activity的引用。

而handler通常会伴随着一个耗时的后台线程一起出现,这个后台线程在任务执行完毕后发送消息去更新ui。然而,如果用户在网络请求过程中关闭了activity,正常情况下,activity不再被使用,它就有可能在gc检查时被回收掉,但由于这时线程尚未执行完,而该线程持有handler的引用(不然它怎么发消息给handler?),这个handler又持有activity的引用,就导致该activity无法被回收(即内存泄露),直到网络请求结束。

另外,如果执行了handler的postdelayed()方法,那么在设定的delay到达之前,会有一条messagequeue -> message -> handler -> activity的链,导致你的activity被持有引用而无法被回收。

解决方法之一,使用弱引用:

static class myhandler extends handler {
  weakreference<activity > mactivityreference;
  myhandler(activity activity) {
    mactivityreference= new weakreference<activity>(activity);
  }
  @override
  public void handlemessage(message msg) {
    final activity activity = mactivityreference.get();
    if (activity != null) {
      mimageview.setimagebitmap(mbitmap);
    }
  }
}

以上就是对android handler 消息机制的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网