- 4.1 为什么 Handler 会造成内存泄漏?
- 4.2 怎么防止 Handler 内存泄漏?
- 4.3 Loop.loop() 为什么不会造成应用卡死?
#####5. 总结
1. 作用
Handler 是一种用于线程间的消息传递机制。
因为 Android 中不允许在非主线程更新UI,所以最常使用的地方就是用于子线程获取某些数据后进行UI的更新。
2.基本用法
step1:创建Handler实例
//1.自定义Handler类
static class CustomHandler extends Handler{
@Override
public void handleMessage(Message msg) {
//更新UI等操作
}
}
CustomHandler customHandler = new CustomHandler();
//2.内部类
Handler innerHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//更新UI等操作
}
};
//3.callback方式
Handler callbackHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//更新UI等操作
return true;
}
});
step2:发送消息
//1.发送普通消息
Message msg = Message.obtain();
msg.what = 0; //标识
msg.obj = “这是消息体”; //消息内容
innerHandler.sendMessage(msg);
//2.发送Runnale消息
innerHandler.post(new Runnable() {
@Override
public void run() {
//更新UI等操作,消息接收后执行此方法
}
});
Handler 的创建以及消息的发送都有很多种方法,各种方式的异同会在下面讲到。
3. 源码分析
带着问题看源码 —— 鲁某
先抛出我们的第一个问题:
3.1 为什么 Handler 能够切换线程执行?
我们在发送 Message 的时候在子线程,为什么执行的时候就切换成了主线程?想要知道答案,基本就要把 Handler 的运行流程给了解一遍。
因为最终的处理是在 handleMessage
方法中进行的,所以我们看看 handleMessage
方法是怎么被调用起来的。
先打个 debug , 看看调用链:
画个图直观一点:
可能有点奇怪,整个调用流程都没有出现我们发送消息的方法,那我们发送的 Message
对象在哪里被使用了呢?
看下上图的 Step 2 ,在 loop()
方法里面调用了 msg.target.dispatchMessage(msg)
方法,debug 中查看 msg 对象的属性,发现这个 msg
正是我们发送的那个 Message
对象,这个 target
就是在 MainActivity
中创建的 Handler 对象。
也就是说,我们发送消息后,不知道什么原因,Looper.loop()
方法内会拿到我们发送的消息,并且最终会调用发送该消息的 Handler 的 handleMessage(Message msg)
方法。先看看 loop()
方法是怎么拿到我们的 Message
的:
// Looper.java ,省略部分代码
loop(){
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
final MessageQueue queue = me.mQueue;
for (;😉 {
Message msg = queue.next(); // might block , 从队列取出一个msg
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg); //Handler处理消息
msg.recycleUnchecked(); //回收msg
}
}
首先,loop()
方法会判断当前线程是否已经调用了 Looper.prepare()
,如果没有,则抛异常,这就是我们创建非主线程的 Handler 为什么要调用Looper.prepare()
的原因。而主线程中会在上面流程图的 Step 1 中,即 ActivityThread.main()
方法里面调用了 prepare 方法,所以我们创建默认(主线程)的 Handler 不需要额外创建 Looper 。
loop()
里面是一个死循环,只有当msg为空时才退出该方法。msg 是从 queue.next
中取出来的,这个 queue
就是我们经常听到的消息队列了(MessageQueue ),看看 next 方法的实现:
//MessageQueue.java ,删减部分代码
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
//如果队列已经停止了(quit or dispose)
return null;
}
for (;😉 {
synchronized (this) {
final long now = SystemClock.uptimeMillis(); //获取当前时间
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//msg == target 的情况只能是屏障消息,即调用postSyncBarrier()方法
//如果存在屏障,停止同步消息,异步消息还可以执行
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous()); //找出异步消息,如果有的话
}
if (msg != null) {
if (now < msg.when) {
//当前消息还没准备好(时间没到)
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 消息已准备,可以取出
if (prevMsg != null) {
//有屏障,prevMsg 为异步消息 msg 的前一节点,相当于拿出 msg ,链接前后节点
prevMsg.next = msg.next;
} else {
//没有屏障,msg 即头节点,将 mMessages 设为新的头结点
mMessages = msg.next;
}
msg.next = null; //断开即将执行的 msg
msg.markInUse(); //标记为使用状态
return msg; //返回取出的消息,交给Looper处理
}
}
// Process the quit message now that all pending messages have been ha
if (mQuitting) {
//队列已经退出
dispose();
return null; //返回null后Looper.loop()方法也会结束循环
}
}
}
源码中可以发现,虽然 MessageQueue
叫消息队列,但却是使用了链表的数据结构来存储消息。 next()
方法会从链表的头结点开始,先看看头结点是不是消息屏障(ViewRootImpl
使用了这个机制),如果是,那么就停止同步消息的读取,异步消息照常运作。
如果有消息,还会判断是否到了消息的使用时间,比如我们发送了延时消息,这个消息不会马上调用,而是继续循环等待,直到消息可用。这里就有一个新的问题2:**问什么 next()
中一直循环却不会导致应用卡死?**这个问题等下再说。
到这里,我们就大致能理清 Handler.handleMessage()
方法是怎么调起来的了。但是MessageQueue
里面的消息是怎么来的呢?这个其实不看源码也能猜出来了,肯定是由我们发送的消息那里传过来的,但是为了理解更深刻,还是得看看消息是怎么传递到消息队列中的(MessageQueue );
//Handler.java
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w(“Looper”, e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
可以看到,sendMessage()
方法最终是调用了 sendMessageAtTime()
方法,分析下这个方法,首先将会拿到一个消息队列 mQueue,这个队列是在创建 Looper
的时候默认初始化的,然后会调用enqueueMessage()
方法进队:
//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这个进队方法里面会将msg.target
设为当前Handler,也就是上面说到的 Looper.loop()
方法内最终调用的msg.target.dispatchMessage(msg)
的这个 msg 的 target 来源。
如果当前 Handler 是异步的话,还会将发送的消息置为同步消息,这个 mAsynchronous
标识是我们构造 Handler 的时候传递的参数,默认为 false
。
最后就是真正的进队方法 MessageQueue.enqueueMessage
:
//MessageQueue.java 删减部分代码
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException(“Message must have a target.”);
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
我的面试经验分享可能不会去罗列太多的具体题目,因为我依然认为面试经验中最宝贵的不是那一个个具体的题目或者具体的答案,而是结束面试时,那一刻你的感受以及多天之后你的回味~
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我整理了一些资料,需要的可以免费分享给大家
在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
【Android核心高级技术PDF文档,BAT大厂面试真题解析】
【算法合集】
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
序员的圈子,让我们一起学习成长!**](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算