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.wait,等待句柄写的时候再唤醒,这个时候其实是阻塞的。
比如屏幕刷新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本身不会导致应用卡死。
第二个问题?
源码中调用 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没有自己处理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()区别,本质上是没区别的,看源码都是调用 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();
}
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 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 只能绑定一个 looper,但是多个线程(也就是多个 handler )可以往同一个 looper 发送消息,发消息的时候都是通过aHandler.sendMessage() 或者 aHandler.post() 的方式,这里发送出去的消息会持有该发送 handler 的引用,如这里发送出去的消息就会持有 aHandler 的引用,把消息发送到 aHandler 对应的 looper 内部维护的 MessageQueue 中,轮询分发消息的时候,就会根据 message 找到所持有的 Handler 的引用,找到对应的 Handler 然后传递消息。