经过上篇文章我发现,自己写出一篇技术文章比看书、看视频还要学习的更好、更快、更深刻,毕竟你没有吃透这个技术点,你就无法详细有序的写出这个技术点的每一个环节,所以要再接再厉!
这次重新深入了解一下常用的Handler机制;虽然常用,但也有许多不为人知的细节。
一、前言
本次参考了以下几篇文章,感谢各位作者!!!
二、了解Handler
在Android体系中,UI操作并不是线程安全的,并且这些操作必须在UI线程执行,于是Android封装了一套消息封装、传递、处理机制,这就是Handler。Handler主要用于异步消息处理,涉及到的类有:Looper、MessageQueue、Message。
Looper:每一个线程只有一个Looper,每个线程在初始化Looper之后,Looper会维护好该线程的消息队列MessageQueue,用来存放Handler发送的Message;其中Loopler.looper方法是一个死循环,不断地从MessageQueue取消息,如果有消息就发给Handler处理消息,没有消息就阻塞;
Looper的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。MessageQueue:这是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue;其他非主线程,不会自动创建Looper;列队中的消息是根据消息的时间来排序的。
-
Message:消息对象,存放在MessageQueue中,一个MessageQueu可以包括多个Message;当我们需要发送一个Message时,不建议直接new Message(),Message内部保存了一个缓存的消息池,用Message.obtain()的方式来从缓存池中获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。
image.png
三、Handler发送消息的方法
//发送消息
new Handler().sendMessage(Message msg);
//延时发送消息(比如填1000表示延迟1秒)
new Handler().sendMessageDelayed(Message msg, long delayMillis);
//延时发送消息(第二个参数为:相对系统开机时间的绝对时间,填SystemClock.uptimeMillis()+1000表示延迟一秒)
new Handler().sendMessageAtTime(Message msg, uptimeMillis);
//发送带标记的空消息(内部创建了message,并设置msg.what);
new Handler().sendEmptyMessage(int what);
//延时发送带标记的延时空消息
new Handler().sendEmptyMessageDelayed(int what, long delayMillis);
new Handler().sendEmptyMessageAtTime(int what, long uptimeMillis);
//发送消息
new Handler().post(Runnable r);
//发送延时消息
new Handler().postDelayed(Runnable r, long delayMillis);
new Handler().postAtTime(Runnable r, long uptimeMillis);
- 问题来了sendMessage和post有什么区别?
通过查看源码能够发现,post的方法内也是通过sendMessage来实现消息传递的,它是发送了一个没有标记的Message,简化了消息发送、处理的过程;当我们需要通过标识来执行多个不同的动作的时候,使用sendMessage,当我们只用执行一个动作的时候使用post更加方便快捷。
四、如何在子线程中使用Handler
Handler的使用离不开Looper,在Main线程中会自动生成Looper,而子线程则需要自己新建Looper,并跟Handler绑定,才能正常使用Handler;
- 通过先调用 Looper.prepare() 在当前线程初始化一个 Looper
//初始化一个Looper
Looper.prepare();
Handler handler = new Handler();
// Handler调用完成后需要进行这一步
Looper.loop();
- 也可以通过HandlerThread在子线程中使用Handler,HandlerThread自动帮我们创建了Looper,通过getLooper()可以获得Looper
HandlerThread downloadBThread = new HandlerThread("downloadBThread");
downloadBThread.start();
Handler downloadBHandler = new Handler(downloadBThread.getLooper());
// 通过postDelayed模拟耗时操作
downloadBHandler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "下载B完成", Toast.LENGTH_SHORT).show();
mainHandler.post(new Runnable() {
@Override
public void run() {
tv_B.setText("B任务已经下载完成");
}
});
}
}, 1000 * 7);
五、Handler的泄露事件
先来看看下面的代码
public class MainActivity extends AppCompatActivity {
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
......
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//activity被执行时,被延迟的这个消息存于主线程消息队列中1分钟,
//此消息包含handler引用,而handler由匿名内部类创建,持有activity引用,
//activity便不能正常销毁,从而泄露
handler.postDelayed(new Runnable() {
@Override
public void run() {
......
}
}, 1000 * 60);
}
}
- 外部类Activity中定义了一个非静态内部类Handler,非静态内部类默认持有对外部类的引用。如果外部Activity突然关闭了,但是MessageQueue中的消息还没处理完,那么Handler就会一直持有对外部Activty的引用,垃圾回收器无法回收Activity,从而导致内存泄漏。
- 在postDelayed中,我们在参数中传入一个非静态内部类Runnable,这同样会造成内存泄漏,假如此时关闭了Activity,那么垃圾回收器在接下来的1000000ms内都无法回收Activity,造成内存泄漏。
解决方案:
- 将非静态内部类Handler和Runnable转为静态内部类,因为非静态内部类(匿名内部类)都会默认持有对外部类的强引用。
- 改成静态内部类后,对外部类的引用设为弱引用,因为在垃圾回收时,会自动将弱引用的对象回收。
避免内存泄漏的例子:
public class MainActivity extends AppCompatActivity {
//创建静态内部类
private static class MyHandler extends Handler{
//持有弱引用MainActivity,GC回收时会被回收掉.
private final WeakReference<MainActivity> mAct;
public MyHandler(MainActivity mainActivity){
mAct =new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainAct=mAct.get();
super.handleMessage(msg);
if(mainAct!=null){
//执行业务逻辑
}
}
}
private static final Runnable myRunnable = new Runnable() {
@Override
public void run() {
//执行我们的业务逻辑
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyHandler myHandler=new MyHandler(this);
//延迟5分钟后发送
myHandler.postDelayed(myRunnable, 1000 * 60 * 5);
}
}
六、Handler其他可能发生的错误
- 错误1:如果Activity被关闭,但是handler刚好还在处理消息,需要用的资源已被释放,会出现空指针异常;需要在ondestory中去remove掉我们要处理的事件。
//避免内存泄露的方法:
//移除标记的消息
new Handler().removeMessages(int what);
//移除回调的消息
new Handler().removeCallbacks(Runnable runnable);
//移除回调和所有message
new Handler().removeCallbacksAndMessages(null);
- 错误2:有时候:removeCallbacks会失效,不能从消息队列中移除;出现这情况是activity切入后台,再回到前台,此时的runnable由于被重定义,和原先的runnable并非同一个对象;给runnable加上static可以解决这个问题。