Android 深入系统完全讲解(7)

news2025/1/21 6:23:18

7 如何调试代码,JNI,Framework,APP
调试技巧是我在每入职一家新公司,都会给大家分享的。在 MTK 官方培训还是需要编译才能调试的时候,我无意中调试 MMS 代码,发现跟进了系统代码,调试了相关的匹配搜索子串代码(联系人的搜索界面),定位解决了一个问题。

而此时我没有编译代码,也没有加载代码(用的官方的),就这么幸运的知道原来调试只依赖包和对应的进程,那时候开始系统代码学习就加速了,有了长足的进步,这也是我一般不写流程的原因。

通过关键函数下断点,流程跟踪看堆栈,基本上很快就能画出来时序图,剩下的就是多跟,学习整理就出来框架了。

于是,我分享的都是通用解,是脱离了代码的道,也就是底层逻辑。而调试就是最核心的一个,而另一个就是阅读代码的技巧。

这里我直接分享之前写的文章,大家直接使用就可以了。
1 Framework 的调试

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

在这里插入图片描述
从图中你可以看到主要有以下几部分组成:
Linux 内核
Android Runtime
原生 C/C++库
Java API 框架(后面我称之为 Framework 框架层)
系统应用

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

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

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

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

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

在这里插入图片描述
如何快速地学习、梳理 Framework 知识体系呢?常见的学习方法有下面几种:
1 阅读书籍(方便梳理知识体系,但对于解决问题只能提供方向)。
2 直接阅读源码(效率低,挑战难度大)。
3 打 Log 和打堆栈 (效率有所提升,但需要反复编译,添加 Log 和堆栈代码)。
4 直接联调,实时便捷(需要调试版本)。
首先可以通过购买相关的书籍进行学习,其中主要的知识体系有 Linux 操作系统,比如进程、线程、进程间通信、虚拟内存,建立起自己的软件架构。在此基础上学习 Android 的启动过程、服务进程 SystemServer 的创建、各个服务线程(AMS/PMS 等)的创建过程,以及 Launcher的启动过程。熟悉了这些之后,你还要了解 ART 虚拟机的主要工作原理,以及 init 和 Zygote的主要工作原理。之后随着在工作和实践过程中你会发现,Framework 主要是围绕应用启动、显示、广播消息、按键传递、添加服务等开展,这些代码的实现主要使用的是 Java 和 C++这两种语言。

通过书籍或者网络资料学习一段时间后,你会发现很多问题都没有现成的解决方案,而此时就需要我们深入源码中进行挖掘和学习。但是除了阅读官方文档外,别忘了调试 Framework
也是一把利刃,可以让你游刃有余快速定位和分析源码。
下面我们来看看调试 Framework 的 Java 部分,关于 C++的部分,需要使用 GDB 进行调试,
你可以在课下实践一下,后面我们会讲。
我们这里使用 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 进程进行跟踪。

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

打开 Genymotion,选择下载好 Android 9.0 的镜像文件,启动模拟器。
找到模拟器对应的 ActivityManagerService.java 代码。 我是从 http://androidxref.com/下载Android 9.0 对应的代码。

打开 Android Studio,File -> New -> New Project 然后直接 Next 直到完成就行。
新建一个包名,从 ActivityManagerService.java 文件中找到它,这里为 com.android.server.am,然后把 ActivityManagerService.java 放到里面即可。

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

在这里插入图片描述
这时候我们点击 Genymotion 模拟器中桌面的一个图标,启动新的界面。会发现这时候我们设定的断点已经生效。

在这里插入图片描述
你可以看到断下来的堆栈信息,以及一些变量值,然后我们可以一步步调试下去,跟踪启动的流程。

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

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

ps -T 353

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

在这里插入图片描述
在上面图中,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 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 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 控件)确定。

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

在这里插入图片描述
然后我们可以使用界面上的调试按钮(或者快捷键)进行调试代码。

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/151888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java多线程(一)——Hotspot的锁( Synchronized)

1. 锁的概念 Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurren包等 2. Synchronized的基本使用 synchronized是Java提供的一个并发控制的关键字。主要…

【LINUX】工具篇--gcc的使用

我们知道,在程序翻译的过程中一般会经过四个步骤预处理头文件展开,条件编译,宏替换,去注释编译C语言代码--->汇编代码汇编汇编代码--->可重定向目标二进制文件(只把自己写的函数形成二进制文件,此阶段无法被执行…

Vue3一学就会系列:02 模板语法与计算属性

系列文章目录 Vue3一学就会系列:01 vue3安装与搭建项目 文章目录系列文章目录文本插值html 插入属性绑定常用指令计算属性总结文本插值 最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号): 知识点: {{}}…

(考研湖科大教书匠计算机网络)第一章概述-第二节:三种交换方式(电路交换、报文交换和分组交换)

文章目录一:电路交换(Circuit Switching)二:分组交换(Packet Switching)三:报文交换(Message Switching)四:三种交换方式对比(1)概述&…

一个自定义的html5视频播放器

// 功能:// 1.视频的播放与暂停(图标变化)// 2.总时间的显示// 3.当前时间的显示(进度)// 4.进度条的显示// 5.跳跃播放// 6.全屏<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport"…

利用决策树学习基金持仓并识别公司风格类型

