Android面试必刷Framewrok面试题(附答案),打破面试难点(2023年最新版)

最近收到身边很多人反馈,现在的android面试,大多数企业除了对求职者的语言和编码等基础能力提出要求外,越来越强调对于 Framework 层的理解和 UI 框架的掌控能力。而完整的项目经历和多端知识也成了重要的加分项。

于是小编收拾了一下自己学习Framework过程中的笔记,将Framework面试过程中经常涉及到的一些问题整理出来。没有总结到的知识点,欢迎大家在评论里提出来,另外准备了一些《Android企业常见面试题总结(附答案)》给大家,在文末底下名片即可直接领取~

背题更方便,一文在手,面试我有

一、Looper如何在子线程中创建

创建子线程的looper必须要通过Looper.prepare()初始化looper,然后再通过Looper.loop()方法让Loop运行起来。

那么具体的细节请看下面的说明:

Handler消息处理流程前面已经介绍过了,还不清楚的朋友,一定要先看下前面章节《Handler怎么进行线程通信,原理是什么》。
首先我们要知道Looper相关的几个重要方法:

Looper.prepare():Looper 初始化, 同时会初始化MessageQueue, Looper消息机制必须要初始化Looper。

Looper.myLooper(): 获取当前调用线程中ThreadLocal缓存的Looper对象。

Looper.loop():让Loop进入死循环。

getLooper(): Handler中的方法, 获取Handler中缓存的Looper对象。

Looper.quit(): 终止 Looper.looper() 死循环, 执行 quit后Handler机制将失效, 执行时如果MessageQueue中还有Message未执行, 将不会执行未执行Message, 直接退出, 调用quit后将不能发消息给Handler。

Looper.quitSafely(): 作用同 quit(), 但终止Looper之前会先执行完消息队列中所有非延时任务, 调用quit后将不能发消息给Handler。

子线程中使用Looper如下:

Handler mHandler;
new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();//Looper初始化
        //Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
        mHandler = new Handler(Looper.myLooper());
        Looper.loop();//死循环
        //注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
    }
}).start();

其中主要需要注意的就是Handler的初始化,需要传入当前线程的 Looper 对象。然而,一般情况下我们并不会这样去给子线程创建looper,因为这样的方式只能创建一个handler对象。我们最正确的创建子线程 Looper的方法一般是调用HandlerThread。


二、Looper、handler、线程间的关系。例如一个线程可以有几个Looper可以对应几个Handler?

一个线程可以只能创建一个Looper,但是可以创建任意多个handler 对象。

具体的实现原理细节如下:

Handler消息处理流程如果不熟悉的同学请先阅读《Handler怎么进行线程通信,原理是什么?》

Looper相关

