Android第十五次面试总结(第三方组件和adb命令)

Android 第三方组件转为系统组件核心流程

这通常是在进行 Android 系统定制(如 ROM 开发、固件制作)时完成,目的是让第三方应用拥有更高的权限和系统身份。主要过程如下:

  1. 核心准备:签名!赋予系统身份

    • 核心问题:​ Android 系统如何信任一个应用是自己人?答案是通过签名
    • 第三方签名:​ 普通应用使用的是开发者自己的证书签名。
    • 系统签名:​ 系统组件必须 使用与 Android 操作系统本身相同的特定签名密钥 进行签名,通常称为平台签名(Platform Signing)​。这个密钥文件一般由设备制造商或定制ROM开发者持有。
    • 操作:​ 你需要获取你的 AOSP (Android 开源项目) 编译环境或目标设备对应的平台签名密钥,并用它来重新签名你的第三方APK文件。这一步至关重要,没有正确的签名,应用永远无法被识别为系统组件。
    java 复制代码
    java -jar signapk.jar platform.x509.pem platform.pk8 app.apk app_signed.apk
  2. 放入正确的"家":系统分区的位置

    • 签名后的 APK 不能随意放置。你需要将它放入即将编译成 Android 系统镜像(system.img)的特定子目录中,这决定了它的权限等级:
      • /system/app/: 放置普通系统应用。这些应用拥有比用户应用高的权限,但仍有限制。
      • /system/priv-app/: 放置特权系统应用 。这是放置需要最高级别系统权限(称为 privileged permissions)的应用的地方,位置本身就赋予了应用更高的信任等级。
    • 在准备编译源码时,你需要将签名后的 APK 文件放在 AOSP 源码树中代表设备特定配置的相应目录下(比如 vendor/your_company/your_device/prebuilts/apps/.../priv-app/)。
  3. ​"登记注册":告知编译系统它的存在

    • 仅仅把 APK 文件放进源码目录还不够。你需要明确地告知 Android 的编译系统(Build System)​:这个位置有一个需要被打包进最终系统镜像的应用。
    • 这需要在编译配置文件(通常是定义设备软件包列表的文件,如 device.mk)中添加这个应用的标识名称。编译系统接收到这个名称后,会去之前放 APK 的目录查找它。
  4. ​"赋予力量":权限与系统身份声明

    • 仅位置不足以授权。应用本身也需要在其 AndroidManifest.xml 文件中声明:
      • android:sharedUserId="android.uid.system":这行代码声明该应用想要共享 Android 系统进程的 UID (用户ID)​ 。这是成为核心系统组件的关键标志。注意:​ 这必须与之前的平台签名严格配套才能生效。
      • <uses-permission>:除了标准权限,特权应用可以申请需要 <protection level="privileged"/> 的权限(在框架的 AndroidManifest.xml 中定义)。这些权限只有 放置在 /system/priv-app/ 目录下且正确签名的应用才能获得。
  5. ​"塑造新身":编译与刷机

    • 完成以上配置后,你需要完整地重新编译 包含该应用的 Android 系统镜像(主要是 system.imgsystem_other.img, product.img, system_ext.img 等分区,具体取决于 Android 版本和设备分区方案)。
    • 将编译生成的新系统镜像文件刷入目标设备。之后,该应用就会作为设备的一部分,在系统分区中启动运行,拥有系统身份和权限。

Android 的 adb (Android Debug Bridge) 命令来查找和分析 ​

OOM (Out Of Memory,内存溢出)​

和 ​ANR (Application Not Responding,应用程序无响应)​

1. 主线程阻塞

  • 场景​:在主线程执行耗时操作。

    • 网络请求(同步请求)

    • 数据库读写(特别是大文件或复杂查询)

    • 复杂计算(如图像处理、大量循环)

  • 原因 ​:Android 要求主线程在 ​5秒内​ 响应用户输入(如点击),否则触发 ANR。


2. BroadcastReceiver 超时

  • 场景​:

    • BroadcastReceiver.onReceive() 执行超过 ​前台广播10秒/后台广播60秒

    • 在广播接收器中直接执行耗时任务。

  • 示例

    复制代码
    public class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 错误:在主线程执行耗时操作
            heavyWork(); // 超过10秒触发ANR
        }
    }

