稳定性性能系列之二——ANR机制深度解析:从触发到上报

引言:那个让人又爱又恨的ANR Dialog

还记得第一次看到ANR Dialog吗?

"应用XX无响应,是否关闭?"

作为用户,这个对话框让人抓狂------应用卡住了,什么都做不了。但作为开发者,这个对话框却是救命稻草------至少它告诉你出问题了,还给你留下了traces.txt这个"作案现场"。

我曾经遇到过一个让人头疼的ANR:用户在设置界面切换WiFi时,整个系统界面就卡死了。看logcat,发现主线程在等一个锁;再看traces.txt,发现持锁的线程正在进行Binder调用;继续追踪,原来是ContentObserver注册过多导致的Binder资源耗尽...这就像一场侦探游戏,而ANR Dialog是案件的起点。

本文将带你深入ANR机制的内部,从Input事件分发到超时检测,从traces信息收集到Dialog弹出,完整剖析ANR的全生命周期。读完本文,你将能够:

  1. 理解ANR的4种类型及其超时时间
  2. 掌握InputDispatcher如何检测Input ANR
  3. 了解ActivityManagerService如何处理ANR
  4. 学会分析traces.txt和找到根因
  5. 构建ANR监控和预防机制

准备好了吗?让我们揭开ANR机制的神秘面纱!


一、ANR基础概念

1.1 什么是ANR?

ANR (Application Not Responding) 是Android系统的一种保护机制。当应用在规定时间内没有响应用户操作或系统事件时,系统会认为应用"死了",弹出ANR对话框让用户选择是继续等待还是强制关闭。

打个比方:ANR就像餐厅的"上菜超时提醒"。你点了一道菜,如果厨房5分钟还没上菜,服务员就会过来询问"要不要继续等,还是换一道菜"。这避免了你无限期地等下去。

ANR的核心作用:

  • 保护用户体验: 避免应用长时间无响应
  • 及时发现问题: 通过traces.txt记录"作案现场"
  • 系统自我保护: 防止单个应用拖垮整个系统

1.2 ANR的4种类型

Android系统对不同类型的事件设置了不同的超时时间:

ANR类型 超时时间 触发场景 检测位置
Input ANR 5秒 用户触摸/按键事件未被处理 InputDispatcher
Broadcast ANR 前台10秒 / 后台60秒 BroadcastReceiver的onReceive()未执行完 ActivityManagerService
Service ANR 前台20秒 / 后台200秒 Service的生命周期方法未执行完 ActivityManagerService
ContentProvider ANR 10秒 ContentProvider发布超时 ActivityManagerService

为什么超时时间不一样?

这是基于用户体验和系统响应性的权衡:

  • Input ANR最短(5秒): 用户操作必须得到快速反馈,否则会认为设备坏了
  • Broadcast ANR中等(10秒/60秒): 广播接收器本应该快速执行,但允许一定的处理时间
  • Service ANR较长(20秒/200秒): 后台服务可以处理耗时操作,给予更多宽容
  • 后台进程超时更长: 后台操作不影响前台体验,可以等待更久

1.3 ANR vs Freeze vs Watchdog

经常有人混淆这几个概念,让我们对比一下:

特性 ANR Freeze Watchdog
检测对象 应用进程 应用进程 System Server
检测维度 特定事件响应 整体响应性 核心线程健康
超时时间 5-200秒 通常小于秒 30-60秒
后果 弹出ANR Dialog 可能触发ANR 系统重启
作用域 单个应用 单个应用 整个系统

形象的比喻:

  • ANR: 餐厅服务员检查你点的菜是否上齐了
  • Freeze: 餐厅经理监控服务员是否在工作
  • Watchdog: 消防队定时巡查餐厅的消防系统是否正常

二、Input ANR的完整流程

Input ANR是最常见也是最典型的ANR类型。让我们通过一个用户点击按钮的场景,完整走一遍ANR的检测流程。

2.1 整体流程概览

2.2 InputReader:事件的源头

InputReader运行在一个独立的线程中,负责从内核读取输入事件:

cpp 复制代码
// frameworks/native/services/inputflinger/reader/InputReader.cpp

void InputReader::loopOnce() {
    // 1. 从/dev/input/eventX读取原始事件
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    // 2. 处理每个事件
    for (size_t i = 0; i < count; i++) {
        const RawEvent& rawEvent = mEventBuffer[i];

        // 3. 根据设备类型(触摸屏、键盘等)转换为Android事件
        processEventsLocked(rawEvent);
    }

    // 4. 将处理好的事件交给InputDispatcher
    mQueuedListener->flush();
}

关键点:

  • InputReader是事件的"搬运工",只负责读取和初步处理
  • 它不关心事件是否被应用处理,只管往前送
  • 真正的超时检测发生在InputDispatcher

2.3 InputDispatcher:超时检测的核心

InputDispatcher是ANR检测的"心脏"。让我们看看它如何检测超时:

cpp 复制代码
// frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

// 分发事件的核心逻辑
void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;

    // 1. 从队列中取出待分发的事件
    if (mPendingEvent == nullptr) {
        mPendingEvent = mInboundQueue.dequeueAtHead();
    }

    // 2. 找到事件的目标Window
    std::vector<InputTarget> inputTargets;
    int32_t injectionResult = findFocusedWindowTargetsLocked(
            currentTime, mPendingEvent, inputTargets, nextWakeupTime);

    // 3. 分发事件到目标Window
    if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
        dispatchEventLocked(currentTime, mPendingEvent, inputTargets);
    }

    // 4. 检查是否有连接超时
    nsecs_t nextAnrCheck = checkUnresponsiveConnectionsLocked(currentTime);
    if (nextAnrCheck < nextWakeupTime) {
        nextWakeupTime = nextAnrCheck;
    }
}