Looper的创建是通过在线程中执行Looper.prepare()方法创建,那么这个方法到底做了什么呢?请看下面的代码:

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { //code1
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

其中关键性的一句,就是sThreadLocal.set(new Looper(quitAllowed)),那我们来看看sThreadLocal。

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

ThreadLocal:代表了一个线程局部的变量,每个线程中的值都是独立存在、互不影响。在这里ThreadLocal是保证了每个线程都有各自的Looper。而且通过code1 我们知道,一旦sThreadLocal有值,那么再次prepare的时候就会报错,这就保障了每个线程只能有一个Looper可以被创建。

接下来看看建立Looper实例的方法new Looper(quitAllowed):

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

即一个Looper中有一个 MessageQueue。

Handler相关

当前线程创建了Looper之后,就可以创建Handler用来处理消息,Handler是怎么跟Looper关联上的呢?请看下面的代码:

public Handler(@Nullable Callback callback, boolean async) {
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

在Handler中有两个全局变量mLooper(当前Handler关联Looper)和mQueue(消息队列),并在构造函数中进行了初始化,重要的就是调用了:Looper.myLooper():

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

从上面代码可以知道handler通过调用线程局部变量sThreadLocal,获取当前线程的Looper,这里需要注意的是,如果当前线程没有关联的Looper,这个方法会返回null。
注意:Handler在哪个线程创建的,就跟哪个线程的Looper关联,也可以在Handler的构造方法中传入指定的Looper。

总结

一个线程 只能有一个 Looper,一个MessageQueue,可以有无数个 Handler。

三、子线程发消息到主线程进行更新 UI, 除了 handler 和 AsyncTask, 还有什么

android给我们提供了一些接口用于在异步线程中更新UI,比如 AsyncTask(),runOnUiThread(),View.post()方法等等,但是这些方法的底层都是调用handler来完成。具体的细节如下:

方式一:
在子线程中可以使用Activity的runOnUiThread方法更新

new Thread(){
    public void run(){
        runOnUiThread(new Runnable(){
            @Override
            public void run(){
                //更新UI
            }
        });
    }
}

其实原理是Handler的post方法,其内部代码如下:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

先判断当前的线程是不是主线程,如果是,则直接运行,如果不是,调用post方法,所以最终仍旧是使用handler来进行的处理。

方式二:
用View.post()方法更新

imageView.post(new Runnable(){
    @Override
    public void run(){
        // 更新UI
    }
});

View中的post源码如下:

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

所以View自己内部也是有自己的异步处理机制,从上面代码就可以看出,调用的是HandlerActionQueue 的post方法,而在Handler内部调用post的时候,先执行的是sendMessageDelayed方法,然后执行sendMessageAtTime方法,紧接着执行enqueueMessage,最终执行的是queue.enqueueMessage,最终执行的方式都是一样的。

总结:无论是哪种UI更新方式,其核心内容依旧还是Handler机制。

四、IdleHandler是什么?怎么使用,能解决什么问题?

IdleHandler 是 MessageQueue内定义的一个接口,一般可用于做性能优化。当消息队列内没有需要立即执行的 message时,会主动触发 IdleHandler 的 queueIdle方法。返回值为 false,即只会执行一次;返回值为 true,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法。具体的细节说明如下:

IdleHandler 是 闲时Handler 机制,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务。
IdleHandler 被定义在 MessageQueue 中,它是一个接口。

// MessageQueue.java
public static interface IdleHandler {
    boolean queueIdle();
}

可以看出,定义时需要实现其 queueIdle() 方法。同时返回值为 true 表示重复使用 IdleHandler,返回 false 表示是一个一次性的 。
既然 IdleHandler 被定义在 MessageQueue 中,使用它也需要借用 MessageQueue。在 MessageQueue 中申明了对应的 add 和 remove 方法。

// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
    // ...
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

可以看到 add 、 remove 其实操作的都是 mIdleHandlers,它的类型是一个 ArrayList。
既然 IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行,那么什么时候出现空闲?
MessageQueue 是一个基于消息触发时间的优先级队列,队列出现空闲存在两种情况。

  1. 其一MessageQueue 为空,没有消息;
  2. 其二MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行;

这两个场景,都会尝试调用 IdleHandler。
处理 IdleHandler 的情况,就在 Message.next() 这个获取消息队列下一个待执行消息的方法中,我们看下具体的逻辑。

Message next() {
    // ...
    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // ...
            if (msg != null) {
                if (now < msg.when) {
                    // 得出休眠的时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Other code
                    // 寻找消息处理后返回
                    return msg;
                }
            } else {
                // 没有更多的消息
                nextPollTimeoutMillis = -1;
            }

            if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; 

            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;
    }
}

我们先了解一下 next() 中关于 IdleHandler 执行的主要逻辑:

  1. 准备调用 IdleHandler 时,说明当前待调用的消息为 null,或者这条消息的执行时间没有到;
  2. 当 pendingIdleHandlerCount < 0 时,根据 mIdleHandlers.size() 赋值给 pendingIdleHandlerCount,它是后面循环的基础;
  3. 将 mIdleHandlers 中的 IdleHandler 复制到 mPendingIdleHandlers 数组中,这个数组是临时的,之后进入 for 循环;
  4. 循环中从mPendingIdleHandlers数组中取出 IdleHandler,并执行其 queueIdle() 记录返回值存到 keep 中;
  5. 当 keep 为 false 时,从 mIdleHandler 中移除当前循环的 IdleHandler,反之则保留;

可以看到 IdleHandler 机制中,最重要的就是在 next() 中,如果messageQueue队列空闲(没有消息需要立刻处理),那么就循环 mIdleHandler 中的 IdleHandler 对象,如果其 queueIdle() 返回为 false 时,就会从 mIdleHander 中移除。
需要特别注意的是,对 mIdleHandler 这个 List 的所有操作,都是通过 synchronized 来保证线程安全的。

一些关于IdleHandler的细节

IdleHandler 可以是可以保证不会进入死循环。
当队列空闲时,会循环调用一遍 mIdleHandlers 数组并执行 IdleHandler.queueIdle() 函数。而如果数组中有一系列 IdleHander 的 queueIdle() 返回了 true,则会保留在 mIdleHanders 数组中,下次依然会再调用一遍。
注意此时代码逻辑还在 MessageQueue.next() 的循环中,在这个情况下 IdleHandler 机制是如何保证不会进入死循环的?
部分文章会说 IdleHandler 不会死循环,是因为下次循环调用了 nativePollOnce() 借助 epoll 机制进入休眠状态,下次有新消息入队的时候会被重新唤醒,但这是显然是不对的。
注意看前面 next() 中的代码,在方法的末尾会重置 pendingIdleHandlerCount 和 nextPollTimeoutMillis。

Message next() {
    // ...
    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // ...
        // 循环执行 mIdleHandlers
        // ...
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

nextPollTimeoutMillis 确定下次进入 nativePollOnce() 超时的时间,它传递 0 的时候代表不会进入休眠,所以讲 natievPollOnce() 进入休眠所以不会死循环是不正确的。
这很好理解,毕竟 IdleHandler.queueIdle() 执行在主线程,它执行的时间是没办法控制的,那么 MessageQueue 中的消息情况可能会变化,所以需要重新处理一遍。
实际不会引起死循环的关键是在于 pendingIdleHandlerCount,我们看看下面的代码。

Message next() {
    // ...
    // Step 1
    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // ...
            // Step 2
            if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            // Step 3
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }
            // ...
        }
        // Step 4
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

我们梳理一下:

  • Step 1,循环执行前,pendingIdleHandlerCount 的初始值为 -1;
  • Step 2,在 pendingIdleHandlerCount<0 时,才会通过 mIdleHandlers.size() 赋值。也就是说只有第一次循环才会改变 pendingIdleHandlerCount 的值;
  • Step 3,如果 pendingIdleHandlerCount<=0 时,则循环 continue;
  • Step 4,重置 pendingIdleHandlerCount 为 0;

当第一次循环时,pendingIdleHandlerCount<0,会给pendingIdleHandlerCount赋值,然后执行到Step4,此时 Step3不会执行,当第二次循环时,pendingIdleHandlerCount 等于 0,在 Step 2 不会改变它的值,那么在 Step 3 中会直接 continus 继续会下一次循环,此时没有机会修改 nextPollTimeoutMillis。
那么 nextPollTimeoutMillis 有两种可能:-1 或者下次唤醒的等待间隔时间,在执行到 nativePollOnce() 时则会进入休眠,等待下一次被唤醒。
下次唤醒的时候,mMessage 必然会有一个等待执行的 Message,则 MessageQueue.next() 返回到 Looper.loop() 的循环中,分发处理这个 Message,之后又是一轮新的 next() 中去循环。

IdleHandler能解决什么问题?

通过上面的分析我们不难发现,IdleHandler是在主线程空闲的时候被执行。因此基于这个点,我们可以将一些相对耗时或者一些影响整个线程运行的事务放到IdleHandler里面处理,譬如当我们需要收到调用GC的时候,GC一般会带来STW问题,于是我们可以将这个动作在IdleHandler里面执行,而android源码确实也是这样进行的。

五、为什么 Android 要采用 Binder 作为 IPC 机制?

简单来说,Binder 是android系统工程师为android 定制的一个跨进程通信方法,当然它也不是android 系统原创的,是参考了OpenBinder的实现而引进到Google的。Binder是综合了android系统的特点,从性能,设计架构,安全性等几个方面的综合平衡而设计的

应该从几个方面与传统IPC机制做对比。

  1. 性能方面
    • 拷贝数据需要花时间,Binder只需拷贝一次,共享内存无需拷贝,其他的需要拷贝两次。
    • 从速度上来说,Binder仅次于共享内存,优于Socket,消息队列,管道,信号,信号量等。
  2. 特点方面
    • Binder:基于C/S 架构,易用性高。
    • 共享内存:
      • 多个进程共享同一块内存区域,必然需要某种同步机制。
      • 使用麻烦,容易出现数据不同步,死锁等问题。
    • Socket:
      • socket作为一款通用接口,其传输效率低,开销大。
      • 主要用在跨网络的进程间通信和本机上进程间的低速通信。
  3. 安全性方面
    • Binder:(安全性高)
      • 为每个APP分配不同UID,通过UID鉴别进程身份。
      • 即支持实名Binder,又支持匿名Binder。
    • 传统IPC:(不安全)
      • 完全依赖上层协议,只能由用户在数据包中填入UID/PID。
      • 访问接入点是开放的,任何程序都可以与其建立连接。

通过上面几个比较,特别是安全性这块,所以最终Android选择使用Binder机制进行通信。

六、Handler的Callback存在,但返回true,handleMessage是否会执行?

七、Handler的sendMessage和postDelay的区别?

八、Looper.loop会不会阻塞主线程?

九、Looper无限循环的阻塞为啥没有ANR

十、Android中多进程通信的方式有哪些?

十一、描述下Binder机制原理?

十二、Binder线程池的工作过程是什么样?

十三、AIDL 的全称是什么?如何工作?能处理哪些类型的数据?

十四、Android中Pid&Uid的区别和联系

十五、Handler怎么进行线程通信,原理是什么?

篇幅原因,暂时只展示前五道面试题解析,需要其完整版面试解析的,直接添加下方二维码,免费领取。

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Framework 中添加自定义库,可以通过以下步骤实现: 1. 将自定义库的源代码添加到 Android Framework 中,通常情况下位于 `frameworks/base/core/java` 或 `frameworks/base/services/core/java` 目录下。 2. 在 `frameworks/base/core/res` 或 `frameworks/base/services/core/res` 目录下添加自定义库的资源文件,比如布局文件、字符串资源等。 3. 在 Android.mk 文件中添加自定义库的编译配置信息,包括源代码的编译和资源文件的打包。 4. 在 AndroidManifest.xml 文件中注册自定义库,以便系统可以识别和加载该库。 5. 在 Java 代码中使用 `import` 语句导入自定义库的类,并在代码中调用该类的方法。 需要注意的是,自定义库需要在 APK 中进行引用才能被 APK 调用。可以通过以下步骤将自定义库引用到 APK 中: 1. 在 APK 的 build.gradle 文件中添加依赖库的引用,例如: ``` dependencies { implementation project(':mylibrary') } ``` 其中 `mylibrary` 是自定义库的名称。 2. 在 APK 的 AndroidManifest.xml 文件中添加自定义库的使用权限,例如: ``` <uses-library android:name="mylibrary" android:required="true" /> ``` 其中 `mylibrary` 是自定义库的名称。 完成以上步骤后,自定义库就可以被 APK 调用了。需要注意的是,自定义库只能被具有相应权限的应用调用。如果需要让所有应用都能调用该库,可以考虑将库打包成系统级别的 APK,并将其放置到 `/system/app` 目录下。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值