Handler
Handler初识
- 概念:Handler:是一个消息分发对象,进行发送和处理信息,且其通信是单向;
- 作用:调度消息,切换任务线程;在子线程中不允许访问UI线程,而Handler就是解决子线程无法访问UI线程的问题,来减轻UI线程的压力。
- 常用的四个类:
- Handler:将Message对象发送到MessageQueue中,同时将自己的引用赋值给Message
- Looper:将Message对象从MessageQueue中取出,并将其交给dispatchMessage(Message)方法
- MessageQueue:消息队列(先进先出),负责Message的插入和取出,是有序的,其排序依据是Message的相对时间when字段。
- Message:消息对象
Handler的工作流程
Looper就像传送带传送消息,handler通过send/post方法,将货物送上传送带,这些货物就是message,排成messageQueue。当Looper.loop()调用,就是给传送带插上了电,无休止地转动,当传送到那一头了,就通过handler.dispatch()方法传递给需要货物的地方。
Handler发送消的方法:
Post(Runnable)
- 创建一个工作线程,实现 Runnable 接口,实现 run 方法,处理耗时操作
- 创建一个 handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作
new Thread(()->{
handler.post(()->{
textView.setText("111");
});
}).start();
sendMessageQueue(Message)
- 创建一个工作线程,继承 Thread,重新 run 方法,处理耗时操作
- 创建一个 Message 对象,设置 what 标志及数据
- 通过 sendMessage 进行投递消息
- 创建一个handler,重写 handleMessage 方法,根据 msg.what 信息判断,接收对应的信息,再在这里更新 UI。
new Thread(()->{
Message message = Message.obtain();
//自定义标识,从message中取内容时,可以先对标识进行判断
message.what = 0;
message.obj = "111";
handler.sendMessage(message);
}).start();
Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
Log.d("TAG", "handleMessage: " + msg);
textView.setText((String) msg.obj);
break;
case 1:
Log.d("TAG", "handleMessage: " + msg.obj);
textView.setText((String) msg.obj);
break;
default:
break;
}
}
};
内存泄漏
概念
程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
简单使用的问题
- 内存方面:Handler 在 Activity 中作为一个匿名内部类来定义,它的内部持有来自 Activity 的实例。当 Activity 被用户关闭时,因为 Handler 持有了 Activity 的引用,就造成了 Activity 无法被回收,从而导致了内存泄漏。
- 异常方面:当 Activity finish 时,在 onDestroy 方法中释放了一些资源。此时 Handler 执行到 handlerMessage 方法,但相关资源已经被释放,从而引起空指针的异常。
问题解决
- 如果使用sendMessage并在Handler内部重写了handlerMessage方法,则需要在方法中添加TryCatch
- 如果使用post方法,则需要在Runnable方法中添加TryCatch
Handler的进一步使用,解决内存泄漏
private static class MyHandler extends Handler {
private final WeakReference<Activity> mActivityReference;
MyHandler(Activity activity) {
this.mActivityReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = (MainActivity) mActivityReference.get(); //获取弱引用队列中的activity
switch (msg.what){
case 0:
Log.d("TAG", "handleMessage: " + msg);
if (msg.obj instanceof Person) {
activity.textView.setText(((Person) msg.obj).getName());
} else {
activity.textView.setText((String) msg.obj);
}
break;
case 1:
Log.d("TAG", "handleMessage: " + msg.obj);
activity.textView.setText((String) msg.obj);
break;
default:
break;
}
}
}
记得在onDestroy中销毁
@Override
protected void onDestroy() {
super.onDestroy();
//避免activity销毁时,messageQueue中的消息未处理完;故此时应把对应的message给清除出队列
//handler.removeCallbacks(postRunnable); //清除runnable对应的message
//handler.removeCallbacksAndMessages(null);
handler.removeMessages(0); //清除what对应的message
}
Handler调用流程
sendMessage方法
sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,携带者传来的message与Handler的mQueue一起通过enqueueMessage进入队列了。
post
通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。
enqueueMessage()源码分析
摘自知乎大佬的分析:https://www.zhihu.com/question/19703357
boolean enqueueMessage(Message msg, long when) {
// 检查当前 msg 的 target 是否为空
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// msg 如果正在被执行,就抛出异常
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
// 在 quit() 方法中,mQuitting 会被设置为 true
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// 标记当前的 msg 正在执行
msg.markInUse();
// 设置 msg 的 when 为传进来的 when 参数,when 是 Message 想要被执行的时间
msg.when = when;
// 得到当前消息队列的头部消息
Message p = mMessages;
boolean needWake;
// 当前消息队列为空,新消息的触发时间为 0,或者新消息的触发时间早于消息中第一条消息的触发时间
// 则将新消息插入到队列的头部,作为当前消息队列的第一条消息
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 将当前消息的下一条消息指向头部消息
msg.next = p;
// 头部消息修改为当前消息
mMessages = msg;
// 当阻塞时,需要唤醒
needWake = mBlocked;
} else {
// 将新消息插入到当前消息队列当中,(不是头部)
// 通常我们不必唤醒事件队列,
// 除非队列头部有消息障碍,并且消息是队列中最早的异步消息。
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 开始循环便利消息队列,比较新消息和队列中消息的 when(触发事件)的值,将新消息插入到适当位置
for (;;) {
// 循环第一次遍历时,将当前队列中的头部消息赋值给 prev
prev = p;
// p 指向队列中的第二个消息
p = p.next;
// 如果下一个消息为空,或者新消息的触发时间早于下一个消息,找到了要插入的位置,退出循环
if (p == null || when < p.when) {
break;
}
// needWake 为 true,并且 下一条消息是异步的,则不需要唤醒。
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 将新消息插入到 p 之前,头消息之后。
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 如果需要唤醒,调用 nativeWake 方法去唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
Demo测试
public class MainActivity extends AppCompatActivity {
private TextView textView;
private MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
handler = new MyHandler(this);
textView.setText("0%");
button.setOnClickListener(v -> {
//开启一个子线程,用于模拟耗时操作
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
//自定义标识,从message中取内容时,可以先对标识进行判断
message.what = 0;
try {
Thread.sleep(500);
message.obj="20%";
handler.sendMessage(message);
Thread.sleep(500);
message.obj="40%";
handler.sendEmptyMessage(0);
Thread.sleep(500);
message.obj="60%";
handler.sendEmptyMessage(0);
Thread.sleep(500);
message.obj="80%";
handler.sendEmptyMessage(0);
Thread.sleep(500);
message.obj="100%";
handler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
/* new Thread(()->{
handler.post(()->{
textView.setText("111");
});
}).start();*/
/*new Thread(()->{
Message message = Message.obtain();
message.what = 0;
message.obj = "111";
handler.sendMessage(message);
}).start();*/
});
}
/* //内存泄漏
Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
Log.d("TAG", "handleMessage: " + msg);
if (msg.obj instanceof Person) {
textView.setText(((Person) msg.obj).getName());
} else {
textView.setText((String) msg.obj);
}
break;
case 1:
Log.d("TAG", "handleMessage: " + msg.obj);
textView.setText((String) msg.obj);
break;
default:
break;
}
}
};*/
private static class MyHandler extends Handler {
private final WeakReference<Activity> mActivityReference;
MyHandler(Activity activity) {
this.mActivityReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = (MainActivity) mActivityReference.get(); //获取弱引用队列中的activity
switch (msg.what){
case 0:
Log.d("TAG", "handleMessage: " + msg);
if (msg.obj instanceof Person) {
activity.textView.setText(((Person) msg.obj).getName());
} else {
activity.textView.setText((String) msg.obj);
}
break;
case 1:
Log.d("TAG", "handleMessage: " + msg.obj);
activity.textView.setText((String) msg.obj);
break;
default:
break;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeMessages(0); //清除what对应的message
}
}
- 在上面的使用中,会发现我们在创建Message时,通过Message.obtain()的方法来创建的,这也是官方推荐我们采用的,原因在于其是从MessagePool(消息池)中获取的,省去了我们new Message()申请的内存开销;
- 我们先了解一下sendMessage和sendEmptyMessage的区别
- sendMessage(Message)其传递的是一个Message对象
- sendEmptyMessage(int)则是传递标识符,且创建的是一个带有标识符的空的Message
- 按照sendEmptyMessage的方法,按理说TextView的值不会改变,但实际上为什么会改变呢。这里就要说到Handler的工作原理了,在enqueueMessage()源码分析中我们也看到了其中间有一个for(;;)的操作,这里是一个死循环,所以我们在每次更新Message对象的值时,由于循环的缘故,所以会再次显示Message中的值,故而TextView的值会变化。这里可以打印Log来观察Message对象的变化。
问题
摘选自:Android消息机制Handler原理分析 - 掘金 (juejin.cn)
-
为什么主线程不会因为Looper.loop()里的死循环卡死
主线程确实是通过Looper.loop()进入循环状态,因为这样主线程才不会像我们创建的一般线程一样,当可执行的代码执行完毕后,线程生命周期就被终止了。
在主线程中MessageQueue中没有可处理的消息是,便阻塞在MessageQueue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到新消息达到,所以主线程大多时候都处于休眠状态,并不会消耗大量CPU资源
这里采用的是Linux的epoll机制, 是一种IO多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪,则立刻通知相应程序进行读或写操作拿到最新的消息,进而唤醒等待的线程。
-
post和sendMessage两类发送消息的方法有什么区别?
post一类的方法发送的Runable对象,但是其最后还是会被封装成Message对象,将Runable对象赋值给Message对象中的callBack变量,然后交由sendMessageAtTime()方法发送出去,在处理消息时,会在dispatchMessage()方法里首先被handleCallback(msg)方法执行.实际上就是执行了Message对象里Runable对象的run方法。 而sendMessage一类的方法发送的直接是Message对象,处理消息是,在dispatchMessage()里优先级会低于handleCallback(msg)方法,是通过自己重写的handleMessage(msg)方法来处理的。
-
为什么要通过Message.obtain()方法获取Message对象?
obtain()方法可以从全局消息池中得到一个空的Message对象,这样可以有效的节省系统资源。同时通过obtain各种重载方法还能获得一些Message拷贝,或对Message对象进行初始化。
-
Handler实现发送延迟消息的原理是什么
我们常用的postDelayed与sendMessageDelayed来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过sendMessageAtTime()->enqueueMessage->queue.enqueueMessage这一系列方法将消息插入MessageQueue中,所以并不是延迟发送消息,而是直接发送消息,在借助MessageQueue的设计来实现消息的延迟处理。
消息延迟处理的原理涉及MessageQueue的两个静态方法next()和enqueueMessage()通过native方法阻塞一段时间等到消息的执行时候在取出消息
-
同步屏障SyncBarrier是什么?有什么作用
在一般情况下,同步和异步消息处理没有什么不同,只有在设置了同步屏障消息后才会有差异,同步屏障消息的target为null,在messageQueue中如果当前消息是一个同步屏障消息,则会跳过后面所有同步消息,找到第一个异步消息来处理。但是开发者调用不了,在ViewRootImpl的UI测绘流程中有体现
-
IdleHandler是什么?有什么用?
当消息队列中没有需要处理的消息时调用(MessageQueue此时可能还有消息,但是不需要处理),会调用此方法,用以监听主线程空闲状态
-
为什么非静态类的Handler导致内存泄漏
首先非静态类,匿名内部类,局部内部类都会隐式的持有外部类的引用,也就时说在Activity中创建的Handler会持有Activity引用。
当我们在主线程中使用Handler的时候,Handler会默认绑定这个线程的Looper对象,并关联其MessageQueue,Handler发出的消息都会加入到这个MessageQueue中,Looper对象的生命周期贯穿了整个主线程的生命周期,所以当Looper对象中的MessageQueue里还有未处理的Message时,因为每个Message都持有Handler的引用,所以Handler无法被回收,自然其持有引用的外部类Activity也无法回收,造成泄漏。