3. Service 执行超时

  • 前台 Service ​:onStartCommand()onBind() 中耗时操作超过 ​20秒

  • 后台 Service ​:超过 ​200秒​(系统可能直接杀死进程,非严格ANR)。

  • 常见错误​:在 Service 主线程中直接处理任务。


4. Input 事件超时

  • 场景​:

    • 主线程处理触摸/按键事件超过 ​5秒​ 未完成。

    • 频繁的 UI 更新阻塞主线程(如循环动画卡顿)。

  • 典型问题

    复制代码
    button.setOnClickListener {
        Thread.sleep(7000) // 模拟耗时操作,阻塞主线程
    }

5. ContentProvider 操作超时

  • 场景​:

    • ContentProvider 的 CRUD 方法(如 query()insert())执行过久。
  • 原因​:其他应用通过 ContentResolver 调用时,系统会限制响应时间。


6. 进程间通信(IPC)阻塞

  • 场景​:

    • 跨进程调用(如 AIDL)时,被调用方主线程阻塞导致调用方等待超时。
  • 示例​:

    • App A 调用 App B 的 Service,如果 B 的 Service 主线程卡顿,A 可能触发 ANR。

7. 死锁(Deadlock)​

  • 场景​:

    • 主线程等待子线程释放锁,而子线程又在等待主线程资源。

    • 主线程与子线程同步竞争资源导致互相阻塞。

  • 示例

    复制代码
    // 主线程
    synchronized(lock) {
        thread.post {
            synchronized(lock) { ... } // 子线程等待主线程释放锁
        }
        Thread.sleep(10000) // 主线程持有锁不释放
    }

8. 过度绘制/布局嵌套过深

  • 场景​:

    • 复杂的 RecyclerView 布局(例如嵌套滚动容器)。

    • onMeasure()/onLayout() 执行超时(>5秒)。

  • 原因​:UI 渲染被迫在主线程完成,复杂布局会阻塞主线程。


如何定位 ANR?​

  1. 检查日志​:

    • 通过 adb logcat 或 Android Studio 的 ​Logcat ​ 过滤 ANR in

    • 查看 /data/anr/traces.txt 文件(需要 root 或 adb bugreport)。

  2. 分析 traces.txt​:

    • 找到主线程(通常名为 main)的堆栈,检查阻塞点:

      复制代码
      "main" prio=5 tid=1 Native
        | group="main" sCount=1 dsCount=0 flags=1 obj=0x74755210
        | sysTid=12345 nice=-10 cgrp=top-app sched=0/0 handle=0x7f99e7dc50
        | state=S schedstat=( 1234567890 ) utm=12 stm=5 core=0 HZ=100
        | stack=0x7f99e7d000-0x7f99e7f000
        at java.lang.Thread.sleep(Native Method)
        at com.example.MyActivity.blockMainThread(MyActivity.java:10) 
场景 超时阈值 解决方案
主线程阻塞 5秒 移走耗时操作到子线程
BroadcastReceiver 前台10秒/后台60秒 goAsync()WorkManager
前台 Service 20秒 使用 IntentService/JobScheduler
ContentProvider 操作 不定(应尽快返回) 异步查询或预加载

一、 查找 OOM (Out Of Memory) 错误