超时检测的关键逻辑:

cpp 复制代码
// 检查连接是否响应
nsecs_t InputDispatcher::checkUnresponsiveConnectionsLocked(nsecs_t currentTime) {
    nsecs_t nextAnrCheck = LONG_LONG_MAX;

    for (const auto& [token, connection] : mConnectionsByToken) {
        // 检查这个连接的等待队列
        if (connection->waitQueue.empty()) {
            continue;
        }

        // 获取队头事件
        DispatchEntry* head = connection->waitQueue.head;
        nsecs_t eventTime = head->eventEntry->eventTime;

        // 计算等待时间
        nsecs_t waitDuration = currentTime - eventTime;

        // 如果超过5秒(5000ms),触发ANR
        if (waitDuration > 5000ms) {
            onAnrLocked(connection);
        } else {
            // 记录下次检查时间
            nsecs_t timeoutTime = eventTime + 5000ms;
            if (timeoutTime < nextAnrCheck) {
                nextAnrCheck = timeoutTime;
            }
        }
    }

    return nextAnrCheck;
}

精妙的设计:

  1. 事件入队列时记录时间戳 : 每个事件分发时,都会记录eventTime
  2. 定时检查队头事件: InputDispatcher会定期检查waitQueue的队头
  3. 计算等待时长 : waitDuration = currentTime - eventTime
  4. 超时触发ANR : 如果等待超过5秒,调用onAnrLocked()

为什么检查队头而不是队尾?

因为队头是最早发送的事件,如果队头都还没处理完,说明应用真的卡住了。这就像排队买票:如果队伍最前面的人还在办理,后面的人自然也走不了。

2.4 等待队列(waitQueue)的秘密

理解waitQueue是理解Input ANR的关键:

ini 复制代码
waitQueue的生命周期:

1. 事件发送前
   waitQueue: []

2. 事件发送给应用
   dispatchEntry入队
   waitQueue: [Event1]
   记录Event1.eventTime = T0

3. 应用处理事件(正常情况)
   应用调用finish()
   waitQueue: []  ← Event1被移除

4. 应用卡住(异常情况)
   T0 + 5秒后
   waitQueue: [Event1]  ← Event1还在!
   InputDispatcher检测到超时
   触发ANR

代码实现:

cpp 复制代码
// 应用处理完事件后调用
void InputDispatcher::finishDispatchCycleLocked(
        nsecs_t currentTime, sp<Connection> connection) {
    // 从waitQueue移除已处理的事件
    while (!connection->waitQueue.empty()) {
        DispatchEntry* dispatchEntry = connection->waitQueue.head;
        connection->waitQueue.dequeue(dispatchEntry);

        // 释放资源
        releaseDispatchEntry(dispatchEntry);

        // 如果这是最后一个事件,重置超时检查
        if (connection->waitQueue.empty()) {
            connection->lastEventTime = LLONG_MIN;
        }
    }
}

2.5 ANR分析示例

让我们看一个真实的Input ANR场景的分析简化:

场景: 用户在列表界面快速滑动,突然应用无响应

logcat日志:

csharp 复制代码
12-25 10:30:15.123  1234  1250 E InputDispatcher: channel 'e8d3a72 com.example.app/com.example.app.MainActivity (server)' ~ Channel is unresponsive! Waited 5008ms
12-25 10:30:15.234  1234  1250 I InputDispatcher: Delivering ANR to com.example.app

traces.txt片段:

ini 复制代码
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8 self=0x7a4e014c00
  | sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7b5a9f49a8
  | state=S schedstat=( 15678923456 8765432109 1234 ) utm=1500 stm=67 core=2 HZ=100
  at com.example.app.RecyclerAdapter.onBindViewHolder(RecyclerAdapter.java:150)
  - waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 15
  at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3500)
  at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:4064)
  at android.view.View.layout(View.java:19839)
  ...

根因分析:

  1. 现象 : 主线程被阻塞,状态是Blocked
  2. 位置 : 卡在RecyclerAdapter.onBindViewHolder()
  3. 原因 : 主线程在等待一个锁<0x0a1b2c3d>,这个锁被线程15持有
  4. 解决: 找到线程15在干什么,解开死锁

关键教训:

  • 永远不要在主线程中等待锁
  • RecyclerView的Adapter中不要做耗时操作
  • 复杂的数据处理应该在后台线程完成

三、Broadcast ANR的检测机制

相比Input ANR的"被动检测"(等事件处理完),Broadcast ANR采用"主动检测"(主动设置超时)。

3.1 广播分发流程

3.2 超时定时器的实现

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

private void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) {
    // 1. 设置超时时间
    final long timeout = r.isForeground() ?
            BROADCAST_FG_TIMEOUT : BROADCAST_BG_TIMEOUT;

    // 2. 发送延时消息
    mHandler.sendMessageDelayed(
            mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, r),
            timeout);

    // 3. 调用receiver的onReceive()
    app.thread.scheduleReceiver(...);
}

// Handler处理超时消息
@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case BROADCAST_TIMEOUT_MSG:
            BroadcastRecord r = (BroadcastRecord) msg.obj;
            // 触发ANR
            broadcastTimeoutLocked(r, true);
            break;
    }
}

// 广播执行完毕后取消超时
private void performReceiveLocked(ProcessRecord app, ...) {
    // 1. 执行完毕
    r.receiver = null;

    // 2. 取消超时消息
    mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, r);

    // 3. 继续处理下一个广播
    processCurBroadcastLocked(r, app);
}

关键点:

  • 使用Handler的延时消息实现超时检测
  • 如果在超时前完成,removeMessages()取消定时器
  • 如果超时,Handler收到消息,触发ANR

