You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

163 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 聊聊Framework的学习方法
大家好我是陆晓明现在在一家互联网手机公司担任Android系统开发工程师。很高兴可以在极客时间Android开发高手课专栏里分享一些我在手机行业9年的经验以及学习Android的方法。
今天我要跟你分享的是Framework的学习和调试的方法。
首先Android是一种基于Linux的开放源代码软件栈为广泛的设备和机型而创建。下图是Android平台的[主要组件](https://developer.android.google.cn/guide/platform)。
![](https://static001.geekbang.org/resource/image/90/df/90763fd9662c8a75553dc92a78112ddf.png)
从图中你可以看到主要有以下几部分组成:
* **Linux内核**
* **Android Runtime**
* **原生C/C++库**
* Java API框架后面我称之为Framework框架层
* **系统应用**
我们在各个应用市场看到的大多是第三方应用也就是安装在data区域的应用它们可以卸载并且权限也受到一些限制比如不能直接设置时间日期需要调用到系统应用设置里面再进行操作。
而我们在应用开发过程中使用的四大组件便是在Framework框架层进行实现应用通过约定俗成的规则在AndroidMainfest.xml中进行配置然后继承对应的基类进行复写。系统在启动过程中解析AndroidMainfest.xml将应用的信息存储下来随后根据用户的操作或者系统的广播触发启动对应的应用。
那么我们先来看看Framework框架层都有哪些东西。
Framework框架层是应用开发过程中调用的系统方法的内部实现比如我们使用的TextView、Button控件都是在这里实现的。再举几个例子我们调用ActivityManager的getRunningAppProcesses方法查看当前运行的进程列表还有我们使用NotificationManager的notify发送一个系统通知。
让我们来看看Framework相关的代码路径。
![](https://static001.geekbang.org/resource/image/17/d4/178ef00a181a85e85a3b75d4c60abcd4.jpg)
如何快速地学习、梳理Framework知识体系呢常见的学习方法有下面几种
* 阅读书籍(方便梳理知识体系,但对于解决问题只能提供方向)。
* 直接阅读源码(效率低,挑战难度大)。
* 打Log和打堆栈 效率有所提升但需要反复编译添加Log和堆栈代码
* 直接联调,实时便捷(需要调试版本)。
首先可以通过购买相关的书籍进行学习其中主要的知识体系有Linux操作系统比如进程、线程、进程间通信、虚拟内存建立起自己的软件架构。在此基础上学习Android的启动过程、服务进程SystemServer的创建、各个服务线程AMS/PMS等的创建过程以及Launcher的启动过程。熟悉了这些之后你还要了解ART虚拟机的主要工作原理以及init和Zygote的主要工作原理。之后随着在工作和实践过程中你会发现Framework主要是围绕应用启动、显示、广播消息、按键传递、添加服务等开展这些代码的实现主要使用的是Java和C++这两种语言。
通过书籍或者网络资料学习一段时间后你会发现很多问题都没有现成的解决方案而此时就需要我们深入源码中进行挖掘和学习。但是除了阅读官方文档外别忘了调试Framework也是一把利刃可以让你游刃有余快速定位和分析源码。
下面我们来看看调试Framework的Java部分关于C++的部分需要使用GDB进行调试你可以在课下实践一下调试的过程可以参考[《深入Android源码系列》](https://mp.weixin.qq.com/s/VSVUbaEIfrmFZMB1k49fyA)。
我们这里使用Android Studio进行调试在调试前我们要先掌握一些知识。Java代码的调试主要依据两个因素一个是你要调试的进程一个是调试的类对应的包名路径同时还要保证你所运行的手机环境和你要调试的代码是匹配的。只要这两个信息匹配编译不通过也是可以进行调试的。
我们调试的系统服务是在SystemServer进程中可以使用下面的命令验证我这里使用Genymotion上安装Android对应版本镜像的环境演示
```
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进程进行跟踪。
下来我们来建立调试环境。
* 打开Genymotion选择下载好Android 9.0的镜像文件,启动模拟器。
* 找到模拟器对应的ActivityManagerService.java代码。 我是从[http://androidxref.com/](http://androidxref.com/)下载Android 9.0对应的代码。
* 打开Android StudioFile -> New -> New Project然后直接Next直到完成就行。
* 新建一个包名从ActivityManagerService.java文件中找到它这里为`com.android.server.am`然后把ActivityManagerService.java放到里面即可。
* 在ActivityManagerService.java的startActivity方法上面设置断点然后找到菜单的Run -> Attach debugger to Android process勾选Show all process选中SystemServer进程确定。
![](https://static001.geekbang.org/resource/image/ba/f0/ba1eb6bded9167f26ae48b34a6d792f0.png)
这时候我们点击Genymotion模拟器中桌面的一个图标启动新的界面。
![](https://static001.geekbang.org/resource/image/c9/45/c92b62d1065f967696dbdd2851037b45.png)
会发现这时候我们设定的断点已经生效。
![](https://static001.geekbang.org/resource/image/76/05/763f222e01a30c969024d8cf77dd0705.png)
你可以看到断下来的堆栈信息,以及一些变量值,然后我们可以一步步调试下去,跟踪启动的流程。
对于学习系统服务线程来讲,通过调试可以快速掌握流程,再结合阅读源码,便可以快速学习,掌握系统框架的整个逻辑,从而节省学习的时间成本。
以上我们验证了系统服务AMS服务代码的调试其他服务调试方法也是一样具体的线程信息可以使用下面的命令查看。
```
ps -T 353
这里353是使用ps -A |grep SystemServer查出 SystemServer的进程号
```
![](https://static001.geekbang.org/resource/image/62/a8/62d0d79e490a14f19422486c5da85fa8.png)
在上面图中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的线程这个是线程的名称通过查看这行的TID368就知道下面的Log就是这个线程输出的。
```
03-10 08:47:33.574 353 368 I ActivityManager: Force stopping com.android.providers
```
学习完上面的知识,相信你应该学会了系统服务的调试。通过调试分析,我们便可以将系统服务框架进行庖丁解牛般的学习,面对大量庞杂的代码掌握起来也可以轻松一些。
我们回过头来,再次在终端中输入`ps -A`,看看下面这一段信息。
![](https://static001.geekbang.org/resource/image/29/4e/298cadbc90a1f04d02e1e116f6db464e.png)
你可以看到这里的第一列代表的是当前的用户这里有system root和u0\_axx不同的用户有不同的权限。我们当前关注的是第二列和第三列第二列代表的是PID也就是进程ID第三列代表的是PPID也就是父进程ID。
你发现我这里框住的都是同一个父进程那么我们来找下这个323进程看看它到底是谁。
```
root 323 1 1089040 127540 poll_schedule_timeout f16fcbc9 S zygote
```
这个名字在学习Android系统的时候总被反复提及因为它是我们Android世界的孵化器每一个上层应用的创建都是通过Zygote调用fork创建的子进程而子进程可以快速继承父进程已经加载的资源库这里主要指的是应用所需的JAR包比如/system/framework/framework.jar因为我们应用所需的基础控件都在这里像View、TextView、ImageView。
接下来我来讲解下一个调试也就是对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进程中下断点了。
下来我们建立调试环境:
* 打开Genymotion选择下载好Android 9.0的镜像文件启动模拟器然后在桌面上启动Gallery图库应用。
* 找到模拟器对应的TextView.java代码。
* 打开Android StudioFile -> 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控件确定。
然后我们点击下这个界面左上角的菜单,随便选择一个点击,发现断点已生效,具体如下图所示。
![](https://static001.geekbang.org/resource/image/c2/85/c2a9a5a71d4bd4a02b5bee113d866b85.png)
然后我们可以使用界面上的调试按钮(或者快捷键)进行调试代码。
![](https://static001.geekbang.org/resource/image/c3/f8/c395c9f16a7c057c1076b4619dd1b5f8.png)
今天我讲解了如何调试Framework中的系统服务进程的AMS服务线程其他PMS、WMS的调试方法跟AMS一样。并且我也讲解了如何调试一个应用里面的TextView控件其他的比如Button、ImageView调试方法跟TextView也是一样的。
通过今天的学习,我希望能够给你一个学习系统框架最便捷的路径。在解决系统问题的时候,你可以方便的使用调试分析,从而快速定位、修复问题。