保存成功
订阅成功
保存失败,请重试
提交成功

Android Framework 学习和调试的方法

向作者提问
维护公众号 明哥的江湖 。聚焦职场,知识变现,以及个人成长的相关内容,欢迎大家加入。
查看本场Chat

@

本文适合 Android 系统开发工程师,以及对系统感兴趣却不知该如何快速学习的朋友。

学完此课程,你便能掌握系统调试,以及应用调试的方法,让你摆脱低效的 Log 分析方法。同时文章给了一些思考,希望大家在掌握调试的方法后,能够理解操作系统的实现原理。

  1. 本文导语
  2. Android 系统框架
  3. Framework 框架层都有哪些东西
  4. 调试 Framework 的 Java 部分
  5. 调试应用中 TextView 代码
  6. 调试系统服务线程(AMS、WMS)
  7. 课后习题

本文导语

本文首发于极客时间的《Android开发高手课》,这个版本是在原有基础上加入了一些文字,希望大家能够更加清晰熟练的掌握。如果你想更多的学习安卓课程,开拓视野,这份课程可以帮你提供技术的广度和深度,让你对于Android更加好奇,更加有吸引力。欢迎跟明哥一起,纵横江湖。

我们这节用最简单的方式,来处理调试源码,主要围绕安卓的 Java 层面,关于 C 的,AS 上可以使用 NDK 的 LLVM 调试,工程中的使用 Log 或者是 GDB 调试。

好了,正文开始!

今天我要跟你分享的是Framework的学习和调试的方法。

Android 系统框架

首先,Android是一种基于Linux的开放源代码软件栈,为广泛的设备和机型而创建。下图是Android平台的主要组件 全景图

从图中你可以看到主要有以下几部分组成:

  • Linux 内核
  • Android Runtime
  • 原生 C/C++ 库
  • Java API 框架(后面我称之为 Framework 框架层)
  • 系统应用

我们在各个应用市场看到的,大多是第三方应用,也就是安装在 data 区域的应用,它们可以卸载,并且权限也受到一些限制,比如不能直接设置时间日期,需要调用到系统应用设置里面再进行操作。

而我们在应用开发过程中使用的四大组件,便是在 Framework 框架层进行实现,应用通过约定俗成的规则,在 AndroidMainfest.xml 中进行配置,然后继承对应的基类进行复写。系统在启动过程中解析 AndroidMainfest.xml,将应用的信息存储下来,随后根据用户的操作,或者系统的广播触发,启动对应的应用。

那么,我们先来看看 Framework 框架层都有哪些东西。

Framework 框架层都有哪些东西

Framework 框架层是应用开发过程中,调用的系统方法的内部实现,比如我们使用的 TextView 、Button 控件,都是在这里实现的。再举几个例子,我们调用 ActivityManager 的getRunningAppProcesses 方法查看当前运行的进程列表,还有我们使用 NotificationManager 的notify 发送一个系统通知。

让我们来看看Framework相关的代码路径。

enter image description here

如何快速地学习、梳理 Framework 知识体系呢?常见的学习方法有下面几种:

  • 阅读书籍(方便梳理知识体系,但对于解决问题只能提供方向)。
  • 直接阅读源码(效率低,挑战难度大)。
  • 打 Log 和打堆栈 (效率有所提升,但需要反复编译,添加 Log 和堆栈代码)。
  • 直接联调,实时便捷(需要调试版本)。

首先可以通过购买相关的书籍进行学习,其中主要的知识体系有 Linux 操作系统,比如进程、线程、进程间通信、虚拟内存,建立起自己的软件架构。在此基础上学习 Android 的启动过程、服务进程 SystemServer 的创建、各个服务线程(AMS / PMS 等)的创建过程,以及 Launcher 的启动过程。熟悉了这些之后,你还要了解ART虚拟机的主要工作原理,以及 init 和 Zygote 的主要工作原理。之后随着在工作和实践过程中你会发现,Framework 主要是围绕应用启动、显示、广播消息、按键传递、添加服务等开展,这些代码的实现主要使用的是 Java 和 C++ 这两种语言。

通过书籍或者网络资料学习一段时间后,你会发现很多问题都没有现成的解决方案,而此时就需要我们深入源码中进行挖掘和学习。但是除了阅读官方文档外,别忘了调试 Framework 也是一把利刃,可以让你游刃有余快速定位和分析源码。

调试 Framework 的 Java 部分

下面我们来看看调试 Framework 的 Java 部分,关于 C++ 的部分,需要使用 GDB 进行调试,你可以在课下实践一下,调试的过程可以参考《深入 Android 源码系列(一)》。