3.3 前台 vs 后台广播的区别

java 复制代码
// 超时时间定义
static final int BROADCAST_FG_TIMEOUT = 10 * 1000;  // 10秒
static final int BROADCAST_BG_TIMEOUT = 60 * 1000;  // 60秒

// 如何判断前台还是后台?
boolean isForeground() {
    // 1. 广播发送时指定了FLAG_RECEIVER_FOREGROUND
    if ((intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0) {
        return true;
    }

    // 2. 接收进程是前台进程
    if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
        return true;
    }

    // 3. 有序广播的当前接收者是前台
    if (ordered && curReceiver.priority > 0) {
        return true;
    }

    return false;
}

实战案例:

java 复制代码
// 案例:一个耗时的BroadcastReceiver
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // ❌ 错误:在主线程做耗时操作
        for (int i = 0; i < 1000000; i++) {
            // 大量计算
        }
        // 如果超过10秒,触发ANR
    }
}

// ✅ 正确做法:使用goAsync()
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 1. 获取PendingResult
        final PendingResult result = goAsync();

        // 2. 在子线程处理
        new Thread(() -> {
            try {
                // 耗时操作
                doHeavyWork();
            } finally {
                // 3. 完成后通知系统
                result.finish();
            }
        }).start();
    }
}

goAsync()的作用:

  • 告诉系统"我需要异步处理,别急着超时"
  • 系统会延长超时时间(但仍有上限)
  • 必须在完成后调用result.finish()

四、Service ANR的检测机制

Service ANR的检测逻辑与Broadcast类似,但更复杂,因为Service有多个生命周期方法需要监控。

4.1 Service生命周期的超时监控

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

// Service启动超时检测
private final void bringUpServiceLocked(ServiceRecord r, ...) {
    // 1. 设置超时时间
    final long timeout = r.isForeground() ?
            SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT;

    // 2. 发送超时消息
    mAm.mHandler.sendMessageDelayed(
            mAm.mHandler.obtainMessage(SERVICE_TIMEOUT_MSG, r),
            timeout);

    // 3. 启动Service
    app.thread.scheduleCreateService(r, ...);
}

// Service生命周期方法执行完毕
private void serviceDoneExecutingLocked(ServiceRecord r, ...) {
    // 1. 取消超时消息
    mAm.mHandler.removeMessages(SERVICE_TIMEOUT_MSG, r);

    // 2. 更新Service状态
    r.executeNesting--;
    if (r.executeNesting <= 0) {
        r.executing = false;
    }
}

监控的生命周期方法:

  • onCreate(): Service创建
  • onStartCommand(): Service启动
  • onBind(): Service绑定
  • onUnbind(): Service解绑
  • onDestroy(): Service销毁

4.2 前台Service的特殊处理

java 复制代码
// 前台Service的超时时间更短
static final int SERVICE_TIMEOUT = 20 * 1000;       // 20秒
static final int SERVICE_BACKGROUND_TIMEOUT = 200 * 1000;  // 200秒

// 启动前台Service的正确姿势
public class MyService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();

        // 在onCreate()中尽快调用startForeground()
        // 否则可能触发ANR
        Notification notification = createNotification();
        startForeground(NOTIFICATION_ID, notification);

        // 然后再做其他初始化
        initializeResources();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 如果有耗时操作,放到子线程
        new Thread(() -> {
            doHeavyWork();
        }).start();

        return START_STICKY;
    }
}

为什么前台Service超时时间短?

因为前台Service通常与用户当前的操作相关(如音乐播放、导航),必须快速启动,否则影响用户体验。

4.3 IntentService的超时问题

IntentService是一个特殊的Service,它自带工作线程:

java 复制代码
// IntentService的内部实现
public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;

    @Override
    public void onCreate() {
        super.onCreate();
        // 创建工作线程
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 将任务发送到工作线程
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
        return START_REDELIVER_INTENT;
    }

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            // 在工作线程调用onHandleIntent()
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    // 子类实现这个方法
    protected abstract void onHandleIntent(Intent intent);
}

重要细节:

  • onStartCommand()在主线程,但很快返回
  • onHandleIntent()在工作线程,可以耗时
  • 不会触发Service ANR,因为主线程方法都很快

但是要注意onCreate()!

java 复制代码
// ❌ 错误:在onCreate()中初始化耗时资源
public class MyIntentService extends IntentService {
    @Override
    public void onCreate() {
        super.onCreate();
        // 这里仍然在主线程!
        loadLargeDatabase();  // 可能触发ANR
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 这里在工作线程,可以耗时
        processData();
    }
}

// ✅ 正确:耗时初始化也放到工作线程
public class MyIntentService extends IntentService {
    @Override
    public void onCreate() {
        super.onCreate();
        // 只做轻量级初始化
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 第一次调用时再初始化
        if (!initialized) {
            loadLargeDatabase();
            initialized = true;
        }
        processData();
    }
}

五、ANR信息的收集与上报

当InputDispatcher或AMS检测到ANR后,会触发一系列的信息收集操作。这些信息对于分析ANR至关重要。

5.1 ANR触发和信息搜集流程

5.2 appNotResponding()的实现

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java

