项目地址: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发生了泄漏,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这个项目还处于测试阶段,不过,本人会在近期开源到Jcenter,欢迎不怕踩坑的同窗踊跃尝试。