Android面试:Handler八大问题汇总,二本学渣考研失败

本文详细阐述了Handler的底层实现原理,包括Looper的作用、sendMessage和post的区别,以及Looper是否会持续消耗系统资源。重点讲解了Looper在不同线程中的应用和消息处理机制,以及如何避免资源浪费。
摘要由CSDN通过智能技术生成

1、讲讲 Handler 的底层实现原理?


下面的这幅图很完整的表现了整个handler机制。

要理解handler的实现原理,其实最重要的是理解Looper的实现原理,Looper才是实现handler机制的核心。任何一个handler在使用sendMessage或者post时候,都是先构造一个Message,并把自己放到message中,然后把Message放到对应的LooperMessageQueueLooper通过控制MessageQueue来获取message执行其中的handler或者runnable。 要在当前线程中执行handler指定操作,必须要先看当前线程中有没有looper,如果有looperhandler就会通过sendMessage,或者post先构造一个message,然后把message放到当前线程的looper中,looper会在当前线程中循环取出message执行,如果没有looper,就要通过looper.prepare()方法在当前线程中构建一个looper,然后主动执行looper.loop()来实现循环。

梳理一下其实最简单的就下面四条:

1、每一个线程中最多只有一个Looper,通过ThreadLocal来保存,Looper中有Message队列,保存handler并且执行handler发送的message

2、在线程中通过Looper.prepare()来创建Looper,并且通过ThreadLocal来保存Looper,每一个线程中只能调用一次Looper.prepare(),也就是说一个线程中最多只有一个Looper,这样可以保证线程中Looper的唯一性。

3、handler中执行sendMessage或者post操作,这些操作执行的线程是handlerLooper所在的线程,和handler在哪里创建没关系,和Handler中的Looper在那创建有关系。

4、一个线程中只能有一个Looper,但是一个Looper可以对应多个handler,在同一个Looper中的消息都在同一条线程中执行。

2、Handler机制,sendMessage和post(Runnable)的区别?


要看sendMessagepost区别,需要从源码来看,下面是几种使用handler的方式,先看下这些方式,然后再从源码分析有什么区别。 例1、 主线程中使用handler

//主线程

Handler mHandler = new Handler(new Handler.Callback() {

@Override

public boolean handleMessage(@NonNull Message msg) {

if (msg.what == 1) {

//doing something

}

return false;

}

});

Message msg = Message.obtain();

msg.what = 1;

mHandler.sendMessage(msg);

上面是在主线程中使用handler,因为在Android中系统已经在主线程中生成了Looper,所以不需要自己来进行looper的生成。如果上面的代码在子线程中执行,就会报

Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()

如果想着子线程中处理handler的操作,就要必须要自己生成Looper了。

例2 、子线程中使用handler

Thread thread=new Thread(new Runnable() {

@Override

public void run() {

Looper.prepare();

Handler handler=new Handler();

handler.post(new Runnable() {

@Override

public void run() {

}

});

Looper.loop();

}

});

上面在Thread中使用handler,先执行Looper.prepare方法,来在当前线程中生成一个Looper对象并保存在当前线程的ThreadLocal中。 看下Looper.prepare()中的源码:

//prepare

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException(“Only one Looper may be created per thread”);

}

sThreadLocal.set(new Looper(quitAllowed));

}

//Looper

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

可以看到prepare方法中会先从sThreadLocal中取如果之前已经生成过Looper就会报错,否则就会生成一个新的Looper并且保存在线程的ThreadLocal中,这样可以确保每一个线程中只能有一个唯一的Looper

另外:由于Looper中拥有当前线程的引用,所以有时候可以用Looper的这种特点来判断当前线程是不是主线程。

@RequiresApi(api = Build.VERSION_CODES.KITKAT)

boolean isMainThread() {

return Objects.requireNonNull(Looper.myLooper()).getThread() ==

Looper.getMainLooper().getThread();

}

sendMessage vs post

先来看看sendMessage的代码调用链:

enqueueMessage源码如下:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,

