稳定性性能系列之二——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系统的精彩世界! 找到我: 个人主页

相关推荐
阿巴斯甜5 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker5 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95276 小时前
Andorid Google 登录接入文档
android
黄林晴7 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab20 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android