public void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
        String parentShortComponentName, WindowProcessController parentProcess,
        boolean aboveSystem, String annotation) {

    // 1. 防止重复ANR
    synchronized (mService) {
        if (mNotResponding) {
            Slog.w(TAG, "App already notresponding: " + this);
            return;
        }
        mNotResponding = true;

        // 2. 记录ANR信息
        mNotRespondingReport = generateErrorReport(
                annotation, null, null, null, null, null);
    }

    // 3. 收集CPU使用情况
    updateCpuStatsNow();

    // 4. 输出到logcat
    Slog.e(TAG, "ANR in " + processName + " (" + activityShortComponentName + ")");
    Slog.e(TAG, "PID: " + pid);
    Slog.e(TAG, "Reason: " + annotation);
    Slog.e(TAG, "Load: " + loadAverage);
    Slog.e(TAG, "CPU usage from " + cpuUsageStart + "ms to " + cpuUsageEnd + "ms later:");
    Slog.e(TAG, cpuUsageString);

    // 5. dump所有相关线程堆栈
    ArrayList<Integer> firstPids = new ArrayList<>(5);
    firstPids.add(pid);  // ANR进程
    firstPids.add(Process.myPid());  // System Server

    File tracesFile = ActivityManagerService.dumpStackTraces(
            firstPids, processCpuTracker, ...);

    // 6. 写入DropBox
    mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
            parentShortComponentName, parentProcess, annotation,
            cpuUsageString, tracesFile, null);

    // 7. 弹出ANR Dialog
    Message msg = Message.obtain();
    msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
    msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);
    mService.mUiHandler.sendMessage(msg);
}

5.3 traces.txt的生成

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public static File dumpStackTraces(ArrayList<Integer> firstPids,
        ProcessCpuTracker processCpuTracker, ...) {

    // 1. 创建traces文件
    File tracesFile = new File("/data/anr/traces.txt");

    // 2. dump第一批进程(ANR进程和System Server)
    for (int pid : firstPids) {
        dumpJavaTracesTombstoned(pid, tracesFile, timeout);
    }

    // 3. dump Native进程(如果ANR进程有native线程)
    if (DEBUG_ANR) {
        dumpNativeBacktraceToFile(pid, tracesFile);
    }

    // 4. dump其他重要进程
    ArrayList<Integer> extraPids = new ArrayList<>();

    // 4.1 最近使用的3个应用
    if (lastPids != null) {
        for (int i = 0; i < lastPids.size() && i < 3; i++) {
            extraPids.add(lastPids.get(i));
        }
    }

    // 4.2 所有persistent进程
    synchronized (mPidsSelfLocked) {
        for (int i = 0; i < mPidsSelfLocked.size(); i++) {
            ProcessRecord r = mPidsSelfLocked.valueAt(i);
            if (r.persistent) {
                extraPids.add(r.pid);
            }
        }
    }

    // 4.3 dump这些进程
    for (int pid : extraPids) {
        dumpJavaTracesTombstoned(pid, tracesFile, timeout);
    }

    return tracesFile;
}

traces.txt的结构:

ini 复制代码
----- pid 12345 at 2024-12-25 10:30:15 -----
Cmd line: com.example.app
Build fingerprint: 'google/sdk_gphone_x86_64/generic_x86_64:11/RSR1.201013.001/6903271:user/release-keys'
ABI: 'x86_64'
...

"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8
  | sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7b5a9f49a8
  | state=S schedstat=( 15678923456 8765432109 1234 ) utm=1500 stm=67
  at com.example.app.MainActivity.onClick(MainActivity.java:150)
  - waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 15
  at android.view.View.performClick(View.java:7125)
  ...

"Thread-15" prio=5 tid=15 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x13579bdf
  | sysTid=12360 nice=0 cgrp=default sched=0/0 handle=0x7a4e028800
  at android.os.BinderProxy.transactNative(Native Method)
  - locked <0x0a1b2c3d> (a java.lang.Object)
  at android.os.BinderProxy.transact(Binder.java:1129)
  ...

"Binder:12345_2" prio=5 tid=12 Native
  ...

----- end 12345 -----

关键信息解读:

  • Cmd line: 进程名称
  • tid: 线程ID
  • 状态: Blocked/Native/Waiting/Runnable
  • 堆栈: Java方法调用链
  • 锁信息 : waiting to lock <地址>locked <地址>

5.4 DropBox:ANR的"黑匣子"

DropBox是Android的系统日志服务,专门用于存储系统异常事件:

java 复制代码
// 写入DropBox
void addErrorToDropBox(String eventType, ProcessRecord process,
        String processName, String activityShortComponentName, ...) {

    // 1. 构建DropBox条目
    StringBuilder sb = new StringBuilder(1024);
    sb.append("Process: ").append(processName).append("\n");
    sb.append("PID: ").append(process.pid).append("\n");
    sb.append("Reason: ").append(annotation).append("\n");
    sb.append("Load: ").append(loadAverage).append("\n");
    sb.append(cpuUsageString);

    // 2. 添加traces内容
    if (tracesFile != null) {
        sb.append("\n\n*** TRACES ***\n");
        sb.append(FileUtils.readTextFile(tracesFile, DROPBOX_MAX_SIZE, "..."));
    }

    // 3. 写入DropBox
    DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
    dbox.addText(eventType, sb.toString());
}

查看DropBox内容:

bash 复制代码
# 列出所有ANR记录
adb shell dumpsys dropbox --print anr

# 导出最近的ANR
adb shell dumpsys dropbox --print anr > anr_dropbox.txt

# 清空DropBox
adb shell dumpsys dropbox --wipe

DropBox vs traces.txt:

特性 DropBox traces.txt
存储位置 /data/system/dropbox /data/anr/
存储格式 结构化条目 纯文本
容量限制 有限(会滚动删除) 单个文件
包含信息 ANR+CPU+内存 仅线程堆栈
查看方式 dumpsys dropbox adb pull

六、ANR Dialog的展示

ANR检测和信息收集完成后,最终会弹出一个对话框让用户决定如何处理。

6.1 ANR Dialog的类型

Android有两种ANR Dialog:

