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