Handler源码和机制分析

本文详细解析了Android中Handler的工作原理,包括其属性、构造方法、消息机制、内存泄漏问题及解决方案。Handler作为线程间通信的重要组件,文章强调了Looper、MessageQueue的角色及其与Handler的关系。

在这个包 android.os.Handler下的Handler

注:所有”英语解释“均来自源码英文的有道翻译结果。

打开这个类,发现这个类其实很简单只有850行。
其中属性:
在这里插入图片描述
在这里插入图片描述

IMessenger :传送门

其中属性变量MAIN_THREAD_HANDLER是从ActivtyThread.java来的,看图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为什么要注意static,是因为如果你不做好的处理的话它会导致内存泄漏,它引用着MainThread,app销毁时,Handler对象里一直持有它的强引用,导致它不会被GC回收,导致内存泄漏。

顺便补充一点:
volatile关键字:volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值(也就是从内存中读取,平时我们读取的值是在寄存器。)。volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
参考链接:Java并发编程:volatile关键字解析

Handler的构造方法(一共是7个构造方法)

由于代码复制看不见提示所以截图给出
在这里插入图片描述callback是你要执行任务线程的回调
async是传递是否同步,ture:使所有message都同步; flase: 不同步。
调用两个参数的构造

callback, async
在这里插入图片描述
调用两个参数的构造

looper, callback, async
在这里插入图片描述
looper: 用于为线程运行消息循环 并且会绑定当前线程,handler创建前必须先有looper,这个后面会解释。
调用三个参数的构造

looper ,callback ,async
在这里插入图片描述
调用三个参数的构造

async
在这里插入图片描述
调用两个参数的构造

callback ,asyn
在这里插入图片描述
两个参数构造的实现,然后发现内部创建了looper,如果没有looper就会报异常,这也就是上面的那个问题的答案,looper提供了循环队列。还有这个FIND_POTENTIAL_LEAKS标志,true,表示需要执行检查内存泄露操作,就会执行里面的一堆判断,大概意思是,如果是Handler匿名内部类或子类或本地类,并且成功获取到Handler类修饰是静态的,就会出警告。

looper ,callback , async
在这里插入图片描述
三个参数构造的具体内容,因为需要的参数都有了,所以直接赋值。

接下来我们会看到这样一个接口,里面有一个handleMessage方法,这个方法也是我们经常重写的。事实上,我们经常会这样使用handler:①直接 Handler handler = new Handler(){ handleMessage( msg)};②自己写一个class类继承Handler。这样做的目的都是为了重写handleMessage方法来让Handler绑定的线程去做一些事情。这个接口也是为了我们方便使用handler来处理不同线程的事务。不过这两种方法都用的子类继承的方式。

//英文解释:在实例化处理程序时可以使用回调接口,以避免必须实现自己的处理程序子类
 public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

下面是我们用第二种方法时会遇到的:

//英语解释:子类必须实现这个才能接收消息
public void handleMessage(Message msg) {
}

再接着看这个Handler类,会遇到它的一个方法dispatchMessage(Message msg):
其逻辑:如果传入的msg的callback不为空,也就是msg其实是一个callback(线程里的接口,用来执行线程内容),它就会调用handleCallback(msg),也就是执行这个callback,否则,它会判断mCallback(也就是接口,上面有提到)是否为null,如果不为空,就执行用户重写的handleMessage;为空,就执行Handler类里的handleMessage方法,总之,它就是要把msg(消息)按一个方法逻辑处理掉。

//英文解释:在这里处理系统消息。
 public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

在接下来是handler关于message的一堆获取方法(一共5个obtainMessage方法):
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们随便看一个Message.obtain方法看看它怎么获取的?
在这里插入图片描述

在这里插入图片描述
我们可以得到下面结论:
m.target 的类型是Handler用,来说明是哪个handler持有的这个消息
m.what 的类型是int,标识作用
m.arg1和m.arg2类型是int,携带两个int值
m.obj类型是Object,传递Object类型值
总的来说就是根据你给的参数,创建了一个msg对象,然后返回给你。

然后在Handler类里就又能看到下面这样的一堆方法:
先总的来说吧:
我认为主要分两类和一特殊:
①postXXX 一般是指内容为Runnable类型的
按时间分:
- AtTime 定时
- Delayed 延时,并最终会通过计算调用AtTime

②sendXXX 一般是指内容为Message类型的
- AtTime 定时
- Delayed 延时,并最终会通过计算调用AtTime
特殊:postAtFrontOfQueue(Runnable r),然后里面调用sendMessageAtFrontOfQueue(Message msg) 这两个方法都有贴。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述这个方法主要作用就是把msg直接放到mQueue的队头,它通过把uptimeMills这个参数设置为0来做到的。

总的来看,最终都会调用这个方法enqueueMessage(queue,msg,uptimeMillis)
在这里插入图片描述
这个方法的作用是把msg入队。这个方法里先判断是否同步,然后设置是否同步。是的,会发现msg并不是你一发出去就会执行的,它会放到一个队列里,这个队列是按时间(需要执行时的时间先后)排的。这也就是为什么说上一个方法是特殊的了,因为它直接将这个参数设置为了0。

既然有队列又有入队的方法,那么就一定有出队移除的方法:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

还有这些判断msg或者callback是否已经在队列:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