1. 应用ANR Dialog(普通应用):

css 复制代码
─────────────────────────────
│  应用无响应                  │
│                            │
│  "XX"未响应。               │
│                            │
│  您要关闭它吗?               │
│                            │
│  [等待]  [关闭应用]          │
─────────────────────────────

2. 系统ANR Dialog(系统应用或System Server):

css 复制代码
─────────────────────────────
│  系统未响应                 │
│                            │
│  "System UI"未响应。        │
│                            │
│  [等待]  [确定]             │
─────────────────────────────

6.2 Dialog的实现

java 复制代码
// frameworks/base/services/core/java/com/android/server/am/AppNotRespondingDialog.java

final class AppNotRespondingDialog extends BaseErrorDialog {
    private final ActivityManagerService mService;
    private final ProcessRecord mProc;

    public AppNotRespondingDialog(ActivityManagerService service,
            Context context, Data data) {
        super(context);

        mService = service;
        mProc = data.proc;

        // 1. 设置Dialog标题
        setTitle(context.getText(com.android.internal.R.string.anr_title));

        // 2. 设置消息内容
        StringBuilder msg = new StringBuilder();
        msg.append(context.getString(com.android.internal.R.string.anr_activity_application,
                data.aInfo.loadLabel(context.getPackageManager()).toString(),
                data.proc.info.processName));

        setMessage(msg);

        // 3. 设置按钮
        setCancelable(false);

        // "等待"按钮
        setButton(DialogInterface.BUTTON_POSITIVE,
                context.getText(com.android.internal.R.string.wait),
                mHandler.obtainMessage(WAIT));

        // "关闭应用"按钮
        setButton(DialogInterface.BUTTON_NEGATIVE,
                context.getText(com.android.internal.R.string.force_close),
                mHandler.obtainMessage(FORCE_CLOSE));

        // "报告"按钮(可选)
        if (data.proc.errorReportReceiver != null) {
            setButton(DialogInterface.BUTTON_NEUTRAL,
                    context.getText(com.android.internal.R.string.report),
                    mHandler.obtainMessage(APP_INFO));
        }
    }

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case WAIT:
                    // 用户选择等待,关闭Dialog
                    mService.mUiHandler.sendEmptyMessage(DISMISS_DIALOG);
                    break;

                case FORCE_CLOSE:
                    // 用户选择关闭,杀死应用
                    mService.killAppAtUsersRequest(mProc, AppNotRespondingDialog.this);
                    break;

                case APP_INFO:
                    // 用户选择报告,打开应用信息页
                    mService.mUiHandler.obtainMessage(SHOW_APP_ERROR_REPORT, mProc)
                            .sendToTarget();
                    break;
            }
        }
    };
}

6.3 用户操作的后续处理

用户选择"等待":

java 复制代码
// 什么都不做,只是关闭Dialog
// ANR进程继续运行
// 如果还是没响应,可能再次触发ANR

用户选择"关闭应用":

java 复制代码
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
    synchronized (this) {
        // 1. 标记为用户主动关闭
        app.crashing = false;
        app.crashingReport = null;
        app.notResponding = false;
        app.notRespondingReport = null;

        // 2. 杀死进程
        Process.killProcess(app.pid);

        // 3. 清理进程记录
        handleAppDiedLocked(app, false, true);
    }
}

用户选择"报告"(某些系统):

java 复制代码
// 打开Feedback或BugReport应用
Intent intent = new Intent("android.intent.action.APP_ERROR");
intent.putExtra("error_type", "anr");
intent.putExtra("package_name", app.info.packageName);
intent.putExtra("process_name", app.processName);
intent.putExtra("pid", app.pid);
context.startActivity(intent);

七、ANR分析实战

理论讲了这么多,让我们通过几个真实案例,看看如何实战分析ANR。

7.1 案例1:主线程等锁导致的ANR

现象: 用户点击按钮后,应用卡住5秒,弹出ANR

logcat日志:

markdown 复制代码
12-25 10:30:15.123  1234  1250 E InputDispatcher: Application is not responding: AppWindowToken{e8d3a72 token=Token{b6c7d3e ActivityRecord{a9f8e1d u0 com.example/.MainActivity}}}
12-25 10:30:15.234  1234  1250 I ActivityManager: ANR in com.example.app (com.example.app/.MainActivity)
12-25 10:30:15.234  1234  1250 I ActivityManager: PID: 12345
12-25 10:30:15.234  1234  1250 I ActivityManager: Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.  Wait queue length: 1.)

traces.txt分析:

php 复制代码
----- pid 12345 at 2024-12-25 10:30:15 -----
Cmd line: com.example.app

"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8
  | sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x7b5a9f49a8
  | state=S schedstat=( 15678923456 8765432109 1234 ) utm=1500 stm=67
  at com.example.app.DataManager.getData(DataManager.java:45)
  - waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 15
  at com.example.app.MainActivity.onClick(MainActivity.java:100)
  at android.view.View.performClick(View.java:7125)
  at android.view.View.performClickInternal(View.java:7102)
  at android.view.View.access$3500(View.java:801)
  at android.view.View$PerformClick.run(View.java:27851)
  at android.os.Handler.handleCallback(Handler.java:883)
  at android.os.Handler.dispatchMessage(Handler.java:100)
  at android.os.Looper.loop(Looper.java:214)
  at android.app.ActivityThread.main(ActivityThread.java:7356)
  ...

