安卓系统
其实安卓系统核心就是 Linux 系统,保持着“万物皆文件”的特性。
一、根目录文件夹:
根目录 (/):系统的最顶层目录,包含系统核心文件和目录链接。
系统目录 (/system):存放系统核心文件和库。
/system/app:系统应用的 APK 文件。/system/bin:Linux 系统命令和工具。/system/etc:系统配置文件。/system/framework:系统框架库。/system/lib:系统共享库文件(.so)。/system/media:系统媒体文件,如铃声、图片等。
数据目录 (/data):用户数据和应用程序数据。
/data/app:用户安装的应用 APK 文件。/data/data/<package_name>:每个应用的私有数据目录。
缓存目录 (/cache):临时文件和缓存数据。
存储目录 (/sdcard): 外部存储,用户视角下的“根目录”
设备目录 (/dev):设备文件。
进程目录 (/proc):系统运行时信息。映射内存中的进程信息
系统属性文件 (/system/build.prop):系统属性配置。
二、Android 启动过程

1. 引导加载程序(Bootloader)启动:
引导加载程序通常存储在设备固件中,类似电脑的 UEFI 固件(或手机的底层引导程序),主要任务是初始化硬件(如 CPU、内存、存储),并加载操作系统内核。其关键功能是验证系统分区的完整性和签名:通过厂商公钥校验核心分区(如boot内核分区、system系统分区、vbmeta签名校验分区)头部的签名数据,确保只有官方认证的系统才能启动。
我们常说的 Bootloader 解锁,是指通过厂商允许的操作(如 Fastboot 指令)触发 Bootloader 的解锁机制,使其不再强制执行分区签名校验(部分厂商会标记 “解锁状态” 并清除校验逻辑),从而允许用户刷入第三方系统或修改系统分区。2. 内核加载:
引导加载程序会根据预定义的配置从设备存储中加载操作系统内核。3. 内核初始化:
一旦内核加载到内存中,引导加载程序会将控制权转交给内核。内核开始执行初始化过程,包括对硬件进行初始化、建立虚拟文件系统、创建进程和线程等。4. 启动 init 进程:
内核初始化完成后,会启动名为 init 的用户空间进程。init 进程是 Android 系统的第一个用户空间进程,它负责系统的进一步初始化和启动。init 进程会读取系统配置文件(例如 init.rc),并根据其中的指令启动系统服务和应用程序。5. 启动 Zygote 进程:
在 init 进程启动后,会启动名为 Zygote 的进程。Zygote 进程是 Android 应用程序的孵化器,它会预加载常用的 Java 类和资源,以加速应用程序的启动。6. 启动系统服务(SystemServer):
在 Zygote 进程启动后,还会启动一系列系统服务,例如 SurfaceFlinger、ActivityManager、PackageManager 等。这些系统服务负责管理系统的各个方面,包括显示、应用程序生命周期、包管理等。7. 启动桌面程序(Launcher):
一旦系统服务启动完成,Android 系统就处于可用状态。就会启动桌面程序,用户可以开始启动应用程序并使用设备进行各种操作了。
三、Android Runtime
JVM
安卓的 app 大多都是由 Java 或者 Kotlin 编写的,JVM 就是 Java 程序的运行时环境,但是严格来说不算是安卓运行时。
使用 Java 编写的代码,需要经过 javac 或者 kotlinc 编译成.class 文件,最后在 JVM 上个运行。
而 JVM 又可简单分为 Class Loader、Runtime Data Area 以及 Execution Engine 三个部分,Class Loader 负责从.class 加载和分配内存。Runtime Data 负责存储数据,分为方法区、堆区、栈区、程序计数器以及本地方法栈。Execution Engine 负责二进制代码的执行以及垃圾回收,其中又有 Interpreter(解释器执行)和 JIT(即时编译)两种执行模式,JIT 会把长期执行的热点代码编译成本地机器码缓存起来,后续无需解释可直接执行。
Dalvik
针对 JVM 还要说的是,这是一种基于栈的虚拟机,如果移动端使用这种运行时,必然会对设备的性能要求很高。因此引入基于寄存器的虚拟机——Dalvik。
Dalvik 虚拟机的可执行文件是.dex,可以通过.class 转换得来。Dalvik 同样有 interpreter 和 JIT 两种执行模式。
ART
到了 Android4.4,Google 推出了新的 Android Runtime——ART。
为了缩短应用启动时长,采用了 AOT(Ahead-of-time)编译方式,即在程序安装时就将 dex 提前编译成机器码,生成一个 oat 文件。但这会导致安装时间延长,占用更多的存储空间。
四、进程创建
1. 整个过程涉及到多个 IPC(进程间通信),主要是 binder、sockert 机制。
binder 是安卓系统特有的跨进程通信机制,基于 C/S 架构,主要用于系统进程和应用进程间通信
socket 是一种是网络通信方式,多用于跨设备间进程通信,但是同样可以用本地回环地址实现本地跨进程通信。
安卓系统中还有一种通信用方式得比较多吧,intent机制,又可以分为显式和隐式两种。但是它是实现的组件间通信,结合 binder 机制可以实现跨进程的组件通信。
2. 进程创建大致分为四个阶段:
a. 点击桌面快捷方式之后,Laucher所在进程向 system_server 发送请求
b. system_server中的 AMS 启动 Proccess.start,向 Zygote 发送新建进程的请求。
c. Zygote 进程在系统启动过程开始创建,经过执行
ZygoteInit.main()后便进入runSelectLoop()循环体内,当收到创建新的进程请求后,Zygote 进程执行 ZygoteConnection.runOnce()方法,最后通过 fork() 创建新进程。d. 新建的 app 进程相当于 Zygote 的子进程,接着执行 handleChildProc 方法,最后调用 ActivityThread.main() 方法。
每个阶段都有着较为复杂的函数调用,具体的源码分析移步理解 Android 进程创建流程
3. 下面这个图可以大致解释启动一个 app 都发生了什么:

安卓文件结构
一、APK文件基本结构
我们随便找个 apk 解压,能看到这样的目录:

1.assets:
存放静态资源文件,如图片、视频等。这些文件不会被编译,而是直接打包到最终的程序中
2.lib:
存放.so 库文件,可能会针对不同架构,在其下一级目录设计多个文件夹,如 armeabi-v7a、arm64-v8a、x86
3.META-INF:
存放数字签名相关文件,包括 MANIFEST.MF、CERT.SF 和 CERT.RSA
MANIFEST.MF:这是摘要文件。程序遍历 Apk 包中的所有文件 (entry),对非文件夹非签名文件的文件,逐个用 SHA1(安全哈希算法) 生成摘要信息,再用 Base64 进行编码。如果你改变了 apk 包中的文件,那么在 apk 安装校验时,改变后的文件摘要信息与 MANIFEST.MF 的检验信息不同,于是程序就不能成功安装。
CERT.SF:这是对摘要的签名文件。对前一步生成的 MANIFEST.MF,使用 SHA1-RSA 算法,用开发者的私钥进行签名。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息(即,MANIFEST.MF 文件)进行对比,如果相符,则表明内容没有被异常修改。
CERT.RSA 文件中保存了公钥、所采用的加密算法等信息。
4.AndroidMainfest.xml:
包含应用程序的基本信息,如包名、版本号、权限、组件等。系统根据这个文件来管理应用程序的生命周期
<?xml version="1.0" encoding="utf-8"?>
<!-- AndroidManifest.xml 是每个 Android 应用的核心配置文件,
用于声明应用的组件、权限、配置信息以及与系统的交互方式 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<!-- 声明应用兼容的 SDK 版本范围 -->
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="33"
android:maxSdkVersion="33" />
<!-- 声明应用期望的设备配置 -->
<uses-configuration
android:reqFiveWayNav="false"
android:reqHardKeyboard="false"
android:reqKeyboardType="nokeys"
android:reqNavigation="nonav"
android:reqTouchScreen="finger" />
<!-- 声明应用需要的硬件或软件特性 -->
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<!-- 请求应用运行所需的权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- 自定义权限定义,用于保护应用组件 -->
<permission
android:name="com.example.myapp.MY_PERMISSION"
android:label="string:permission_label"
android:description="string:permission_desc"
android:protectionLevel="normal" />
<!-- 权限树定义,用于组织相关权限组 -->
<permission-tree
android:name="com.example.myapp.permission_tree"
android:label="string:permission_tree_label" />
<!-- 权限组定义,用于对权限进行逻辑分组 -->
<permission-group
android:name="com.example.myapp.permission_group"
android:label="string:permission_group_label"
android:description="string:permission_group_desc" />
<!-- 声明测试工具类,用于自动化测试 -->
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.example.myapp" />
<!-- 声明应用支持的屏幕尺寸和密度 -->
<supports-screens
android:resizeable="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true" />
<!-- 应用定义标签,包含应用的全局属性和组件 -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!-- Activity 组件声明,用于提供用户界面 -->
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true">
<!-- Intent Filter 定义 Activity 可以响应的 Intent 类型 -->
<intent-filter>
<!-- 声明此 Activity 可以作为应用入口点 -->
<action android:name="android.intent.action.MAIN" />
<!-- 声明此 Activity 应显示在应用启动器中 -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Activity 别名声明,用于为现有 Activity 提供替代入口 -->
<activity-alias
android:name=".MainActivityAlias"
android:targetActivity=".MainActivity"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 元数据定义,用于存储额外配置信息 -->
<meta-data
android:name="additional_info"
android:value="alias_info" />
</activity-alias>
<!-- Service 组件声明,用于在后台执行操作 -->
<service
android:name=".MyService"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.example.myapp.MY_SERVICE" />
</intent-filter>
<meta-data
android:name="service_config"
android:value="config_value" />
</service>
<!-- BroadcastReceiver 组件声明,用于监听系统或应用事件 -->
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<meta-data
android:name="receiver_data"
android:value="data_value" />
</receiver>
<!-- ContentProvider 组件声明,用于跨应用共享数据 -->
<provider
android:name=".MyContentProvider"
android:authorities="com.example.myapp.provider"
android:enabled="true"
android:exported="false">
<!-- 声明允许访问的 URI 权限 -->
<grant-uri-permission
android:path="/data"
android:pathPattern="/.*"
android:pathPrefix="/images" />
<meta-data
android:name="provider_info"
android:value="provider_value" />
</provider>
<!-- 声明应用需要链接的共享库 -->
<uses-library
android:name="android.test.runner"
android:required="true" />
</application>
</manifest>
5.classes.dex:
包含应用程序的所有 Java 类和方法,是应用程序的可执行文件。它是一个被编译过的 DEX(Dalvik Executable)文件,用于在 Android 设备上运行 Java 代码,通常不止一个。
6.resources.arsc:
包含应用程序的所有资源信息,如布局文件、字符串、图片等。这个文件在应用程序编译过程中由 aapt 工具生成,并被打包进 APK 文件中
二、ELF文件格式
ELF 是 unix 系统的二进制文件。前面说过安卓是基于 Linux 内核,如 init(系统初始化进程)、zygote(安卓应用孵化器)都是 ELF 文件,APP 的动态链接库也是 ELF 文件。

