【面试专题】2021 年字节,面试安卓工程师会问到那些问题
nextPollTimeoutMillis 决定了堵塞与否,以及堵塞的时间,三种情况:
等于 0 时,不堵塞,立即返回,Looper 第一次处理消息,有一个消息处理完 ;
大于 0 时,最?堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);
等于-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 方法,外部无法调用的,这个细节问题网易问过。顺带后面还问了如果我外部就是要发送一个异步消息怎么办?(不急,后边马上说)
postSyncBarrier()里面的 Message 对象初始化的时候并没有给 target 赋值,因此, target == null。这样,一条 target == null 的消息就进入了消息队列。
Message next()分拣消息的时候,如果发现消息队列开启同步屏障(即标识为 msg.target == null),就会优先处理异步消息。
异步消息在源码中的应用
ViewRootImpl 的 scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//开启同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息
mChoreographer.postCa
llback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
如何外部发送一个异步消息
public Handler(boolean async)
构造 handler 的时候选用对应的构造函数,那么通过这个 handler 发送的消息就都是异步的了,handler 会自动给每个 message 添加 setAsynchronous(true)。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();
}
评论