"Thread-15" prio=5 tid=15 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x13579bdf
  | sysTid=12360 nice=0 cgrp=default sched=0/0 handle=0x7a4e028800
  | state=S schedstat=( 9876543210 5432109876 123 ) utm=900 stm=87
  at android.os.BinderProxy.transactNative(Native Method)
  at android.os.BinderProxy.transact(Binder.java:1129)
  - locked <0x0a1b2c3d> (a java.lang.Object)
  at android.content.IContentProvider$Stub$Proxy.query(IContentProvider.java:xxx)
  at android.content.ContentResolver.query(ContentResolver.java:xxx)
  at com.example.app.DataManager.loadFromDatabase(DataManager.java:80)
  at com.example.app.DataManager$1.run(DataManager.java:60)
  ...

分析步骤:

  1. 看主线程状态 : Blocked - 主线程被阻塞了
  2. 看阻塞位置 : DataManager.getData()第45行,在等一个锁<0x0a1b2c3d>
  3. 找持锁线程 : Thread-15持有这个锁(locked <0x0a1b2c3d>)
  4. 看持锁线程在干嘛 : 正在进行Binder调用transactNative(),查询ContentProvider
  5. 根本原因: 线程15持锁做耗时操作,主线程等不到锁

解决方案:

java 复制代码
// ❌ 原代码
public class DataManager {
    private final Object mLock = new Object();

    public void onClick() {
        // 主线程调用
        synchronized (mLock) {
            Data data = getData();
            updateUI(data);
        }
    }

    private Data getData() {
        // 还是在持锁状态!
        return loadFromDatabase();  // Binder调用,很慢
    }
}

// ✅ 修复后
public class DataManager {
    private final Object mLock = new Object();

    public void onClick() {
        // 异步加载
        new Thread(() -> {
            Data data;
            synchronized (mLock) {
                data = getData();
            }
            // 在主线程更新UI
            runOnUiThread(() -> updateUI(data));
        }).start();
    }
}

7.2 案例2:Binder调用超时导致的ANR

现象: 应用在后台,突然收到Broadcast ANR

logcat:

ini 复制代码
12-25 11:00:00.123  1234  1250 W BroadcastQueue: Timeout of broadcast BroadcastRecord{a1b2c3d u0 android.intent.action.BATTERY_CHANGED} - receiver=android.content.IIntentReceiver$Stub$Proxy@e4f5g6h
12-25 11:00:00.234  1234  1250 I ActivityManager: ANR in com.example.app (com.example.app/.MyReceiver)
12-25 11:00:00.234  1234  1250 I ActivityManager: Reason: Broadcast of Intent { act=android.intent.action.BATTERY_CHANGED }

traces.txt:

ini 复制代码
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x74e04dd8
  | sysTid=23456 nice=0 cgrp=default sched=0/0 handle=0x7b5a9f49a8
  | state=S schedstat=( 2345678901 1234567890 234 ) utm=200 stm=34
  at android.os.BinderProxy.transactNative(Native Method)
  at android.os.BinderProxy.transact(Binder.java:1129)
  at android.app.IActivityManager$Stub$Proxy.registerContentObserver(IActivityManager.java:xxxx)
  at android.content.ContentResolver.registerContentObserver(ContentResolver.java:xxxx)
  at com.example.app.MyReceiver.onReceive(MyReceiver.java:30)
  at android.app.ActivityThread.handleReceiver(ActivityThread.java:xxxx)
  ...

分析:

  1. ANR类型: Broadcast ANR
  2. 主线程状态 : Native - 正在执行Native方法
  3. 卡在哪里 : BinderProxy.transactNative() - Binder调用
  4. 在做什么: 注册ContentObserver
  5. 为什么慢: 可能System Server繁忙或Binder资源耗尽

根因: 在BroadcastReceiver中进行同步Binder调用,而System Server响应慢

解决方案:

java 复制代码
// ❌ 原代码
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 同步注册ContentObserver,可能很慢
        context.getContentResolver().registerContentObserver(
                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS),
                false,
                new ContentObserver(null) {
                    @Override
                    public void onChange(boolean selfChange) {
                        // ...
                    }
                });
    }
}

// ✅ 修复方案1:使用goAsync()
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final PendingResult result = goAsync();

        new Thread(() -> {
            try {
                context.getContentResolver().registerContentObserver(...);
            } finally {
                result.finish();
            }
        }).start();
    }
}

// ✅ 修复方案2:延迟注册
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // 启动Service延迟注册
        Intent serviceIntent = new Intent(context, RegistrationService.class);
        context.startService(serviceIntent);
    }
}

7.3 案例3:主线程做数据库操作

traces.txt:

less 复制代码
"main" prio=5 tid=1 Native
  | sysTid=34567 nice=-10
  at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
  at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:609)
  at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:820)
  at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
  at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:144)
  at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:134)
  at com.example.app.DatabaseHelper.queryAllRecords(DatabaseHelper.java:150)
  at com.example.app.MainActivity.onCreate(MainActivity.java:50)
  ...

分析:

  • 主线程在执行SQLite查询
  • getCount()会加载所有数据到Cursor
  • 数据量大时会很慢

解决方案:

java 复制代码
// ❌ 原代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 主线程查询数据库
    Cursor cursor = mDbHelper.queryAllRecords();
    int count = cursor.getCount();  // 触发实际查询
    processData(cursor);
}

// ✅ 使用AsyncTask(已废弃,仅作示例)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    new QueryTask().execute();
}

private class QueryTask extends AsyncTask<Void, Void, Cursor> {
    @Override
    protected Cursor doInBackground(Void... voids) {
        return mDbHelper.queryAllRecords();
    }

    @Override
    protected void onPostExecute(Cursor cursor) {
        processData(cursor);
    }
}

// ✅ 使用Kotlin Coroutines(推荐)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    lifecycleScope.launch {
        val cursor = withContext(Dispatchers.IO) {
            dbHelper.queryAllRecords()
        }
        processData(cursor)
    }
}