long uptimeMillis) {

msg.target = this;

msg.workSourceUid = ThreadLocalWorkSource.getUid();

return queue.enqueueMessage(msg, uptimeMillis);

}

enqueueMessage的代码处理很简单,msg.target = this;就是把当前的handler对象给message.target。然后再讲message进入到队列中。

post代码调用链:

调用post时候会先调用getPostMessage生成一个Message,后面和sendMessage的流程一样。下面看下getPostMessage方法的源码:

private static Message getPostMessage(Runnable r) {

Message m = Message.obtain();

m.callback = r;

return m;

}

可以看到getPostMessage中会先生成一个Messgae,并且把runnable赋值给messagecallback.消息都放到MessageQueue中后,看下Looper是如何处理的。

for (;😉 {

Message msg = queue.next(); // might block

if (msg == null) {

return;

}

msg.target.dispatchMessage(msg);

}

Looper中会遍历message列表,当message不为null时调用msg.target.dispatchMessage(msg)方法。看下message结构:

也就是说msg.target.dispatchMessage方法其实就是调用的Handler中的dispatchMessage方法,下面看下dispatchMessage方法的源码:

public void dispatchMessage(@NonNull Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

}

handleMessage(msg);

}

}

//

private static void handleCallback(Message message) {

message.callback.run();

}

因为调用post方法时生成的message.callback=runnable,所以dispatchMessage方法中会直接调用 message.callback.run();也就是说直接执行post中的runnable方法。 而sendMessage中如果mCallback不为null就会调用mCallback.handleMessage(msg)方法,否则会直接调用handleMessage方法。

总结 post方法和handleMessage方法的不同在于,postrunnable会直接在callback中调用run方法执行,而sendMessage方法要用户主动重写mCallback或者handleMessage方法来处理。

3、Looper会一直消耗系统资源吗?


首先给出结论,Looper不会一直消耗系统资源,当LooperMessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有Looper的线程就会进入阻塞状态。

下面看下looper所在的线程是如何进入阻塞状态的。looper阻塞肯定跟消息出队有关,因此看下消息出队的代码。

消息出队

Message next() {

// Return here if the message loop has already quit and been disposed.

// This can happen if the application tries to restart a looper after quit

// which is not supported.

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

int nextPollTimeoutMillis = 0;

for (;😉 {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

nativePollOnce(ptr, nextPollTimeoutMillis);

// While calling an idle handler, a new message could have been delivered

// so go back and look again for a pending message without waiting.

if(hasNoMessage)

{

nextPollTimeoutMillis =-1;

}

}

}

上面的消息出队方法被简写了,主要看下面这段,没有消息的时候nextPollTimeoutMillis=-1

if(hasNoMessage)

{

nextPollTimeoutMillis =-1;

}

看for循环里面这个字段所其的作用:

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

nativePollOnce(ptr, nextPollTimeoutMillis);

Binder.flushPendingCommands();这个方法的作用可以看源码里面给出的解释:

/**

  • Flush any Binder commands pending in the current thread to the kernel

  • driver. This can be

  • useful to call before performing an operation that may block for a long

  • time, to ensure that any pending object references have been released

  • in order to prevent the process from holding on to objects longer than

  • it needs to.

*/

也就是说在用户线程要进入阻塞之前跟内核线程发送消息,防止用户线程长时间的持有某个对象。再看看下面这个方法:nativePollOnce(ptr, nextPollTimeoutMillis);nextPollingTimeOutMillis=-1时,这个native方法会阻塞当前线程,线程阻塞后,等下次有消息入队才会重新进入可运行状态,所以Looper并不会一直死循环消耗运行内存,对队列中的颜色消息还没到时间时也会阻塞当前线程,但是会有一个阻塞时间也就是nextPollingTimeOutMillis>0的时间。

当消息队列中没有消息的时候looper肯定是被消息入队唤醒的。

消息入队

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.");

}

synchronized (this) {

if (mQuitting) {

IllegalStateException e = new IllegalStateException(

msg.target + " sending message to a Handler on a dead thread");

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

【Android高级架构视频学习资源】

**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-ysmXcrYz-1712447216323)]

【Android高级架构视频学习资源】

**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值