简介:Android 5.1(Lollipop)源码包,面向Android系统开发者、应用框架研究者以及自定义ROM定制人群,用于深入理解系统启动流程、内核调度、组件通信机制和多窗口特性。资源以zip压缩包形式提供,大小约27.07MB,当前标注文件总数为0,未给出逐文件明细;目录结构按内核(kernel)、安卓框架(android)、支持库(androidx)、Java标准库(javax)、第三方服务(com、org)、性能基准(benchmarks)、模拟环境(mock_android)、Java核心库及测试并发库(junit、jsr166)等模块划分,便于分层查阅,适合作为系统源码学习的基础素材。已有661人次浏览学习。借助该源码可厘清Linux内核与硬件交互、Activity Manager等核心服务实现、Java并发与单元测试机制;同时可结合内存管理、电源效率和多窗口支持等改动,为后续应用性能优化、系统级调优或定制ROM开发提供一手参考,尤其适合有Android开发基础、想向系统层进阶的读者。 Android 5.1源码(Lollipop)这套东西,我在好几个项目里翻来覆去折腾过,今天把值得说的部分整理出来,从怎么拉源码、目录要看哪里、编译怎么过,到lollipop这代特有的坑,一次讲清楚。不管你是要做系统定制、驱动移植,还是单纯想搞懂Android框架到底怎么跑起来,这篇文章都能给你一个可以照着走的路线。
1. Android 5.1源码的框架价值与核心设计思路
很多人拿到AOSP源码第一反应是直接找app层代码,其实方向就偏了。Android 5.1作为Lollipop的维护版本,在整个Android演进史里属于承上启下的位置:上面延续了Material Design的整套视觉体系,下面的大规模改动则是ART运行时全面替换Dalvik,以及默认64位内核支持铺开。从源码角度讲,这一版比4.4更适合用来做深入阅读,因为关键的架构模块已经稳定,但又比6.0以后保留了更多“老派”的简单感——没有那么多Jeankins、Soong等构建系统新品种的干扰。
1.1 从系统启动角度看代码框架
如果你把Android 5.1源码当作一本书,最好的读法不是按字母序读,而是跟一次开机流程走。按下电源键后,Boot ROM加载Bootloader,然后是Linux内核启动,接着进入init进程,解析init.rc,拉起zygote进程。zygote之后就是Android世界的起点:SystemServer进程里面装着一大堆系统服务,ActivityManagerService、PackageManagerService、WindowManagerService等等。看framework/base/services/java/com/android/server/下面那几百个文件,你会理解什么叫“一切皆服务”。
值得多说一句的是Lollipop在启动流程上的一个变化:zygote的启动从原来单进程拆成了zygote和zygote64两个,专门处理32位和64位应用的兼容问题。这个设计在5.0引入,5.1里做了大量修正,如果你在代码里看到process/zygote相关的两个目录,不要觉得是冗余,那是为了兼容不同instruction set的App进程所必须的。
1.2 Lollipop源码目录快速导航
AOSP根目录看我建议按四个层次掌握:
基础架构层在frameworks/和system/目录。前者是核心框架,包括Java层的framework-res、服务、内容提供器,后者包含init、vold、adb、core等基础C++模块。硬件抽象层在hardware/目录下,libhardware、libhardware_legacy还有各家芯片厂商的HAL实现都在这里,驱动要跟系统对接主要看这一层。中间件和运行库看external/和art/,前者内置大量开源库,后者就是这一代的主角ART运行时。最底层是在kernel/,不过AOSP默认不带内核源码,需要单独从对应芯片商或kernel.org同步。
如果你关注界面和View,重点看frameworks/base/core/java/android/view/、frameworks/base/packages/SystemUI/,Material Design的视觉细节全在这些渲染逻辑里。
2. 源码获取、分支选择与编译环境准备
我在实际项目中见过太多人卡在第一步:要么是源码下载到一半失败,要么是下完源码编译一个多小时直接内存不足崩掉。准备阶段把这些硬性条件摆平,后面会省心很多。
2.1 分支选择与同步要点
Android 5.1对应的AOSP分支,最常用的是android-5.1.1_r38,这是5.1.x系列的最后一个较大维护版本,修复了大量安全补丁,而且兼容市面上绝大多数基于5.1的机型适配。如果你的目标是研究原生机型或者做CTS/GTS认证,优先选择这个tag;如果只做简单的framework定制或个人学习,任何5.1.x r几都行,差异在安全补丁里,不在架构上。
同步源码时有个很实际的建议:不要直接网盘下载别人打包的“完整源码包”。因为那种包很难验证完整性,而且往往不包含repo清单对应的确切git历史,后续你想切分支或者做git blame根本没法用。推荐的做法是用repo工具初始化并同步:
mkdir android-5.1.1 cd android-5.1.1 repo init -u https://android.googlesource.com/platform/manifest -b android-5.1.1_r38 repo sync -j8 -c这里-c参数表示只同步当前分支,不带历史,能省不少流量和时间。同步过程中的网络问题属于常见情况,中断后重新执行repo sync即可,它会断点续传。源码全部下完大概在30GB左右,如果只看不编译,可以按需精简目录,但编译的话建议完整同步。
2.2 硬盘、内存与操作系统要求
编译Android 5.1对机器的要求我实测下来是这样:
- 磁盘空间:AOSP源码加out目录,建议预留80~100GB。源码30GB,out目录30GB,ccache缓存另占20GB左右。
- 内存:16GB起步,32GB才舒服。低于12GB编译到一半out of memory的概率非常高,这时就得靠swap硬撑。
- 操作系统:官方支持Ubuntu 14.04和16.04,但实测Ubuntu 20.04也能跑通,前提是装好兼容库。
核心依赖包的安装,Ubuntu 16.04/18.04下大概是这么一串命令:
sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache libgl1-mesa-dev libxml2-utils xsltproc unzip要注意的是在Ubuntu 20.04及更高版本上,lib32ncurses5-dev这个包已经找不到了,需要用libncurses5-dev libncursesw5-dev替代。还有openjdk版本,Android 5.1要求OpenJDK 7。如果系统默认是OpenJDK 8或11,编译会报错,可以用:
sudo apt-get install openjdk-7-jdk或者找一个JDK7的tar包拷进去,再配置JAVA_HOME。这个坑反复出现,一定要先确认java -version输出版本是1.7.x。
3. 编译Android 5.1源码的完整流程与参数解析
源码同步完,依赖装好,接下来就是围观的编译环节。Android 5.1这个时代还是Makefile体系的黄金时代,没有后来那么多构建系统变形,编译过程相对可预期。
3.1 配置编译环境
初始化编译环境这一步很多人忽略了,这步没做后面lunch直接命令找不到:
source build/envsetup.sh执行完后,lunch等命令才能用。然后选择编译目标:
lunch会弹出一个菜单让你选设备型号。如果想直接指定,可以写成:
lunch aosp_arm64-eng这里aosp_arm64-eng中的aosp_是product名,arm64是架构,eng是构建类型。构建类型有三种,eng表示工程版,带root调试权限,适合开发;user则是正式用户版,不带额外调试工具。个人测试和二次开发选eng最方便。
3.2 开启ccache加速与系统参数调整
编译一次Android全量工程通常在1~2小时(视机器而异),第二次能不能压缩到半小时,关键在ccache。
export USE_CCACHE=1 export CCACHE_DIR=/home/user/android-5.1/.ccache prebuilts/misc/linux-x86/ccache/ccache -M 50G然后把后面两行export写到~/.bashrc里,下次开终端自动生效。ccache缓存的是C/C++预处理后的编译结果,框架层Java改动频繁,但C++层原生库编译结果可以复用,实测第二次编译能节省50%~70%时间。
如果机器内存紧张,建议在编译前关闭图形桌面,或者加一个8G的swap文件,否则整机编译时内存很容易被打满。
3.3 make指令与错误日志定位
一切就绪后执行:
make -j8-j参数建议设置为CPU核心数×2,例如4核8线程的CPU可以-j8。如果编译中途报错,系统会打印错误文件路径,但有时候日志刷屏太快看不清楚,最好先指定输出日志再从头查看:
make -j8 2>&1 | tee build.log关键的几个错误特征先提前说下:
Out of memory:加swap,或减少-j参数。No rule to make target:同步不完整,补repo sync。Jack server类报错:这是5.1引入的Java编译服务器问题,通常和JDK版本不匹配有关,确认你用的OpenJDK 7。libncurses.so.5 cannot open:系统缺32位兼容库,安装前面列的依赖。
整个编译产物在out/target/product/目录下,生成的镜像文件包括boot.img、system.img、userdata.img和recovery.img。用模拟器或刷机工具验证镜像时,记得确认编译架构和你要运行的设备架构匹配,arm64的镜像不能直接跑在32位模拟器上。
4. 核心子系统源码剖析:你该重点读哪些模块
源码能编译通过只是开始,真正有含金量的是读懂那些决定系统行为的关键模块。在Android 5.1中,我比较推荐花时间深读这四块。
4.1 ART运行时与垃圾回收
Android 5.1是ART全面替代Dalvik正式落地的版本,所以art/目录是整个系统最能体现性能设计思路的地方。ART把应用在安装时或首次运行时编译成native机器码,而不是像Dalvik那样每次运行时解释执行或JIT编译,所以App的启动速度和执行效率都有明显提升。
读ART建议从runtime/gc/collector/看起,里面包含了各种垃圾回收策略的实现。如果你关心的是“为什么老手机上5.1比4.4更流畅”,答案就在这些代码里。5.1里的GC相比5.0做了大量调优,比如更好的并发标记、更短的应用停顿时间。代码里那些复杂的并发条件和状态机是硬骨头,但啃下来对理解Java虚拟机内存管理帮助极大。
4.2 Binder IPC通信机制
Android系统进程间通信全靠Binder,这也是Android相对传统Linux IPC最具特色的地方。源码重点看frameworks/native/libs/binder/和kernel/drivers/android/binder.c。
框架层的Binder调用最终要通过/dev/binder设备节点进入内核,内核驱动程序负责实际的数据拷贝与线程管理。很多人看完这块代码才明白为什么Android的跨进程调用这么快:Binder只拷贝一次数据,而传统管道和System V消息队列要拷贝两次。Framework层里,ServiceManager也是通过Binder管理的,整个服务的注册、查询、调用都得跟它打交道。
4.3 ActivityManagerService与四大组件调度
AMS是整个framework里最庞杂的服务之一,新版本里大几千行的类,一上来就通读容易劝退。我的建议是先看关键方法的调用链,比如startActivity的整个流程:App进程通过Binder调用AMS.startActivity,AMS通过ActivityStackSupervisor完成任务栈管理,再通过ProcessRecord和ApplicationThread回调到App进程执行生命周期。
阅读时可以用“应用冷启动”为线索,从Launcher点击图标开始,一直追到Activity.onCreate执行完,这条链走通后,你对Android应用进程的完整生命周期和Linux进程管理之间的关系都会有很具体的理解。
4.4 PowerManagerService与省电机制
Lollipop这一代对功耗管理格外重视,PowerManagerService里实现了wakeup、wakelock、Doze等逻辑。5.1里部分Doze机制还比较初版,但它设定的框架——根据设备静止状态和屏幕关闭时间降低后台活动频率——直接影响到后来所有Android版本的省电策略设计。
如果你有做系统级省电优化的需求,这块值得逐行读,看懂suspend、early_suspend和wakeup source的管理,比在App层想办法省电要有效得多。
5. 框架层二次开发:常见改动场景实战
源码下来了,编译通了,读也有方向了,很多人的实际目的是做系统定制。这里我整理几个典型的二次开发场景,都是基于5.1的真实可落地改动。
5.1 默认语言、时区、开机动画修改
最基础的三件套:默认语言、默认时区、开机动画。
默认语言和时区在build/target/product/full_base.mk里定义:
PRODUCT_PROPERTY_OVERRIDES += \ persist.sys.language=zh \ persist.sys.country=CN \ persist.sys.timezone=Asia/Shanghai也可以直接在定制产品的mk文件里加这些属性。开机动画是bootanimation.zip,放在device/<厂商>/<产品>/目录,编译时会被打包进/system/media/。它是图片帧加desc.txt的zip包,可以用ps等工具将视频拆帧,然后按desc.txt定义的分辨率和帧率打包。
5.2 系统应用预置与去除
预置应用分两种情况:预置可卸载和预置不可卸载。
可卸载的预置应用只要把APK放到vendor/<厂商>/<产品>/packages/下,并在产品的mk文件里加上PRODUCT_PACKAGES引用即可。不可卸载的,要放到device/<厂商>/<产品>/app/目录下,编译系统会把它们编进system分区,用户无法通过普通方式卸载。
去除系统应用时,注意别只删APK了事。很多系统应用同时注册了service、contentprovider和权限,如果只删了manifest文件,系统启动时ServiceManager找不到对应服务,会导致部分功能异常。稳妥的做法是在源码build配置里去掉对应PRODUCT_PACKAGES条目,或注释掉AndroidManifest中的service注册,而不是直接删文件。
5.3 开机自启动系统服务流程修改
做定制系统时,经常会遇到“开机后自动启动一个后台服务”的需求。在5.1里可以像这样实现:
在frameworks/base/services/core/java/com/android/server/SystemServer.java的startOtherServices()方法里,加入你自己的服务启动代码,类似:
try { Slog.i(TAG, "My Custom Service"); ServiceManager.addService("my_custom_service", new MyCustomService(context)); } catch (Throwable e) { Slog.e(TAG, "Failure starting My Custom Service", e); }然后新建自己的服务类,放在frameworks/base/services/core/java/com/android/server/下。这种方式的服务属于系统服务,不会被用户杀掉,也可以通过ServiceManager让普通App调用。不过要注意,系统服务写得不好会拖慢整个系统的启动速度,尽量在服务里做异步操作,不要让主线程卡在复杂初始化上。
6. 内核源码与驱动适配协同
Android整个系统最底层的部分就是内核,5.1源码树里不含内核代码,但设备要跑起来,内核和HAL层必须跟AOSP版本匹配,这是一项容易踩坑但被很多人忽略的工作。
6.1 内核版本选择与打补丁
Android 5.1通常搭载Linux 3.10或3.14内核,高通的msm平台用的往往是3.10或3.18。如果你有一个旧设备想升级到5.1,最稳妥的方式不是从零移植,而是在同芯片平台的、已经适配过5.1的kernel基础上来。比如手头是msm8916平台,就去找该平台官方release过5.1的kernel tag,然后在上面做板级适配。
刷内核补丁时可能涉及kernel specific的改动,主要是arch/arm64/configs/下的defconfig文件里配置项的变化,以及drivers/staging/android/里ion、ashmen等驱动版本对齐。ION内存在5.1之后是主流,旧内核里可能有PMEM遗留,必须在defconfig里关掉,否则图形缓冲分配会出现诡异花屏或黑屏问题。
6.2 HAL层与内核驱动的配合
HAL接口定义在hardware/libhardware/include/hardware/下,每个硬件模组通常有对应的.h头文件定义统一接口。以Camera为例,5.1的Camera HAL必须实现camera_module_t和camera_device_t接口,这些接口内部对应底层V4L2或更底层的厂商私有驱动接口。
在调试摄像头相关问题时,我会先用一个简单的V4L2测试程序直接打开/dev/video节点,确认硬件驱动工作正常,再去查HAL层的问题。把问题分层能节省大量时间,不要一上来就在framework里打log。
7. 常见问题与调试心得
最后这部分,我把这几年和Android 5.1打交道经常出现的问题,以及对应的排查方式一次性列出来,你可以直接照着对照。
| 问题现象 | 可能原因 | 解决/排查方式 |
|---|---|---|
| 编译爆内存 Out of memory | -j开太高,物理内存不足 | 改小-j数,加swap,别让桌面环境占内存 |
| Jack server编译失败 | JDK版本不匹配, 默认JDK8 | 安装OpenJDK 7, 重新source,清理out目录 |
| libncurses.so.5 报错 | 缺少32位兼容库 | 安装libncurses5-dev, 或做软链接 |
| 开机卡在第二屏 | framework服务崩溃 或system_server起不来 | 抓logcat,查system_server FATAL EXCEPTION |
| 相机黑屏/花屏 | ION内存配置不对, Camera HAL和驱动不匹配 | 查dmesg,检查ion分配, 先做V4L2测试 |
| SystemUI不停停止 | 资源ID冲突 或framework改动不完全 | 清理out目录,整编一次 确认不会有旧id残留 |
| 触摸屏方向不对 | 内核input设备上报 坐标轴跟LCD方向不一致 | 修改驱动坐标映射, 或调整.fb/panel旋转参数 |
实际踩坑过程中,最有用的一个工具组合是serial console + adb + logcat + dmesg。如果设备起不来,先接串口看kernel log是否到了init阶段;能进init但SystemServer崩溃,用adb logcat抓Java层异常;纯驱动问题则dmesg里会直接反映出来。Android源码里很多问题都能通过全量编译前做一次make clean解决,不要只在增量编译上打转,错误往往来自过期的中间文件。
还有一个容易被忽略的细节:当你修改framework层Java代码后,仅re-build framework.jar未必能保证镜像生效。在Android 5.1里,framework相关改动通常需要重新生成system.img,必要时还需要同步更新boot.img中的ramdisk,因为系统服务的Binder服务描述符或权限配置可能已经变化。我在早期开发时多次遇到“改了代码但手机没有变化”的情况,最后发现是只编了单个模块没生成全镜像导致的。
另一个操作习惯是从5.1开始有效至今的:每次修改AOSP源码前,最好创建单独的git分支或至少用git stash保存原始状态。AOSP每个项目本身就是git仓库,利用repo工具可以方便地diff改动内容,这也方便后续做系统升级时合并代码。别一直改在同一个裸分支上,代码搞乱了很难回退。
我个人实际操作中的体会是,Android 5.1源码的学习路线,要比高版本顺畅很多——它不像Android 10以后动辄需要面对HIDL、AIDL接口分区、Treble结构之类的复杂性,5.1的结构还保留着相对直接的模块边界,你改一个功能,能顺着一个线索从头摸到尾。推荐想深入研究Android系统的人,从这一版本入手,把经典流程过一遍,之后再去看高版本会轻松很多。
最后再分享一个小技巧:编译前先把out目录里上个版本的残留产物清理干净,同时确认一下ccache的命中率,如果发现命中率突然很低,通常是源码路径发生了变化,这是正常的。构建工具链和源码版本匹配这件事,在Android 5.1时代还很“感性”,很多问题看着是编译错误,实际是环境和版本的拧巴。遇到报错先别急,多看几行上下文,很多答案就藏在编译器告诉你的那个文件路径里。
本文还有配套的精品资源,点击获取