最后还有这样的几个方法,主要是用Printer对象来打印出打印出Handler以及Looper和Queue中的一些信息。
在这里插入图片描述

在这里插入图片描述
如果还想继续了解Printer:传送门

到此为止,Hanlder类就解析完毕了。
这里再给出一个Handler使用时的内存泄漏链接:链接1

Handler消息机制

先看一张你们可能比较熟悉的图:
在这里插入图片描述

首先,Handler在哪个线程创建,就会哪个线程执行handleMessage方法。它会持有该线程的Looper和循环队列。
所以我们从Handler创建开始看这张图,我们需要创建Handler,但这是不够的,不能构成循环,根据源码知道先有Looper才有Handler,Looper的作用是该类包含设置和管理事件循环所需的代码,比如loop方法,它会将ThreadLocal与当前线程looper建立<key,value>键值对放入map中。消息队列messageQueue是Looper的一个成员,它维护了一个队列,这个队列是按照消息执行时间顺序排列的。这个类中有一些队列操作但实际是通过navtive方法完成的,比如nativeWake(long ptr)用来唤醒,nativePollOnce(long ptr, int timeoutMillis)用来进行一次轮询。在java层MessageQueue是通过next()方法取出一条消息的,在next方法中,当消息队列无消息时 nextPollTimeoutMillis = -1,这个变量是nativePollOnce(long ptr, int timeoutMillis)的第二个参数,它为-1会导致会调用消息读取阻塞, nextPollTimeoutMillis这个变量会影响到消息的读取,它为-1表示阻塞,等于0时表示有一个新消息并且不需要等待,MessageQueue将会立即获得该消息并处理这个消息,大于0表示,下一次轮询等待时间,也就是过这个值大小ms时间后MessageQueue会再次进行一次轮询操作读取并处理该消息。这个类还有一些其他方法,比如postSyncBarrier(long when)它可以设置一个同步屏障,也就是说会阻塞同步消息执行,对异步消息无作用,它会导致这个屏障之后的同步消息阻塞,直到调用removeSyncBarrier(int token)方法移除这个屏障,在设置屏障的过程中会返回一个token用来后面移除的验证。这个队列中的元素对象为Message类型,Message是一个单链表结构,这个类的内部实现了一个缓存池,大小50,结构也为单链表,通过obtain()方法获取缓存池sPool中的Message对象,进行重复利用,就不需要再创建和销毁对象,优化了系统性能。

一个(同步消息)Message完整从发送到执行的过程是这样的:
在当前线程(UI线程)创建Handler对象handler,非主线程在创建需要先调用Loop. prepare()方法让当前messageQueue与当前线程建立联系才可以创建Handler对象,handler通过sendMessage发送Message对象或通过post方法发送一个runnable对象,不管是Message对象还是runnable对象,最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)这个方法,其中runnable对象会被转换成message对象,然后handler调用enqueueMessage(queue, msg, uptimeMillis)将一个Message对象放入消息队列MessageQueue,消息队列将会按照Message的when字段按照消息执行时间排序,Looper则负责一直无限循环执行queue.next()方法去取消息,MessageQueue通过next()方法获取到一个message对象并返回到Looper.loop()方法中,然后这个方法会对Message进行一些检测和记录打印,然后调用msg.target.dispatchMessage(msg)方法去分发处理消息。在处理消息的过程中,会通过handler进行dispatchMessage(Message msg),在这个方法中分三种情况,第一种是msg.callback不会空也就是runnable情况,将会直接执行,第二种是handler的mCallback不为空,就执行handler的mCallback接口中的handleMessage方法,第三种就是执行我们重写handleMessage方法。注意一点:Message对象的target是在handler对象执行enqueueMessage(queue, msg, uptimeMillis)方法的第一句进行赋值绑定的。

是如何通过handler切换线程执行message?
答:例如现在有A、B两个线程,在A线程中有创建了handler,然后在B线程中调用handler发送一个message。

通过上面的分析我们可以知道,当在A线程中创建handler的时候,同时创建了MessageQueue与Looper,Looper在A线程中调用loop进入一个无限的for循环从MessageQueue中取消息,当B线程调用handler发送一个message的时候,会通过msg.target.dispatchMessage(msg);将message插入到handler对应的MessageQueue中,Looper发现有message插入到MessageQueue中,便取出message执行相应的逻辑,因为Looper.loop()是在A线程中启动的,所以则回到了A线程,达到了从B线程切换到A线程的目的。

Handler的三种交互场景:
①UI线程子发消息到子线程
②子线程发消息到UI线程
③子线程与子线程
详细:传送门

补充:
主线程中的Looper.loop()一直无限循环为什么不会造成ANR?传送门
深入理解MessageQuene传送门

总结:
1.Handler可以进行线程间通信;
2.Handler创建前必须现有Looper对象,子线程调用Looper.parpare(),Main线程自带。
3.Handler的handleMessage执行线程是Handler对象创建线程。
4.Handler在使用过程中的隐患:内存泄漏。
解决:Ⅰ. 它里面有个标志位FIND_POTENTIAL_LEAKS用来标志是否有内存泄漏情况,如果有它会使用它会使用
final Class<? extends Handler> klass = getClass();
创建一个静态内部类,让这个类里Activity引用为弱引用。
Ⅱ.若你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值