2024年最全字节跳动:必面题说一下Android消息机制(4),vivo面试难吗 知乎

《设计思想解读开源框架》

第一章、 热修复设计

  • 第一节、 AOT/JIT & dexopt 与 dex2oat

  • 第二节、 热修复设计之 CLASS_ISPREVERIFIED 问题

  • 第三节、热修复设计之热修复原理

  • 第四节、Tinker 的集成与使用(自动补丁包生成)

    第二章、 插件化框架设计

  • 第一节、 Class 文件与 Dex 文件的结构解读

  • 第二节、 Android 资源加载机制详解

  • 第三节、 四大组件调用原理

  • 第四节、 so 文件加载机制

  • 第五节、 Android 系统服务实现原理

    第三章、 组件化框架设计

  • 第一节、阿里巴巴开源路由框——ARouter 原理分析

  • 第二节、APT 编译时期自动生成代码&动态类加载

  • 第三节、 Java SPI 机制

  • 第四节、 AOP&IOC

  • 第五节、 手写组件化架构

    第四章、图片加载框架

  • 第一节、图片加载框架选型

  • 第二节、Glide 原理分析

  • 第三节、手写图片加载框架实战

    第五章、网络访问框架设计

  • 第一节、网络通信必备基础

  • 第二节、OkHttp 源码解读

  • 第三节、Retrofit 源码解析

    第六章、 RXJava 响应式编程框架设计

  • 第一节、链式调用

  • 第二节、 扩展的观察者模式

  • 第三节、事件变换设计

  • 第四节、Scheduler 线程控制

    第七章、 IOC 架构设计

  • 第一节、 依赖注入与控制反转

  • 第二节、ButterKnife 原理上篇、中篇、下篇

  • 第三节、Dagger 架构设计核心解密

    第八章、 Android 架构组件 Jetpack

  • 第一节、 LiveData 原理

  • 第二节、 Navigation 如何解决 tabLayout 问题

  • 第三节、 ViewModel 如何感知 View 生命周期及内核原理

  • 第四节、 Room 架构方式方法

  • 第五节、 dataBinding 为什么能够支持 MVVM

  • 第六节、 WorkManager 内核揭秘

  • 第七节、 Lifecycles 生命周期


    本文包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

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

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

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

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mHandler = new Handler() {

@Override

public void handleMessage (Message msg) {

// 4. 根据不同的消息类型进行不同的处理

switch (msg.what) {

// 在这里进行和 UI 相关的操作,例如显示处理结果。

}

}

};

mThread = new Thread() {

@Override

public void run() {

// 2. 休眠一段时间,模拟子线程在处理耗时任务。

try {

Thread.sleep(30000);

} catch (InterruptedException ie) {

ie.printStackTrace();

}

// 3. 发送消息

mHandler.sendEmptyMessage(0);

}

};

// 1. 开启子线程

mThread.start();

}

}

示例代码里通过序号标注了逻辑流程,即先开启子线程并在线程内部处理任务,任务处理完成后通过Handler向主线程发送消息,最后在主线程中处理消息并更新UI。

看起来Android消息机制很简单嘛,只要利用Handler发送消息并处理其中的消息就可以了嘛。真的这么简单吗?当然不是!前面提到过在消息机制中涉及到几个关键点:发送消息、存储消息、消息循环和分发处理消息,在这个示例中我们只看到了发送消息和处理消息,并没有看到存储消息和消息循环。

这是因为这个例子中的Handler使用的消息是发送和存储在主线程中的消息队列中,这个消息队列的创建和循环都是在主线程创建的时候系统自动进行的,对我们是透明的,不利于理解消息机制的整体流程。

现在给出一个更为通用的示例,从这个例子中可以清楚地看到消息队列的创建和消息循环的开启:

class LooperThread extends Thread {

public Handler mHandler;

public void run() {

// 初始化 Looper 对象,其内部会创建消息队列。

Looper.prepare();

mHandler = new Handler() {

public void handleMessage(Message msg) {

// 处理消息队列中的消息。

}

};

// 开启消息循环,会从消息队列中取出消息,没有消息时等待新消息的到来。

Looper.loop();

}

}

