Android第十五次面试总结(第三方组件和adb命令)
Android 第三方组件转为系统组件核心流程
这通常是在进行 Android 系统定制(如 ROM 开发、固件制作)时完成,目的是让第三方应用拥有更高的权限和系统身份。主要过程如下:
-
核心准备:签名!赋予系统身份
- 核心问题: Android 系统如何信任一个应用是自己人?答案是通过签名。
- 第三方签名: 普通应用使用的是开发者自己的证书签名。
- 系统签名: 系统组件必须使用与 Android 操作系统本身相同的特定签名密钥进行签名,通常称为平台签名(Platform Signing)。这个密钥文件一般由设备制造商或定制ROM开发者持有。
- 操作: 你需要获取你的 AOSP (Android 开源项目) 编译环境或目标设备对应的平台签名密钥,并用它来重新签名你的第三方APK文件。这一步至关重要,没有正确的签名,应用永远无法被识别为系统组件。
-
java -jar signapk.jar platform.x509.pem platform.pk8 app.apk app_signed.apk
-
放入正确的“家”:系统分区的位置
- 签名后的 APK 不能随意放置。你需要将它放入即将编译成 Android 系统镜像(
system.img
)的特定子目录中,这决定了它的权限等级:/system/app/
: 放置普通系统应用。这些应用拥有比用户应用高的权限,但仍有限制。/system/priv-app/
: 放置特权系统应用。这是放置需要最高级别系统权限(称为privileged permissions
)的应用的地方,位置本身就赋予了应用更高的信任等级。
- 在准备编译源码时,你需要将签名后的 APK 文件放在 AOSP 源码树中代表设备特定配置的相应目录下(比如
vendor/your_company/your_device/prebuilts/apps/
或.../priv-app/
)。
- 签名后的 APK 不能随意放置。你需要将它放入即将编译成 Android 系统镜像(
-
“登记注册”:告知编译系统它的存在
- 仅仅把 APK 文件放进源码目录还不够。你需要明确地告知 Android 的编译系统(Build System):这个位置有一个需要被打包进最终系统镜像的应用。
- 这需要在编译配置文件(通常是定义设备软件包列表的文件,如
device.mk
)中添加这个应用的标识名称。编译系统接收到这个名称后,会去之前放 APK 的目录查找它。
-
“赋予力量”:权限与系统身份声明
- 仅位置不足以授权。应用本身也需要在其
AndroidManifest.xml
文件中声明:android:sharedUserId="android.uid.system"
:这行代码声明该应用想要共享 Android 系统进程的 UID (用户ID)。这是成为核心系统组件的关键标志。注意: 这必须与之前的平台签名严格配套才能生效。<uses-permission>
:除了标准权限,特权应用可以申请需要<protection level="privileged"/>
的权限(在框架的AndroidManifest.xml
中定义)。这些权限只有放置在/system/priv-app/
目录下且正确签名的应用才能获得。
- 仅位置不足以授权。应用本身也需要在其
-
“塑造新身”:编译与刷机
- 完成以上配置后,你需要完整地重新编译包含该应用的 Android 系统镜像(主要是
system.img
或system_other.img
,product.img
,system_ext.img
等分区,具体取决于 Android 版本和设备分区方案)。 - 将编译生成的新系统镜像文件刷入目标设备。之后,该应用就会作为设备的一部分,在系统分区中启动运行,拥有系统身份和权限。
- 完成以上配置后,你需要完整地重新编译包含该应用的 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 {@Overridepublic 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?
-
检查日志:
-
通过
adb logcat
或 Android Studio 的 Logcat 过滤ANR in
。 -
查看
/data/anr/traces.txt
文件(需要 root 或adb bugreport
)。
-
-
分析 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-0x7f99e7f000at 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 的核心是分析崩溃日志。
-
收集崩溃日志 (
logcat
):-
实时监控: 连接设备,在崩溃发生时实时捕获日志是最准确的方式。
adb logcat -v time | grep -E 'AndroidRuntime|OutOfMemory|Out of memory'
-v time
: 在日志中显示时间戳,方便定位。grep
: 过滤包含AndroidRuntime
(大部分崩溃异常会通过它打印)、OutOfMemory
或Out of memory
关键字的行。
-
导出完整日志: 如果崩溃已经发生,或者你需要更全面的分析:
adb logcat -d -v time > logcat_oom.txt
-d
: 转储当前日志并退出。- 将导出的
logcat_oom.txt
文件用文本编辑器打开,搜索上述关键字 (OutOfMemory
,AndroidRuntime
崩溃堆栈等)。
-
-
定位关键信息:
- 在 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
)等。
- 错误类型: 是
- 在 logcat 中找到类似
-
获取内存状态快照 (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
大量残留,就是内存泄漏的强证据。 - 数据库/文件泄漏:
SQL
和Asset Allocations
部分可能揭示相关资源未关闭。
- 频繁抓取此命令(例如在关键操作前后或定时)可以监控内存增长趋势,帮助定位泄漏点。
- 在怀疑 OOM 发生前后,可以手动抓取应用的内存详情:
-
结合工具分析 (可选但推荐):
- 导出内存快照文件 (
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 主要用来提取这些报告文件和分析相关日志。
-
访问 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 文件通常是纯文本文件,用文本编辑器打开即可。
- 这是查找 ANR 信息的最直接方式,包含了问题发生瞬间的详细快照。但此目录通常需要 root 权限。
-
分析 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
)?布局过于复杂?
- 其他线程堆栈: 有时也需要关注其他重要的应用线程。
- 主线程堆栈 (
- ANR 原因:
- 关键信息:
-
通过
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 事件日志后,查看这条日志前后的其他日志,可能会提供更多线索(如系统状态、错误信息、其他相关事件)。
- 即使没有
-
复现期间查看当前线程状态 (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
还是SLEEPING
、BLOCKED
、WAITING
等。
- 如果 ANR 难以捕捉报告文件,可以在怀疑可能发生 ANR的操作执行期间或之后立即手动查看主线程状态:
-
分析磁盘 I/O (dumpsys diskstats / profiling):
- 如果在堆栈中看到主线程在执行文件操作,或者在低端机上频繁 ANR,需要考虑磁盘 I/O 是否成为瓶颈:
adb shell dumpsys diskstats # 显示系统整体I/O状态 (较粗略)
- 更精确的 I/O 分析可能需要使用
systrace
、Perfetto
等性能剖析工具。
- 如果在堆栈中看到主线程在执行文件操作,或者在低端机上频繁 ANR,需要考虑磁盘 I/O 是否成为瓶颈:
总结与关键点
- OOM:
- 靠
logcat
找OutOfMemoryError
堆栈。 - 用
dumpsys meminfo
分析内存使用模式、趋势和泄露嫌疑对象。 - 用
dumpheap
+HPROF
分析 +MAT/Profiler
精确诊断泄露。
- 靠
- ANR:
- **根目录
/data/anr/
** 是金矿(需要 root)。 - 在 ANR 报告文件中重点解读主线程堆栈,看清楚它在等什么锁、做什么耗时操作。
- 分析 ANR 发生时的 CPU 使用情况(整个系统)。
- 通过
logcat
搜索ANR in
关键字作为辅助。 - 必要时使用
dumpsys activity processes
手动检查线程状态。
- **根目录