Handler学习

Handler

Handler初识

  1. 概念:Handler:是一个消息分发对象,进行发送和处理信息,且其通信是单向;
  2. 作用:调度消息,切换任务线程;在子线程中不允许访问UI线程,而Handler就是解决子线程无法访问UI线程的问题,来减轻UI线程的压力。
  3. 常用的四个类:
    1. Handler:将Message对象发送到MessageQueue中,同时将自己的引用赋值给Message
    2. Looper:将Message对象从MessageQueue中取出,并将其交给dispatchMessage(Message)方法
    3. MessageQueue:消息队列(先进先出),负责Message的插入和取出,是有序的,其排序依据是Message的相对时间when字段。
    4. Message:消息对象

Handler的工作流程

请添加图片描述

Looper就像传送带传送消息,handler通过send/post方法,将货物送上传送带,这些货物就是message,排成messageQueue。当Looper.loop()调用,就是给传送带插上了电,无休止地转动,当传送到那一头了,就通过handler.dispatch()方法传递给需要货物的地方。

Handler发送消的方法:

Post(Runnable)

  1. 创建一个工作线程,实现 Runnable 接口,实现 run 方法,处理耗时操作
  2. 创建一个 handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作
			new Thread(()->{
                handler.post(()->{
                   textView.setText("111");
                });
            }).start();

sendMessageQueue(Message)

  1. 创建一个工作线程,继承 Thread,重新 run 方法,处理耗时操作
  2. 创建一个 Message 对象,设置 what 标志及数据
  3. 通过 sendMessage 进行投递消息
  4. 创建一个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;
            }
        }
    };

内存泄漏

概念

程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

简单使用的问题

  1. 内存方面:Handler 在 Activity 中作为一个匿名内部类来定义,它的内部持有来自 Activity 的实例。当 Activity 被用户关闭时,因为 Handler 持有了 Activity 的引用,就造成了 Activity 无法被回收,从而导致了内存泄漏。
  2. 异常方面:当 Activity finish 时,在 onDestroy 方法中释放了一些资源。此时 Handler 执行到 handlerMessage 方法,但相关资源已经被释放,从而引起空指针的异常。

问题解决

  1. 如果使用sendMessage并在Handler内部重写了handlerMessage方法,则需要在方法中添加TryCatch
  2. 如果使用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
    }
}
  1. 在上面的使用中,会发现我们在创建Message时,通过Message.obtain()的方法来创建的,这也是官方推荐我们采用的,原因在于其是从MessagePool(消息池)中获取的,省去了我们new Message()申请的内存开销;
  2. 我们先了解一下sendMessage和sendEmptyMessage的区别
    1. sendMessage(Message)其传递的是一个Message对象
    2. sendEmptyMessage(int)则是传递标识符,且创建的是一个带有标识符的空的Message
  3. 按照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也无法回收,造成泄漏。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

立花泷える宫水三叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值