综合这两个示例,我们了解了Android消息机制的使用方法,也看到了发送消息、创建消息队列、开启消息循环以及处理消息的过程,下面给出一个更直观的“消息传递流程图”:

img

通过流程图可以看到整个消息传递过程,也可以看到在不同的阶段涉及的类:

  • 消息发送:通过 Handler向关联的MessageQueue发送消息;

  • 消息存储: 把发送的消息以Message的形式存储在MessageQueue中;

  • 消息循环:通过Looper不停地从MessageQueue中获取消息,队列中没有消息时就阻塞等待新消息;

  • 消息分发和处理:Looper获取消息后分发给Handler进行处理。

3. 理解 Android 消息机制


前面提到消息传递流程主要分为“发送消息”、“存储消息”、“消息循环”和“消息分发和处理”几个不同阶段,我本打算按照这个流程来分别讲解每个阶段,但是在具体行文的时候发现每个阶段并不是完全分割开来的,比如在讲“发送消息”之前要先了解“消息的存储结构”和“消息循环的开启”,而“消息的分发”又是属于“消息循环”的功能。

正是由于这几个阶段之间的相互关系,导致没有办法严格按照消息传递的顺序讲解Android消息机制。思虑再三,我决定通过上面讲解的Android消息机制通用示例来一步步解析其背后的逻辑流程。

再来看下通用示例:

class LooperThread extends Thread {

public Handler mHandler;

public void run() {

// 1. 初始化 Looper 对象,其内部会创建消息队列。

Looper.prepare();

mHandler = new Handler() {

public void handleMessage(Message msg) {

// 4. 处理消息队列中的消息。

}

};

// 2. 开启消息循环,会从消息队列中取出消息,没有消息时阻塞等待新消息的到来。

Looper.loop();

}

// 3. 发送消息

mHandler.sendEmptyMessage(0);

}

在示例代码中用不同的序号标注了“消息传递机制”的各个关键点,以下的内容也都是根据这些关键节点进行讲解的。

3.1 消息载体

“消息”是Android消息机制中信息的载体,它包含了在整个消息传递过程中想要传送的数据,要理解“消息机制”就要先了解这个消息载体类Message:

/**

  • Defines a message containing a description and arbitrary data object that can be

  • sent to a {@link Handler}. This object contains two extra int fields and an

  • extra object field that allow you to not do allocations in many cases.

  • While the constructor of Message is public, the best way to get

  • one of these is to call {@link #obtain Message.obtain()} or one of the

  • {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull

  • them from a pool of recycled objects.

*/

public final class Message implements Parcelable { … }

Android框架中对消息载体Message的声明虽然简短,却传达了最核心最重要的两点信息:

  1. Message的作用:Message是包含了描述信息和数据对象并且在消息机制中发送给Handler的对象,其中的数据对象主要包括两个整型域和一个对象域,通过这些域可以传递信息。整型的代价是最小的,所以尽量使用整型域传递信息。

/**

  • User-defined message code so that the recipient can identify

  • what this message is about. Each {@link Handler} has its own name-space

  • for message codes, so you do not need to worry about yours conflicting

  • with other handlers.

*/

public int what;

/**

  • arg1 and arg2 are lower-cost alternatives to using

  • {@link #setData(Bundle) setData()} if you only need to store a

  • few integer values.

*/

public int arg1;

public int arg2;

/**

  • An arbitrary object to send to the recipient. When using

  • {@link Messenger} to send the message across processes this can only

  • be non-null if it contains a Parcelable of a framework class (not one

  • implemented by the application). For other data transfer use

  • {@link #setData}.

  • Note that Parcelable objects here are not supported prior to

  • the {@link android.os.Build.VERSION_CODES#FROYO} release.

*/

public Object obj;

  1. Message的创建方式:虽然Message有公有构造函数,但是建议使用其提供的obtain系列函数来获取Message对象,这种创建方式会重复利用缓存池中的对象而不是直接创建新的对象,从而避免在内存中创建太多对象,避免可能的性能问题。