我们这里使用 Android Studio 进行调试,在调试前我们要先掌握一些知识。Java 代码的调试,主要依据两个因素,一个是你要调试的进程;一个是调试的类对应的包名路径,同时还要保证你所运行的手机环境和你要调试的代码是匹配的。只要这两个信息匹配,编译不通过也是可以进行调试的。

这里备注下,因为之前发出去很多人说下载代码地方不对之类的。这个代码从哪找呢?从你编译环境去找,要找到和运行的模拟器或者手机是匹配的,这样子才能调试,否则会出现断点无效等问题,特此说明。

我们调试的系统服务是在 SystemServer 进程中,可以使用下面的命令验证(我这里使用Genymotion上安装安卓对应版本镜像的环境演示)。 ps -A |grep system_server 查看系统服务进程 pid cat /proc/pid/maps |grep services 通过 cat 查看此进程的内存映射,看看是否 services 映射到内存里面。

这里我们看到信息:/system/framework/oat/x86/services.odex 。

odex 是 Android 系统对于 dex 的进一步优化,目的是为了提升执行效率。从这个信息便可以确定,我们的 services.jar 确实是跑到这里了,也就是我们的系统服务相关联的代码,可以通过调试 SystemServer 进程进行跟踪。

下来我们来建立调试环境。

  1. 打开 Genymotion,选择下载好 Android 9.0 的镜像文件,启动模拟器。

  2. 找到模拟器对应的 ActivityManagerService.java 代码。 我是从http://androidxref.com/下载Android 9.0对应的代码。

  3. 打开 Android Studio,File -> New -> New Project 然后直接 Next 直到完成就行。

  4. 新建一个包名,从 ActivityManagerService.java 文件中找到它,这里为 com.android.server.am,然后把 ActivityManagerService.java 放到里面即可。

  5. 在 ActivityManagerService.java 的 startActivity 方法上面设置断点,然后找到菜单的 Run -> Attach debugger to Android process勾选 Show all process,选中 system_server 进程确定。 enter image description here

这时候我们点击 Genymotion 模拟器中桌面的一个图标,启动新的界面。

会发现这时候我们设定的断点已经生效。

你可以看到断下来的堆栈信息,以及一些变量值,然后我们可以一步步调试下去,跟踪启动的流程。

enter image description here

对于学习系统服务线程来讲,通过调试可以快速掌握流程,再结合阅读源码,便可以快速学习,掌握系统框架的整个逻辑,从而节省学习的时间成本。

以上我们验证了系统服务 AMS 服务代码的调试,其他服务调试方法也是一样,具体的线程信息,可以使用下面的命令查看。

ps -T 353 这里 353 是使用 ps -A |grep system_server 查出 SystemServer 的进程号

enter image description here

在上面图中,PID = TID 的只有第一行这一行,如果 PID = TID 的话,也就是这个线程是主线程。下面是我们平时使用 Logcat 查看输出的信息。

03-10 09:33:01.804 240 240 I hostapd : type=1400 audit(0.0:1123): avc: de 03-10 09:33:37.320 353 1213 D WificondControl: Scan result ready event 03-10 09:34:00.045 404 491 D hwcomposer: hw_composer sent 6 syncs in 60s

这里我还框了一个 ActivityManager 的线程,这个是线程的名称,通过查看这行的 TID(368)就知道下面的 Log 就是这个线程输出的。

03-10 08:47:33.574 353 368 I ActivityManager: Force stopping com.android.providers

学习完上面的知识,相信你应该学会了系统服务的调试。通过调试分析,我们便可以将系统服务框架进行庖丁解牛般的学习,面对大量庞杂的代码掌握起来也可以轻松一些。

我们回过头来,再次在终端中输入 ps -A ,看看下面这一段信息。

你可以看到这里的第一列,代表的是当前的用户,这里有 system root 和 u0_axx,不同的用户有不同的权限。我们当前关注的是第二列和第三列,第二列代表的是 PID,也就是进程 ID;第三列代表的是 PPID,也就是父进程 ID。

你发现我这里框住的都是同一个父进程,那么我们来找下这个323进程,看看它到底是谁。

root 323 1 1089040 127540 pollscheduletimeout f16fcbc9 S zygote

这个名字在学习 Android 系统的时候,总被反复提及,因为它是我们 Android 世界的孵化器,每一个上层应用的创建,都是通过 Zygote 调用 fork 创建的子进程,而子进程可以快速继承父进程已经加载的资源库,这里主要指的是应用所需的 JAR 包,比如 /system/framework/framework.jar,因为我们应用所需的基础控件都在这里,像 View、TextView、ImageView。

调试应用中 TextView 代码

接下来我来讲解下一个调试,也就是对 TextView 的调试(其他 Button 调试方式一样)。如前面所说,这个代码被编译到 /system/framework/framework.jar,那么我们通过 ps 命令和 cat /proc/pid/maps 命令在 Zygote 中找到它,同时它能够被每一个由Zygote创建的子进程找到,比如我们当前要调试 Gallery 的主界面 TextView。