摘要与声明 1&#xff1a;本文主要利用决策树学习基金持仓并反向推理出一套更受市场认可的风格划分标准&#xff0c;最后借助该模型识别公司所属的风格类型&#xff1b; 2&#xff1a;本文主要为理念的讲解&#xff0c;模型也是笔者自建&#xff0c;文中假设与观点是基于笔者…

JVM的内存配置参数

VM的结构问题&#xff1a;JVM分两块&#xff1a;PermanentSapce和HeapSpace&#xff0c; HeapSpace 【old new{Eden&#xff0c;from&#xff0c;to}】 PermantSpace主要负责存放加载Class类级别的class本身&#xff0c;method&#xff0c;field等反射对象&#xff0c;一般不…

重磅指挥棒!2023年国资委央企指标考核体系从两利四率到一利五率变化解读

前几天&#xff0c;2023年的第三个工作日&#xff0c;国资委召开了中央企业负责人会议&#xff0c;提出了优化中央企业经营考核指标体系的六个指标 —— 一利五率&#xff0c;目标是一增一稳四提升。一增&#xff0c;就是确保利润总额增速高于全国GDP增速。一稳&#xff0c;资产…

小程序学习(1)-------小程序的结构及作用

获取APPID 开发-开发管理->开发设置&#xff08;新建项目时需要输入appid&#xff09; 小程序的文件结构 pages 用来存放所有小程序的页面 utils 用来存放工具性质的模块&#xff08;例如&#xff1a;格式化时间的自定义模块&#xff09; app.js 小程序项目的入口文件 ap…

【Redis】分别从互斥锁与逻辑过期两个方面来解决缓存击穿问题

文章目录前言一.什么是缓存击穿二.基于互斥锁解决缓存击穿三.基于逻辑过期解决缓存击穿四.接口测试五.两者对比前言 身逢乱世&#xff0c;未雨绸缪 一.什么是缓存击穿 说直白点&#xff0c;就是一个被非常频繁使用的key突然失效了请求没命中缓存&#xff0c;而因此造成了无数…

使用electron将vue项目打包成exe

文章目录一、前言二、实现方法1.跑通示例代码 electron-quick-start<1>clone示例代码<2>进入项目根目录&#xff0c;下载依赖<3>测试运行2.打包自己的 vue 项目3.将vue项目整合到示例代码中打包exe<1>将打包好的 dist 文件夹复制到示例代码 electron-q…

sklearn之OPTICS聚类

文章目录简介sklearn实现cluster_optics_dbscan简介 OPTICS算法&#xff0c;全称是Ordering points to identify the clustering structure&#xff0c;是一种基于密度的聚类算法&#xff0c;是DBSCAN算法的一种改进。 众所周知&#xff0c;DBSCAN算法将数据点分为三类&#…

ResNet精读(2)

FLOPs &#xff1a;整个网络要计算多少个浮点运算 卷积层的浮点运算等价于 输入的高*输入的宽*通道数*输出通道数再乘以卷积核的高和宽再加上全连接的一层 我们发现训练的时候的精度是要比测试精度来的高的在一开始&#xff0c;这是因为训练的时候用了数据增强 使得训练误差…

2022年莱佛士大盘点 ,设计的种子遍地开花!

2022似乎过得尤其之快&#xff0c;反复的居家隔离和线上网课&#xff0c;似乎给2022蒙上了一层雾蒙蒙的灰色。但2022总还给我们留下了些东西&#xff0c;在莱佛士设计学院&#xff0c;我们共同见证了梦想的种子在设计的各个领域遍地开花。现在我们一起来看看2022年莱佛士学生们…

广义表——LISP的基石

线性表中存放的是同一类型的元素&#xff0c;而广义表是线性表的推广&#xff0c;即广义表中除包含类型相同的元素外&#xff0c;还可以包含具有其自身结构的元素。在人工智能领域使用十分广泛的 LISP语言中&#xff0c;广义表是一种基本数据类型&#xff0c;LISP 语言中的数据…

Vue3案例-todoMVC-pinia版 (可跟做练手)

列表展示功能 &#xff08;1&#xff09; 在main.js中引入pinia import { createApp } from vue import App from ./App.vue import { createPinia } from pinia import ./styles/base.css import ./styles/index.cssconst pinia createPinia() createApp(App).use(pinia).m…

Spring源码学习~11、Bean 的加载步骤详解(二)

Bean 的加载步骤详解&#xff08;二&#xff09; 一、循环依赖 1、什么是循环依赖 循环依赖就是循环引用&#xff0c;即两个或多个 bean 互相之间持有对方&#xff0c;如下图&#xff1a; 循环引用不是循环调用&#xff0c;循环调用是方法之间的环调用&#xff0c;循环调用是…

谷粒学院——Day18【权限管理Spring Security、配置中心Nacos、代码托管git】

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

Linux搭建Gitlab保姆级教程

文章目录1、gitlab安装1.1、gitlab介绍1.1.1、概念1.1.2、gitlab与github的区别1.1.3、gitlab的优势1.1.4、gitlab主要服务构成1.1.5、gitlab的工作流程1.2、准备工作1.3、安装1.4、配置1.5、启动1.6、测试2、gitlab安装目录3、gitlab常用命令4、注册账号5、gitlab相关设置5.1、…

上半年要写的博客文章23

上半年要写的博客文章21 这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个…