2024年最全Android面试6家一线大厂,这个问题是必问!(1),2024年最新嵌入式软件开发面试题库

结语

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!以下是目录截图:

由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。

再附一部分Android架构面试视频讲解:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

变量

//MessageQueue链表中的第一个Message

Message mMessages;

next:从消息队列中取出下一条要执行的消息

如果是同步屏障消息,找到第一个队列中中第一个异步消息

如果第一个Message的执行时间比当前时间见还要晚,记录还要多久开始执行;否则就找到下一条要执行的Message。

后面的Looper的loop方法会从过queue.next调用该方法,获取需要执行的下一个Message,其中会调用到阻塞的native方法nativePollOnce,该方法用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做,不会因此会出现ANR,ANR和这个没有半毛钱关系。

关键代码如下:

Message next() {

//native层MessageQueue的指针

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

for (;😉 {

//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒

//nativePollOnce用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做,不会因此会出现ANR,ANR和这个没有半毛钱关系。

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

//创建一个新的Message指向 当前消息队列的头

Message msg = mMessages;

//如果是同步屏障消息,找到第一个队列中中第一个异步消息

if (msg != null && msg.target == null) {

do {

prevMsg = msg;

msg = msg.next;

} while (msg != null && !msg.isAsynchronous());

}

if (msg != null) {

//如果第一个Message的执行时间比当前时间见还要晚,记录还要多久开始执行

if (now < msg.when) {

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

} else {

//否则从链表中取出当前的Message ,并且把链表中next指向指向下一个Message

mBlocked = false;

if (prevMsg != null) {

prevMsg.next = msg.next;

} else {

mMessages = msg.next;

}

//取出当前的Message的值,next置为空

msg.next = null;

msg.markInUse();

return msg;

}

}

//android.os.MessageQueue#quit时mQuitting为true

//如果需要退出,立即执行并返回一个null的Message,android.os.Looper.loop收到一个null的message后退出Looper循环

if (mQuitting) {

dispose();

return null;

}

if (pendingIdleHandlerCount <= 0) {

// 注意这里,如果没有消息需要执行,mBlocked标记为true,在enqueueMessage会根据该标记判断是否调用nativeWake唤醒

mBlocked = true;

continue;

}

}

}

enqueueMessage:向消息队列中插入一条Message

如果消息链表为空,或者插入的Message比消息链表第一个消息要执行的更早,直接插入到头部

否则在链表中找到合适位置插入,通常情况下不需要唤醒事件队列,以下两个情况除外:

  1. 消息链表中只有刚插入的这一个Message,并且mBlocked为true即,正在阻塞状态,收到一个消息后也进入唤醒

  2. 链表的头是一个同步屏障,并且该条消息是第一条异步消息

唤醒谁?MessageQueue.next中被阻塞的nativePollOnce

具体实现如下,

关于如何找到合适的位置?这涉及到链表的插入算法:引入一个prev变量,该变量指向p也message(如果是for循环的内部第一次执行),然后把p进行向next移动,和需要插入的Message进行比较when

关键代码如下:

boolean enqueueMessage(Message msg, long when) {

synchronized (this) {

msg.markInUse();

msg.when = when;

Message p = mMessages;

boolean needWake;

//如果消息链表为空,或者插入的Message比消息链表第一个消息要执行的更早,直接插入到头部

if (p == null || when == 0 || when < p.when) {

msg.next = p;

mMessages = msg;

needWake = mBlocked;

} else {

//否则在链表中找到合适位置插入

//通常情况下不需要唤醒事件队列,除非链表的头是一个同步屏障,并且该条消息是第一条异步消息

needWake = mBlocked && p.target == null && msg.isAsynchronous();

//具体实现如下,这个画张图来说明

//链表引入一个prev变量,该变量指向p也message(如果是for循环的内部第一次执行),然后把p进行向next移动,和需要插入的Message进行比较when

Message prev;

for (;😉 {

prev = p;

p = p.next;

if (p == null || when < p.when) {

break;

}

if (needWake && p.isAsynchronous()) {

needWake = false;

}

}

msg.next = p;

prev.next = msg;

}

//如果插入的是异步消息,并且消息链表第一条消息是同步屏障消息。

//或者消息链表中只有刚插入的这一个Message,并且mBlocked为true即,正在阻塞状态,收到一个消息后也进入唤醒

唤醒谁?MessageQueue.next中被阻塞的nativePollOnce

if (needWake) {

nativeWake(mPtr);

}

}

return true;

}

简单着看下native的epoll (这块还没有深入分析,后面篇章补上吧)

nativePollOnce 和 nativeWake 利用 epoll 系统调用, 该系统调用可以监视文件描述符中的 IO 事件. nativePollOnce 在某个文件描述符上调用 epoll_wait, 而 nativeWake 写入一个 IO 操作到描述符

epoll属于IO复用模式调用,调用epoll_wait等待. 然后 内核从等待状态中取出 epoll 等待线程, 并且该线程继续处理新消息

removeMessages: 移除消息链表中对应的消息

需要注意的是,在该函数的实现中分为了头部meg的移除,和非头部的msg的移除。

移除消息链表中头部的和需要移除相同的msg

eg:msg1.what=0;msg2.what=0;msg3.what=0; msg4.what=1; 需要移除what为0的msg,即移除前三个

移除消息链表中非头部的对应的消息,eg:msg1.what=1;msg2.what=0;msg3.what=0; 需要移除what为0的消息,即移除后续的消息,处处体现链表的查询和移除算法

关键代码如下:

void removeMessages(Handler h, int what, Object object) {

synchronized (this) {

Message p = mMessages;

//移除消息链表中头部的和需要移除相同的msg eg:msg1.what=0;msg2.what=0;msg3.what=0; msg4.what=1; 需要移除what为0的msg,即移除前三个

while (p != null && p.target == h && p.what == what

&& (object == null || p.obj == object)) {

Message n = p.next;

mMessages = n;

p.recycleUnchecked();

p = n;

}

//移除消息链表中非头部的对应的消息,eg:msg1.what=1;msg2.what=0;msg3.what=0; 需要移除what为0的消息,即移除后续的消息,处处体现链表的查询和移除算法

while (p != null) {

Message n = p.next;

if (n != null) {

if (n.target == h && n.what == what

&& (object == null || n.obj == object)) {

Message nn = n.next;

n.recycleUnchecked();

p.next = nn;

continue;

}

}

p = n;

}

}

}

postSyncBarrier:发送同步屏障消息

同步屏障也是一个message,只不过这个Message的target为null,. 通过ViewRootImpl#scheduleTraversals()发送同步屏障消息

同步屏障消息的插入位置并不是都是消息链表的头部,而是根据when等信息而定:如果when不为0,消息链表也不空,在消息链表中找到同步屏障要插入入的位置;如果prev为空,该条同步消息插入到队列的头部。

关键代码如下:

/**

  • android.view.ViewRootImpl#scheduleTraversals()发送同步屏障消息

  • @param when

  • @return

*/

private int postSyncBarrier(long when) {

// Enqueue a new sync barrier token.

// We don’t need to wake the queue because the purpose of a barrier is to stall it.

synchronized (this) {

final int token = mNextBarrierToken++;

//同步屏障也是一个message,只不过这个Message的target为null

final Message msg = Message.obtain();

msg.markInUse();

msg.when = when;

msg.arg1 = token;

Message prev = null;

Message p = mMessages;

if (when != 0) {

//如果when不为0,消息链表也不空,在消息链表中找到同步屏障要插入入的位置

while (p != null && p.when <= when) {

prev = p;

p = p.next;

}

}

if (prev != null) { // invariant: p == prev.next

msg.next = p;

prev.next = msg;

} else {

//如果prev为空,该条同步消息插入到队列的头部

msg.next = p;

mMessages = msg;

}

return token;

}

}

dump: MessageQueue信息

有时候我们需要dump出当前looper的Message信息来分析一些问题,比不,是否Queue中有很多消息,如果太多就影响队列中后面的Message的执行,可能造成逻辑处理比较慢,甚至可能导致ANR等情况,MessageQueue的默认复用池是50个,如果太多排队的Message也会影响性能。通过dump Message信息可以帮助分析。mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);

void dump(Printer pw, String prefix, Handler h) {

synchronized (this) {

long now = SystemClock.uptimeMillis();

int n = 0;

for (Message msg = mMessages; msg != null; msg = msg.next) {

if (h == null || h == msg.target) {

pw.println(prefix + "Message " + n + ": " + msg.toString(now));

}

n++;

}

pw.println(prefix + "(Total messages: " + n + “, polling=” + isPollingLocked()

  • “, quitting=” + mQuitting + “)”);

}

}

