前言#
首先必须要声明中文中间插入英文不是我的爱好,这一点别吐槽我,因为有些单词我想不到特别准备的翻译,就跟人家说读英文书和读翻译书完全是两个感觉,重点是意会,你懂得。
上次聊了聊inflater,这次来聊聊Handler,他俩绝对是我们最常用的左青龙,右白虎,屌爆了。
正文#
<h2>Handler 的作用</h2>
再熟悉的东西都要简单介绍一下,都是一种礼貌和尊敬,Handler主要是为我们提供子线程和UI线程之间进行操作上的切换,还提供了延时任务、定时功能等非常强大的功能,他还有两个好基友Looper 和MessageQueue,帮助他去实现上面的功能,这些知识大家都懂,面试必考内容就不多说了,感兴趣的朋友可以自己去查看一下相关资料。
<h2>This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... </h2>
这个熟悉的警告你还记得吗,说不得的人肯定是没走心,尤其是写过下面这段代码的人:
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.e("lzp", "我是一个屌爆了的handler" );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
相信很多人还保持着这种写法,对于右侧提出的warning视而不见,觉得无伤大雅,反正是警告,编译运行都很完美,例如我之前就是这样的,那么我希望你能够自我批评一下。
这段警告大概就是这个意思:
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
由于Handlder被声明为内部类,他可能阻止外部类被垃圾回收。如果Handler是采用新的Looper 或 MessageQueue,而不是主线程,那么就没有问题。如果Handlder是采用主线程的Looper 或 MessageQueue,你需要修正这个Handler的声明如下:声明Handler作为一个静态类;在外部类中,实例化Hander时,在他的内部也实例化一个WeakReference(弱引用),并传入他自己;确保这个外部类中的所有成员都使用WeakReference(弱引用)中的这个对象。
这段翻译是我自己翻译的,感慨我的英文水平屌爆了的同时,也直接明白了这段警告的意思:
要么给Handler创建一个新的Handler,要不就使用静态类,并且弱引用,防止对垃圾回收进行影响。
那直接创建一个新的Looper 不就完事了吗?很遗憾,Looper的构造方法是私有的(),我们无法去手动创建一个新的Looper:
看的出来Looper创建的同时,也同时创建了MessageQueue,而且绑定了相应的线程。
为什么不把构造方法公开呢?我个人觉得可能是设计者不希望开发者滥用Looper。因为Looper涉及到跨线程的问题,大家都懂的跨线程需要考虑很到安全操作,就显得比较棘手,所以开放了一个静态方法:Looper.prepare() 和 Looper.loop()配合使用,并且Handler已经满足了大部分应用场景,估计是想让开发者尽量用Handler吧,我就是小小的意淫一下设计者的想法。
在伤心和痛苦中突然发现Looper.myLooper()方法,好像抓住了一个救命稻草,于是我充满期待的做了下面的测试:
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {
Log.e("lzp", "我是一个屌爆了的handler" + getLooper().hashCode() );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.sendEmptyMessage(0);
Log.e("lzp", "UI handler:" + Looper.myLooper().hashCode() );
}
}
好尴尬,和UI线程是一样的,仔细一想很容易理解,本身我传进去的就是UI线程的Looper,得到肯定还是UI线程的Looper。
Looper.myLooper()返回的是当前线程的Looper,如果你是在线程中创建了looper,返回的就是这个线程的Looper,例如我运行了下面的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("lzp", "Main Looper:" + Looper.myLooper().hashCode());
new Thread(){
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, "屌爆了哦~", Toast.LENGTH_SHORT).show();
Log.e("lzp", "Thread Looper:" + Looper.myLooper().hashCode());
Looper.loop();
}
}.start();
}
打印的Log:
果然hashcode不一样,说明已经是不同的Looper了。
顺便贴一下Looper.prepare()的源码:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
...
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.prepare()先从ThreadLocal(有点类似于线程池,里面的线程可以复用)中找到对应的Looper,没有就创建一个新的Looper并放入ThreadLocal,对ThreadLocal不了解的朋友可以自己去百度。
那就没办法了,只能去研究第二种方法了:静态类,弱引用。
废话没有,直接看代码:
public class MainActivity extends AppCompatActivity {
private TestHandler handler = new TestHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, 2000);
}
private void handleMessage(Message msg){
Toast.makeText(this, "hahaha", Toast.LENGTH_LONG).show();
}
static class TestHandler extends Handler{
private WeakReference<Activity> mActivity;
TestHandler(Activity activity){
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) mActivity.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
}
刚才扯了那么多高大上的蛋,一看代码大概就明白差不多的意思了:
静态类不会和外部类直接引用(就是强引用),并且TestHandler和activity 的关系是弱引用,所以就不会对外部类造成垃圾回收上的影响。
上面这种写法是比较流行的写法,因为静态类无法直接声明,所以只能用静态内部类。我是把TestHandler写到BaseActivity中,并且在BaseActivity中设置一个方法handlerMessage(),给TestHandler调用,我会把自己的用法放在最后供大家下载参考。
<h2>Handler使用不当,会出现什么问题?</h2>
说了这么多,写了这么多,到底Handler使用不当会出现啥问题啊?
其实主要是内存上的问题,如果仅仅是更新了UI界面,那其实是无所谓的,但是谁也不会为了更新UI还特地去使用Handler,直接UI线程就好了。
问题就是出线程上,大部分都是配合Thread使用,下面举几个例子:
1、网络请求或者是耗时任务,成功或者失败更新UI,但是还没完成,Activity被销毁了;
2、有一个延时/定时任务,还没有触发,Activity销毁了,并且没有removeCallback。
这两个情况非常常见,Handler保持着对Activity的强引用,在任务完成之前,是无法被回收的,这就可能出现内存溢出等情况,并且Activity被回收再去更新UI很明显是没有意义的。
总结#
又到了总结的时间了,Handler最推荐的使用方法就结束了,可能过多的新技术新花样吸引了我们太多的目光,而忽略了不断的扎实自己的基础,所以在不停学习新招式的时候,别忘了练习和加强我们的基本功。
到此结束,有问题或者有讲解有误的地方欢迎批评指正,拜拜~