/**

  • Return a new Message instance from the global pool. Allows us to

  • avoid allocating new objects in many cases.

*/

public static Message obtain() {

synchronized (sPoolSync) {

// 缓存池中存在可用对象时去缓存池获取 Message 对象。

if (sPool != null) {

// 获取缓存中的对象,并把缓存池指针后移。

Message m = sPool;

sPool = m.next;

m.next = null;

// 清除标志位

m.flags = 0; // clear in-use flag

// 更新当前缓存池大小

sPoolSize–;

return m;

}

}

// 缓存池中没有可用对象时直接创建一个新的 Message 对象。

return new Message();

}

Message中有一系列obtain函数用以在不同场景中获取对象,但这个是最核心的,其他函数都会在其内部调用它,有兴趣的同学可以自行查看源码,考虑到篇幅问题,这里就不再一一列举说明了。

看到这里,相信大家都会有一个疑问:obtain函数是从缓存池中获取Message对象,那缓存池中的对象是什么时候被添加进去的呢?既然缓存池中的对象都是一些可以被重复使用的对象,很明显是在Message对象不再被需要的时候,即从MessageQueue中取出并分发给Handler的时候,被添加到缓存中的,使用的是recycleUnchecked函数:

/**

  • Recycles a Message that may be in-use.

  • Used internally by the MessageQueue and Looper when disposing of queued Messages.

*/

void recycleUnchecked() {

// 设置标志位为“使用中”,在从缓存中取出时会清除这个标志位。

flags = FLAG_IN_USE;

// Message 对象中的信息都不再有意义,在放入缓存池前直接清空。

what = 0;

arg1 = 0;

arg2 = 0;

obj = null;

replyTo = null;

sendingUid = -1;

when = 0;

target = null;

callback = null;

data = null;

synchronized (sPoolSync) {

// 缓存池中只缓存一定数量的 Message 对象,默认是 50 个。

if (sPoolSize < MAX_POOL_SIZE) {

// 把对象放在缓存池的链表首部。

next = sPool;

sPool = this;

// 及时更新缓存池大小。

sPoolSize++;

}

}

}

3.2 创建消息队列

消息队列的创建对消息传递至关重要,它决定了消息在传递过程中的存取方式。但是线程在默认情况下是没有消息队列的,也无法在其内部进行消息循环。如果想为线程开启消息循环就需要使用到Looper类,它可以为关联的线程创建消息队列并开启消息循环,创建消息队列的方式是调用prepare()接口:

/**

  • Class used to run a message loop for a thread. Threads by default do

  • not have a message loop associated with them; to create one, call

  • {@link #prepare} in the thread that is to run the loop, and then

  • {@link #loop} to have it process messages until the loop is stopped.

*/

public final class Looper {

// 省略无关代码

// sThreadLocal.get() will return null unless you’ve called prepare().

static final ThreadLocal sThreadLocal = new ThreadLocal();

// 内部的消息队列和关联的线程

final MessageQueue mQueue;

final Thread mThread;

// 省略无关代码

/** Initialize the current thread as a looper.

  • This gives you a chance to create handlers that then reference

  • this looper, before actually starting the loop. Be sure to call

  • {@link #loop()} after calling this method, and end it by calling

  • {@link #quit()}.

*/

public static void prepare() {

// 创建可退出的消息循环,主线程的消息循环是不可退出的。

prepare(true);

}

private static void prepare(boolean quitAllowed) {

// 如果当前线程已经有了 Looper 对象就直接抛出异常,

// 因为一个线程只能有一个消息队列。

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

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

}

// 创建 Looper 对象并和线程关联。

sThreadLocal.set(new Looper(quitAllowed));

}

// 私有构造函数,创建消息队列并获取当前线程对象。

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

可以看到Looper.prepare()只是在内部创建了一个MessageQueue对象并和当前线程关联起来,同时还保证了每个线程只能有一个消息队列。

很显然MessageQueue就是用来存储消息对象的结构了,看下它的声明:

