Jarry
发布于 2025-05-14 / 21 阅读
0
0

ANR 监控

ANR 简介

ANR(Application Not Responding),即应用程序无响应,Android 系统指定某些事件需要在规定时间内完成,如果超过预定时间还能未能得到有效响应,就会造成 ANR

常见 ANR 场景

ANR 类型

前台超时时间

后台超时时间

KeyDispatch Timeout

KEY_DISPATCHING_TIMEOUT = 5*1000

Activity Timeout

LAUNCH_TIMEOUT = 10*1000

Broadcast Timeout

BROADCAST_FG_TIMEOUT = 10*1000

BROADCAST_BG_TIMEOUT = 60*1000

Service Timeout

SERVICE_TIMEOUT = 20*1000

SERVICE_BACKGROUND_TIMEOUT = 200*1000

ContentProvider Timeout

CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000

系统 ANR 流程

  • 埋炸弹 (9. 延时消息 SERVICE_TIMEOUT_MSG)

  • 无 ANR 拆炸弹 (19. 移除消息 SERVICE_TIMEOUT_MSG)

  • 有 ANR 爆炸弹 (10. 处理消息 appNotResponding)

系统处理消息

  • 输出发生 ANR 进程的信息到 EventLog

  • 如果是后台 ANR, 则只收集当前进程;如果是前台 ANR,则会收集其他进程。具体的收集顺序为:

  • 发生 ANR 的进程

  • SystemServer 进程

  • 持久化进程:比如输入法进程,电话进程,NFC 进程,系统网络进程,安全守护进程(脸部,指纹识别)等等

  • 最近运行过进程(按照 CPU 使用率取排名前 5 进程)

  • 依次对收集到的进程发送 SIGQUIT 信息,请求 Dump Trace 信息

  • Dump 总时长最多为 20 秒,超时则会中断 Dump 过程,仅输出已经收集到的进程信息

  • 收集各相关进程 CPU 使用情况

  • 输出 ANR Info 到 Logcat

  • 输出 ANR Info 到 DropBox

  • 如果是后台 ANR,系统会直接把进程杀掉,如果是前台 ANR,系统会设置进程 ANR 错误状态,显示 ANR 弹框

ANR 监控

感知 ANR

Linux 内核可以通过使用 sigaction 方法注册 signal handler 进行异步监听 SIGQUIT信号

  • 通过 pthread_sigmask 或者 sigprocmask 把 SIGQUIT 设置为 UNBLOCK

sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &sigSet, nullptr);
  • 通过 sigaction 方法,建立一个 signal handler 进行异步监听

void signalHandler(int sig, siginfo_t* info, void* uc) {
    if (sig == SIGQUIT) {
        //Got An ANR
    }
}

struct sigaction sa;
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
sigaction(SIGQUIT, &sa, nullptr);
  • 在 signal handler 里面重新向 signal catcher 线程发送一个 SIGQUIT 信号,让系统正常走 ANR 流程

int tid = getSignalCatcherThreadId(); //遍历/proc/[pid]目录,找到SignalCatcher线程的tid
tgkill(getpid(), tid, SIGQUIT);

获取 ANR Trace 信息

  • 通过 Native PLT Hook 方式 hook socket open / connect / write 方法获取系统 dump ANR trace

  • 只会在接收到SIGQUIT信号后(重新发送SIGQUIT信号给Signal Catcher前)进行 hook

  • 只处理 signal catcher 线程 open / connect 后的第一次 write

int (*original_connect)(int __fd, const struct sockaddr* __addr, socklen_t __addr_length);
int my_connect(int __fd, const struct sockaddr* __addr, socklen_t __addr_length) {
    if (strcmp(__addr->sa_data, "/dev/socket/tombstoned_java_trace") == 0) {
        isTraceWrite = true;
        signalCatcherTid = gettid();
    }
    return original_connect(__fd, __addr, __addr_length);
}

