Skip to content

StrictMode Handler 边界

从 StrictMode ThreadPolicy、BlockGuard 违规入口和 Handler/Looper 线程边界,追踪检测、penalty、Binder 传播与策略恢复。

基于android-17.0.0_r1
AndroidStrictModeHandlerBlockGuard源码阅读

StrictMode Handler 边界 ​

StrictMode 不是 Handler 的消息拦截器,也不会在每条 Message 开始时自动替换 ThreadPolicy。 Android 17 的关系是:ThreadPolicy 绑定当前线程,BlockGuard/StrictMode 检测点在磁盘、网络和 自定义慢调用发生处读取该策略;Handler 只决定这些检测代码在哪个 Looper 线程执行。违规处理 可能同步记录、抛异常、调用 ActivityManager,或把 listener 投递到 Executor。

本文面向已经读过 Looper消息监控、Looper Observer 机制、 消息延迟边界 和 Handler消息处理 的读者。 本文只追踪 ThreadPolicy 与 Handler/Looper 的交界:策略如何安装,检测点如何触发,penalty 如何处理,临时放宽如何恢复,以及 Binder 和 Executor 为什么会改变违规处理线程。

1. 策略归属 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

ThreadPolicy 的检测 mask 和 penalty mask 属于线程级 BlockGuard policy;VmPolicy 是进程级策略。 Handler 不拥有 ThreadPolicy,Looper 也不会为每条消息创建新策略。

java
public static final class ThreadPolicy {
    final @ThreadPolicyMask int mask;
    final OnThreadViolationListener mListener;
    final Executor mCallbackExecutor;
}

private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
        new ThreadLocal<>();
private static final ThreadLocal<Executor> sThreadViolationExecutor =
        new ThreadLocal<>();

Handler 消息运行在目标 Looper 线程已有的策略下;消息跨线程不等于策略跨线程复制。

2. 策略安装 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

Builder 把 detect bits 和 penalty bits 组合成 ThreadPolicy;如果设置检测位却没有显式 penalty, build() 会默认启用 penaltyLog()。

