1. Context
Context 是什么?
在 Android 中,Context 是一个非常核心的概念,直译为“上下文”。它代表了应用程序运行时的环境信息,是应用与系统进行交互的接口。通过 Context 我们可以:
获取应用中的资源与类信息(如 strings、drawable、colors、styles);
访问系统级服务(如 LayoutInflater、ActivityManager、ConnectivityManager 等);
执行应用级操作(如启动 Activity、Service、发送广播、获取 ContentResolver);
进行文件与数据库操作(如读写 SharedPreferences、访问缓存文件、打开 SQLite 数据库)。
Context 继承结构
Context
本身是一个抽象类,其主要实现类为 ContextImpl
,继承结构如下图:
ContextImpl:是 Context 真正的实现类,完成了对抽象方法的具体实现。Activity、Service 等组件在内部都是通过 ContextImpl 来完成环境交互的;
ContextWrapper:典型的装饰器(Wrapper)模式,内部持有一个
mBase
(即 Context 实例),所有方法的调用都委托给mBase
完成。ContextThemeWrapper:在 ContextWrapper 的基础上增加了“主题”功能,Activity 继承自它,因为 Activity 需要主题支持。而 Application、Service 不需要主题,因此直接继承自 ContextWrapper。
一个 App 中 Context 的数量有多少?
通过上面的继承关系已经很清晰了,Application、Service 和 Activity 都继承自 Context,由于一个应用程序中只有一个 Application,因此一个应用程序中的 Context 数量 = Activity 数 + Service 数 + 1。
2. Intent
3. Handler
什么是 Android 的消息机制?
Android 的消息机制基于 Handler、Looper、MessageQueue 实现,用于同一进程内的线程间通信。其核心目的是将任务切换到指定线程执行(如子线程更新 UI)。下面是一个简单的入门案例:
// 主线程创建 Handler(关联主线程 Looper)
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// 处理消息(例如更新 UI)
if (msg.what == 1) {
Log.d("my_tag", "${Thread.currentThread().name}, $msg")
}
}
}
Thread {
// 子线程发送消息到主线程
val message = Message.obtain()
message.what = 1
handler.sendMessage(message)
}.start()
Android 消息机制中的三个核心组件如下:
Handler:消息的发送者和接收者;
Looper:消息循环器,负责从消息队列中取出消息,并分发给对应的 Handler;
MessageQueue:消息队列,用于存储待处理的消息。
Looper 消息循环器
每个线程可以通过 Looper.prepare()
来初始化自己的消息循环器。主线程在应用启动时就已经创建好了 Looper:
public final class ActivityThread extends ClientTransactionHandler implements ActivityThreadInternal {
public static void main(String[] args) {
// ...
Looper.prepareMainLooper(); // 为主线程准备消息循环
// ...
Looper.loop(); // 主线程开始处理消息(进入死循环)
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
Looper 的创建通过 ThreadLocal
实现,保证每个线程只存在一个 Looper 实例,每个 Looper 内部持有一个唯一的 MessageQueue
,用于处理该线程内所有的消息。
public final class Looper {
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
// 通过 ThreadLocal 确保一个线程只能存在一个 Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 构造函数,每个 Looper 持有一个唯一的 MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
// 获取当前线程中的 Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
Handler 发送消息
Handler 在构造时会绑定指定线程的 Looper(默认是当前线程的 Looper),并持有其内部的 MessageQueue。调用 sendMessage()
方法时,消息被放入该线程的队列中。
public class Handler {
// 构造函数
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async, boolean shared) {
mLooper = looper;
mQueue = looper.mQueue; // 获取 looper 中的消息队列
mCallback = callback;
mAsynchronous = async;
mIsShared = shared;
}
// 消息的发送本质是通过 enqueueMessage() 完成的:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
msg.target = this; // 设置消息的目标为当前 Handler
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
}
Looper 分发消息
Looper 创建后还需要开启消息循环,不然不能处理消息。主线程的 Looper 会在程序启动时就开启消息循环,它是一个死循环,会不断从 MessageQueue
中取出消息,并调用 msg.target.dispatchMessage()
进行分发:
public final class Looper {
public static void loop() {
final Looper me = myLooper();
// ....
for (;;) { // 无限循环
// 处理一条消息
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
// ...
try {
msg.target.dispatchMessage(msg); // 分发消息
// ...
} catch (Exception exception) {
// ...
} finally {
// ...
}
}
}
消息在被 Handler 发送时,将 msg.target
属性设置为了自己 this
,当 Looper 拿到一条消息后,会调用 msg.target
(也就是发送时的 Handler)去处理消息,这样就实现了分发到指定的 Handler。
在 dispatchMessage()
内部,会调用我们覆写的 handleMessage()
方法,如果是 post/postDelay 提交的任务,会执行 handleCallback()
。
public class Handler {
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
}
延迟消息机制
当调用 sendMessageDelayed() 方法时,最终会调用 sendMessageAtTime() 方法,然后在入队时按照时间升序排序。
// Handler.java
// 发送延迟消息
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 在指定的系统运行时间(uptimeMillis)发送一条消息
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
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);
}
消息机制总结
初始化:线程的 Looper 必须先初始化和开启消息循环,主线程的 Looper 启动时已经创建好;
绑定:Handler 绑定到某个线程的 Looper,间接持有该线程的消息队列;
发送:Handler 发送消息,写入目标线程的消息队列;
分发:Looper 从消息队列中获取消息,调用消息中的
target
(即原始发送的 Handler),执行handleMessage()
处理逻辑。
runOnUiThread 的原理
它是 Activity
提供的一个方法,用于在子线程中切换到主线程执行一段 UI 操作。本质上它是封装了一个内部 Handler,如果当前线程不是主线程时,会发生消息给 handler 去处理。
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
延迟消息的 delay 靠谱吗?
delay 的时间大于 Handler Looper 的周期时基本可靠(例如主线程 > 50ms)。
Looper 负载越高,任务越容易积压,进而导致卡顿,导致真正执行到延迟消息的时候,delay 已经超出了。如果对实时要求较高,不要使用 Handler 的 delay 作为计时的依据。
优化手段:
重复消息过滤
互斥消息取消
复用消息:使用 obtain() 获取消息,而不是直接 new。
使用独享的 Looper:创建独立的 HandlerThread,启动 Looper.loop();
MessageQueue 中没有消息了会怎么样?
Looper 为什么不会导致 CPU 占用率高?
4. Fragment
5. Binder
6. AIDL
7. SharedPreferences
SharedPreferences 是 Android 提供的一种轻量级、键值对(Key-Value)形式的 XML 文件的数据存储方式,适合用于保存少量简单的持久化数据。
示例代码,在 Activity 中:
// 获取实例
val sharedPref = getSharedPreferences("MyPrefs", MODE_PRIVATE)
// 写入数据
sharedPref.edit(commit = true) {
putString("username", "Tony")
putInt("age", 22)
putBoolean("isFirstLaunch", false)
}
// 读取数据
val username = sharedPref.getString("username", "defaultUser")
val age = sharedPref.getInt("age", 0)
val isFirstLaunch = sharedPref.getBoolean("isFirstLaunch", true)
SP 的读写操作是线程安全的,内部用了很多 synchronized 锁来实现线程同步。
commit 和 apply 的区别
commit()
方法是同步提交,调用线程会直接写磁盘(阻塞),并返回boolean
表示是否成功;apply()
方法是异步提交,会先把数据写入内存缓存,立即返回(无返回值)。磁盘写入由 异步线程池 完成。
SP 有哪些坑?
多进程不安全
SP 文件本质是一个 XML 文件,跨进程时没有一致性保证。
进程 A
apply()
了,进程 B 不一定立刻看到;甚至可能出现覆盖写。
多线程锁竞争
内部用
synchronized
,多线程同时读写时容易产生性能瓶颈。大量写操作会阻塞读,主线程卡顿。
数据丢失
apply()
不会告诉你磁盘写入结果,只有commit()
返回boolean
。以如果应用崩溃/kill 发生在磁盘写入完成之前,数据可能丢失。
异常风险
如果写入时进程被杀,可能出现文件损坏(xml truncated),导致后续解析
XmlPullParserException
。