// ✅ 使用Room + LiveData(最佳实践)
@Dao
interface RecordDao {
    @Query("SELECT * FROM records")
    fun getAllRecords(): LiveData<List<Record>>
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    viewModel.records.observe(this) { records ->
        processData(records)
    }
}

八、ANR的预防和监控

预防胜于治疗。让我们看看如何在开发阶段就避免ANR,以及如何建立有效的监控体系。

8.1 开发阶段的预防措施

1. 使用StrictMode检测主线程违规

java 复制代码
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        if (BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()    // 检测磁盘读取
                    .detectDiskWrites()   // 检测磁盘写入
                    .detectNetwork()      // 检测网络操作
                    .detectCustomSlowCalls()  // 检测自定义慢调用
                    .penaltyLog()         // 输出到logcat
                    .penaltyDeath()       // 直接crash(开发阶段)
                    .build());

            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()  // 检测数据库泄漏
                    .detectLeakedClosableObjects() // 检测未关闭的对象
                    .penaltyLog()
                    .build());
        }
    }
}

2. 使用BlockCanary检测卡顿

java 复制代码
// 初始化BlockCanary
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        BlockCanary.install(this, new AppBlockCanaryContext()).start();
    }
}

// 自定义配置
public class AppBlockCanaryContext extends BlockCanaryContext {
    @Override
    public int provideBlockThreshold() {
        return 500;  // 超过500ms判定为卡顿
    }

    @Override
    public boolean displayNotification() {
        return BuildConfig.DEBUG;  // 仅debug环境显示通知
    }
}

3. 使用TraceView分析性能

java 复制代码
// 在可疑代码前后添加trace
Debug.startMethodTracing("my_trace");
// 可疑的代码
suspiciousMethod();
Debug.stopMethodTracing();

// 导出trace文件
adb pull /sdcard/Android/data/<package>/files/my_trace.trace .

// 使用Android Studio的Profiler分析

8.2 线上监控体系

1. ANR捕获和上报

java 复制代码
public class ANRWatchDog extends Thread {
    private final int mTimeout;
    private final Handler mHandler = new Handler(Looper.getMainLooper());

    public ANRWatchDog(int timeout) {
        super("ANR-WatchDog");
        this.mTimeout = timeout;
    }

    @Override
    public void run() {
        while (!isInterrupted()) {
            // 1. 记录当前时间
            final long startTime = System.currentTimeMillis();
            final AtomicLong executionTime = new AtomicLong();

            // 2. 向主线程发送任务
            mHandler.post(() -> {
                executionTime.set(System.currentTimeMillis());
            });

            // 3. 等待一段时间
            try {
                Thread.sleep(mTimeout);
            } catch (InterruptedException e) {
                return;
            }

            // 4. 检查任务是否执行
            long execTime = executionTime.get();
            if (execTime == 0 || System.currentTimeMillis() - execTime > mTimeout) {
                // 检测到ANR!
                onAnrDetected();
            }
        }
    }

    private void onAnrDetected() {
        // 1. 收集堆栈信息
        Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();

        // 2. 上报到服务器
        ANRReporter.report(stackTraces);
    }
}

// 启动监控
new ANRWatchDog(5000).start();

2. 使用Bugly等第三方平台

java 复制代码
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // 初始化Bugly
        CrashReport.initCrashReport(getApplicationContext(), "YOUR_APP_ID", BuildConfig.DEBUG);

        // 配置ANR监控
        CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(getApplicationContext());
        strategy.setAppReportDelay(10000);  // 延迟上报
        CrashReport.initCrashReport(getApplicationContext(), "YOUR_APP_ID", BuildConfig.DEBUG, strategy);
    }
}

3. 监控关键指标

java 复制代码
public class PerformanceMonitor {
    // 监控主线程消息处理时间
    public void monitorMainLooper() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            private static final String START = ">>>>> Dispatching";
            private static final String END = "<<<<< Finished";

            @Override
            public void println(String msg) {
                if (msg.startsWith(START)) {
                    // 消息开始处理
                    mStartTime = System.currentTimeMillis();
                } else if (msg.startsWith(END)) {
                    // 消息处理完毕
                    long duration = System.currentTimeMillis() - mStartTime;
                    if (duration > 100) {  // 超过100ms
                        // 记录慢消息
                        logSlowMessage(msg, duration);
                    }
                }
            }
        });
    }

    // 监控方法执行时间
    @Around("execution(* com.example.app..*(..))")
    public Object measureMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long duration = System.currentTimeMillis() - start;

        if (duration > 50) {  // 超过50ms
            String method = joinPoint.getSignature().toShortString();
            Log.w("Performance", method + " took " + duration + "ms");
        }

        return result;
    }
}

8.3 ANR优化的最佳实践

1. 主线程的黄金法则

markdown 复制代码
主线程只做三件事:
1. 更新UI
2. 分发事件
3. 启动异步任务

绝对不能做:
1. 磁盘I/O (读写文件、数据库)
2. 网络I/O (HTTP请求、Socket通信)
3. 复杂计算 (大数据处理、加密解密)
4. 同步Binder调用 (ContentProvider、System Service)
5. 等待锁 (synchronized、Lock)

2. 使用异步机制

场景 推荐方案 示例
简单异步任务 HandlerThread + Handler 后台数据处理
生命周期感知 ViewModel + LiveData UI数据加载
复杂协程 Kotlin Coroutines 网络请求+数据库
定时任务 WorkManager 定期同步
文件I/O Executors.newSingleThreadExecutor() 日志写入
数据库 Room + LiveData 本地数据访问

3. 优化Binder调用

java 复制代码
// ❌ 主线程同步调用ContentProvider
Cursor cursor = getContentResolver().query(uri, null, null, null, null);

