【教程】Android(AOSP)Framework开发/ROM定制快速教程
备注
2025/03/13 星期四
记录一下完整的Android系统开发知识,方便自己查阅
一、基础知识
Android是Google基于Linux内核研发的移动操作系统,Google将Android源码进行了开源称为AOSP(Android Open Source Project)。Android经过多年发展,除了手机还广泛应用于手表、平板、电视、车机等智能设备中。对AOSP源码做二次开发的工作一般称为Framework开发或者ROM定制。
Android设备制造行业一个基本的分工是:
1.Google开发AOSP
2.芯片厂商根据芯片适配AOSP(如高通、展锐、联发科、全志)
3.主板厂商(有的芯片厂商也当主板厂商)设计电路板,增加其他配件,在芯片厂商源码基础上继续修改做适配
4.设备制造商对主板厂商的源码定制UI、增加功能、优化系统(如华为、小米、OPPO、VIVO)
芯片厂商和主板厂商一般被称为vendor,设备制造商一般被称为oem或odm
另外,与传统固件(BIOS/UEFI、BootROM、硬件控制程序)概念不同,Android领域的固件很多时候也指包含了系统镜像、Linux内核、SE/TEE、Bootloader、Recovery等软件的线刷包。而“系统”多指基于AOSP修改得到的操作系统。
二、基本操作
1.源码获取
Google建议在Ubuntu上进行开发,提供了Android Studio for Platform作为开发工具。获取AOSP源码的操作如下:
# 安装基本依赖
sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig
# 安装源码管理工具repo
sudo apt-get install repo
# 初始化仓库
repo init --partial-clone -b [分支] -u https://android.googlesource.com/platform/manifest
# 拉取源码
repo sync -c -j8
2.编译刷机
对源码进行编译的操作如下:
source build/envsetup.sh
lunch
make -j$(nproc)
(注:Android中的内核文件是预编译好的,如果想要修改内核需要拉取对应的内核代码,修改编译后将编译结果放到指定路径,再重新编译打包Android镜像。)
编译完成后进行刷机的操作如下:
adb reboot bootloader
fastboot flashall -w
顺便一提,刷机的方式有fastboot、recovery、EDL和ota应用,这里我们选的是fastboot俗称线刷,也可以自行选用其他方式进行刷机(recovery俗称卡刷,EDL是紧急下载模式用于救砖,最知名是高通的9008,其他芯片厂商也有类似的工具,ota应用最常见的就是手机设置中的系统更新)
3.构建系统
Android提供了两种构建方式,在Android 7.0之前使用基于make的构建系统,使用Android.mk文件描make述构建规则,在Android 7.0后引入了soong构建系统,使用Android.bp文件描述soong的构建规则。soong中采用了kati GNU Make克隆工具和ninja后端来加速对系统源码的构建,用于解决make在Android中构建缓慢、容易出错、无法扩展、难以测试等问题。虽然make构建系统已经逐步被soong构建系统取代,但是仍然可以使用。
Android.mk:
// 设置当前构建所在目录,通常作为一个Android.mk文件中的第一行
LOCAL_PATH := $(call my-dir)
// 清空所有的LOCAL_变量,避免模块之间的变量相互干扰。
include $(CLEAR_VARS)
// 定义模块名称,必须是唯一不重复的,构建系统会根据类型自动添加前缀后缀
LOCAL_MODULE := my-module
// 模块类型(可选项),如APPS、 EXECUTABLES、SHARED_LIBRARIES、ETC
LOCAL_MODULE_CLASS := EXECUTABLES
// 模块所需全部源文件
LOCAL_SRC_FILES := src/test.cpp
// C/C++搜索头文件的路径
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
// 模块依赖的库文件(静态库.a和动态库.so)
LOCAL_STATIC_LIBRARIES:= lib1
LOCAL_SHARED_LIBRARIES:= lib2
// 编译选项
LOCAL_CFLAGS := -Wall -Wextra
LOCAL_CPPFLAGS := -std=c++11
// 构建类型,如BUILD_EXECUTABLE、BUILD_SHARED_LIBRARY、BUILD_STATIC_LIBRARY、BUILD_PACKAGE、BUILD_JAVA_LIBRARY、BUILD_PREBUILT
include $(BUILD_XXX)
Android.bp:
// 构建类型,如cc_binary、cc_library_static、cc_library_shared、android_app、java_library、java_library_static、prebuilt_apk
cc_library_shared {
// 模块名,如果是库文件通常约定加上'lib'前缀
name: "libdemo",
// 源文件列表
srcs: [
"src/test.c",
],
// C/C++搜索头文件的路径
include_dirs: ["include"],
// 编译选项
cflags: ["-Wall", "-Wextra"],
cppflags: ["-std=c++11"],
// 模块依赖的库文件(静态库.a和动态库.so)
shared_libs: [
"liblog",
"libcutils",
],
// 控制哪些模块可以依赖此模块,如:["//visibility:public"]、["//visibility:private"]、["//path/to:other_module"]
visibility: ["//visibility:public"],
}
另外Android提供了androidmk工具用于将Android.mk文件转换为Android.bp文件
三、系统结构
1.系统架构
Android分为5层结构,从上到下依次是应用层、系统框架层、原生库和运行时层、HAL层、内核层
应用层:用户直接与之交互的部分,包括各种应用程序和系统界面
框架层:提供Java/Kotlin类接口
原生库:C/C++ 库,一般是用于实现核心系统功能高性能的原生库
运行时:ART和Dalvik的虚拟机,用于执行dex文件
HAL:标准硬件接口,使Android以统一的方式调用硬件的功能
(Linux的硬件驱动程序是嵌入或动态加载到内核的。Android不允许将所有驱动嵌入到内核饼禁用了模块动态加载机制,而是定义了一个API,硬件供应商必须提供一个软件模块负责实现在定义的API)
Linux内核:Android定制后的Linux内核,提供最基本的CPU调度、内存管理、文件系统等功能
2.目录结构
目录 | 作用 |
---|---|
/acct | Linux内核管理进程的CPU、内存、IO等资源的信息 |
/cache | 系统临时更新和缓存 |
/cache/backup | 备份目录 |
/cache/recovery | recovery目录 |
/config | |
/data | 用户数据目录 |
/data/anr | 系统异常记录 |
/data/adb | adb数据目录 |
/data/app | 存放用户安装的第三方应用app文件 |
/data/dalvik-cache | 存放优化后的字节码,用于应用快速启动 |
/data/data | 所有应用包括系统应用和第三方应用的私有数据 |
/data/media | 内部存储/sdcard或/storage/emulated/0的实际挂载点 |
/data/misc | wifi、蓝牙、vpn、adb密钥等数据 |
/data/property | persist属性 |
/data/system | 存放系统核心配置,如应用列表、账户信息、设备策略、锁屏设置等 |
/data/user | 多用户的环境下指向/data/data的符号链接,用于实现多用户数据隔离 |
/dev | |
/mnt | 挂载外接设备 |
/oem | 设备制造商目录 |
/proc | 进程实时信息 |
/root | root用户家目录 |
/sbin | root用户的二进制bin |
/sdcard | 符号链接,指向data/media或/storage/emulated/0 |
/storage | 挂载所有存储设备的根目录 |
/sys | Linux内核的文件系统,提供虚拟文件系统、内核对象、设备驱动、硬件属性等 |
/system | Android系统的核心文件 |
/system/app | 存放Android的系统应用app |
/system/bin | 存放系统的命令行工具二进制elf |
/system/etc | 系统主机名、ip地址映射等配置文件 |
/system/lib | 存放系统的动态库so |
/system/framework | 系统框架的核心jar |
/system/media | 存放系统铃声、提示音、开机动画等多媒体文件 |
/system/fonts | 存放系统的字体 |
/system/priv-app | 系统特权应用app |
/system/usr | 存放用户键盘布局、共享、时区等配置文件 |
/system/xbin | 存放额外的命令行工具二进制elf |
3.源码结构
目录 | 作用 |
---|---|
art | 该目录是在Android 5.0中新增加的,主要是实现Android RunTime(ART)的目录,它作为Android 4.4中的Dalvik虚拟机的替代,主要处理Java字节码执行 |
bionic | Android的C库,包含了很多标准的C库函数和头文件,还有一些Android特有的函数和头文件 |
build | 该目录包含了编译Android源代码所需要的脚本,包括makefile文件和一些构建工具 |
compatibility | Android设备的兼容性测试套件(CTS)和兼容性实现(Compatibility Implementation) |
cts | Android设备兼容性测试套件(CTS),主要用来测试设备是否符合Android标准 |
dalvik | Dalvik虚拟机,它是Android 2.3版本之前的主要虚拟机,它主要处理Java字节码执行 |
developers | Android开发者文档和样例代码 |
development | 调试工具,如systrace、monkey、ddms等 |
device | 特定的Android设备的驱动程序 |
external | 第三方库,如WebKit、OpenGL等 |
frameworks | Android应用程序调用底层服务的API |
hardware | Android设备硬件驱动代码,如摄像头驱动、蓝牙驱动等 |
kernel | Android系统内核的源代码,它是Android系统的核心部分 |
libcore | Android底层库,它提供了一些基本的API,如文件系统操作、网络操作等 |
packages | Android系统中的系统应用程序的源码,例如短信、电话、浏览器、相机等 |
pdk | Android平台开发套件,它包含了一些工具和API,以便开发者快速开发Android应用程序 |
platform_testing | 测试工具,用于测试Android平台的稳定性和性能 |
prebuilts | 预先编译的文件,如编译工具、驱动程序等 |
sdk | Android SDK的源代码,Android SDK的API文档、代码示例、工具等 |
system | Android系统的核心部分,如系统服务、应用程序、内存管理机制、文件系统、网络协议等 |
test | 测试代码,用于测试Android系统的各个组件 |
toolchain | 编译器和工具链,如GCC、Clang等,用于编译Android源代码 |
tools | 开发工具,如Android SDK工具、Android Studio、Eclipse等 |
vendor | 硬件厂商提供的驱动程序,如摄像头驱动、蓝牙驱动等 |
四、二次开发
首先了解一下不同分区的作用,这里优先区分一下system、vendor、odm和product分区,
分区 | 作用 |
---|---|
system | AOSP系统组件,所有product都通用的软件 |
vendor | 芯片和主板厂商针对硬件开发的通用的可执行文件、库、系统服务和 app (不包含驱动) |
odm | 产品硬件差异导致的相关软件差异部分都会放在odm分区 |
product | 软件差异都放在product分区 |
因此可以从软硬件、通用和差异方面简单理解为:
软件 | 硬件 | |
---|---|---|
通用 | system | vendor |
差异 | product | odm |
编译后的文件位置和编译命令如下:
分区 | Android.mk | Android.bp |
---|---|---|
system | 默认就是输出到 system 分区 | 默认就是输出到 system 分区 |
vendor | LOCAL_VENDOR_MODULE := true | vendor: true |
odm | LOCAL_ODM_MODULE := true | device_specific: true |
product | LOCAL_PRODUCT_MODULE := true | product_specific: true |
1.添加产品
不同产品的源码会存在差异,通过配置文件来实现区分,这些配置文件称为 Product,每一个 Product 适用于特定的硬件产品,在编译时通过lunch进行选择。
Google提供的product 配置文件会保存在build/target目录下,芯片厂商或主板厂商提供的product配置文件在device目录下。
当我们想要添加自己的product 配置文件时一般也会选择在device目录下新增<公司名>/<Product名>,再添加AndroidProducts.mk、<Product名>.mk、BoardConfig.mk,可以参考AOSP原生文件进行编写。
AndroidProducts.mk是由构建系统自动扫描的入口文件,基本内容如下:
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/<Product名>.mk \
COMMON_LUNCH_CHOICES := \
<Product名>-user \
<Product名>-userdebug \
<Product名>-eng
<Product名>.mk是产品的核心配置文件,用于定义产品的基础信息、引用其他配置文件、设置系统分区、预装程序、系统属性等,基本内容如下:
# 基本信息
PRODUCT_NAME := <Product名>
PRODUCT_DEVICE := <Product名>
PRODUCT_BRAND := <公司名>
PRODUCT_MODEL := <机型名>
# 引用其他配置
$(call inherit-product, <path/file.mk>)
# 构建类型
PRODUCT_BUILD_VARIANT := user
#是否使用自定义内核
TARGET_NO_KERNEL_OVERRIDE := true
# 分区配置
PRODUCT_SYSTEM_PROPERTIES += \
ro.system.size=4G
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 4294967296
# 添加程序
PRODUCT_PACKAGES += \
<XXX> \
<YYY> \
<ZZZ>
# 添加属性
PRODUCT_PROPERTY_OVERRIDES := \
persist.sys.flag=1 \
ro.control.flag=0
# 添加文件
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/<source_path/source_file>:$(TARGET_COPY_OUT_SYSTEM)/<target_path/target_file>
BoardConfig.mk是定义硬件底层配置、芯片架构、分区大小、bootloader 和 kernel, 是否支持摄像头,GPS导航等一些板级特性的文件。
2.添加程序
Android中有很多bin目录,其中都是一些二进制可执行文件或shell脚本,这些程序源码主要来自于external、system/core、frameworks/native/cmds和frameworks/base/cmds,这里我们参考已有工具新建工具名目录、编写Android.bp文件、新建src/工具名目录并编写源码,在<Product名>.mk文件中添加
PRODUCT_PACKAGES += 程序名
如果直接编译文件会被放到system分区,还需要在<Product名>.mk文件中指定目标位置才不会报错
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST += \
system/bin/程序名\
system/app/程序名/程序名.apk \
system/lib/lib库名.so \
但是我们编写的工具大概率是产品相关的官方建议放到product分区下,Android.bp中添加product_specific: true即可
3.添加其他
类似的,还可以去添加库文件、配置文件、app等,方法大多差不多,只有一些细节差别,这里不过多赘述可以自行搜索
五、启动流程
Android系统启动可以分为七个阶段:BootROM阶段、Bootloader阶段、Kernel阶段、init阶段、zygote阶段、System Server阶段和Launcher阶段。
1.BootROM阶段
BootROM阶段在设备通电后,由SoC芯片内置只读程序初始化最基础的硬件(如CPU核心、时钟),查找并验签位于特定位置的Bootloader程序,如果验签通过则会加载Bootloader程序
2.Bootloader阶段
Bootloader一般分为两级,由BootROM加载的称为一级导加载程序(PBL),一级引导加载程序负责初始化更多基础硬件(如DRAM、eMMC/UFS、显示器),查找并验签位于特定分区的二级引导加载程序(SBL),如果验签通过则会加载二级引导加载程序。
二级引导加载程序继续初始化更多硬件,验签启动的镜像boot.img,如果验签通过则会加载boot.img到内存中。
3.Kernel阶段
Bootloader将boot.img 中的内核加载到内存中,CPU将控制权从Bootloader转交给内核,内核会初始化各类硬件设备、加载驱动程序、管理内存中断信号等。内核还会加载boot.img中一个最小临时文件系统initramfs,然后启动init进程
4.init阶段
init进程是Android系统中的第一个用户空间进程,PID为1。init进程又会读取init.rc文件,根据该文件中的配置信息加载正式的文件系统、启动并配置SELinux、启动Android系统的各个组件。init进程启动流程可以分为三个主要阶段:第一阶段初始化(first_stage_init)、SELinux设置(selinux_setup)二阶段初始化(second_stage_init)。第一阶段初始化主要是挂载分区,SELinux设置阶段会初始化SELinux相关的内容,第二阶段初始化完成进程和服务的启动。
init进程刚开始运行的时候是内核态,然后运行一个用户态程序把自己强行转成用户态,后面的操作全在用户态下进行,当init进程从内核态跳到用户态后,用户态下的程序无法再回到内核态,想要进入内核态只能通过系统调用。
init.rc文件是一个专门用于Android inti的配置文件,有三类语句:On、Service、Import。On是在Action的情况下执行Command,Service是定义服务的名称、路径、启动参数和配置项,Import是引入其他rc文件:
import /init.environ.rc
on <trigger>
<command1>
<command2>
...
<commandn>
service <service_name> <path> [ <argument> ]
<option1>
<option2>
...
<optionn>
5.Zygote阶段
Zygote是由init启动的进程,Zygote负责预加载核心资源(framework.jar, framework-res.apk 等)、 初始化 Android Runtime(ART),启动 system_server(com.android.server.SystemServer)。
6.system_server阶段
system_server是Zygote fork出来的第一个进程,负责启动和管理几乎所有关键native和java服务(如AMS、PMS、WMS、LocationManagerService、TelephonyManagerService、Wi-FiService、BluetoothService等),system_server启动完成后会通过广播所有已启动的服务。
7.Launcher阶段
AMS收到system_server发送的启动完成广播后会启动 Launcher,Android系统就完全启动了,用户可以进入桌面使用各种应用程序。
六、Binder
1.原理
Binder是Android为了代替Linux IPC设计的,是具有远程过程调用RPC(Remote Procedure Call)能力的进程间通信IPC(Inter-Process Communication)机制。为了保证不同进程之间互不干扰,Linux为每个进程都分配有独立的虚拟内存空间,不同进程之间的用户空间相互独立实现进程隔离,系统上的所有进程共用一个内核空间,如果进程间想要相互访问数据就需要借助内核空间来实现。将数据从一个进程的用户空间复制到内核空间,再从内核空间将数据复制到另一个进程的用户空间,就可以达到跨进程访问数据的目的。但是需要做两次复制操作,如果将一个进程用户空间虚拟地址直接映射到内核空间的物理地址,再让另一个进程从内核空间复制数据,就可以优化为一次复制操作,这就是Binder的一次复制原理。
同时Binder提供了RPC能力。RPC就是像调用本地函数一样调用远程函数(远程包括逻辑和隔离和物理隔离,即本机其他进程或者网络上其他机器的进程),因此需要将数据按照一定的规则打包并发送给目标进程,目标进程解析数据执行函数后再将结果按一定格式发回给调用者。
Android中常见的IPC机制如Intent、Messenger、ContentProvider、AIDL底层都是由Binder实现的。
2.组成
Android的Binder主要包括了Client、Server、ServiceManager和Binder驱动四个部分。
Client是发起调用的进程
Server是提供Service的进程,Service是被调用的函数集合
ServiceManager是用于管理Service的进程,Server需要将Service注册到ServiceManager才能被Client调用
Binder驱动是内核层的特殊字符设备驱动,用于实现IPC需要具有访问内核空间的能力,但是Android并不属于Linux内核,不能直接访问内核空间,因此使用Linux的动态可加载内核模块(Loadable Kernel Module,LKM)的机制,将Binder驱动单独编译后再链接到内核中。
3.流程
Binder的核心流程包括:系统启动ServiceManager、Server注册Service、Client调用Service
4.AIDL
由上文可见使用binder需要涉及到内核驱动、naive层、java层非常复杂,Android提供了AIDL用于简化binder使用