int (*original_open)(const char *pathname, int flags, mode_t mode);
int my_open(const char *pathname, int flags, mode_t mode) {
    if (strcmp(pathname, "/data/anr/traces.txt") == 0) {
        isTraceWrite = true;
        signalCatcherTid = gettid();
    }
    return original_open(pathname, flags, mode);
}

ssize_t (*original_write)(int fd, const void* const __pass_object_size0 buf, size_t count);
ssize_t my_write(int fd, const void* const buf, size_t count) {
    if(isTraceWrite && signalCatcherTid == gettid()) {
        isTraceWrite = false;
        signalCatcherTid = 0;
        char *content = (char *) buf;
        printAnrTrace(content);
    }
    return original_write(fd, buf, count);
}

void hookAnrTraceWrite() {
    int apiLevel = getApiLevel();
    if (apiLevel < 19) {
        return;
    }
    if (apiLevel >= 27) {
        plt_hook("libcutils.so", "connect", (void *) my_connect, (void **) (&original_connect));
    } else {
        plt_hook("libart.so", "open", (void *) my_open, (void **) (&original_open));
    }

    if (apiLevel >= 30 || apiLevel == 25 || apiLevel ==24) {
        plt_hook("libc.so", "write", (void *) my_write, (void **) (&original_write));
    } else if (apiLevel == 29) {
        plt_hook("libbase.so", "write", (void *) my_write, (void **) (&original_write));
    } else {
        plt_hook("libart.so", "write", (void *) my_write, (void **) (&original_write));
    }
}

ANR Trace

// 进程号、时间戳、时区
----- pid 24078 at 2025-02-19 15:39:30.671716696+0800 -----
// 进程名
Cmd line: com.miravia.android.dev
// os 构建指纹
Build fingerprint: 'Xiaomi/lisa_global/lisa:13/TKQ1.220829.002/V14.0.7.0.TKOMIXM:user/release-keys'
// 处理器架构
ABI: 'arm64'
// 构建类型 debug|optimized
Build type: optimized
Debug Store: 
// 总时间(Sum): 109.525毫;99%置信区间(99% C.I.);大多数时间挂起都发生在 4 微秒到 24299.520 微秒这一时间范围内;平均时间(Avg): 挂起的平均时间是897.745微秒;最大时间(Max): 最大挂起时间是28399微秒
suspend all histogram:	Sum: 109.525ms 99% C.I. 4us-24299.520us Avg: 897.745us Max: 28399us
// 应用包含162个活跃线程
DALVIK THREADS (162):
// 主线程状态和堆栈
"main" prio=5 tid=1 Native
  | group="main" sCount=1 ucsCount=0 flags=1 obj=0x71cd4ab0 self=0xb400007c72442c00
  | sysTid=24078 nice=0 cgrp=background sched=0/0 handle=0x7d1dfc44f8 // 线程属于后台控制组
  | state=S schedstat=( 1071297876235 50447682972 415777 ) utm=101560 stm=5569 core=0 HZ=100 // 当前线程状态为 "S"(睡眠)
  | stack=0x7ff8e30000-0x7ff8e32000 stackSize=8188KB // 堆栈地址范围和大小
  | held mutexes=  // 持有的互斥锁,当前没有持有任何互斥锁
  native: #00 pc 000e1b9c  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+12) (BuildId: 449f781894033dce6346794a1ee593e0)
  native: #01 pc 00017e40  /system/lib64/libutils.so (android::Looper::pollInner+192) (BuildId: 104125701f0f8a41374b9998e94209e9)
  native: #02 pc 00017d1c  /system/lib64/libutils.so (android::Looper::pollOnce+116) (BuildId: 104125701f0f8a41374b9998e94209e9)
  native: #03 pc 001660f8  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce+48) (BuildId: 1809a12249dabac956d486048631a652)
  native: #04 pc 00384370  /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #05 pc 0036db74  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #06 pc 003671e4  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+1928) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #07 pc 0076e170  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #08 pc 003869d8  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #09 pc 001ef900  /system/framework/framework.jar (android.os.MessageQueue.next)
  native: #10 pc 00359650  /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +428) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #11 pc 00367a78  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+4124) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #12 pc 0076e170  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #13 pc 003869d8  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #14 pc 001ee83c  /system/framework/framework.jar (android.os.Looper.loopOnce)
  native: #15 pc 00359650  /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +428) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #16 pc 00367a78  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+4124) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #17 pc 0076e170  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #18 pc 003869d8  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #19 pc 001ef02c  /system/framework/framework.jar (android.os.Looper.loop)
  native: #20 pc 00359650  /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +428) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #21 pc 00367a78  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+4124) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #22 pc 0076e170  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #23 pc 003869d8  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #24 pc 001c760c  /system/framework/framework.jar (android.app.ActivityThread.main)
  native: #25 pc 003589dc  /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+1932) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #26 pc 00384498  /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+88) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #27 pc 0036de40  /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+640) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #28 pc 003698f4  /apex/com.android.art/lib64/libart.so (_jobject* art::InvokeMethod<8>+732) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #29 pc 006c6738  /apex/com.android.art/lib64/libart.so (art::Method_invoke +32) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #30 pc 00384370  /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #31 pc 0036db74  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #32 pc 003671e4  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+1928) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #33 pc 0076e170  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #34 pc 003869d8  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #35 pc 004f69e8  /system/framework/framework.jar (com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run)
  native: #36 pc 003589dc  /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+1932) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  native: #37 pc 00384498  /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+88) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.MessageQueue.next(MessageQueue.java:341)
  at android.os.Looper.loopOnce(Looper.java:168)
  at android.os.Looper.loop(Looper.java:299)
  at android.app.ActivityThread.main(ActivityThread.java:8252)
  at java.lang.reflect.Method.invoke(Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)
DumpLatencyMs: 39.0555

// ReferenceQueueDaemon 线程状态和堆栈
"ReferenceQueueDaemon" daemon prio=5 tid=6 Waiting
  | group="system" sCount=1 ucsCount=0 flags=1 obj=0x15767058 self=0xb400007c423c5000
  | sysTid=24086 nice=4 cgrp=background sched=0/0 handle=0x7c55719cb0
  | state=S schedstat=( 413060873 112656828 239 ) utm=36 stm=4 core=2 HZ=100
  | stack=0x7c55616000-0x7c55618000 stackSize=1039KB
  | held mutexes=
  at java.lang.Object.wait(Native method)
  - waiting on <0x0f0c1d8f> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
  at java.lang.Object.wait(Object.java:405)
  at java.lang.Object.wait(Object.java:543)
  at java.lang.Daemons$ReferenceQueueDaemon.runInternal(Daemons.java:251)
  - locked <0x0f0c1d8f> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
  at java.lang.Daemons$Daemon.run(Daemons.java:131)
  at java.lang.Thread.run(Thread.java:1012)
DumpLatencyMs: 544.047
// 其他线程状态和堆栈
......

ANR 监控痛点及改进方案

误报

发生ANR的进程一定会收到 SIGQUIT 信号,但是收到 SIGQUIT 信号的进程并不一定发生了ANR

  • 其他进程的 ANR:监控到SIGQUIT时,可能是监听到了其他进程产生的ANR,从而产生误报

  • 非ANR发送 SIGQUIT:开发者和厂商都可以很容易的发送一个SIGQUIT(Java 层调用android.os.Process.sendSignal 方法;Native 层调用 kill 或者 tgkill 方法)

NOT_RESPONDING 标识

进程处于NOT_RESPONDING的状态可以确认该进程发生了ANR

  • 监控到 SIGQUIT 信号后,在 20 秒内(20 秒是ANR dump 的 timeout 时间)不断轮询自己进程是否有 NOT_RESPONDING 标识,一旦发现有这个标识,那么马上就可以认定发生了一次 ANR

private static boolean checkErrorState() {
    try {
        Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
        ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
        if (procs == null) return false;
        for (ActivityManager.ProcessErrorStateInfo proc : procs) {
            if (proc.pid != android.os.Process.myPid()) continue;
            if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
            return true;
        }
        return false;
    } catch (Throwable t){
        MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
    }
    return false;
}

厂商自定义 ANR

《手淘黑科技ANRHacker # MIUI自定义ANR》中提到主线程卡顿超过2.5s就会有一个系统 com.miui.daemon进程向APP发出 Signal 3(ANR信号),不仅是 MIUI 系统,其他国内厂商的系统也会有类似的逻辑,这会对现有CrashSDK的ANR监控有很大的干扰,其中一个表现是 EMAS 中 Unknown 类型 ANR 问题占比较大。

miui.mqsas.sdk.MQSEventManagerDelegate

如果 MQSEventManagerDelegate.getMQSService 返回 null 则不会上报 AppScoutEvent。

从上面的代码可以分析出:

  1. 需要强制将MQSEventManagerDelegate#mService设置为 null,可以通过反射实现。

  2. 需要让 miui.mqsas.IMQSService.asInterface 这个方法一直返回 null,可以传入 null 实现。

剩下的关键点在于如何让 ServiceManager.getService(MQS_SERVICE_NAME)强制返回 null?答案是通过 Java 动态代理代理 ServiceManager.getService 方法,然后判断入参值为MQS_SERVICE_NAME时直接返回 null

App 退出原因

发生ANR的进程并不一定会被设置为NOT_RESPONDING状态,如后台ANR,闪退 ANR。

Android 11 及以上版本提供了退出原因查询接口:ActivityManager#getHistoricalProcessExitReasons,其返回的是一个 ApplicationExitInfo 的列表,为系统保存的多次 App 退出的原因

  • 进程退出原因分类

原因码

原因

主要方式

描述信息举例

备注

0

UNKNOWN

未知

iAwareR[LowMem](started-services) -1ms

通常为低内存被杀

1

EXIT_SELF

System.exit() 自杀

null

status 表明 exit code

2

SIGNALED

收到 KILL 信号,

包括通过Process.kill() 自杀

null

status 表明具体信号值

3

LOW_MEMORY

系统内存不足被杀

cleaner

4

APP CRASH(EXCEPTION)

Java 异常

crash

该原因大部分都是 Remote 异常被系统强杀,Java异常只占小部分

Remote 异常被系统强杀

schedulecrash for 'can't deliver broadcast' failed

5

APP CRASH(NATIVE)

Native 异常

crash

status 表明异常的信号值

6

ANR

后台 ANR

bg anr

前台 ANR 用户选择杀进程

user request after error

7

INITIALIZATION FAILURE

ContentProvider onCreate耗时

timeout publishing content providers

App 所有的 ContentProvider的 onCreate 必须在 10 秒内执行完成

8

PERMISSION CHANGE

运行过程中权限丢失

one-time permission revoked

9

EXCESSIVE RESOURCE USAGE

资源过度使用

excessive cpu 8480 during 301823 dur=60377353 limit=2.0

10

USER_REQUESTED

多任务用户手动杀

remove task

进程被杀的主要原因之一

覆盖安装杀

stop com.alibaba.android.rimet due to installPackageLI

adb 命令杀

stop com.alibaba.android.dingtalk due to from pid 12082by app

设置-应用管理-强行停止

stop com.alibaba.android.dingtalk due to from pid 11078by app

11

USER STOPPED

多用户切换

stop user 1000

12

DEPENDENCY DIED

ContentProvider依赖杀

depends on provider com.google.android.gms/.phenotype.provider.ConfigurationProvider in dying proc com.google.android.gms.persistent (adj 0)

13

OTHRE KILLS BY SYSTEM

其他原因被系统杀掉

empty for too long

通常情况是停留在后台被系统回收

  • 前后台状态