// ✅ 使用ContentProviderOperation批量操作
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
ops.add(ContentProviderOperation.newInsert(uri).withValues(values1).build());
ops.add(ContentProviderOperation.newInsert(uri).withValues(values2).build());
getContentResolver().applyBatch(AUTHORITY, ops);

// ✅ 使用AsyncQueryHandler异步查询
new AsyncQueryHandler(getContentResolver()) {
    @Override
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
        processData(cursor);
    }
}.startQuery(0, null, uri, null, null, null, null);

4. 减少锁竞争

java 复制代码
// ❌ 粗粒度锁
public class DataCache {
    private final Map<String, Data> mCache = new HashMap<>();

    public synchronized Data get(String key) {
        Data data = mCache.get(key);
        if (data == null) {
            data = loadFromDisk(key);  // 耗时操作在持锁状态!
            mCache.put(key, data);
        }
        return data;
    }
}

// ✅ 细粒度锁 + Double-Check
public class DataCache {
    private final ConcurrentHashMap<String, Data> mCache = new ConcurrentHashMap<>();
    private final Object mLock = new Object();

    public Data get(String key) {
        Data data = mCache.get(key);
        if (data == null) {
            synchronized (mLock) {
                data = mCache.get(key);
                if (data == null) {
                    data = loadFromDisk(key);  // 只有必要时才持锁
                    mCache.put(key, data);
                }
            }
        }
        return data;
    }
}

// ✅ 使用Lock + tryLock避免死等
public class DataCache {
    private final ReentrantLock mLock = new ReentrantLock();

    public Data get(String key) {
        if (mLock.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                return loadData(key);
            } finally {
                mLock.unlock();
            }
        } else {
            // 获取锁失败,返回默认值或异步加载
            return getDefaultValue();
        }
    }
}

九、总结与展望

让我们回顾一下ANR机制的全貌:

9.1 核心要点回顾

ANR的4种类型:

  • Input ANR: 5秒无响应,由InputDispatcher检测
  • Broadcast ANR: 10秒/60秒,由ActivityManagerService检测
  • Service ANR: 20秒/200秒,由ActivityManagerService检测
  • ContentProvider ANR: 10秒,由ActivityManagerService检测

ANR检测机制:

  • Input: 事件入队时记录时间戳,定时检查waitQueue
  • Broadcast/Service: 发送延时消息,超时触发Handler回调

ANR处理流程:

scss 复制代码
检测超时 → 调用appNotResponding() → 收集CPU/内存信息
→ dump线程堆栈 → 生成traces.txt → 写入DropBox → 弹出Dialog

traces.txt的关键信息:

  • 线程状态: Blocked/Waiting/Native/Runnable
  • 堆栈信息: 方法调用链
  • 锁信息: waiting to lock / locked

9.2 ANR优化的金字塔

9.3 实战建议

  1. 开发阶段:

    • 开启StrictMode
    • 使用BlockCanary
    • Code Review关注主线程操作
  2. 测试阶段:

    • 压力测试
    • Monkey测试
    • 性能测试(TraceView、Systrace)
  3. 线上阶段:

    • 接入Bugly等监控平台
    • 定期分析ANR Top榜
    • 建立ANR快速响应机制

9.4 后续文章预告

ANR机制只是稳定性的一个方面。在后续文章中,我们将继续深入:

文章 主题 核心内容
第3篇 ANR问题排查实战 traces.txt深度解读、Systrace分析、常见ANR案例
第4篇 异常日志机制 Crash处理、Tombstone分析、信号机制
第5篇 Native Crash深度分析 debuggerd、符号化、coredump
第6篇 Java异常与JE分析实战 Java异常的分析实战
第7篇 Watchdog机制 系统看门狗、半死锁检测、System Server保护

参考资料

  1. Android官方文档 - ANR
  2. AOSP源码 - InputDispatcher.cpp
  3. AOSP源码 - ActivityManagerService.java
  4. AOSP源码 - BroadcastQueue.java
  5. Android Performance Patterns

上一篇稳定性性能系列之一------Android稳定性基础:系统架构与关键机制
下一篇稳定性性能系列之三------ANR问题排查实战:日志分析与工具实战
返回专栏目录 : Android稳定性&性能深入理解专栏介绍


作者简介: 多年Android系统开发经验,专注于系统稳定性与性能优化领域。欢迎关注本系列,一起深入Android系统的精彩世界!


🎉 感谢关注,让我们一起深入Android系统的精彩世界! 找到我: 个人主页

相关推荐
江上清风山间明月2 小时前
Android BIND_NOTIFICATION_LISTENER_SERVICE 权限详解
android·notification·service·bind·listener
Lei活在当下2 小时前
【日常知识积累】Kotlin let 函数、inline 函数以及 DSL
android·kotlin·编程语言
世界美景2 小时前
一种基于 ART 内存特征的 LSPosed/Xposed/分身环境 完美检测方案
android·安全·安卓·xposed
卓码软件测评3 小时前
第三方软件测试机构【Gatling源码的本地编译构建方法】
测试工具·性能优化·单元测试·测试用例
2501_946230983 小时前
Cordova&OpenHarmony外观主题设置
android·javascript
小韩博4 小时前
小迪之盲注第44课
android·网络安全·adb
Hy行者勇哥4 小时前
JavaScript性能优化实战:从入门到精通
开发语言·javascript·性能优化
Kiyra4 小时前
八股篇(1):LocalThread、CAS和AQS
java·开发语言·spring boot·后端·中间件·性能优化·rocketmq
被风吹过的会不会要逝去5 小时前
Java后端开发性能优化排查思路及工具
java·性能优化