OOM 错误通常会导致应用程序直接崩溃 (Force Close)。adb 查找 OOM 的核心是分析崩溃日志

  1. 收集崩溃日志 (logcat):​

    • 实时监控:​​ 连接设备,在崩溃发生时实时捕获日志是最准确的方式。

      复制代码
      adb logcat -v time | grep -E 'AndroidRuntime|OutOfMemory|Out of memory'
      • -v time: 在日志中显示时间戳,方便定位。
      • grep: 过滤包含 AndroidRuntime (大部分崩溃异常会通过它打印)、OutOfMemoryOut of memory 关键字的行。
    • 导出完整日志:​​ 如果崩溃已经发生,或者你需要更全面的分析:

      复制代码
      adb logcat -d -v time > logcat_oom.txt
      • -d: 转储当前日志并退出。
      • 将导出的 logcat_oom.txt 文件用文本编辑器打开,搜索上述关键字 (OutOfMemory, AndroidRuntime 崩溃堆栈等)。
  2. 定位关键信息:​

    • 在 logcat 中找到类似 java.lang.OutOfMemoryError 开头的行,紧跟着的就是崩溃的堆栈轨迹 (stack trace)​
    • 仔细阅读堆栈轨迹:
      • 错误类型:​java.lang.OutOfMemoryError: Java heap space (Java 堆内存不足),还是 java.lang.OutOfMemoryError: Failed to allocate a ... byte allocation ... (试图分配过大连续内存块失败),亦或是 java.lang.OutOfMemoryError: pthread_create (stack size ...) failed (线程创建失败)? 不同的错误类型指向不同的问题根源(内存泄漏 vs 大内存分配 vs 线程过多)。
      • 代码位置:​ 堆栈会清晰地指出是应用中的哪一行代码(方法、类名、行号)尝试分配内存失败。这是最重要的线索!
      • 内存快照 (有时):​ 有些 OOM 错误前后可能会打印内存统计信息,显示当前已用内存、总内存、剩余内存、各个内存空间的占比(如 PSS)等。
  3. 获取内存状态快照 (dumpsys meminfo):​

    • 在怀疑 OOM 发生前后,可以手动抓取应用的内存详情:

      复制代码
      adb shell dumpsys meminfo <your_app_package_name>
    • 这能提供非常详细的内存使用情况报告:

      • PSS / RSS / USS:​ 理解应用实际占用物理内存的关键指标。
      • Java Heap:​ Java 堆的使用情况(已用、空闲、总量)。
      • Native Heap:​ Native 代码分配的内存。
      • Bitmap 占用:​ 是 OOM 大户,报告中 Objects 部分会列出 Bitmap 占用的内存量。
      • Activity / View 泄漏:​ Activities 部分能看到所有存在的 Activity 实例。如果某个本该销毁的 Activity 大量残留,就是内存泄漏的强证据。
      • 数据库/文件泄漏:​ SQLAsset Allocations 部分可能揭示相关资源未关闭。
    • 频繁抓取此命令(例如在关键操作前后或定时)可以监控内存增长趋势,帮助定位泄漏点。

  4. 结合工具分析 (可选但推荐):​

    • 导出内存快照文件 (HPROF):

      复制代码
      adb shell am dumpheap <your_app_package_name> /data/local/tmp/memory_snapshot.hprof
      adb pull /data/local/tmp/memory_snapshot.hprof .
      • 这个文件可以在 Android Studio 的 Profiler (Analyzer > Load from File)​ 或专业的 MAT (Memory Analyzer Tool)​ / LeakCanary 中打开分析,精确找出内存中大量持有且无法回收的对象以及它们的引用链。

二、 查找 ANR (Application Not Responding)​

