Handler的正确打开方式

前言

handler,相信每一个Android开发者都不会陌生,在那个没有rxjava,没有AsyncTask的年代,但凡涉及到线程交互的,就会用到handler,否则你就会看到
Handler的正确打开方式_第1张图片

怎么样,是不是觉得很眼熟,很亲切,正是因为要避免上面的bug,所以我们才用到了handler作为非ui thread和ui thread之间的交互。

handler最简单的使用

创建一个Handler,然后发送message,最后通过handler的handleMessage来处理

Handler的正确打开方式_第2张图片

Handler的正确打开方式_第3张图片

通过上面两步,我们就可以做到子线程做耗时操作,然后通知主线程更新ui了,但,上面这种写法有问题,可能会引起OOM和内存泄漏,那么接下来我们就来看看,到底是为什么?

handler内存泄露

当我们把鼠标停留在创建handler的地方,AS会给我们一个提示

Handler这个类应该写成static否则它可能引起内存泄漏
由于Handler被声明为一个内部类,他可能会阻止外部类被GC回收。如果Handler正在被非主线程使用Looper或者MessageQueue,那没问题。但是如果是主线程使用Looper或MessageQueue,你需要按照以下操作来处理。将Handler作为static类;在外部类中,使用WeakReference来实例化外部类对象,并且在创建Handler的时候将外部类的实例传给Handler。

把Handler变成static我们很好理解,一个类变成了static就相当于是一个单独的类了,与外部类脱离了关系,那么WeakReference是个什么鬼呢?少侠别急,在文章末尾我会解释一下WeakReference。

代码实现

1.将Handler变成static
  private static class NoLeakHandler extends Handler{

  }
2.将外部类(这里就是activity)使用WeakReference包裹起来传给Handler
 private NoLeakHandler noLeakHander;
    private static class NoLeakHandler extends Handler{
        private WeakReference weakReference;

        //重写构造,传入外部类对象
        private NoLeakHandler(HandlerActivity activity){
            //使用WeakReference来包裹外部类的对象
            weakReference = new WeakReference<>(activity);
        }

        //处理消息
        @Override public void handleMessage(Message msg) {
            super.handleMessage(msg);
            HandlerActivity activity = weakReference.get();
            //如果activity没有销毁,则继续做以下操作
            if(activity!=null){
                // do something
            }
        }
    }
3.如果Activity已经销毁,我们应该停止所有的任务和消息
  @Override protected void onDestroy() {
        super.onDestroy();
        noLeakHander.removeCallbacksAndMessages(null);
   }

这才是我们使用创建handler的正确方式,有效的避免了内存泄漏,那么接下里我们说一下可能会导致OOM的地方。

创建Message

Handler已经创建出来了,那么必不可少的就是我们的Message,毕竟消息是要通过Message来传递的,传统的创建Message的方式很简单,new一个就完了,然后handler.sendMessage,比如:

 Message newMessage = new Message();
 newMessage.what = 1;
 handler.sendMessage(newMessage);

看似没啥问题,创建Message对象,然后发送出去啊。如果你只发送一两次,这样写是没什么问题,但是如果你要做轮询操作呢?比如隔0.1S向后台发个请求(0.1S有点夸张,就是说个意思,你懂的),而这个轮询需要很久,是不是就new了很多Message对象,大家都知道,每new一个对象,都是在堆中开辟一片空间,而这个时候如果JVM回收不及时,是不是就可能导致OOM,当然以现在的手机性能,这样导致OOM的几率很小,但是作为开发,我们要尽量避免潜在的问题。
所以,创建Message的正确方式,应该是:

 Message message = noLeakHander.obtainMessage();
 message.what = 1;
 message.sendToTarget();

为什么要这样创建?我们来看看源码
Handler的正确打开方式_第4张图片

Handler的obtainMessage方法,调用了Message的obtain方法,并且把自己传了进去

Handler的正确打开方式_第5张图片

Message的obtain方法,调用了自己的obtain空参方法,并且把传入的handler赋值给了target变量

Handler的正确打开方式_第6张图片
空参obtain

首先使用同步锁保证同时只有一个线程能够访问,如果sPool不为空,则给Message对象赋值,然后返回message对象,这个sPool是什么呢,其实就是一个消息池,Message的总数是固定的,增强了复用性。

强软弱虚四种引用

可能看到这里,大家还是会有疑问,为什么AS要让我们WeakReference呢,这样做的意义是什么?
首先我要说明的一点就是,在java中,有4中引用,分别是强引用,软引用,弱引用和虚引用

1.强引用

Object obj = new Object()
创建一个新的对象,就是一个强引用,所谓的强引用,就是即便内存不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

2.软引用(SoftReference)

如果内存空间足够,JVM就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要JVM没有回收它,该对象就可以被程序使用。

  • 2.1 软引用应用场景

例如从网络上获取图片,然后将获取的图片显示的同时,通过软引用缓存起来。当下次再去网络上获取图片时,首先会检查要获取的图片缓存中是否存在,若存在,直接取出来,不需要再去网络上获取。

  • 2.2 软引用的简单使用
MyObject obj = new  MyObject();
SoftReference softObj = new SoftReference(obj);
MyObject anotherObj = (MyObject)softObj.get();

3.弱引用(WeakReference)

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在GC线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于GC是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

4.虚引用(PhantomReference)

虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,奖这个虚引用加入引用队列。

四种引用用一张表总结

Handler的正确打开方式_第7张图片

你可能感兴趣的:(Handler的正确打开方式)