当前位置: 首页 > news >正文

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

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

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

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

    • 核心问题:​​ Android 系统如何信任一个应用是自己人?答案是通过签名
    • 第三方签名:​​ 普通应用使用的是开发者自己的证书签名。
    • 系统签名:​​ 系统组件必须使用与 Android 操作系统本身相同的特定签名密钥进行签名,通常称为平台签名(Platform Signing)​。这个密钥文件一般由设备制造商或定制ROM开发者持有。
    • 操作:​​ 你需要获取你的 AOSP (Android 开源项目) 编译环境或目标设备对应的平台签名密钥,并用它来重新签名你的第三方APK文件。这一步至关重要,没有正确的签名,应用永远无法被识别为系统组件。
    • 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.img 或 system_other.imgproduct.imgsystem_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 {@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?​

  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-0x7f99e7f000at java.lang.Thread.sleep(Native Method)at com.example.MyActivity.blockMainThread(MyActivity.java:10) 
场景超时阈值解决方案
主线程阻塞5秒移走耗时操作到子线程
BroadcastReceiver前台10秒/后台60秒goAsync()WorkManager
前台 Service20秒使用 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 手动检查线程状态。

相关文章:

  • 嵌入式面试高频(5)!!!C++语言(嵌入式八股文,嵌入式面经)
  • JAVA学习 DAY3 注释与编码规范讲解
  • Kerberos面试内容整理-未来发展趋势
  • SQL进阶之旅 Day 20:锁与并发控制技巧
  • 霍夫变换(Hough Transform)原理简要介绍
  • 基于51单片机的多功能洗衣机仿真
  • 食品计算—Food Portion Estimation via 3D Object Scaling
  • 力扣HOT100之二分查找:153. 寻找旋转排序数组中的最小值
  • 第二十八课:深度学习及pytorch简介
  • Vue3中computed和watch的区别
  • Faiss vs Milvus 深度对比:向量数据库技术选型指南
  • [面试精选] 0094. 二叉树的中序遍历
  • UDP 与 TCP 调用接口的差异:面试高频问题解析与实战总结
  • SQL慢可能是触发了ring buffer
  • Ubuntu下有关UDP网络通信的指令
  • Vue学习之---nextTick
  • 《经济学原理》第9版第5章弹性及其应用
  • Nodejs工程化实践:构建高性能前后端交互系统
  • PC与Windows远程连接与串流:方案简介(ZeroTier + Parsec、Moonlight + Sunshine、网易UU远程)
  • [C++] list双向链表使用方法
  • 个人备案网站放什么手续/怎样做好竞价推广
  • 宁德北京网站建设/2345浏览器官网
  • mindmanager网站建设流程图/百度搜索关键词查询
  • 四川 网站建设/百度关键词搜索技巧
  • 在线名片制作网站开发/seo优化seo外包
  • 广西流行病毒最新消息新闻/沈阳seo顾问