ELF Header: 描述了描述了体系结构和操作系统等基本信息并指出 Section Header Table 和 Program Header Table 在文件中的什么位置
Program Header Table: 保存了所有 Segment 的描述信息
Section Header Table: 保存了所有 Section 的描述信息
常见字段含义:

GOT
全称:Global Offset Table(全局偏移表)。
位置:数据段(
.got/.got.plt)。作用:存储全局变量和外部函数的地址,运行时可被动态链接器修改。
PLT
全称:Procedure Linkage Table(程序链接表)。
位置:代码段(
.plt)。作用:包含汇编指令,实现外部函数的延迟绑定和调用跳转。
特性:程序加载后不可修改(只读),但可通过 GOT 间接控制跳转目标。
重定位
重定位(Relocation)是计算机程序链接和加载过程中的关键机制,用于解决程序中符号(如函数、变量)地址的动态分配问题。简单来说,它是将程序中符号的逻辑地址转换为实际物理地址 ** 的过程。那么在 Linux 系统中,重定位的过程实际就是通过 GOT 表和 PLT 表来实现的。可以分为两个阶段:
链接重定位:也叫静态重定位。在链接阶段,当发现函数是外部函数时,链接器在 PLT 段生成一段跳转代码 PLT Stub, 然后将代码中调用该函数的地址换成 stub 的地址
运行时重定位:也叫动态重定位。在运行时,把外部函数的地址加载到 got 表中,调用该函数时,通过 plt 调转到 got 表中存储的地址实现调用。
延迟绑定
延迟绑定指的是将外部函数地址的解析推迟到首次调用时,从而减少程序启动时间。
程序启动时,仅初始化 PLT(程序链接表)和 GOT(全局偏移表)的结构,不立即解析函数地址。
静态重定位是在给延迟绑定做准备,动态重定位是在执行阶段。

与延迟绑定对应的是立即绑定。 动态链接器遍历所有外部符号(如printf、malloc)。 一次性解析所有符号的真实地址,并更新 GOT 表。 直接通过 GOT 表中的真实地址调用,无需动态链接器介入。
参考文档
Android 目录结构全解析
Android 启动流程
理解 Android 进程创建流程
深入理解 Android-Runtime
AndroidManifest.xml 最全详解
Android Hook 技术学习——常见的 Hook 技术方案总结