Handler机制整体流程;Looper.loop()为什么不会阻塞主线程;IdHandler(闲时机制);postDelay()的具体实现;post()与sendMessage()区别;使用Handler需要注意什么问题,怎么解决的?
https://www.jianshu.com/p/a77af781f678
第一个问题:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
loop在没有消息的时候会沉眠,释放CPU,只有在有消息的时候在会被再次唤醒,阻碍线程的不是Looper.loop(),是loop中调用handler进行主线程操作才阻碍了线程
Activity 的生命周期都有对应的 case 条件了,ActivityThread 有个 getHandler 方法,得到这个 handler 就可以发送消息,然后 loop 里就分发消息,然后就发给 handler, 然后就执行到 H(Handler )里的对应代码。所以这些代码就不会卡死~,有消息过来就能执行。举个例子,在 ActivityThread 里的内部类 ApplicationThread 中就有很多 sendMessage 的方法。
简单的来说:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。
从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。
如果你了解下linux的epoll你就知道为什么不会被卡住了,先说结论:阻塞是有的,但是不会卡住
主要原因有2个
- epoll模型
当没有消息的时候会epoll.wait,等待句柄写的时候再唤醒,这个时候其实是阻塞的。
- 所有的ui操作都通过handler来发消息操作。
比如屏幕刷新16ms一个消息,你的各种点击事件,所以就会有句柄写操作,唤醒上文的wait操作,所以不会被卡死了。
这里涉及线程,先说说说进程/线程:
进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。
线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。
总结:
这里涉及线程,先说说说进程/线程,进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。
线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。
有了这么准备,再说说死循环问题:
对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。
真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
第二个问题?
第三个问题:postDelay()的具体实现
源码中调用 sendMessageDelayed(getPostMessage(r), delayMillis); 这个方法 参数一是 Message 对象,参数二是延时的时间
阅读之前先问大家一个问题:Handler.postDelayed()是先delay一定的时间,然后再放入messageQueue中,还是先直接放入MessageQueue中,然后在里面wait delay的时间?为什么?如果你不答不上来的话,那么此文值得你看看?
答案:先发消息,在延时等待
https://www.cnblogs.com/ldq2016/p/9260783.html
先说一下:是先发消息后延时的,MessageQueue.next() 在这个方法中进行了延时
1. postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
2. 紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
3. MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
4. Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
5. 直到阻塞时间到或者下一次有Message进队;
这样,基本上就能保证Handler.postDelayed()发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。
Handler.postDelayed()的调用路径
一步一步跟一下Handler.postDelayed()的调用路径:
- Handler.postDelayed(Runnable r, long delayMillis)
- Handler.sendMessageDelayed(getPostMessage(r), delayMillis)
- Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
- Handler.enqueueMessage(queue, msg, uptimeMillis)
- MessageQueue.enqueueMessage(msg, uptimeMillis)
最后发现Handler没有自己处理Delay,而是交给了MessageQueue处理
总结:另外,这里在阅读原文的基础上添加一点思考内容:
MessageQueue会根据post delay的时间排序放入到链表中,链表头的时间小,尾部时间最大。因此能保证时间Delay最长的不会block住时间短的。当每次post message的时候会进入到MessageQueue的next()方法,会根据其delay时间和链表头的比较,如果更短则,放入链表头,并且看时间是否有delay,如果有,则block,等待时间到来唤醒执行,否则将唤醒立即执行。
所以handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。使用后者的方式,我认为是集中式的统一管理了所有message,而如果像前者的话,有多少个delay message,则需要起多少个定时器。前者由于有了排序,而且保存的每个message的执行时间,因此只需一个定时器按顺序next即可。
第四个问题:post()与sendMessage()区别
post()与sendMessage()区别,本质上是没区别的,看源码都是调用 sendMessageDelayed(getPostMessage(r), delayMillis); 这个方法 参数一是 Message 对象,参数二是延时的时间 最后走的 sendMessageAtTime 这个方法
只是用法上的区别:
private TextView tv_up;
private String new_str = "";
/*post方法解决UI更新问题handler创建方式*/
private Handler handler_post = new Handler();
/*sendMessage方法解决UI更新问题handler创建方式*/
Handler handler_senM = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1) {
/*sendMessage方法更新UI的操作必须在handler的handleMessage回调中完成*/
tv_up.setText(new_str);
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
new_str = "更新UI";
/*sendMessage方法解决UI更新发送消息给handler(主线程中的handler)*/
handler_senM.sendEmptyMessage(1);
/*post方法解决UI更新,直接在runnable里面完成更新操作,这个任务会被添加到handler所在线程的消息队列中,即主线程的消息队列中*/
handler_post.post(new Runnable() {
@Override
public void run() {
tv_up.setText(new_str);
}
});
}
}).start();
}
第五个问题:使用Handler需要注意什么问题,怎么解决的?
https://blog.csdn.net/weixin_30500105/article/details/95117094
注意内存泄漏,加入 WeakReference 弱引用,把Activity 放到弱引用中,
由上图大家可以看到,提示说 Handler 这样写就是一个内部类,其实我也没搞明白,这怎么就成了内部类了,不过话说回来既然是内部类,这么写就有问题了,大家都知道内部类会隐形(或者说默认)持有外部类的引用,这样当外部类销毁的时候,内部类由于被执行中的线程引用了,这时候外部类就不会被GC 回收,就会造成内存泄漏。
我们知道,Handler是和Looper以及MessageQueue一起工作的,在Android中,一个应用启动后,系统默认会创建一个为主线程服务的Looper对象,该Looper对象用于处理主线程的所有Message对象,它的生命周期贯穿于整个应用的生命周期。在主线程中使用的Handler都会默认绑定到这个Looper对象。在主线程中创建Handler对象,它会立即关联到主线程Looper对象的MessageQueue,这时发送到MessageQueue中的Message对象都会只有这个Handler对象的引用,这样在Looper处理消息时常能回调到Handler的handlerMessage方法。因此,如果Message还没有被处理完成,那么Handler对象也就不会被垃圾回收。
在上面的代码中,将Handler的实例声明为HandlerActivity类的内部类。而在Java语言中,非静态内部匿名类会持有外部类的一个隐式的引用,这样就可能会导致外部类无法被垃圾回收。因此,最终由于MessageQueue中Message还没处理完成,就会持有Handler对象的引用,而非静态的Handler对象会持有外部类HandlerActivity的引用,这个Activity无法被垃圾回收,从而导致内存泄漏。
https://www.jianshu.com/p/804e774d9f76
①先说handler导致activity内存泄露的原因:
handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露。
②为何handler要定义为static?
因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露
③为何handler要定义为static的同时,还要用WeakReference 包裹外部类的对象?
这是因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。
静态内部类的Handler的写法
解决内存泄露
public class MainActivity extends AppCompatActivity {
TextView mTextView;
MyHandler mMyHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv_handler);
mMyHandler = new MyHandler(MainActivity.this);
doInBackground();
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> mWeakReference;
public MyHandler(MainActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mWeakReference.get();
switch (msg.what) {
case 1:
if (mainActivity != null) {
mainActivity.mTextView.setText(msg.obj + "");
}
break;
default:
break;
}
}
}
private void doInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//耗时操作完成,发送消息给UI线程
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "更新UI";
mMyHandler.sendMessage(msg);
}
}).start();
}
}
请忽略activity的弱引用,因为关系不太,之所以弱引用activity,是为了访问activity对象的成员
抛出一个问题,handler是怎么进行线程间的通讯的呢?
可以从下面这个方面回答,把知识点提炼一下:
一个handler 只能绑定一个 looper,但是多个线程(也就是多个 handler )可以往同一个 looper 发送消息,发消息的时候都是通过aHandler.sendMessage() 或者 aHandler.post() 的方式,这里发送出去的消息会持有该发送 handler 的引用,如这里发送出去的消息就会持有 aHandler 的引用,把消息发送到 aHandler 对应的 looper 内部维护的 MessageQueue 中,轮询分发消息的时候,就会根据 message 找到所持有的 Handler 的引用,找到对应的 Handler 然后传递消息。
1、在线程A中要预先创建一个Looper, 同时在Looper中会创建一个MessageQueue对象。
2、在线程A中创建Handler对象并重写了handleMessage方法,等待消息到来处理消息。也就是处理消息的线程是线程A。在Handler构造方法中,会去拿到上面这个Looper对象,和Looper中的MessageQueue对象
3、在线程B发送消息,Handler会把消息放入到MessageQueue中,但在这之前,把自己赋值给了这个Message的target变量。 也就是说,在消息中会有一个target变量就是当初发送自己的handler对象
4、调用Looper中的loop()方法。方法开启了一个死循环,判断当前Looper中的MessageQueue中是否有消息Message,如果有就调用该消息的target变量(handler对象)的dispatchMessage()方法。
5、处理消息,在Handler中的dispatchMessage()方法中分发消息,判断具体把消息交给handler的那个回调方法处理。
最后结果就是,线程B中发出的消息,在线程A中得到处理。。。注意第4步流程,可以直接在第1步之后做,总之是要开启这个死循环。
其他文章还介绍了ThreadLocal, 这里也说下,ThreadLocal可以创建一个只属于线程自己的变量,这个变量与其他线程是隔离的,不会被共享。线程A中创建的Looper对象就是放到了的ThreadLocal中。Handler构造中也是通过ThreadLocal去拿到的Looper对象。
IdHandler
IdleHandler是主线程在开始加载页面完成后调用的方法,可以提高性能:
IdleHandler优化Activity启动时间
@Override
protected void onResume() {
super.onResume();
Looper.myQueue().addIdleHandler(() -> {
initializeData();
return false;
});
Looper.myQueue().addIdleHandler(() -> {
resumeData();
return true;
});
}
如果为true,会重复执行,false只执行一次
IdleHandler是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。
同步屏障
一、消息种类
Message分为3中:普通消息(同步消息)、屏障消息(同步屏障)和异步消息。我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。
二、什么是屏障消息
同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。
MessageQueue#postSyncBarrier
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//1、屏障消息和普通消息的区别是屏障消息没有tartget。
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//2、根据时间顺序将屏障插入到消息链表中适当的位置
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
//3、返回一个序号,通过这个序号可以撤销屏障
return token;
}
}
postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单,从这个方法我们可以知道如下:
- 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
- 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
- postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
- postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
- 插入普通消息会唤醒消息队列,但是插入屏障不会。
三、屏障消息的工作原理
通过postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过的呢?我们知道MessageQueue是通过next方法来获取消息的。
Message next() {
//1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//2、遇到屏障 msg.target == null
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
}
if (msg != null) {
//4、如果找到异步消息
if (now < msg.when) {//异步消息还没到处理时间,就在等会(超时时间)
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//异步消息到了处理时间,就从链表移除,返回它。
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 如果没有异步消息就一直休眠,等待被唤醒。
nextPollTimeoutMillis = -1;
}
//。。。。
}
}
可以看到,在注释2如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if (msg != null){}中的代码。可以看到通过这种方式就挡住了所有的普通消息。
四、如何发送异步消息
Handler有几个构造方法,可以传入async标志为true,这样构造的Handler发送的消息就是异步消息。不过可以看到,这些构造函数都是hide的,正常我们是不能调用的,不过利用反射机制可以使用@hide方法。
/**
* @hide
*/
public Handler(boolean async) {}
/**
* @hide
*/
public Handler(Callback callback, boolean async) { }
/**
* @hide
*/
public Handler(Looper looper, Callback callback, boolean async) {}
当调用handler.sendMessage(msg)发送消息,最终会走到:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);//把消息设置为异步消息
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到如果这个handler的mAsynchronous为true就把消息设置为异步消息,设置异步消息其实也就是设置msg内部的一个标志。而这个mAsynchronous就是构造handler时传入的async。除此之外,还有一个公开的方法:
Message message=Message.obtain();
message.setAsynchronous(true);
handler.sendMessage(message);
在发送消息时通过 message.setAsynchronous(true)将消息设为异步的,这个方法是公开的,我们可以正常使用。
五、移除屏障
移除屏障可以通过MessageQueue的removeSyncBarrier方法:
//注释已经写的很清楚了,就是通过插入同步屏障时返回的token 来移除屏障
/**
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
//找到token对应的屏障
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
final boolean needWake;
//从消息链表中移除
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
//回收这个Message到对象池中。
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);//唤醒消息队列
}
}
测试:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Handler handler;
private int token;
public static final int MESSAGE_TYPE_SYNC=1;
public static final int MESSAGE_TYPE_ASYN=2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initHandler();
initListener();
}
private void initHandler() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_TYPE_SYNC){
Log.d("MainActivity","收到普通消息");
}else if (msg.what == MESSAGE_TYPE_ASYN){
Log.d("MainActivity","收到异步消息");
}
}
};
Looper.loop();
}
}).start();
}
private void initListener() {
findViewById(R.id.btn_postSyncBarrier).setOnClickListener(this);
findViewById(R.id.btn_removeSyncBarrier).setOnClickListener(this);
findViewById(R.id.btn_postSyncMessage).setOnClickListener(this);
findViewById(R.id.btn_postAsynMessage).setOnClickListener(this);
}
//往消息队列插入同步屏障
@RequiresApi(api = Build.VERSION_CODES.M)
public void sendSyncBarrier(){
try {
Log.d("MainActivity","插入同步屏障");
MessageQueue queue=handler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token= (int) method.invoke(queue);
} catch (Exception e) {
e.printStackTrace();
}
}
//移除屏障
@RequiresApi(api = Build.VERSION_CODES.M)
public void removeSyncBarrier(){
try {
Log.d("MainActivity","移除屏障");
MessageQueue queue=handler.getLooper().getQueue();
Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
method.setAccessible(true);
method.invoke(queue,token);
} catch (Exception e) {
e.printStackTrace();
}
}
//往消息队列插入普通消息
public void sendSyncMessage(){
Log.d("MainActivity","插入普通消息");
Message message= Message.obtain();
message.what=MESSAGE_TYPE_SYNC;
handler.sendMessageDelayed(message,1000);
}
//往消息队列插入异步消息
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private void sendAsynMessage() {
Log.d("MainActivity","插入异步消息");
Message message=Message.obtain();
message.what=MESSAGE_TYPE_ASYN;
message.setAsynchronous(true);
handler.sendMessageDelayed(message,1000);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
int id=v.getId();
if (id == R.id.btn_postSyncBarrier) {
sendSyncBarrier();
}else if (id == R.id.btn_removeSyncBarrier) {
removeSyncBarrier();
}else if (id == R.id.btn_postSyncMessage) {
sendSyncMessage();
}else if (id == R.id.btn_postAsynMessage){
sendAsynMessage();
}
}
}