五、Looper


Looper主要涉及到构造、prepare和loop几个重要的方法,在保证一个线程有且只有一个Looper的设计上,采用了ThreadLocal以及代码逻辑的控制。

变量

//一些重要的变量

static final ThreadLocal sThreadLocal = new ThreadLocal();

final MessageQueue mQueue;

final Thread mThread;

构造方法

在构造Looper的时候 创建和Looper一一对应的MessageQueue

private Looper(boolean quitAllowed) {

//在构造Looper的时候 new一一对应的MessageQueue

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

prepare

我们这里可以看到消息机制是 如何保证一个线程只有一个Looper。

//quitAllowed参数是否允许quit,UI线程的Looper不允许退出,其他的允许退出

private static void prepare(boolean quitAllowed) {

//保证一个线程只能有一个Looper,这里的sThreadLocal

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

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

}

sThreadLocal.set(new Looper(quitAllowed));

}

loop

我们在MessageQueue的next方法已经分析过nativePollOnce这个方法可能会阻塞,直到拿到message。

如果next返回一个null的Message退出Looper循环,否则进行msg的派发。

取出的msg执行完之后,会加入到回收池中等待复用。recycleUnchecked我们在Message中也已经分析过了。不清楚的可以再回看。

public static void loop() {

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);

}

