Handler基础知识

 Handler消息机制原理

  1. 在主线程创建mHandler,在构造方法中传入主线程的looper(主线程不需要手动传递,默认会传,其他线程需要手动传递)并重写handleMessage方法。子线程通过mHandler的sendMessageAtTime发送消息,在sendMessageAtTime方法中获取主线程的消息队列,并将当前mHandler赋值给message中的target属性并通过消息队列的enqueueMessage方法将message插入到消息队列中
  2. 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属性是否为空,如果为空说明是屏障消息,此时会跨过同步消息去执行异步消息

Handler机制——同步屏障-CSDN博客

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

 

注意

  1. 主线程会默认创建looper所以不需要传,其他线程需要手动创建looper并把它传入当前线程
  2. 创建looper的时候在looper的构造方法中会创建了一个消息队列
  3. sendEmptyMessageAtTime(int what, long uptimeMillis)//发送延时消息。第二个参数的意思uptimeMillis秒之后发送消息,以系统开机时间为标准。(如果系统开机时间到此时正好是uptimeMillis秒,那么就会立即执行并发送消息)。
  4. sendMessageAtTime(Message msg, long uptimeMillis)//发送延时消息。第二个参数的意思uptimeMillis秒之后发送消息,以系统开机时间为标准。(如果系统开机时间到此时正好是uptimeMillis秒,那么就会立即执行并发送消息)
  5. sendMessageDelayed(Message msg, long delayMillis)//发送延时消息。第二个参数的意思delayMillis秒之后发送消息,以系统当前时间为标准。(系统当前时间delayMillis秒之后发送消息)
  6. sendEmptyMessageDelayed(int what, long delayMillis) //发送延时消息。第二个参数的意思delayMillis秒之后发送消息,以系统当前时间为标准。(系统当前时间delayMillis秒之后发送消息)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值