java
public ThreadPolicy build() {
    if (mListener == null && mMask != 0
            && (mMask & (PENALTY_DEATH | PENALTY_LOG
                    | PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
        penaltyLog();
    }
    return new ThreadPolicy(mMask, mListener, mExecutor);
}

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        .detectDiskReads().detectNetwork().penaltyLog().build());

策略只影响执行 setThreadPolicy() 的线程。Handler callback 中安装的策略会持续影响同一 Looper 后续消息,直到显式修改或恢复。

3. 检测入口 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

检测点读取当前 BlockGuard policy。检测位未打开时直接返回;noteSlowCall() 也不是 Looper 自动调用的慢消息检测,只有代码显式标记且开启 custom slow call 才生效。

java
public static void noteDiskRead() {
    BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
    if (!(policy instanceof AndroidBlockGuardPolicy)) return;
    policy.onReadFromDisk();
}

public static void noteSlowCall(String name) {
    BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
    if (!(policy instanceof AndroidBlockGuardPolicy)) return;
    ((AndroidBlockGuardPolicy) policy).onCustomSlowCall(name);
}

4. Handler 交界 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

Looper 取消息后直接调用 target Handler,不保存/恢复 StrictMode policy。策略生命周期属于线程 和 callback owner,不属于 Message。

java
Message msg = me.mQueue.next();
if (msg == null) return false;
msg.target.dispatchMessage(msg);

如果 callback 临时放宽 policy 而不在 finally 恢复,后续同一 Looper 的消息会继承放宽后的策略。

5. 网络检测 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

onNetwork() 在检测位开启时,如果配置 PENALTY_DEATH_ON_NETWORK 会直接抛 NetworkOnMainThreadException;否则建立 ViolationInfo 并进入 penalty 流程。

java
public void onNetwork() {
    if ((mThreadPolicyMask & DETECT_THREAD_NETWORK) == 0) return;
    if ((mThreadPolicyMask & PENALTY_DEATH_ON_NETWORK) != 0) {
        throw new NetworkOnMainThreadException();
    }
    if (tooManyViolationsThisLoop()) return;
    startHandlingViolationException(new NetworkViolation());
}

在 Handler 里执行网络不天然违规;要同时满足当前线程 policy 开启检测和 penalty 配置条件。

6. 违规计时 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

违规处理记录发生时间,再尝试使用当前 Looper 估算 duration。没有 Looper 或只配置 death 时, 不能使用 Looper 计时。

java
void startHandlingViolationException(Violation e) {
    final int penaltyMask = (mThreadPolicyMask & PENALTY_ALL);
    final ViolationInfo info = new ViolationInfo(e, penaltyMask);
    info.violationUptimeMillis = SystemClock.uptimeMillis();
    handleViolationWithTimingAttempt(info);
}

void handleViolationWithTimingAttempt(final ViolationInfo info) {
    Looper looper = Looper.myLooper();
    if (looper == null || info.mPenaltyMask == PENALTY_DEATH) {
        info.durationMillis = -1;
        onThreadPolicyViolation(info);
        return;
    }
    // 使用当前 Looper/MessageQueue 估算违规持续时间
}

报告中的 duration 不是 Handler dispatch 时长的同义词;它可能覆盖违规发生后直到 Looper 再次 空闲的区间。

7. Penalty 线程 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

penalty listener 使用调用者提供的 Executor。源码会在 listener 运行前允许线程违规,运行后 恢复旧策略,防止上报本身递归触发 StrictMode。

java
final OnThreadViolationListener listener = sThreadViolationListener.get();
final Executor executor = sThreadViolationExecutor.get();
if (listener != null && executor != null) {
    executor.execute(() -> {
        ThreadPolicy oldPolicy = StrictMode.allowThreadViolations();
        try {
            listener.onThreadViolation(violation);
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }
    });
}

检测线程和上报线程可以不同:Executor 可能是 HandlerExecutor,也可能是 worker pool。

8. Binder 传播 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

StrictMode 可以在 Binder 边界传播违规信息,但类注释明确这是 best effort,JNI 发起的磁盘/网络 访问不一定触发。接收方读取 Parcel 后,再按自己的 BlockGuard policy 处理。

java
public static void readAndHandleBinderCallViolations(Parcel p) {
    Throwable localCallSite = new Throwable();
    final int policyMask = getThreadPolicyMask();
    final boolean gathering = (policyMask & PENALTY_GATHER) != 0;
    final int size = p.readInt();
    for (int i = 0; i < size; i++) {
        final ViolationInfo info = new ViolationInfo(p, !gathering);
        info.addLocalStack(localCallSite);
        BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
        if (policy instanceof AndroidBlockGuardPolicy) {
            ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
        }
    }
}

这不是 Handler 转发:Binder 传播的是违规信息和调用栈,接收线程再按自己的策略处理。

9. 策略恢复 ​

源码文件:frameworks/base/core/java/android/app/ActivityThread.java

framework 临时放宽 policy 的调用点保存旧值,并在 finally 恢复。Handler callback 若需要同样 操作,也必须自己保证所有异常路径回到旧策略。

java
final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
try {
    StrictMode.setThreadPolicy(policy);
    // 允许的系统操作
} finally {
    StrictMode.setThreadPolicy(oldPolicy);
}

Looper 不会替业务恢复;策略泄漏到下一条消息会造成检测盲区。

10. Looper 配置 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

设置 Message logging 本身显式调用 StrictMode.noteSlowCall("setMessageLogging"),但这不表示 Looper 每次 dispatch 都自动调用 custom slow call。

java
public void setMessageLogging(@Nullable Printer printer) {
    if (printer != null) StrictMode.noteSlowCall("setMessageLogging");
    mLogging = printer;
}

Handler 性能日志、StrictMode custom slow call 和 Looper slow dispatch 是三个不同观测点。

11. 线程测试 ​

源码文件:frameworks/base/core/java/android/os/StrictMode.java

可执行验证应在受控 HandlerThread 上安装不同 ThreadPolicy,分别投递调用 noteSlowCall() 的 Runnable,断言违规记录对应执行线程的 policy;再用 HandlerExecutor 和 worker Executor 作为 penalty listener executor,记录 listener 实际线程。

text
主 Looper policy A + Handler.post(taskA) -> taskA 使用 policy A
worker Looper policy B + Handler.post(taskB) -> taskB 使用 policy B
违规 listener Executor C -> 上报在 C 的线程执行

这个实验不证明 JNI I/O 一定被检测,也不证明 listener 在检测线程执行。

12. 复查命令 ​

源码文件:

  • frameworks/base/core/java/android/os/StrictMode.java
  • frameworks/base/core/java/android/os/Looper.java
  • frameworks/base/core/java/android/app/ActivityThread.java
  • frameworks/base/core/java/android/os/HandlerExecutor.java
bash
rg -n "setThreadPolicy|detectDiskReads|detectNetwork|detectCustomSlowCalls|penaltyListener" \
  frameworks/base/core/java/android/os/StrictMode.java

rg -n "noteSlowCall|noteDiskRead|noteDiskWrite|onNetwork|handleViolationWithTimingAttempt" \
  frameworks/base/core/java/android/os/StrictMode.java

rg -n "setMessageLogging|StrictMode.noteSlowCall|setThreadPolicy\(" \
  frameworks/base/core/java/android/os/Looper.java \
  frameworks/base/core/java/android/app/ActivityThread.java

13. 适用边界 ​

StrictMode 适合发现线程上的磁盘、网络和 custom slow call 违规;Handler 只决定违规代码在哪个 Looper 线程运行。它不是安全机制,也不保证捕获所有 JNI/Native 访问,更不提供自动迁移工作。

排查 Handler 中的违规时,先确认当前线程 policy 和检测位,再确认检测点是否经过 BlockGuard/StrictMode,最后追踪 penalty 是同步执行、交给 Executor,还是通过 Binder 传播。 临时放宽策略时保存并恢复旧 policy,避免一条消息改变后续整个 Looper 的检测行为。