……

for (;😉 {

//next方法是一个会阻塞的方法,MessageQueue的next方法前面我们已经分析过nativePollOnce这个方法会可能阻塞,直到拿到message。

Message msg = queue.next();

//收到为空的msg,Loop循环退出。那么何时会收到为空的msg呐? quit

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

//msg的派发,msg.target就是Handler,即调用Handler的dispatchMessage派发消息

msg.target.dispatchMessage(msg);

……

//msg回收

msg.recycleUnchecked();

}

六、HandleThread


HandlerThread是一个带有Looper的Thread。

全局变量

public class HandlerThread extends Thread {

int mPriority;//线程优先级

int mTid = -1;//线程id

Looper mLooper;

private Handler mHandler;

}

构造方法

public HandlerThread(String name) {

super(name);

//用于run时设置线程的优先级Process.setThreadPriority(mPriority);

mPriority = Process.THREAD_PRIORITY_DEFAULT;

}

public HandlerThread(String name, int priority) {

super(name);

mPriority = priority;

}

run方法

进行Looper的prepare和loop的调用,配置好Looper环境

@Override

public void run() {

//线程id

mTid = Process.myTid();

//调用Looper的prepare方法,把当前该线程关联的唯一的Looper加入到sThreadLocal中

Looper.prepare();

synchronized (this) {

//从sThreadLocal中获取Looper

mLooper = Looper.myLooper();

notifyAll();

}

//设置线程的优先级,默认THREAD_PRIORITY_DEFAULT,如果是后台业务可以配置为THREAD_PRIORITY_BACKGROUND,根据具体场景进行设置

Process.setThreadPriority(mPriority);

//可以做一些预设置的操作

onLooperPrepared();

//开始looper循环

Looper.loop();

mTid = -1;

}

使用HandlerThread的一般流程如下

// Step 1: 创建并启动HandlerThread线程,内部包含Looper

HandlerThread handlerThread = new HandlerThread(“xxx”);

handlerThread.start();

// Step 2: 创建Handler

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

handler.sendMessage(msg);

这样有一个弊端,就是每次使用Handler都要new HandlerThread,而Thread又是比较占用内存,

能不能减少Thread的创建,或者说是Thread的复用.

并且实现Message能够得到及时执行,不被队列中前面的Message阻塞;

这的确是一个有很有意思很有挑战的事情。

七、资料


最后的最后,如果你打算开始读源码了,可以先看看我整理的这份资料。

《Android Framework精编内核解析》


本笔记讲解了Framework的主要模块,从环境的部署到技术的应用,再到项目实战,让我们不仅是学习框架技术的使用,而且可以学习到使用架构如何解决实际的问题,由浅入深,详细解析Framework,让你简单高效学完这块知识!

第一章:深入解析Binder

Binder机制作为进程间通信的一种手段,基本上贯穿了andorid框架层的全部。所以首先必须要搞懂的Android Binder的基本通信机制。

本章知识点

  • Binder 系列—开篇

  • Binder Driver 初探

  • Binder Driver 再探

  • Binder 启动 ServiceManager

  • 获取 ServiceManager

  • 注册服务(addService)

  • 获取服务(getService)

  • Framework 层分析

  • 如何使用 Binder

  • 如何使用 AIDL

  • Binder 总结

  • Binder 面试题全解析

第二章:深入解析Handler

本章先宏观理论分析与 Message 源码分析,再到MessageQueue 的源码分析,Looper 的源码分析,handler 的源码分析,Handler 机制实现原理总结。最后还整理Handler 所有面试题大全解析。

Handler这章内容很长,但思路是循序渐进的,如果你能坚持读完我相信肯定不会让你失望。

最后

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Driver 再探

  • Binder 启动 ServiceManager

  • 获取 ServiceManager

  • 注册服务(addService)

  • 获取服务(getService)

  • Framework 层分析

  • 如何使用 Binder

  • 如何使用 AIDL

  • Binder 总结

  • Binder 面试题全解析

第二章:深入解析Handler

本章先宏观理论分析与 Message 源码分析,再到MessageQueue 的源码分析,Looper 的源码分析,handler 的源码分析,Handler 机制实现原理总结。最后还整理Handler 所有面试题大全解析。

Handler这章内容很长,但思路是循序渐进的,如果你能坚持读完我相信肯定不会让你失望。

最后

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。

[外链图片转存中…(img-p7hba4aE-1715857011959)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值