/**

  • Low-level class holding the list of messages to be dispatched by a

  • {@link Looper}. Messages are not added directly to a MessageQueue,

  • but rather through {@link Handler} objects associated with the Looper.

  • You can retrieve the MessageQueue for the current thread with

  • {@link Looper#myQueue() Looper.myQueue()}.

*/

public final class MessageQueue { … }

MessageQueue是一个持有消息对象列表的类,而这些消息对象通过和Looper关联的Handler添加并最终由Looper进行分发,其中有个关键信息需要引起我们的格外关注:list of messages,这是不是告诉我们虽然这个类的名字是queue但是其内部并不是队列而是列表呢?确实如此,MessageQueue的内部是使用单向链表的方法进行存取的,这点在后面解析Message的存取过程中会看到,在这里就不详细讲述了。

3.3 开启消息循环

“消息队列”创建完成了,是不是就可以直接向其中添加消息对象了呢?还不到时候,还需要先开启消息循环,来监听消息队列的情况,这时需要使用Looper.loop()接口:

/**

  • Run the message queue in this thread. Be sure to call

  • {@link #quit()} to end the loop.

*/

public static void loop() {

// 获取当前线程的 Looper 对象,获取失败时抛出异常。

final Looper me = myLooper();

if (me == null) {

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

}

// 获取当前线程的消息队列。

final MessageQueue queue = me.mQueue;

// 省略无关代码

// 开启一个无限循环来监听消息队列的情况

for (;😉 {

// 获取消息队列中的消息对象,如果没有消息对象就阻塞等待。

Message msg = queue.next(); // might block

if (msg == null) {

// 消息队列正在退出时就终止监听并退出循环

return;

}

// 省略无关代码

try {

// 分发消息,把消息发送合适的处理对象。

msg.target.dispatchMessage(msg);

dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;

} finally {

if (traceTag != 0) {

Trace.traceEnd(traceTag);

}

}

// 省略无关代码

// 回收消息对象,放入消息缓存池中以待后续复用。

msg.recycleUnchecked();

}

}

这段代码本身比较复杂,我省略了其中和核心逻辑无关的部分代码,以方便大家阅读和理解,其核心逻辑就是利用一个“无限循环”来监听消息队列,当发现有可用消息就取出并分发处理,如果没有就一直等待。

3.4 发送和存储消息

“消息队列”已经创建完成,“消息循环”也已经开启,终于可用发送消息了。

要发送消息,就要使用到Handler类了,其中的sendpost系列方法都可以进行“消息的发送”,核心方法都是一样的,在这里就以post方法来讲解下发送消息的过程:

/**

  • Causes the Runnable r to be added to the message queue.

  • The runnable will be run on the thread to which this handler is

  • attached.

  • @param r The Runnable that will be executed.

  • @return Returns true if the Runnable was successfully placed in to the

  •     message queue.  Returns false on failure, usually because the
    
  •     looper processing the message queue is exiting.
    

*/

public final boolean post(Runnable r) {

return sendMessageDelayed(getPostMessage®, 0);

}

private static Message getPostMessage(Runnable r) {

// 把 Runnable 对象封装成 Message 并设置 callback,

// 这个 callback 会在后面消息的分发处理中起到作用。

Message m = Message.obtain();

m.callback = r;

return m;

}

public final boolean sendMessageDelayed(Message msg, long delayMillis) {

if (delayMillis < 0) {

delayMillis = 0;

}

// 把延迟时间转换为绝对时间,方便后续执行。

return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

// 消息队列,即通过 Looper.prepare() 创建的消息队列。

MessageQueue queue = mQueue;

if (queue == null) {

RuntimeException e = new RuntimeException(

this + " sendMessageAtTime() called with no mQueue");

Log.w(“Looper”, e.getMessage(), e);

return false;

}

// 把消息添加到消息队列

return enqueueMessage(queue, msg, uptimeMillis);

}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

// 设置消息队列的目标,用于后续的消息分发过程。

msg.target = this;

if (mAsynchronous) {

msg.setAsynchronous(true);

}

// 消息对象入队

return queue.enqueueMessage(msg, uptimeMillis);

}

