Handler+Thread的一种封装尝试RxTask(上篇)

项目地址:github.com/AcgnCodeMon…java

  提及handler,作安卓开发的同窗应该都很熟悉。handler在咱们平常开发中,主要用于子线中刷新ui,由于一般状况下,安卓系统是不容许咱们在子线程中直接操做ui的,这点,你们在才开始学习安卓的时候,想必也必定听老师讲过,甚至有时候一不注意,可能也会在子线程中刷新ui,以致于程序崩溃。至于为何不能在子线程刷新ui以及handler的通讯原理,这里不是本文的重点,有兴趣的同窗,能够本身查阅相关资料。git

  咱们先来看看传统的handler的用法,首先是这种:github

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage (Message msg) {
            super.handleMessage(msg);
            Log.e("Handler", "Message");
            tv.setText("handler改变了控件文本");
            iv.setImageBitmap(mBitmap);
        }
    };
    new Thread(new Runnable() {
                    @Override
                    public void run () {
                        try {
                            Thread.sleep(TimeConfig.SLEEP_TIME);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        mHandler.sendEmptyMessage(0);
                    }
                }).start();
复制代码

  这种写法,才学习安卓的同窗们必定常常用到,简单粗暴。就是直接采用内部类对象的方式实例化一个handler,而后在子线程中经过这个handler发送对应消息进行ui操做。固然,稍有经验的同窗都知道这种写法会形成内存泄漏,由于java中内部类(包括匿名内部类)对象会隐式的持有外部对象的引用,这样,若是耗时操做未完成以前咱们退出了当前activity,就会形成activity由于被handler引用而没法回收。异步

  固然,稍有经验的同窗们必定看到过别人说过必定要使用静态内部类写法,例如:ide

private MyHandler mHandler2;
   @Override
protected void onCreate (@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        tv = findViewById(R.id.tv);
        iv = findViewById(R.id.iv);
        mHandler2 = new MyHandler(this);
        new Thread(new Runnable() {
                    @Override
                    public void run () {
                        try {
                            Thread.sleep(TimeConfig.SLEEP_TIME);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        mHandler2.sendEmptyMessage(0);
                    }
                }).start();
    }
 private static class MyHandler extends Handler {
        private WeakReference<HandlerActivity> mReference;

        public MyHandler (HandlerActivity activity) {
            mReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage (Message msg) {
            super.handleMessage(msg);
            Log.e("Handler2", "Message");
            if (mReference.get() == null) {
                return;
            }
            mReference.get().tv.setText("handler2改变了控件文本");
            mReference.get().iv.setImageBitmap(mReference.get().mBitmap);

        }
    }
复制代码

  那么这两种写法到底效果如何呢?让咱们来运行一下,并用AS自带的内存分析,来看下结果。假设activity中有个耗时30秒的异步任务,任务完成后经过handler发送消息进行ui刷新,为了让内存变化更明显,咱们在activity中声明并实例化一个加载了大图的bitmap,咱们反复开启退出这个界面三次。首先看第一种写法的内存图: 学习

  能够看到,随着咱们反复开启关闭这个activity,应用的内存占用呈阶梯式上涨,咱们完成操做后开始不停的用AS的GC进行内存回收,发如今一段时间内内存变化几乎为0,在过了一段时间后GC有了效果,内存占用一样开始呈阶梯式降低,最后回归未开启界面的水平。

  中间内存没法回收很好理解,由于activity发生了泄漏,bitmap没法被虚拟机回收(bitmap没有作recycle操做),可是为何后面内存又能成功回收了呢?按照以前的想法,内存应该是一直没法回收的吧?其实否则,经过上面的代码咱们能够看出,咱们的子线程作了一个耗时操做(30秒),在这30秒内,咱们确实是隐式持有这activity,形成其没法回收,可是,任务执行完毕后,咱们用handler发送完消息,执行完毕后,咱们就已经再也不持有activity了,这时泄漏的activity也就能够顺利回收了,经过下面的时间轴能够证实咱们的观点,第一次能够回收的点,基本上就是第一次打开activity并开启任务后过了30秒,后面同理,因此才会出现内存阶梯式降低。测试

  也就是说,传统写法,确实会形成在子线程执行期间activity没法被回收,那么下面让我来看看使用静态内部类+弱引用的方法,会不会有效呢?ui

  惊呆了有木有?童话里都是骗人的,说好的用静态内部类不会内存泄漏呢?上图和一图一模一样,并无什么太大的区别,难道是网上其余大佬在胡说?this

  并非,只是咱们的代码有问题而已,的确,咱们的handler确实使用了静态内部类+弱引用,可是咱们忽略了一个问题,上面说过,匿名内部类也会隐式持有外部对象的。而此次咱们的问题就出在thread上,是的,咱们采用了匿名内部类的方式声明了一个子线程。而就是这个线程形成了,activity一直被子线程持有没法回收,直到子线程执行完毕。spa

  意思是说,咱们用子线程也得像handler那样用静态内部类+弱引用?感受整我的都很差了,有木有,静态内部类太麻烦,弱引用用起来更是麻烦,那么有没有什么解决方法呢?

固然是有的了。

  既然子线程和handler都容易形成activity泄漏,没法被回收,咱们为何不本身封装一下,在适当的时候切断子线程,handler和activity的关系呢?

  通过不断尝试,首先使用一个自定义类Emitter来持有handler,同时声明一个自定义类TaskCallable(实现自Callable接口)来实现子线程功能,而后建立一个自定义类Task来协调和调度Emitter及TaskCallable,以此来完成子线程和handler的交互。同时,引入线程池机制,改善处处new thread的low比作法,建立自定义类RxLifeList用于绑定activity的生命周期,这样就能够在activity的onDestroy方法被调用时及时的切断对activity的引用。

  说了那么多,想法很美好,实际效果若是呢?先上图再说:

  这里的逻辑和上面两个例子是同样的,不一样之处在于使用了本身封装的类进行的操做,能够看到,退出界面后GC几乎就能当即顺利的回收掉activitiy了(不要问我为何仍是呈阶梯式降低,我也没想通,QAQ),代码以下:

public class TestRunnable extends TaskCallable {

    @Override
    public boolean run (Emitter emitter) throws Exception {
        Thread.sleep(TimeConfig.SLEEP_TIME);
        Log.e("TestRunnable","执行完毕!");
        return true;
    }
}

private RxLifeList mBindLife;


    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBindLife = new RxLifeList();
        iv = findViewById(R.id.iv);
    }
    @Override
    protected void onDestroy () {
      if (mBindLife != null) {
            mBindLife.onDestroy();
        }
        super.onDestroy();
    }
    //子线程任务部分
            RxExecutor.getInstance()
                        .executeTask(new TestRunnable(),
                                new Task(true) {
                                    @Override
                                    public void bindLife (RxLife rxLife) {
                                        super.bindLife(rxLife);
                                        mBindLife.add(rxLife);
                                    }

                                    @Override
                                    public void onError (Exception e) {
                                        super.onError(e);
                                        toast(e.getMessage() + "\n解绑handler,再也不回调!");
                                    }

                                    @Override
                                    public void onFinished () {
                                        super.onFinished();
                                        tv.setText("handler改变了控件文本");
                                        iv.setImageBitmap(mBitmap);
                                        toast("绑定生命周期的任务执行完毕!");
                                    }
                                });
复制代码

  不要吐槽代码风格,和命名。没错,我就是不要脸的抄袭rxJava的写法,-_-

这篇就到这里,下篇文章将重点讲解RxTask的使用方法,及优点。

  目前,RxTask这个项目还处于测试阶段,不过,本人会在近期开源到Jcenter,欢迎不怕踩坑的同窗踊跃尝试。

相关文章
相关标签/搜索