【教程】Android(AOSP)Framework开发/ROM定制快速教程_androidframework开发教程
【教程】Android(AOSP)Framework开发/ROM定制快速教程
- 备注
- 一、基础知识
- 二、基本操作
-
- 1.源码获取
- 2.编译刷机
- 3.构建系统
- 三、系统结构
-
- 1.系统架构
- 2.目录结构
- 3.源码结构
- 四、二次开发
-
- 1.添加产品
- 2.添加程序
- 3.添加其他
- 五、启动流程
-
- 1.BootROM阶段
- 2.Bootloader阶段
- 3.Kernel阶段
- 4.init阶段
- 5.Zygote阶段
- 6.system_server阶段
- 7.Launcher阶段
- 六、Binder
-
- 1.原理
- 2.组成
- 3.流程
- 4.AIDL
- 七、核心服务
-
- 1.AMS
- 2.PMS
- 3.WMS
- 八、其他特性
-
- 1.应用权限
- 2.文件权限
- 3.SELinux权限
- 4.签名
- 5.属性
- 6.HAL
备注
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# 安装源码管理工具reposudo apt-get install repo# 初始化仓库repo init --partial-clone -b [分支] -u https://android.googlesource.com/platform/manifest# 拉取源码repo sync -c -j8
2.编译刷机
对源码进行编译的操作如下:
source build/envsetup.shlunch make -j$(nproc)
(注:Android中的内核文件是预编译好的,如果想要修改内核需要拉取对应的内核代码,修改编译后将编译结果放到指定路径,再重新编译打包Android镜像。)
编译完成后进行刷机的操作如下:
adb reboot bootloaderfastboot 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、ETCLOCAL_MODULE_CLASS := EXECUTABLES// 模块所需全部源文件LOCAL_SRC_FILES := src/test.cpp// C/C++搜索头文件的路径LOCAL_C_INCLUDES := $(LOCAL_PATH)/include// 模块依赖的库文件(静态库.a和动态库.so)LOCAL_STATIC_LIBRARIES:= lib1LOCAL_SHARED_LIBRARIES:= lib2// 编译选项LOCAL_CFLAGS := -Wall -WextraLOCAL_CPPFLAGS := -std=c++11// 构建类型,如BUILD_EXECUTABLE、BUILD_SHARED_LIBRARY、BUILD_STATIC_LIBRARY、BUILD_PACKAGE、BUILD_JAVA_LIBRARY、BUILD_PREBUILTinclude $(BUILD_XXX)
Android.bp:
// 构建类型,如cc_binary、cc_library_static、cc_library_shared、android_app、java_library、java_library_static、prebuilt_apkcc_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定制后的Linux内核,提供最基本的CPU调度、内存管理、文件系统等功能
2.目录结构
3.源码结构
四、二次开发
首先了解一下不同分区的作用,这里优先区分一下system、vendor、odm和product分区,
因此可以从软硬件、通用和差异方面简单理解为:
编译后的文件位置和编译命令如下:
1.添加产品
不同产品的源码会存在差异,通过配置文件来实现区分,这些配置文件称为 Product,每一个 Product 适用于特定的硬件产品,在编译时通过lunch进行选择。
Google提供的product 配置文件会保存在build/target目录下,芯片厂商或主板厂商提供的product配置文件在device目录下。
当我们想要添加自己的product 配置文件时一般也会选择在device目录下新增/,再添加AndroidProducts.mk、.mk、BoardConfig.mk,可以参考AOSP原生文件进行编写。
AndroidProducts.mk是由构建系统自动扫描的入口文件,基本内容如下:
PRODUCT_MAKEFILES := \\ $(LOCAL_DIR)/<Product名>.mk \\ COMMON_LUNCH_CHOICES := \\ <Product名>-user \\ <Product名>-userdebug \\ <Product名>-eng
.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=4GBOARD_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/工具名目录并编写源码,在.mk文件中添加
PRODUCT_PACKAGES += 程序名
如果直接编译文件会被放到system分区,还需要在.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.rcon ... service [ ] ...
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使用