通过一系列的调用过程,Handler最终会通过 MessageQueue.enqueueMessage()把消息存储到消息队列中,MessageQueue内部又是如何存储这个发送过来的消息对象的呢?

boolean enqueueMessage(Message msg, long when) {

// 消息对象的目标是 null 时直接抛出异常,因为这意味这个消息无法进行分发处理,

// 是不合法的消息对象。

if (msg.target == null) {

throw new IllegalArgumentException(“Message must have a target.”);

}

// 消息正在使用时抛出异常,消息不能并发使用。

if (msg.isInUse()) {

throw new IllegalStateException(msg + " This message is already in use.");

}

synchronized (this) {

// 正在退出消息循环时,回收消息对象。

if (mQuitting) {

IllegalStateException e = new IllegalStateException(

msg.target + " sending message to a Handler on a dead thread");

Log.w(TAG, e.getMessage(), e);

msg.recycle();

return false;

}

msg.markInUse();

msg.when = when;

Message p = mMessages;

boolean needWake;

// 把消息对象添加到消息队列的合适位置

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

// New head, wake up the event queue if blocked.

// 消息队列为空或者当前消息对象的时间最近,直接放在链表首部。

msg.next = p;

// 更新链表指针

mMessages = msg;

// 如果原来消息循环处于阻塞状态就重新唤醒

needWake = mBlocked;

} else {

总结

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!

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

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

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

xception(msg + " This message is already in use.");

}

synchronized (this) {

// 正在退出消息循环时,回收消息对象。

if (mQuitting) {

IllegalStateException e = new IllegalStateException(

msg.target + " sending message to a Handler on a dead thread");

Log.w(TAG, e.getMessage(), e);

msg.recycle();

return false;

}

msg.markInUse();

msg.when = when;

Message p = mMessages;

boolean needWake;

// 把消息对象添加到消息队列的合适位置

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

// New head, wake up the event queue if blocked.

// 消息队列为空或者当前消息对象的时间最近,直接放在链表首部。

msg.next = p;

// 更新链表指针

mMessages = msg;

// 如果原来消息循环处于阻塞状态就重新唤醒

needWake = mBlocked;

} else {

总结

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!

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

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

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

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
知乎中,关于2022Spring面试题的讨论有很多。Spring是一个非常流行的Java企业级开发框架,相关面试题主要围绕该框架的核心概念、特性以及应用进行。 首先,一些常见的Spring面试题可能涉及到IoC(控制反转)和DI(依赖注入)的概念和原理。回答此类问题时,我们可以解释IoC是一种设计模式,通过将对象的创建和管理交给Spring容器来实现,这样可以提高代码的可维护性和扩展性。而DI是IoC的一种具体实现方式,通过依赖注入来实现对象之间的解耦。 其次,关于Spring的AOP(面向切面编程)也是一个常见的面试题。我们可以解释AOP是一种通过将横切关注点(例如日志记录、性能监控等)与主要业务逻辑进行解耦的编程思想。Spring提供了一种基于代理模式的AOP实现方式,可以通过切面、连接点、切点和通知等概念来实现对关注点的管理和处理。 此外,关于Spring MVC的问题也常常出现在面试中。我们可以解释Spring MVC是Spring框架中用于开发Web应用的模块,它采用了基于MVC设计模式的思想,通过DispatcherServlet、HandlerMapping、Controller、ViewResolver等组件来实现请求的处理和响应的生成。 另外,对于Spring中的常用注解(例如@Controller、@Service、@Autowired等)的理解,以及它们在项目中的应用也是一个常见的面试题。我们可以解释这些注解的作用和使用方式,明它们可以简化开发流程、提高代码的可读性和可维护性。 总结来,回答Spring面试题主要需要对Spring框架的核心原理、概念和应用有一定的理解,并能够将其与具体的项目场景进行结合,以展示个人的实际应用能力和经验。同时,在准备面试时,也可以通过查阅相关书籍、官方文档和参与实际开发来巩固和扩展自己的知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值