ANR 发生时,系统会生成包含当时系统状态快照的报告文件(通常需要 root 访问)。adb 主要用来提取这些报告文件和分析相关日志。

  1. 访问 ANR 报告文件 (/data/anr/):​

    • 这是查找 ANR 信息的最直接方式,包含了问题发生瞬间的详细快照。但此目录通常需要 root 权限

      复制代码
      adb root # 获取 root 权限 (需要工程设备或测试设备)
      adb remount # 重新挂载 /system 分区为可写 (如果需要)
      adb shell 'ls -l /data/anr/' # 列出 ANR 文件
      adb pull /data/anr/anr_YYYY-MM-DD-HH-MM-SS # 拉取具体的 ANR 文件
      adb pull /data/anr/traces.txt # 拉取 traces.txt (老版本有时集中写在这里)
    • 导出的 ANR 文件通常是纯文本文件,用文本编辑器打开即可。

  2. 分析 ANR 报告文件内容:​

    • 关键信息:​
      • ANR 原因:​ Reason: <reason> (最常见的是 Input dispatching timed out 输入事件超时、Broadcast of Intent 广播超时、executing service 服务执行超时、ContentProvider not published 内容提供者未就绪)。
      • 负载信息:​ Load: <CPU负载>,负载高可能表明系统整体卡顿导致你的应用无法及时获取CPU。
      • CPU 使用情况:​ 文件包含 ANR 发生前一段时间 (通常是几秒到几十秒) 所有进程的 CPU 使用占比 (<PID>%TOTAL ...)。重点看:
        • 你的应用进程的 CPU 使用率(是0?还是100%?)。
        • 是否有其他进程占用了大量 CPU?
        • 系统进程 (surfaceflinger, system_server, renderer) 使用率是否异常?
      • 等待锁信息:​ "main" prio=5 tid=1 Blocked | Waiting | Native, 主线程的状态会清晰地显示它是被阻塞在锁上(Blocked,包括在 monitor 上等待或等待 Condition 信号),在等待(Waiting),还是在执行本地代码(Native)。
      • 堆栈轨迹:​ 这是最核心的部分!
        • 主线程堆栈 (main):​ ANR 文件会打印主线程 在 ANR 那一刻的具体堆栈。仔细观察主线程当前在做什么!​
          • 是否在执行耗时操作(网络请求、大文件读写、复杂计算、数据库操作)?
          • 是否在等待锁 (synchronized, ReentrantLock)​ ?找 waiting on / locked 的说明。
          • 是否在 Binder 通信 ?(BinderProxy.transactNative),等待另一个进程响应?可能对端进程卡死或无响应。
          • 是否在执行 IO 操作 (InputStream.read, OutputStream.write)?
          • 是否在进行 View/Draw 操作 (measure, layout, draw)?布局过于复杂?
        • 其他线程堆栈:​ 有时也需要关注其他重要的应用线程。
  3. 通过 logcat 查找 ANR 痕迹:​

    • 即使没有 /data/anr/ 的访问权限,系统也会在 logcat 中打印 ANR 信息:

      复制代码
      adb logcat -v time -b main | grep -i "ANR in"
      • -b main: 指定 main 缓冲区(主要系统日志)。
      • grep -i "ANR in": 查找包含 ANR in (不区分大小写) 的行。关键行通常是 ActivityManager: ANR in <your_package_name>
    • 找到 ANR 事件日志后,查看这条日志前后的其他日志,可能会提供更多线索(如系统状态、错误信息、其他相关事件)。

  4. 复现期间查看当前线程状态 (dumpsys cpuinfo & dumpsys activity processes):​

    • 如果 ANR 难以捕捉报告文件,可以在怀疑可能发生 ANR的操作执行期间或之后立即手动查看主线程状态:

      复制代码
      adb shell dumpsys cpuinfo | grep <your_package_name> -A 20 # 查看包名相关进程的CPU占用和线程信息(部分设备有效)
      adb shell dumpsys activity top | grep -A 20 "ACTIVITY MANAGER ACTIVITIES" # 查看最顶层Activity信息
      adb shell dumpsys activity processes <your_package_name> # 详细查看目标进程信息,包括主线程状态
    • 这些命令的输出格式复杂,需要仔细寻找你的应用进程和主线程(名称通常是 main)的信息,看其状态是否是 RUNNING 还是 SLEEPINGBLOCKEDWAITING 等。

  5. 分析磁盘 I/O (dumpsys diskstats / profiling):​

    • 如果在堆栈中看到主线程在执行文件操作,或者在低端机上频繁 ANR,需要考虑磁盘 I/O 是否成为瓶颈:

      复制代码
      adb shell dumpsys diskstats # 显示系统整体I/O状态 (较粗略)
    • 更精确的 I/O 分析可能需要使用 systracePerfetto 等性能剖析工具。

总结与关键点

  • OOM:​
    • logcatOutOfMemoryError 堆栈。
    • dumpsys meminfo 分析内存使用模式、趋势和泄露嫌疑对象。
    • dumpheap + HPROF 分析 + MAT/Profiler 精确诊断泄露。
  • ANR:​
    • **根目录 /data/anr/** 是金矿(需要 root)。
    • 在 ANR 报告文件中重点解读主线程堆栈,看清楚它在等什么锁、做什么耗时操作。
    • 分析 ANR 发生时的 CPU 使用情况(整个系统)。
    • 通过 logcat 搜索 ANR in 关键字作为辅助。
    • 必要时使用 dumpsys activity processes 手动检查线程状态。
相关推荐
千里马学框架9 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台9 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone10 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc10 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo12 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770012 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼13 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone14 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen14 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone14 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui