Handler消息机制原理
- 在主线程创建mHandler,在构造方法中传入主线程的looper(主线程不需要手动传递,默认会传,其他线程需要手动传递)并重写handleMessage方法。子线程通过mHandler的sendMessageAtTime发送消息,在sendMessageAtTime方法中获取主线程的消息队列,并将当前mHandler赋值给message中的target属性并通过消息队列的enqueueMessage方法将message插入到消息队列中
- looper通过for死循环不断的访问消息队列中的next方法获取message,如果message不为空会调用message的target属性的dispatchMessage方法来分发消息,dispatchMessage最终会调用handleMessage方法,由于message中的target中的handler就是主线程的mHandler,所以会调用主线程中的handleMessage,所以此时可以在主线程的handleMessage中更新UI了,消息也已经由子线程传递到了主线程
谈谈Android中的HandlerThread_Mr Lee_的博客-CSDN博客_android handlerthread
Handler为什么会发生内存泄漏
解决办法:handler声明为静态内部类+弱引用
将handler声明为非静态内部类,非静态内部类会持有外部类的引用,当外部类需要被回收的时候,因为Handler持有外部类的引用,消息队列中的消息可能未处理完,外部类就无法被回收所以发生内存泄漏。解决办法是将handler声明为静态内部类,静态内部类不会持有外部类的引用,当handler需要访问外部类的属性时将外部类声明为弱应用,这样每次垃圾回收的时候外部类就都可以被回收了
public class AutoActivity extends Activity {
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auto);
}
}
上面这段代码在handler对象创建的时候却会报警告:This Handler class should be static or leaks might occur。意思是:Handler类应该为static类型,否则可能会造成内存泄漏。为什么会造成这种情况呢?
这种情况就是由于android的特殊机制造成的:当一个android主线程被创建的时候,同时会有一个Looper对象被创建,而这个Looper对象会实现一个MessageQueue(消息队列),当我们创建一个handler对象时,而handler的作用就是放入和取出消息从这个消息队列中,每当我们通过handler将一个msg放入消息队列时,这个msg就会持有一个handler对象的引用。因此当Activity被结束后,这个msg在被取出来之前,这msg会继续存活,但是这个msg持有handler的引用,而handler在Activity中创建,会持有Activity的引用,因而当Activity结束后,Activity对象并不能够被gc回收,因而出现内存泄漏。
这个根本原因就是:Activity在被结束之后,MessageQueue并不会随之被结束,如果这个消息队列中存在msg,则导致持有handler的引用,但是又由于Activity被结束了,msg无法被处理,从而导致永久持有handler对象,handler永久持有Activity对象,于是发生内存泄漏。但是为什么为static类型就会解决这个问题呢?因为在java中所有非静态的对象都会持有当前类的强引用,而静态对象则只会持有当前类的弱引用。声明为静态后,handler将会持有一个Activity的弱引用,而弱引用会很容易被gc回收,这样就能解决Activity结束后,gc却无法回收的情况。
所以解决这个警告就有几种方法:
1、将hanlder对象声明为静态的对象。
2、使用静态内部类,通过WeakReference实现对Activity的弱引用。具体实现看以下代码:
public class AutoActivity extends Activity {
MyHandler handler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auto);
}
static class MyHandler extends Handler{
WeakReference<AutoActivity> mactivity;
public MyHandler(AutoActivity activity){
mactivity = new WeakReference<AutoActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 100:
//在这里面处理msg
//通过mactivity.get()获取Activity的引用(即上下文context)
break;
default:
break;
}
}
}
}
HandlerThread
继承自Thread内部实现了带有消息队列的looper,可以处理消息和发送消息
A线程要想向B线程通信,B线程必须要在handler的构造方法中传入B线程的looper并且重写handleMessage方法。 以前:需要在B线程中通过Looper.prepare()和Looper.loop()手动创建looper 。现在:通过HandlerThread.getLooper()就可以获取到looper了,不需要手动创建
HandlerThread的使用以及原理_handlerthread有什么作用-CSDN博客
handler如何实现延迟消息,原理是什么
详细分析
Handler实现延迟消息的核心原理是通过结合MessageQueue的消息队列机制和系统时间判断,当调用sendMessageDelayed()或postDelayed()时,Handler会将消息的执行时间(when)设置为当前系统时间(SystemClock.uptimeMillis())加上延迟时长,然后将消息按时间顺序插入到MessageQueue中,Looper在循环处理消息时会不断比较队头消息的when与当前时间,若未到执行时间则通过nativePollOnce()进入阻塞等待,直到时间到达或新消息插入唤醒队列,从而实现了精准的延迟执行,整个过程无需单独开线程,完全依托于Looper的事件循环机制。
简单分析
Handler会将消息的执行时间设置为当前时间+延迟时间,然后将消息按时间顺序插入到消息队列中,Looper从消息队列中取出消息时会不断的对比当前时间和执行时间,如果没有到达执行时间或者没有新消息进入Looper会进入阻塞状态,当有新消息进入或者到达执行时间Looper会被唤醒继续执行
修改系统时间可以改变延迟时间么
不会,Handler会将消息的执行时间设置为当前时间+延迟时间,当前时间通过SystemClock.uptimeMillis()获得,SystemClock.uptimeMillis()会返回从系统启动到当前时刻的累计时间(不包括设备休眠模式时间),不受系统时间修改影响
Handler.postDelayed() 消息时间准确吗
多线程去访问消息队列的时候,存入消息和取出消息都会被加锁,当第一个线程还没有访问完成的时候,第二个线程就无法获取消息队列,所以他实际的时间会被延迟。所以,Handler所发送的Delayed消息时间基本准确,但不完全准确
Handler同步屏障
looper从消息队列中取出消息的时候会判断msg中的target属性是否为空,如果为空说明是屏障消息,此时会跨过同步消息去执行异步消息
loop阻塞为什么不会造成ANR
消息队列为空的时候主线程会进入休眠状态并且不消耗CPU的资源,ANR是指处理消息超时没有得到响应,此时loop没有在处理消息所以不会发生ANR
looper阻塞为什么不会造成ANR?_loopermessagequeue的中没有消息的时候为什么不会anr-CSDN博客
IdelHandler
IdelHandler在消息队列为空或者主线程空闲的时候执行,可以在里边进行延迟初始化,预加载,资源释放等操作。addIdleHandler返回true每次空闲都
执行,返回false
只执行一次
Looper.myQueue().addIdleHandler(() -> {
// 页面已经绘制完成,可以安全地做后续操作
preloadData();
startBackgroundTasks();
return false; // 只执行一次
});
获取Message的方式
Android 内部维护了存放Message的消息池,通过 obtain() 获取消息, recycle() 回收消息。当调用sendMessage(msg) 后,系统会自动调用 recycle()回收消息并把消息放回池中
Handler 十连问
https://juejin.cn/post/7273025171110494268
注意
- 主线程会默认创建looper所以不需要传,其他线程需要手动创建looper并把它传入当前线程
- 创建looper的时候在looper的构造方法中会创建了一个消息队列
- sendEmptyMessageAtTime(int what, long uptimeMillis)//发送延时消息。第二个参数的意思uptimeMillis秒之后发送消息,以系统开机时间为标准。(如果系统开机时间到此时正好是uptimeMillis秒,那么就会立即执行并发送消息)。
- sendMessageAtTime(Message msg, long uptimeMillis)//发送延时消息。第二个参数的意思uptimeMillis秒之后发送消息,以系统开机时间为标准。(如果系统开机时间到此时正好是uptimeMillis秒,那么就会立即执行并发送消息)
- sendMessageDelayed(Message msg, long delayMillis)//发送延时消息。第二个参数的意思delayMillis秒之后发送消息,以系统当前时间为标准。(系统当前时间delayMillis秒之后发送消息)
- sendEmptyMessageDelayed(int what, long delayMillis) //发送延时消息。第二个参数的意思delayMillis秒之后发送消息,以系统当前时间为标准。(系统当前时间delayMillis秒之后发送消息)