简介:面向MT6799开发板与Android系统定制工程师,该文档系统整理了在Ubuntu 16.04虚拟机环境中下载并编译Android Nougat 7.0源码的完整实操经验,同时兼顾Android 4.4版本。包体为1个docx文件(约895KB),但内容密度很高:从修改阿里云软件源加速包管理、配置VMware共享文件夹,到安装OpenJDK 8及Git/编译依赖、执行lunch与make等推荐命令,每一步都给出可直接复制的指令和注意事项。文档还重点剖析了三个高频编译错误:TAREGT_BRM_PLATFORM undefined、ninja subcommand failed、fork: Cannot allocate memory,分别对应vendor mk包含缺失、依赖模块异常和内存不足,并提供具体解决路径。已有633人学习下载,适合需要从零搭建MT6799源码编译环境、排查编译异常的中高级开发人员,也可作为MTK平台系统移植的入门参考。
1. MT6799 编译 Android 7.0:先把内存喂饱,再来谈源码
在 Android 源码编译这件事上,环境往往比代码先出问题。MT6799 开发板的 Android Nougat 7.0 源码包动辄几十 GB,解压后光源文件就占掉接近三个 300G 虚拟盘的一角,编译过程中 ninja 并行任务一开,内存、文件描述符、进程数全部撞在一起。这份资源并不是什么高级优化教程,而是一份能直接照着走的实操笔记:从 Ubuntu 16.04 虚拟机环境配置、软件源替换、工具链安装,到完整编译指令,再到三个典型报错的具体解法。适合第一次接触 MTK 平台 Android 7.0 源码编译的工程师,也适合已经在编译中反复翻车、想找一份完整避坑清单的人。如果你手头正好有这块开发板,又想自己编译 systemimage 或 bootimage,这篇笔记能帮你少走至少两轮弯路。
2. 虚拟机环境搭建:换源、共享文件夹与硬件配比
编译 Android 源码这件事,虚拟机环境的完成度直接决定后面的每一步是否顺利。很多人在真实设备或双系统上编译时遇到的问题,反而比在虚拟机里少,原因很简单:虚拟机多了一层资源配置约束,内存、处理器、磁盘预留稍不匹配,后期就会出现一系列“诡异”的报错。这一章先把环境基础打牢。
2.1 软件源替换:先备份再动 sources.list
Ubuntu 16.04 默认的软件源在国外,下载依赖包的速度让人崩溃。换成阿里源是最常见的做法,但有一个步骤顺序不能乱。
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup sudo gedit /etc/apt/sources.list第一行是备份当前的源列表,若之后换源出了问题,随时可以恢复。第二行用 gedit 打开源列表文件进行编辑。在文件末尾添加阿里云的 Xenial 源时,需要注意一个细节:deb-src行是源代码仓库,Android 编译的大部分依赖工具都从deb对应的二进制仓库安装,如果你不需要下载源码包,deb-src行可以不加;但文档里加上了,因为这个环境下部分额外工具会直接从源码编译安装。
sudo apt-get update sudo apt-get upgradeupdate这一步是换源后必须执行的,它会重新建立软件包索引缓存。文档中建议紧接着做一次apt-get upgrade,这里有一个容易踩的坑:upgrade会更新所有已安装的软件包,包括内核头文件。如果虚拟机的 VMware Tools 是在升级之前装的,升级完内核后需要重新编译一次 VMware Tools 的内核模块,否则/mnt/hgfs共享文件夹可能就消失了。我一般会在换源后先只做update,等把全部依赖工具装完,再做upgrade,避免中途横生枝节。
2.2 VMware Tools 安装:压缩包必须在 Linux 环境解压
共享文件夹是 Windows 宿主机和 Ubuntu 虚拟机之间传递大文件最方便的方式。文档中特别强调了一句话:一定要把压缩文件从共享文件夹复制到 Linux 环境下解压,不要直接在共享目录里解压。这是什么原因?
共享文件夹底层走的是 FUSE 文件系统,对硬链接、软链接、文件权限的支持与 Linux 本地文件系统不同。Android 源码包里有大量符号链接和可执行权限位,直接在/mnt/hgfs下解压会导致链接失效、权限丢失,后续编译时各种奇怪报错都会冒出来。所以先把 tar 包复制到本地,比如放到/home/develop/下,再执行解压。
VMware Tools 的安装命令是常规流程:
tar zxvf VMwareTools-10.1.15-6627299.tar.gz cd vmware-tools-distrib/ ./vmware-install.pl安装过程中会出现交互式提示,文档里的处理方式是:遇到YES的问题输入Y回车,遇到路径问题直接回车接受默认值。如果你安装时发现vmware-install.pl提示权限不足,或者出现找不到内核头文件的错误,可以先执行sudo ./vmware-install.pl,或者在gedit /etc/apt/sources.list后先完成一次apt-get install linux-headers-$(uname -r)再装 Tools。另外,安装完成后建议重启一次虚拟机,让共享文件夹正常挂载到/mnt/hgfs。
2.3 虚拟机资源配置:内存 16G 是底线,磁盘 300G 是底气
文档给出的虚拟机配置是:硬盘 300G、内存 16G、处理器 2 核 2 线程。这套配置不是随手填的,它对应着 Android 7.0 编译的实际资源消耗。
先说内存。AOSP 7.0 编译时,jack-server 要吃掉大量堆内存,ninja 并行任务也会同时 fork 出几十个编译进程。8G 内存的虚拟机在编译过程中几乎必现Cannot allocate memory,而 16G 内存只能算刚好进入舒适区。如果宿主机内存紧张,可以先把编译任务数从make -j4降为make -j2,但编译时长会翻倍。
再说磁盘。源码解压后约 30-40G,构建产物out/目录在编译过程中会膨胀到 100G 以上,再加上编译缓存、临时文件,300G 预留是比较合理的选择。文档提到把它装在 C 盘,同时强调要安装在普通目录而不是系统保护目录,否则虚拟机的磁盘文件无法使用碎片整理功能。这里我补充一个建议:如果宿主机有机械硬盘,尽量把虚拟机文件放到 SSD 上,Android 编译是大量小文件随机读写的负载,机械硬盘的寻道时间会让编译时间翻倍。
提示:虚拟机内存调整后,需要关闭虚拟机再修改配置,有些版本的 VMware 不支持在挂起状态下修改内存参数。
3. 源码解压与依赖安装:别跳过 md5,也别迷信 md5
源码包从宿主机进到虚拟机之后,第一件事是校验完整性,第二件事是解压,第三件事是装依赖工具。这三步看起来简单,里面都有值得说的门道,尤其是文档里那句“md5 报错没关系”,很多新手看到这里会困惑,为什么校验失败还能继续编译?这一章把逻辑拆开讲清楚。
3.1 分包解压:cat 管道合并 tar.bz2 的操作细节
MTK 分发的源码包通常按分包压缩,解压命令相当特殊:
cat MT6799_Android_Nougat_7.0.tar.bz2.* | tar jvxf -这条命令先把所有分包文件拼接成完整流,再交给tar解压。j代表 bzip2 解压,v是显示解压过程,x是解压,f指定从标准输入读取数据。注意最后的-,它告诉 tar 从标准输入读取,而不是从某个物理文件读取。
文档里接下来做了一次 MD5 校验:
md5sum -c MT6799_Android_Nougat_7.0.tar.bz2.md5这里出现的现象是:校验报错,但编译正常。原因通常是分包的.md5校验文件里记录的是分包各自独立的校验和,而后面的.md5文件记录的解压前整体包的校验方式与cat合并后的流不一致;也可能是压缩包附带的分包文件在传输过程中个别字节发生变化,但源码主体完整可用。文档给了一个重要判断标准:只要后续编译能正常跑通、功能没问题,这个报错就可以忽略。但你必须清楚一个底线——如果校验失败发生在源码主目录相关的几个分包上,解压出来的代码可能缺文件,这类缺文件问题不会在解压时报错,而是会在编译中期某个模块上突然翻车。所以我的习惯是:第一次解压务必md5sum -c一次,如果报错,至少确认报错的分包不是核心源码分卷;之后再解压时,可以跳过校验直接解压,节省时间。
源码解压完成后,别忘了确认目录权限。如果解压出来的目录属主不是当前用户,编译时会直接卡在文件写权限上,提示各种Permission denied。处理方式很简单:
sudo chown -R $USER:$USER /home/develop/MT6799_Android_Nougat_7.0把源码目录的属主改成当前用户,这一步是不少人在编译中期才踩到的隐藏雷区。
3.2 依赖工具链:Ubuntu 16.04 的固定组合
AOSP 7.0 对宿主系统的要求是 Ubuntu 14.04 或 16.04,不同版本上需要安装的依赖工具目录也不同。文档给出了一套基于 LTS 16.04 的依赖安装命令,我把关键的组合列出来:
sudo apt-get install -y git-core gnupg flex bison gperf build-essential zip curl sudo apt-get install -y libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev sudo apt-get install -y gcc-multilib g++-multilib lib32z-dev ccache sudo apt-get install -y libgl1-mesa-dev libxml2-utils xsltproc unzip m4 sudo apt-get install -y openjdk-8-jdk这里逐条解释一下选型理由。bison和flex是 Android 编译系统生成中间代码时必需的语法解析器;gperf用于生成哈希函数;lib32ncurses5-dev这类lib32系列库是为了在 64 位系统上编译 32 位目标代码,Android 7.0 的启动引导和内核模块还有大量 32 位组件,缺了这些库会报找不到curses.h或zlib.h。ccache是编译缓存工具,第二次全量编译时可以省下大量时间,但这需要你在编译前通过export USE_CCACHE=1和ccache -M 50G指定缓存路径与容量上限。
OpenJDK 8 的安装值得单独说。Ubuntu 16.04 自带 OpenJDK 8 仓库,但文档里也给了 PPA 安装方式:
sudo add-apt-repository ppa:openjdk-r/ppa sudo apt-get update sudo apt-get install openjdk-8-jdk sudo update-alternatives --config javac sudo update-alternatives --config java如果系统里只有一个 JDK 版本,运行update-alternatives --config时会提示没有可选的版本,文档里说“不用管”,就是这个原因。这里要注意的是,Android 7.0 必须用 JDK 8,不能用 JDK 9 或 JDK 11,否则编译 luni 模块或 core 库时会出现字节码版本不兼容的错误。你可以在配置完成后用java -version和javac -version确认版本号是1.8.0,再进入后续流程。
4. 编译流程:从 envsetup 到 systemimage 的完整路径
依赖装完、源码解压完毕,接下来是编译的核心流程。MTK 平台的 Android 编译和原生 AOSP 的区别在于,它引入了大量 vendor 目录下的专有模块,编译时对环境变量和依赖顺序的要求更高,这也是为什么文档中特别强调“一定要按照 MT6799_Basic_v1.pdf 文档操作”。这一章把编译链路中的每一步讲透。
4.1 make clobber 与 jack-server:开局先清理
编译的第一条命令,不是source,而是make clobber。
make clobber这条命令会删除之前所有编译操作的已有输出,相当于把out/目录清理干净。为什么必须做这一步?因为 Android 编译系统会增量判断哪些模块需要重编,如果旧的构建产物与当前代码的状态不一致,尤其是修改过jack-admin配置或切换过 JDK 版本后,旧产物会直接导致 ninja 解析出错误的依赖图。文档里特别强调“第一步很重要,有时候提示添加了参数,实际上没加进去没成功”,说的就是这个现象——你以为配置生效了,但旧的构建缓存还在。
接下来是 jack-server 的启动:
./prebuilts/sdk/tools/jack-admin list-server ./prebuilts/sdk/tools/jack-admin start-server ./jack_install.shjack-server 是 Android 7.0 编译 Java 代码时的增量编译器服务。list-server查看当前正在运行的 server 实例;start-server启动服务;jack_install.sh是 MTK 工程里附带的本土化安装脚本,负责把需要的 jack 组件放到正确位置。如果编译过程中出现jack server相关报错,最彻底的修复方式是依次执行stop-server、make clobber、start-server,这个组合在后面避坑章节还会用到。
4.2 lunch 目标选择:full_amt6799_evb_n-eng 的含义
环境清理完毕后,正式进入编译状态:
source build/envsetup.sh lunch full_amt6799_evb_n-engsource build/envsetup.sh将 Android 编译系统提供的一系列函数加载到当前 shell,比如lunch、m、mm、mmm。lunch则是选择目标产品与构建变体。full_amt6799_evb_n-eng中,amt6799_evb是设备代号,对应device/<vendor>/amt6799_evb目录;n代表 Android Nougat,即 7.0;eng是 engineering 编译类型,会附带 root 权限、调试工具和adb root支持,在开发板上调试时这是最实用的选择。如果之后要出正式版镜像,可以把eng换成user或userdebug。
执行lunch后,系统会读取设备目录下的AndroidProducts.mk和device-vendor.mk,设置编译环境变量。如果之前的源码解压不完整或 vendor 配置目录缺失,这一步就会直接报错。正常的终端输出末尾会出现PLATFORM_VERSION和TARGET_PRODUCT两行摘要,看到这两个值后再执行后面的编译命令,才算进入安全区。
4.3 模块化编译:bootimage、systemimage、kernel 怎么选
文档给出的编译指令是一组分模块指令,这是 MTK 平台调试时最实用的编译方式:
export LC_ALL=C make -j4 systemimage # make -j4 bootimage # make -j4 kernel # make -j4 userdataimage # make -j4逐条解释。export LC_ALL=C是把当前 shell 的本地化设置强制为 C 语言环境,避免 locale 编码问题导致编译脚本解析异常,如果你之前编译时某一步出现invalid locale之类的错误,多半是这个原因。make -j4指定 4 个并行任务,这个数值不是越大越好,它在文档对应的宿主机配置下刚好匹配 2 核 2 线程的 VM;如果你把虚拟机处理器加到 4 核,可以尝试-j8,但以 16G 内存为上限,并行任务再多就会撞上前文提过的内存墙。
systemimage是 Android 系统分区的镜像,包含整个/system目录内容,是所有模块中编译耗时最长的;bootimage是内核引导镜像,只包含内核和 ramdisk,调试内核修改时只需编译它,速度快得多;kernel单独编译 Linux kernel,适合验证内核配置或驱动改动;userdataimage是用户数据分区镜像,平时调试不太用。如果你只需要产出一个可烧录的镜像,直接make -j4执行全量编译即可,整套流程跑完大约需要 2-3 小时,具体时长取决于机器性能。注意,如果你同时拿到了多个 MTK 平台工程(比如 MT6799 和 MT6779 并存),不要在一个源码树里混编两个工程,这也对应了文档后面避坑章节里的 JDK 冲突案例。
5. 避坑排查:三大报错与两处隐藏雷区
这一章是整份资源里最有价值的部分。文档作者在编译过程中反复踩坑,最终把这几个报错的完整解法记录下来,每一处的处理思路都值得细品。按「现象 → 原因 → 解决」的结构来看。
5.1 TAREGT_BRM_PLATFORM is undefined:device-vendor.mk 没有被正确引入
编译刚开始就直接报错:
vendor/amt/libs/hwcomposer.mt6799.1.5.0/Android.mk:4: *** TAREGT_BRM_PLATFORM is undefined. Please make sure the device-vendor.mk is correctly included.注意这个报错里TAREGT是原文拼写,实际变量名应为TARGET_BRM_PLATFORM。它出现的直接原因是device-vendor.mk没有被编译系统正确加载,而它又是定义BRM_PLATFORM系列变量的入口。
文档给出的处理方式是三步:第一,执行apt-get upgrade更新已安装的包,这可以修复系统中不完整的依赖关系;第二,把jack_install.sh放到 MTK 工程根目录下并运行,这一步看似无关,实际作用是脚本会触发 vendor 目录下某些模块的配置加载;第三,安装g++-multilib等 MTA 平台必需的交叉编译库。
这三步处理完仍报同样的错,基本可以确认是源码目录本身不完整,或者是拷贝源码时没有保留可执行权限。检查源码根目录下的device/和vendor/目录是否存在,并确认其内容完整。如果是从共享文件夹直接复制到 Linux 的,优先回到2.2节提到的解压方式,把符号链接和权限先修好。
5.2 ninja: build stopped: subcommand failed:jack-server 内存撞墙
这个报错出现的频率最高,也最容易让人抓狂:
ninja: build stopped: subcommand failed. build/core/ninja.mk:148: recipe for target 'ninja_wrapper' failed make: *** [ninja_wrapper] Error 1报错信息完全没有告诉你是哪个模块编译失败,subcommand failed只是说某个子任务退出码非 0。最典型的元凶是 jack-server 内存配置。Android 7.0 编译 Java 代码时,jack-server 会在后台跑一个 JVM 进程,默认内存限制为 2G 左右,在编译大模块(比如 framework、telephony)时堆内存溢出,子命令就直接挂掉。
文档给出的修复方案是修改prebuilts/sdk/tools/jack-admin脚本:
vim prebuilts/sdk/tools/jack-admin在脚本中找到这一行:
JACK_SERVER_COMMAND="java -XX:MaxJavaStackTraceDepth=-1 -Djava.io.tmpdir=$TMPDIR$JACK_SERVER_VM_ARGUMENTS -cp $LAUNCHER_JAR $LAUNCHER_NAME"把-Xmx4096m参数加进去,同时修改变量JACK_SERVER_VM_ARGUMENTS:
JACK_SERVER_COMMAND="java -XX:MaxJavaStackTraceDepth=-1 -Djava.io.tmpdir=$TMPDIR$JACK_SERVER_VM_ARGUMENTS -Xmx4096m -cp $LAUNCHER_JAR $LAUNCHER_NAME" JACK_SERVER_VM_ARGUMENTS="${JACK_SERVER_VM_ARGUMENTS:=-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4096m }"注意:这里的-Xmx4096m是小写的m,但文档作者提到一个很玄学的现象——之前用小写m编译正常,某次突然报错,改成大写-Xmx4096M再make clobber就恢复了。这个现象的本质是 JVM 在某些版本下对-Xmx后缀m和M的解析差异,但具体触发条件难以复现,所以文档建议:改完参数后,重启 jack-server 并强制清理一次make clobber,确保新参数真正生效。
make clobber ./prebuilts/sdk/tools/jack-admin stop-server ./prebuilts/sdk/tools/jack-admin start-server这三条命令的顺序不能变。make clobber会删除所有旧编译产物,stop-server结束旧 JVM 进程,start-server以新参数重新拉起服务,之后再进行正常编译。如果你跳过make clobber,旧进程可能仍在运行,参数根本没有重新加载。
5.3 fork: Cannot allocate memory:pid_max 与 swapfile 双管齐下
这个报错的文本特征非常明显:
fatal: fork: Cannot allocate memory ninja: fatal: fork: Cannot allocate memory很多人的第一反应是物理内存不够,但文档里用sysctl kernel.pid_max查看后,发现是系统最大进程数限制被撞穿了。编译时的 ninja 会并行 fork 大量子进程,每个编译任务都会消耗一个 PID。解决办法分两步:
sysctl kernel.pid_max ps -eLf | wc -l echo 1000000 > /proc/sys/kernel/pid_max echo "kernel.pid_max=1000000 " >> /etc/sysctl.conf sysctl -p第一条命令查看当前的进程数上限,第二条命令统计当前线程数,正常情况下编译任务非常多时,ps -eLf | wc -l的输出会逼近上限值。第三条命令临时把上限提升到 1000000,第四、五条命令将该配置写入sysctl.conf使其永久生效。
如果调完pid_max仍然报错,文档还提供了一个兜底方案:创建 swapfile 增加交换空间。
sudo dd if=/dev/zero of=/swapfile bs=2048 count=524288 ls -lh /swapfile sudo mkswap /swapfile sudo swapon /swapfilebs=2048表示块大小 2M,count=524288表示块数量,相乘得到 1G 的 swap 文件。如果创建 512M,将bs设为 1024。这里要提醒的是,swapfile 是给编译进程临时换页用的,不是内存替代品。当你把内存加到 16G 后,swap 基本只起保险作用;但如果虚拟机内存只有 6G,只靠 swap 会让编译时间暴涨,衔接上pid_max调整才有救。文档作者的最终做法是 16G 内存 + pid_max + swap 三管齐下,才把编译稳定跑通。
5.4 JDK 工具冲突:多份源码共存同一工具的问题
文档里还提到一个实战中很坑的场景:在虚拟机里同时拷贝了两份源码(MT6799 和 MT6779),共用同一套工具链,后面在编译过程中报 JDK 相关错误,删掉其中一份源码后才恢复正常。
这个现象的直接原因是jack-server是全局单例进程。当你在两台工程之间切换编译时,jack-admin会在同一个$HOME目录下创建.jack-server状态文件,两份源码的配置不统一时,上一次编译产生的临时状态会污染下一次编译。更隐蔽的是,bin目录下的一些工具会同时注册到系统的update-alternatives里,两份工程对工具链版本的预期不一致,导致某一方的编译脚本拿到错误的工具版本。
解决方案其实简单:同一台编译机上,同时只保留一份正在编译的源码工程,编译完一个平台后,再引入另一个平台源码并重新执行make clobber。如果你必须同时维护多个工程,建议为每个平台单独建一个虚拟机,或者至少使用不同的 Linux 用户账户隔离$HOME/.jack-server目录。
5.5 磁盘权限根因:安装目录决定虚拟机权限
最后一条踩坑记录发生在虚拟机安装阶段。文档作者的宿主机是 500G 磁盘,为了节省空间把虚拟机文件直接放在 C 盘,结果发现安装在部分系统保护目录下时,虚拟机磁盘文件无法使用碎片整理功能,同时编译产物生成时出现权限抖动。
这个问题的根因是 Windows 的 UAC 机制拦截了虚拟机进程对系统目录的高权限写入请求。VMware 安装虚拟机的路径如果落在C:\Program Files这类受保护目录下,非管理员模式下运行 VM 时,磁盘文件的操作会频繁被权限检查打断,表现为编译中途突然出现Cannot create file或Permission denied。文档的处理是改放到C:\Download这种普通目录,避免权限模型干扰。
提示:如果你在编译中途收到的是
ninja: error: writing to file ... No space left on device,那对应的原因就不是权限,而是虚拟磁盘真的满了,可以执行df -h确认。
6. 编译后的验证:三个动作确认镜像真的可用
编译结束时终端输出make completed successfully,这不代表你的镜像就一定能烧录开机。最后的验证环节,我会按下面三个动作依次走一遍,确认产物真的可用。
第一步,检查镜像文件的生成时间和大小。进入输出目录,看所有镜像的时间戳是否与编译结束时间一致,避免拿到上一次残留的旧镜像。第二步,执行一次镜像 MD5 校验,把生成的文件与官方发布的 MD5 清单核对,确保每个镜像都完整生成。第三步,查看out/target/product/amt6799_evb/目录下的android-info.txt,确认编译时选择的构建版本、平台版本和编译类型正确,避免把eng版本当成正式版本烧录出去。
ls -l out/target/product/amt6799_evb/*.img md5sum out/target/product/amt6799_evb/*.img cat out/target/product/amt6799_evb/android-info.txt其中第一条命令用于确认镜像存在及其大小,第二条命令批量计算镜像文件的校验值,第三条命令读取设备的构建信息。如果你对编译系统的中间产物有怀疑,还可以执行make -j4 systemimage再次增量编译,观察 ninja 是否提示No need to rebuild,这个输出能直接证明当前源码与产物的一致性。
排错时还有一个重要的手段:重新执行source build/envsetup.sh和lunch,然后执行make -j4做全量编译之前,先单独编译一个改动最频繁的模块,比如make -j4 framework,确认单模块编译通过后再上整套编译链路,这样定位问题范围会清晰得多。
从那以后,我每次编译完都会强制走一遍这三步,并且在烧录前对镜像做一次 md5sum 对比,直接把风险掐在源头。对于有问题的工程,删掉源码、重解压、按文档里的三步处理重跑一遍,远比硬着头皮反复编译省时间。这套方法不一定能让你一条命令编译成功,但至少可以让你在迷茫时知道下一步该查什么。希望帮到你。
本文还有配套的精品资源,点击获取