写点什么

【面试专题】2021 年字节,面试安卓工程师会问到那些问题

用户头像
Android架构
关注
发布于: 2021 年 11 月 05 日

nextPollTimeoutMillis 决定了堵塞与否,以及堵塞的时间,三种情况:


  1. 等于 0 时,不堵塞,立即返回,Looper 第一次处理消息,有一个消息处理完 ;

  2. 大于 0 时,最?堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);

  3. 等于-1 时,无消息时,会一直堵塞;


阻塞内部就是通过 native 函数 nativePollOnce、nativeWake 来实现的,这边可以展开说,说清楚了你就是 P7 大佬了,我反正是不会······(但是这个 nativePollOnce,面试官必会问你清不清楚实现原理,应该是想考察你的深度,还是建议查阅资料了解下)

如果想要在子线程中 new Handler 要做些什么准备?

简单来说就是在子线程里面初始化 Looper:prepare 后 loop,然后构造 handler 的时候传入对应的子线程 Looper 对象。


Thread thread = new Thread(new Runnable() {


Looper looper;


@Override


public void run() {


// Log.d(TAG, "click2: " + Thread.currentThread().getName());


Looper.prepare();


looper =Looper.myLooper();


Looper.loop();


}


public Looper getLooper() {


return looper;


} });


thread.start();


Handler handler = new Handler(thread.getLooper());


上述代码可能会牵扯到一些多线程的问题,调用 thread.getLooper()的时候,此时的 looper 可能还没 有初始化。


这边需要引入HandlerThread的概念,这东西就是 Android 源码层对子线程 handler 的一个简单的封装:


@Override


public void run() {


mTid = Process.myTid();


Looper.prepare();


synchronized (this) {


mLooper = Looper.myLooper();


notifyAll();


}


Process.setThreadPriority(mPriority);


onLooperPrepared();


Looper.loop();


mTid = -1;


}

Handler 内存泄露

这个问题不记得是哪家中厂问的了,只被问到过一次,低频考察题目(大厂估计都不屑于问这个了)。


匿名内部类持有外部 Activity 引用,导致内存泄漏。

如何监控 handler 中的消息?

这个是字节问的一个问题,我不是很清楚我回答的是不是面试官想要的。


Looper 中有一个 Api:setMessageLogging,会输出一个 Printer,用来打印每一条 message 消息。


如果有大佬有其他方案,欢迎评论区告知。

异步消息、同步屏障

这个也是必问题目。


线程的消息都是放到同一个 MessageQueue 里面,取消息的时候是互斥取消息(里面有大范围的 synchronized 代码块),而且只能从头部取消息,而添加消息是按照消息的执行的先后顺序进行的排序。如果有一个消息是需要立刻执行的,也是想要这个消息插队,那么就可以开启一个同步屏障,阻碍同步消息,只让异步消息通过,就是同步屏障的概念。


开启同步屏障:MessageQueue 的 postSyncBarrier()


要注意这个方法是 hide 方法,外部无法调用的,这个细节问题网易问过。顺带后面还问了如果我外部就是要发送一个异步消息怎么办?(不急,后边马上说)


  1. postSyncBarrier()里面的 Message 对象初始化的时候并没有给 target 赋值,因此, target == null。这样,一条 target == null 的消息就进入了消息队列。

  2. Message next()分拣消息的时候,如果发现消息队列开启同步屏障(即标识为 msg.target == null),就会优先处理异步消息。

异步消息在源码中的应用

ViewRootImpl 的 scheduleTraversals()


void scheduleTraversals() {


if (!mTraversalScheduled) {


mTraversalScheduled = true;


//开启同步屏障


mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();


//发送异步消息


mChoreographer.postCa


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


llback(


Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);


}


}


如何外部发送一个异步消息


  1. public Handler(boolean async)构造 handler 的时候选用对应的构造函数,那么通过这个 handler 发送的消息就都是异步的了,handler 会自动给每个 message 添加 setAsynchronous(true)。

  2. message.setAsynchronous(true),但是这个方法需要 API 22。


IdleHandler




在 MessageQueue 类中有一个 static 的接口 IdleHanlder。当线程将要进入堵塞,以等待更多消息时,会回 调这个接口。简单点说:当 MessageQueue 中无可处理的 Message 时回调。


作用:UI 线程处理完所有 View 事务后,回调一些额外的操作,且不会堵塞主进程。


Message next() {


synchronized (this) {


······


if (pendingIdleHandlerCount < 0


&& (mMessages == null || now < mMessages.when)) {


pendingIdleHandlerCount = mIdleHandlers.size();


}


if (pendingIdleHandlerCount <= 0) {


// No idle handlers to run. Loop and wait some more.


mBlocked = true;


continue; }


if (mPendingIdleHandlers == null) {


mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCo


}


mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);


}


for (int i = 0; i < pendingIdleHandlerCount; i++) {


final IdleHandler idler = mPendingIdleHandlers[i];


mPendingIdleHandlers[i] = null; // release the reference to the handler


boolean keep = false;


try {


keep = idler.queueIdle();


} catch (Throwable t) {


Log.wtf(TAG, "IdleHandler threw exception", t);


}


if (!keep) {


synchronized (this) {


mIdleHandlers.remove(idler);


}


} }


pendingIdleHandlerCount = 0;


nextPollTimeoutMillis = 0;


}


接口中只有一个 queueIdle() 函数,线程进入堵塞时执行的额外操作可以写这里,返回值是 true 的话,执行完此方法后还会保留这个 IdleHandler(会多次执行回调),否则移除该回调(只会执行一次)。


public static interface IdleHandler {


boolean queueIdle();


}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
【面试专题】2021年字节,面试安卓工程师会问到那些问题