Android - 浅谈handler

Handler是Android中用于帮助将子线程的数据传递给主线程的,同时也可以实现任意两个线程间的数据传输

将信息传递给主线程

用法

平时我们最常用handler进行传输消息的方式是:
在主线程中新建一个handler

private Handler handler = new Handler(){
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case MSG_WHAT:
                //操作语句
                break;
        }
    }
};

然后在任意线程中发送消息给主线程的handler

private void sendMessageToMainThreadByWorkThread(){
    new Thread(){
        @Override
        public void run() {
            super.run();
            Message message = handler.obtainMessage(MSG_WHAT);
            message.obj = "hello";
            handler.sendMessage(message);
        }
    }.start();
}

Message属性

属性用途
Message.what一个用于标记信息来源的int值
Message.arg1,Message.arg2在Message初定义时用来传递int类型的两个变量
Message.obj用于传递任何实例化的对象

传输过程

重写主线程中创建的Handler的handlerMessage()方法,通过sendMessage()发送出去的Message会在Handler线程被接收并处理。

子线程间传递信息

private Handler handler;
/**
 * 创建线程A用于接收来自线程B的消息
 */
private void tryHandler(){
    Thread threadA = new Thread(){
        @Override
        public void run() {
            super.run();
            Looper.prepare(); //注意点1
            handler = new Handler(){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    Log.d("ThreadA.out","threadA receiver message: " + ((String) msg.obj));
                }
            };
            Looper.loop(); //注意点2
        }
    };

    Thread threadB = new Thread(){
        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(2000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            Message message = handler.obtainMessage();
            message.obj = "hello ThreadA from ThreadB!";
            handler.sendMessage(message);
        }
    };
    threadA.start();
    threadB.start();
}

运行后看到了接收消息成功的日志
在这里插入图片描述
在代码中,标记了两个注意点。
如果尝试将注意点1Looper.prepare();删去的话,运行就会报错;
如果尝试将注意点2Looper.loop();删去的话,线程A就接收不到消息。

Looper.prepare()

通过查看Looper.java源码可知,prepare()方法是在当前线程里新建了一个Looper()
ThreadLocal用于维护线程的本地变量
在这里插入图片描述
然后在Looper的构造函数中,新建了一个MessageQueue()对象,用于存放Message
在这里插入图片描述
在创建主线程的时候Android已经调用了Looper.prepareMainLooper()Looper.loop()方法,所以在主线程中可以直接创建Handler并使用

发送消息的过程

查看handler.java源码
在这里插入图片描述
可以看到,无论调用哪一种发送消息的方法,最后都会调用到sendMessageDelayed()方法。
在这里插入图片描述
而这个方法又调用了sendMessageAtTime()方法。
在这里插入图片描述
在这里插入图片描述

查看这个方法可以发现,发送消息方法的关键在于MessageQueueenqueueMessage()方法
在这里插入图片描述
可以看到,Message以链表的形式存储在了MessageQueue中,可以保证Message的时序性。

Looper.loop()

查看Looper.loop()源码
在这里插入图片描述
可以看到,蓝框中的for循环在不断的从looper内的MessageQueue中取出Message
在这里插入图片描述
(源码较长,截取部分)
在这里插入图片描述
在成功得到Message对象后,通过Messagetarget调用dispatchMessage()方法分发消息
target就是我们创建的handler

分发消息的过程

查看Handler.java
在这里插入图片描述
如果我们已经设置了callback(Runnable对象),则直接调用该方法
在这里插入图片描述
如果未设置的话,则会调用mCallback.handleMessage()方法,即handlerhandleMessage(),该方法会执行在主线程中,因为handler创建在主线程中。

总结

  • 在使用handler时,在handler所创建的线程维护一个唯一的Looper对象。
    每个线程对应一个Looper,通过ThreadLocal来保证。
    每个Looper对象内部维护唯一一个MessageQueue
    一个线程可以有多个handler,但只能有一个looper和一个MessgaeQueue
  • MessageMessgaeQueue中是通过链表的形式存在的,即Message.next
  • Looper对象通过loop()开启了一个死循环,不断的从looper内的MessageQueue中取出Message,通过handler想消息分发给handler所在的线程。

补充

内存泄漏问题

Handler在使用过程中最需要注意的是内存泄漏问题
首先,Handler使用是用来进行线程间通信的,所以新开启的线程是会持有Handler引用的,如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。同时,MessageQueue中如果存在未处理完的MessageMessagetarget也是对Activity等的持有引用,也会造成内存泄漏。

解决方法:

  1. 使用静态内部类+弱引用的方式
    静态内部类static class TextHandler extend Handler{}
    弱引用private WeakReference<Activity> textActivity;
  2. 在外部类对象被销毁是清空MessageQueue中的消息。
    handler.removeCallbackAndMessages(null)

Message缓存池

回收的Message不会被销毁而是放在缓存池中,要获取新的Message时优先从缓存池中获取容器,缓存池中为null时才会新建,这就是obtian()方法的作用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值