我们验证下,使用 ps -A |grep gallery3d 查到 Gallery 对应的进程 PID,使用 cat /proc/pid/maps |grep framework.jar 看到如下信息:

efcd5000-efcd6000 r--s 00000000 08:06 684 /system/framework/framework.jar

这说明我们要调试的应用进程在内存映射中确实存在,那么我们就需要在gallery3d进程中下断点了。

下来我们建立调试环境:

  1. 打开 Genymotion,选择下载好 Android 9.0 的镜像文件,启动模拟器,然后在桌面上启动Gallery 图库应用。

  2. 找到模拟器对应的TextView.java代码。

打开 Android Studio,File -> New -> New Project 然后直接 Next 直到完成就行。

新建一个包名,从 TextView.java 文件中找到它的包名,这里为 android.widget ,然后把TextView.java 放到里面即可。

在 TextView.java 的 onDraw 方法上面设置断点,然后找到菜单的 Run -> Attach debugger to Android process 勾选 Show all process,选中 com.android.gallery3d 进程(我们已知这个主界面有TextView 控件)确定。

然后我们点击下这个界面左上角的菜单,随便选择一个点击,发现断点已生效,具体如下图所示。

enter image description here

然后我们可以使用界面上的调试按钮(或者快捷键)进行调试代码。 enter image description here

调试系统服务线程(AMS、WMS)

按照第四节所讲,调试 Framework 的 Java 部分,这里演示的便是跟踪应用启动过程,调用的 AMS startActivity 方法,这里 AMS 便是一个服务线程,同时我们也可以在 WMS 的 addWindow 来跟踪添加窗口的过程, PMS 的 queryIntentActivities 查看 Intent 对应的都有哪些界面响应。

这里强调的是,调试依赖的就是对应的进程,以及对应的文件即可。掌握了这个,那么调试系统框架的代码,就变得很简单了。

在这个之外,熟练使用 AS 的条件调试,以及调试加入 Log 输出,那么基本上系统框架的代码,就可以非常轻松的分析定位。

这一篇文章,我讲解了如何调试 Framework 中的系统服务进程的 AMS 服务线程,其他 PMS 、WMS 的调试方法跟 AMS 一样。并且我也讲解了如何调试一个应用里面的 TextView 控件,其他的比如 Button、ImageView 调试方法跟 TextView 也是一样的。

通过今天的学习,我希望能够给你一个学习系统框架最便捷的路径。在解决系统问题的时候,你可以方便的使用调试分析,从而快速定位、修复问题。

以上,便是这节课的分享内容,希望大家行动起来,从现在开始调试吧。

课后习题

那么,你是否已经跃跃欲试,准备调试一下自己的应用呢?提出一个问题,我们调试 Gallery 应用的 TextView 时候,前提是让这个应用先运行起来,如果我们想调试从点击桌面 Gallery 图标到Gallery 主界面绘制出来的过程,该如何调试呢?

我们是否能够调试市面上发行的三方应用呢?比如微信,支付宝的某个界面的 TextView 控件?

再深入一下,如果我们想研究市面一款应用的内部实现,需要反编译分析代码,我们能否调试反编译出来的 Smali 代码呢?

欢迎留言,说出你的答案。

再次感谢大家能够抽出时间阅读此文,愿这篇文章能够给你的安卓开发,提供很好的学习路径。对于我自身来说,学习系统知识,主要是书籍,书籍先给我梳理出来一个框架,然后按照框架进行分析,调试具体的逻辑,验证一条线路,把堆栈多看看,多画画时序图,会帮助你理清楚整个脉络。

有了整体的逻辑,加上调试细节代码,两者结合起来,才是最好的学习路线。


本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

互动评论
评论
化身孤岛的蓝鲸4 个月前
可以多推荐一些关于更多学习framework书籍吗。或者一个完整的学习体系or技术栈~~我是不是要求太多了~
评论
代码GG陆晓明(作者)1 个月前
这块的书籍不多,京东搜集安卓,就可以买到,注意补充下,买一本关于linux 操作系统的书,以及可执行文件elf的解析,这样配合着就可以了
评论
代码GG|陆晓明3 个月前
这个方向要讲的很多,在公众号写了一系列文章,欢迎关注 明哥的江湖 先看发布的原创文。
评论
JIF6 个月前
用 AndroidStudio create new AVD,不能 debug 吗?
评论
代码GG陆晓明(作者)回复JIF6 个月前
才看到你的留言,抱歉哈。。
评论
JIF6 个月前
可以了,原来添加的代码版本不匹配
评论
代码GG陆晓明(作者)8 个月前
欢迎大家留下你的评论,问题。
评论
查看更多
微信扫描登录