前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【面试专题】2021年字节、阿里、网易等 Handler 面试题集合,Android高级开发必备!

【面试专题】2021年字节、阿里、网易等 Handler 面试题集合,Android高级开发必备!

原创
作者头像
Android技术干货分享
修改2021-06-11 17:57:48
1.7K1
修改2021-06-11 17:57:48
举报
文章被收录于专栏:Android技术分享

面试:有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。

如果你是面试高级Android开发,Handler绝对是必问问题,没有之一。本文结合了2021年4-5月份字节、阿里、网易等公司的面试经历,整理了面试过程中被问及的Handler相关的知识点。会涉及到很多细节知识,大家可以作为面试参考了解一下。

Handler基础

你可以说一下Handler吗?

约一半的公司会以这个问题开头,问的比较宽泛,建议整理一下Handler的整体流程,按顺序来讲:

  1. 可以先说一说 Handler -- Looper -- Message -- MessageQueue的关系,这边会涉及到线程中Looper唯一 --> MessageQueue唯一,以及如何通过 ThreadLocal 来保证唯一性的。
  2. 然后可以说一说Handler的整体工作流程,建议按照以下顺序说会比较有条理:
    • handler.sendMessage 发送消息到消息队列MessageQueue
    • looper调用自己的loop()函数带动 MessageQueue,messageQueue的enqueueMessage()消息入队,next()方法通过for死循环轮询messageQueue里面的每个Message。(这边如果你多线程、锁都比较了解的话可以说一下enqueueMessage、next、quit方法中如何通过synchronized关键字来控制线程安全的。)
    • 拿到消息后通过dispatchMessage()处理消息,处理优先级:message自带的callback > handler的callback > 默认handleMessage(msg)实现。
  3. 说一下异步消息、同步屏障的机制。(这边一般说完之后会问你同步屏障的一些应用场景、如何发一个异步消息等等,这些问题后边会细讲。)
  4. 最后可以说一下 IdleHandler。(一般说完这个必会问你IdleHandler的实际使用场景)
获取Message实例的方式?为什么要用obtain的方式获取?

字节问了这个题。

  1. 直接创建,Message msg = new Message。
  2. 通过Message.obtain()或者Handler.obtatinMessage()来得到一个Message对象。这边说一下Message的复用池机制,message通过静态单链表(private static Message sPool)来全局完成消息的复用,recycle的时候清空message中的数据,防止message持有其他对象而造成内存泄漏操作。MAX_POOL_SIZE = 50,链表最大容量也可以了解下,防止被问到细节。
Looper死循环为什么不会导致应用卡死?

Handler是native层的阻塞、唤醒(nativePollOnce、nativeWake)机制,应用卡死那是ANR(这边可以展开说一下 ANR),有条件的可以说一下 Linux 底层的消息机制(这个绝对是加分项)。

如何保证线程和Looper的一对一关系的?

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

Looper.prepare()的时候通过sThreadLocal.set()塞值,Looper.myLooper()获取的时候通过sThreadLocal.get()取值。ThreadLocal内部是key、value的Map结构,key是线程、value是存储的值(这边存储的就是Looper),所以线程和Looper一对一。

这边可能会被问,sThreadLocal为什么是static的?这边答案我不太确定,我面试的时候是答的只需要装载一次,减少对象创建开销;线程内所有操作共享。有知道为什么的大佬可以评论区教教大家。

Handler是如何发送延迟消息的?

MessageQueue -> next函数分拣消息的时候,会根据msg.when和当前时间计算出一个nextPollTimeoutMillis值,这个值就是控制消息延迟的。

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

  1. 等于0时,不堵塞,立即返回,Looper第一次处理消息,有一个消息处理完 ;
  2. 大于0时,最⻓堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);
  3. 等于-1时,无消息时,会一直堵塞;

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

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

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

代码语言:javascript
复制
   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的一个简单的封装:

代码语言:javascript
复制
@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()

代码语言:javascript
复制
void scheduleTraversals() {
     if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //开启同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
        //发送异步消息
        mChoreographer.postCallback(
                  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事务后,回调一些额外的操作,且不会堵塞主进程。

代码语言:javascript
复制
 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(会多次执行回调),否则移除该回调(只会执行一次)。

代码语言:javascript
复制
public static interface IdleHandler {
    boolean queueIdle();
}
面试前的准备

接下来分享的系统学习资源以详解各大互联网公司的 Android 常见面试题为主线,从面试的角度带你介绍必备知识点,以及该知识点在项目中的实际应用

帮你在现在的基础上,重新梳理和建立 Android 开发的知识体系。无论是你短期内想提升 Android 内功实力,突破自己工作中的能力瓶颈,还是准备参加 Android 面试,都会在这份资料中有所一些收获。

从架构基础开始,分了8个模块来逐步从基础进阶到架构师的环节:

多余的话就不讲了,接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!

首先是超级详细得不能再详细的Android开发学习思维导图,因为图片实在是太大了,所以我就只把二级目录的内容放出来。

接下来就需要梳理知识,提升储备了!(Android移动架构师七大专题学习资源)

  • 架构师筑基必备技能:深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IO
  • Android高级UI与FrameWork源码:高级UI晋升+Framework内核解析+Android组件内核+数据持久化
  • 360°全方面性能调优:设计思想与代码质量优化+程序性能优化+开发效率优化
  • 解读开源框架设计思想:热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+Android架构组件Jetpack
  • NDK模块开发:NDK基础知识体系+底层图片处理+音视频开发
  • 微信小程序:小程序介绍+UI开发+API操作+微信对接
  • Hybrid 开发与Flutter:Html5项目实战+Flutter进阶

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

然后再是通过源码来系统性地学习

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

刷大厂面试题备战,增加大厂通过率

以上这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者点击 【这里】 查看获取方式。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Handler基础
    • 你可以说一下Handler吗?
      • 获取Message实例的方式?为什么要用obtain的方式获取?
        • Looper死循环为什么不会导致应用卡死?
          • 如何保证线程和Looper的一对一关系的?
            • Handler是如何发送延迟消息的?
              • 如果想要在子线程中new Handler要做些什么准备?
                • Handler内存泄露
                  • 如何监控handler中的消息?
                  • 异步消息、同步屏障
                    • 异步消息在源码中的应用
                      • 如何外部发送一个异步消息
                      • IdleHandler
                        • 面试前的准备
                        相关产品与服务
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档