取值

名字

意义

100

foreground

前台(有 Activity)

125

foreground_service

前台服务(比如听音乐)

200

visible

可见(比如小窗,悬浮窗)

230

perceptible

用户可感知但不在前台(比如后台播放视频)

300

service

有后台服务(可能会系统清理)

325

top_sleeping

熄屏状态(网络交互,后台服务等都会受限)

350

cant_save_state

后台状态不可能被杀(系统 App 才可能有该状态)

400

cached

缓存状态(无 Activity 存活)

500

empty

空(已过期)

1000

gone

进程不存在

归因

由于系统判断出现 ANR 到系统通知进程执行 Dump trace 之间存在一定的时间差,期间进程主线程依然在执行 MessageQueue 消息 或 IdleHandler 任务,这往往会导致 ANR trace 抓取的堆栈并不是耗时任务的堆栈

MessageQueue 消息

通过代理 Looper 中的 mLogging 对象,监控消息消息的起始点和结束点,然后判断消息耗时是否超时

boolean proxyLogging() {
    try {
        // Android 10 以下代理Looper.mLogging
        Field mLogging = ReflectUtils.getHiddenField(Looper.class, "mLogging");
        if (mLogging == null) {
            return false;
        }
        Printer printer = (Printer) mLogging.get(Looper.getMainLooper());
        if (printer instanceof MainLooperTaskMonitor) {
            return true;
        }
        Looper.getMainLooper().setMessageLogging(this);
        originPrinter = printer;
        return true;
    } catch (Throwable e) {
        TBPLogger.error(TAG, e, "proxyLogging exception");
    }
    return false;
}

// Android 10 及以上代理 Looper.sObserver
boolean proxyLooperObserver() {
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            Class<?>[] interfaces = new Class<?>[]{Class.forName("android.os.Looper$Observer")};
            Object proxyInstance = Proxy.newProxyInstance(MainLooperTaskMonitor.class.getClassLoader(), interfaces, this);
            Field sObserverField = ReflectUtils.getHiddenField(Looper.class, "sObserver");
            if (sObserverField == null) {
                return false;
            }
            originObserver = CommonUtils.replaceFieldValue(Looper.class, "sObserver", null, proxyInstance);
            observerProxied = true;
            return true;
        }
    } catch (Throwable e) {
        TBPLogger.error(TAG, e, "proxyLooperObserver exception");
        observerProxied = false;
    }
    return false;
}

IdleHandler 任务

  • 通过反射替换 MessageQueue 原来的 mIdleHandlers 为自定义的 ArrayList

  • 在自定义的 ArrayList 里重写 add 和 remove 方法,用自定义的 IdleHandlerProxy 包裹住业务方传入的 IdleHandler

  • MessageQueue 每次执行 queueIdle 方法,都会执行到我们的 IdleHandlerProxy

定时堆栈采集

如上图所示,堆栈采样的时间对齐方案具体实现如下:

  • 独立的堆栈采样子线程负责堆栈的超时抓取

  • 基于主线程的任务监听机制,每个任务的开始和结束都会告知到堆栈抓取模块

  • 超时任务触发堆栈抓取的前提条件是:当前最新的任务耗时时长超过最低超时时间

  • 执行完堆栈抓取后,会对超时时长进行渐进,再丢一个超时任务。直到当前任务执行完成

项目实践

ANR 误报消除

小米系统自定义 ANR 消除 (1.57.0 上线)

  • 大盘 Android ANR 率 0.80% -> 0.20% (相对降低 -75%)

  • 小米系统自定义 ANR 消除预估相对降低 -23% (小米系统 ANR 占比 46% * 50% 噪音)

  • ANR 率相对降低另外的 52% 可能是 UC 内核升级引起的;

后续方向

  1. 增强堆栈采集 ANR 归因能力

  2. Input dispatching timed out 相关 ANR 治理

  3. SharedPreferences 相关 ANR 治理


评论