Android Apk 编译原理解析_xiaosayidao的博客-程序员ITS304_安卓编译原理

技术标签: Android-framework  android编译原  apk生成原理  apk编译原理  aapt  编译系统  

本文基于AOSP-7.1.1-R9源码分析,源码可以参见build/+/android-7.1.1_r9

简介

在Android系统中,所有的应用都是以apk的形式存在,那这个apk是如何生成的呢?对于刚开始接触系统开发的开发者来说,经常会使用如下命令编译apk或者系统固件。

source build/envsetup.sh;
lunch
make -j8 
or
mmm packages/app/Settings

这些命令背后的原理是什么呢?最终为什么会生成一个apk文件?接下来的文章将会对相关原理进行剖析。

本文使用AOSP frameworks/base/tests/Split/的源码来进行解析。由于关注点在apk的生成原理,所以重点分析和apk相关的编译原理,对于生成系统固件的原理不是本文讨论的重点,当了解了apk的生成原理之后,其他的编译原理也就好理解了。

apk编译系统

接下来我们将分析Android编译系统里面用来生成apk的原理。大致会分为如下几个步骤:

step1. 通过source 命令,读入envsetup.sh里面定义的各种命令,比如mm、mma、mmm、godir、croot等,方便我们在当前终端进行相关的命令输入。

step2.lunch将要编译的产品,生成产品相关的参数配置。本文不做深入讲解。

source build/envsetup.sh;
lunch

step3. 开始执行编译命令,生成目标的依赖关系。

mmm frameworks/base/tests/Split

查看build/envsetup.sh的源码,可以看到,当我们执行如上命令的时候,实际上执行的是如下的命令:

    ONE_SHOT_MAKEFILE= frameworks/base/tests/Split/Android.mk  make -C /home/tanfuhai/data/code/opengrok/android_n -f build/core/main.mk  all_modules

接下来就直接进入到Android编译系统,可以看到入口makefile文件是main.mk,下面对引用的makefile文件一一讲解:

  • build/core/main.mk 编译系统的入口。Android所有的生成目标都是从这个main.mk开始,Android默认的make targetdroiddroid依赖于droid_targets,Makefile里面定义了各种依赖,当执行make的时候,可以编译出我们需要的所有image,droid的依赖关系如下图:
    .PHONY: droid
    DEFAULT_GOAL := droid
    $(DEFAULT_GOAL): droid_targets

    .PHONY: droid_targets
    droid_targets:

    droid_targets: apps_only

    droid_targets: droidcore dist_files

本文主要讲解apk生成的原理,当我们执行mmm的时候,此时的target是all_modules。make的过程中,首先会读取所有的makefile文件。由于我们传入了ONE_SHOT_MAKEFILE= frameworks/base/tests/Split/Android.mk,所以此时会include这个ONE_SHOT_MAKEFILEall_modules的依赖定义如下:

# phony target that include any targets in $(ALL_MODULES)
.PHONY: all_modules
ifndef BUILD_MODULES_IN_PATHS
all_modules: $(ALL_MODULES)
else

引入 ONE_SHOT_MAKEFILE文件,代码如下:

    ifneq ($(ONE_SHOT_MAKEFILE),)
    # We've probably been invoked by the "mm" shell function
    # with a subdirectory's makefile.
    include $(ONE_SHOT_MAKEFILE)

由于Android.mk里面定义了include $(BUILD_PACKAGE),在引用BUILD_PACKAGE对应的makefile时,会依次引用到如下的makefile文件。

  • build/core/clear_vars.mk 把相关的宏给清理掉。
  • build/core/package.mk 对应include $(BUILD_PACKAGE),入口。
  • build/core/multilib.mk 判断Android.mk里面定义的LOCAL_32_BIT_ONLY或者LOCAL_MULTILIB的值,并且赋值给my_module_multilib,,方便下面的makefile使用。
  • build/core/module_arch_supported.mk 根据my_module_multilib的值来判断当前模块是否需要编译支持32和64位。
  • build/core/package_internal.mk 编译apk定义的规则。会收集我们在Android.mk里面定义的各种变量。比如当前的模块名、资源文件路径等。这个mk定义了很多生成apk的规则和依赖。

    • LOCAL_PACKAGE_OVERRIDES 指定覆盖其他的包,比如系统现在同时编译了Launcher1和Launcher2,我们只需要Launcher2,那么我们可以在Launcher2的Android.mk里面定义LOCAL_PACKAGE_OVERRIDES := Launcher1。
    • LOCAL_PACKAGE_NAME 最后生成的apk的名字.
    • LOCAL_MODULE_SUFFIX 模块的后缀,默认是apk
    • LOCAL_MODULE 模块名,和LOCAL_PACKAGE_NAME一样,只能定义一个,比如Split
    • LOCAL_MODULE_CLASS 模块类型,默认是APPS
    • LOCAL_ASSET_DIR asset目录,默认是assets
    • LOCAL_RESOURCE_DIR 资源文件目录,默认是res
    • PRODUCT_PACKAGE_OVERLAYS DEVICE_PACKAGE_OVERLAYS 资源覆盖目录,资源覆盖机制,DEVICE_PACKAGE_OVERLAYS指定的优先级低。
    • LOCAL_PROGUARD_ENABLED 代码混淆是不是生效。
    • LOCAL_CERTIFICATE 签名文件,产品自己用PRODUCT_DEFAULT_DEV_CERTIFICATE指定,如果也没有指定,默认build/target/product/security/testkey。比如我们在设置模块指定了LOCAL_CERTIFICATE := platform,那么最终使用的签名文件就是build/target/product/security/下面的platform.x509.pem和platform.pk8,同理如果LOCAL_CERTIFICATE := media,那么就用media.x509.pem和media.pk8。
    • LOCAL_PACKAGE_SPLITS 是否定义了apk分包。并且定义分包的相关规则。
    • LOCAL_BUILT_MODULE 定义编译apk的所有文件依赖关系。比如aapt编译生成package.apk、R.stamp、签名依赖,可以说编译apk的整个依赖完全是由这个宏定义生成。
  • build/core/configure_local_jack.mk 定义是否用jack编译,在Android O上,jack已经被弃用。如果定义了,定义jack编译的规则。

  • build/core/android_manifest.mk 主要做两件事情。

    • 获取LOCAL_MANIFEST_FILE宏的值,默认是AndroidManifest.xml文件,应用可以通过LOCAL_FULL_MANIFEST_FILE指定自己的AndroidManifest.xml文件。
    • 合并aar库文件里面的AndroidManifest.xml文件,LOCAL_STATIC_JAVA_AAR_LIBRARIES 指定引用的aar包,和jar相比,aar里面包含有相关的资源。
  • build/core/java.mk

    • 收集所有的java文件,包括.proto文件、RenderScript脚本资源、aidl文件、logtags文件以及编译过程中产生的文件。
    • 收集产生full_classes_jar的所有依赖。
    • 定义生成built_dex_intermediate的依赖关系。
  • build/core/base_rules.mk 主要作用:

    • 判断LOCAL_MODULELOCAL_MODULE_TAGSLOCAL_MODULE_CLASS是否唯一、判断当前模块是否有init.rc文件。
    • 定义ALL_MODULES LOCAL_BUILT_MODULE LOCAL_INSTALLED_MODULE的依赖。展开build/core/base_rules.mk之后,我们前面在main.mk里面的依赖就和对应的模块名Split联系起来了,相当于all_modules依赖于SplitSplit最终依赖于LOCAL_BUILT_MODULELOCAL_INSTALLED_MODULE。依赖规则如下:
    ALL_MODULES += $(my_register_name)
    ifdef OVERRIDE_BUILT_MODULE_PATH
    ifneq ($(LOCAL_MODULE_CLASS),SHARED_LIBRARIES)
        $(error $(LOCAL_PATH): Illegal use of OVERRIDE_BUILT_MODULE_PATH)
    endif
        built_module_path := $(OVERRIDE_BUILT_MODULE_PATH)
    else
        built_module_path := $(intermediates)
    endif
    LOCAL_BUILT_MODULE := $(built_module_path)/$(my_built_module_stem)
    • built_module_path: 最终为out/target/product/generic_x86_64/obj/APPS/Split_intermediates
    • my_built_module_stem: 默认是 package.apk
    • my_module_path: 等于out/target/product/generic_x86_64/data/app/Split
    • my_installed_module_stem: Split.apk
    • $(LOCAL_BUILT_MODULE): out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk
    • $(LOCAL_INSTALLED_MODULE): out/target/product/generic_x86_64/data/app/Split/Split.apk,
  • build/core/configure_module_stem.mk 获取编译模块的中间件名字。

    • my_module_stem ,如果没有定义LOCAL_MODULE_STEM,那么就是模块名,比如Split
    • my_built_module_stem 对于apk来说通常都是package.apk。
    • my_installed_module_stem 最终生成的apk名字,比如 Split.apk
  • build/core/notice_files.mk 编译版权声明相关的文件的规则。
  • build/core/java_common.mk

    • 判断java版本号。
    • 把.proto文件编译生成java文件。
    • 查找LOCAL_JAVA_RESOURCE_DIRS指定的资源目录下的文件,收集LOCAL_JAVA_RESOURCE_FILES指定的资源文件。
    • 指定java的私有变量。如$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_ASSET_DIR
    • 如果jack编译可用,指定一些变量。如PRIVATE_STATIC_JACK_LIBRARIES等。
  • build/core/dex_preopt_odex_install.mk 定义安装odex的一些规则。比如是否要进行odex优化,一般产品会自己定义WITH_DEXPREOPT = true

  • build/core/install_jni_libs.mk 定义如何安装apk需要的jn库。

  • build/core/install_jni_libs_internal.mk 安装JNI库。

编译依赖规则

经过以上的步骤,生成目标Split的依赖就清晰了,如下图所示:
这里写图片描述

查看大图

我们可以看到,有如下规则:

  • rule10 aapt工具把所有的资源文件和AndroidManifes.xml文件一起编译生成out/target/common/obj/APPS/Split_intermediates/src/R.stamp
  • rule12 jack工具会把当前的java文件,以及依赖的相关系统库文件,比如out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.dex.toc一起编译生成out/target/common/obj/APPS/Split_intermediates/with-local/classes.dex

  • rule9 zip命令打包out/target/common/obj/APPS/Split_intermediates/with-local/classes.dexaapt生成的资源文件out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk生成新的out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk,然后通过签名工具进行签名。

  • rule8 acp命令把out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk拷贝到out/target/product/generic_x86_64/data/app/Split/Split.apk
  • rule15 17 19 21 生成分包 Split_hdpi-v4.apk、Split_mdpi-v4.apk 等。

至此apk就生成出来了。附rule规则对应的代码:

rule12 规则

    $(built_dex_intermediate): $(jack_all_deps) | setup-jack-server
    @echo Building with Jack: [email protected]
    $(jack-java-to-dex)

rule8 规则

@build/core/package_internal.apk

    @echo "target Package: $(PRIVATE_MODULE) ([email protected])"
ifdef LOCAL_USE_AAPT2
ifdef LOCAL_JACK_ENABLED
    $(call copy-file-to-new-target)
else
    @# TODO: implement merge-two-packages.
    $(if $(PRIVATE_SOURCE_ARCHIVE),\
      $(call merge-two-packages,$(PRIVATE_RES_PACKAGE) $(PRIVATE_SOURCE_ARCHIVE),[email protected]),
      $(call copy-file-to-new-target))
endif
else  # LOCAL_USE_AAPT2
ifdef LOCAL_JACK_ENABLED
    $(create-empty-package)
else
    $(if $(PRIVATE_SOURCE_ARCHIVE),\
      $(call initialize-package-file,$(PRIVATE_SOURCE_ARCHIVE),[email protected]),\
      $(create-empty-package))
endif
    $(add-assets-to-package)
endif  # LOCAL_USE_AAPT2
ifneq ($(jni_shared_libraries),)
    $(add-jni-shared-libs-to-package)
endif
ifeq ($(full_classes_jar),)
# We don't build jar, need to add the Java resources here.
    $(if $(PRIVATE_EXTRA_JAR_ARGS),$(call add-java-resources-to,[email protected]))
else  # full_classes_jar
    $(add-dex-to-package)
endif  # full_classes_jar
ifdef LOCAL_JACK_ENABLED
    $(add-carried-jack-resources)
endif
ifdef LOCAL_DEX_PREOPT
ifneq ($(BUILD_PLATFORM_ZIP),)
    @# Keep a copy of apk with classes.dex unstripped
    $(hide) cp -f [email protected] $(dir [email protected])package.dex.apk
endif  # BUILD_PLATFORM_ZIP
ifneq (nostripping,$(LOCAL_DEX_PREOPT))
    $(call dexpreopt-remove-classes.dex,[email protected])
endif
endif
    $(sign-package)

rule15 17 19 21 规则

@build/core/package_internal.apk

## APK splits
ifdef LOCAL_PACKAGE_SPLITS
# LOCAL_PACKAGE_SPLITS is a list of resource labels.
# aapt will convert comma inside resource lable to underscore in the file names.
my_split_suffixes := $(subst $(comma),_,$(LOCAL_PACKAGE_SPLITS))
built_apk_splits := $(foreach s,$(my_split_suffixes),$(built_module_path)/package_$(s).apk)
installed_apk_splits := $(foreach s,$(my_split_suffixes),$(my_module_path)/$(LOCAL_MODULE)_$(s).apk)

# The splits should have been built in the same command building the base apk.
# This rule just runs signing.
# Note that we explicily check the existence of the split apk and remove the
# built base apk if the split apk isn't there.
# That way the build system will rerun the aapt after the user changes the splitting parameters.
$(built_apk_splits): PRIVATE_PRIVATE_KEY := $(private_key)
$(built_apk_splits): PRIVATE_CERTIFICATE := $(certificate)
$(built_apk_splits) : $(built_module_path)/%.apk : $(LOCAL_BUILT_MODULE)
    $(hide) if [ ! -f [email protected] ]; then \
      echo 'No [email protected] generated, check your apk splitting parameters.' 1>&2; \
      rm $<; exit 1; \
    fi
    $(sign-package)

# Rules to install the splits
$(installed_apk_splits) : $(my_module_path)/$(LOCAL_MODULE)_%.apk : $(built_module_path)/package_%.apk | $(ACP)
    @echo "Install: [email protected]"
    $(copy-file-to-new-target)

总结

当我们编译frameworks/base/tests/Split/的时候,最终会生成out/target/product/generic_x86_64/data/app/Split/Split.apk,从文件的角度来看,有如下的解析图。

查看大图

查看大图

  1. aapt工具把所有的资源文件编译生成R.stamp,和R.java内容一致,主要用来当做R.java的编译目标。
  2. jack工具把所有的java资源文件以及依赖库编译生成classes.dex文件。
  3. aapt工具将生成的所有文件进行打包,生成package.apk和分包文件,比如package_hdpi-v4.apk,然后通过签名工具进行签名。
  4. 把apk从中间目录拷贝到我们最终需要生成的目录。并且更换名称为Split.apk

apk的编译原理就分析完成了,后面会写一篇文章讲解aapt是如何生成R.java和package.apk。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xiaosayidao/article/details/75096276

智能推荐

flyway遇到的问题Caused by: java.lang.ClassNotFoundException: org.flywaydb.core.api.callback.FlywayCallbac_jalen_zh的博客-程序员ITS304

问题环境是springboot2.1.8+flyway6.0.4,尝试编译出错,异常堆栈的信息是找不到flywaycallback类解决过程1、flywaycallback找不到,导致org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration$FlywayConfiguration这个bean创建失败...

使用lua语言做高并发限流_shecanwin的博客-程序员ITS304

lua语言介绍       Lua[1]  是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入

简述微信砍价小程序运营新玩法_muyuxiaochengxun的博客-程序员ITS304

  砍价小程序日渐火爆,被越来越多的商家所利用以此来获得更多的用户和产品的盈利。如此好的优质的营销工具,要如何去经营呢?接下来就给大家介绍一下关于微信砍价小程序运营新玩法?感兴趣的可以和木鱼小铺(https://www.muyu007.cn)一起来看看。    1.内容营销预热,推广店铺    微信公众号是小程序推广引流的方式之一,主要是内容营销。所以商家可以结合微信公众号的文章内容来为小程序商城砍价活动进行预热,让用户可以通过内容来了解到线下店铺的环境、商品以及购买的优惠力度,吸引客户进入

matplotlib折线图及柱状图绘制_if shining的博客-程序员ITS304

python-matplotlib折线图及柱状图绘制一、折线图绘制二、柱状图绘制一、折线图绘制# 折线图: matplotlib.plot(横坐标,纵坐标,颜色,lw=线条宽度)# 1.导入numpy,matplotlib模块import numpy as npimport matplotlib.pyplot as plt# 2.定义各个点的横纵坐标x = np.array([1,2,3,4,5,6,7,8])y = np.array([3,5,9,2,7,6,3,8])# 3.将参数传入

SQL server 学习记录(一)_Damon_Code的博客-程序员ITS304

1、创建表create table(id int,name char(10))2、删除表中数据:delete tablename;  直接删除 数据库 数据、结构、日志  drop database;  删除表 drop tablename3、把表数据插入到另一个表    3.1:如果要插入目标表不存在: select * into 目标表 from 表 where …    3....

FPGA的学习:PLL-IP核的调用_石小舟的博客-程序员ITS304

锁相环是最常用的IP核之一,其性能强大,可以对输入到FPGA的时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望时钟。其基本原理如下:

随便推点

根据经纬度计算两点之间的距离_csdn_ss1991的博客-程序员ITS304_根据经纬度计算两点距离

package com.bandweaver.tunnel.common.platform.util;import java.math.BigDecimal;import javafx.geometry.Point2D;public class GPSUtil { private static final double EARTH_RADIUS = 6378137;// 赤道半...

基于python的大数据分析基础及实战 百度云_《基于PYTHON的大数据分析基础及实战》余本国著【摘要 书评 在线阅读】-苏宁易购图书..._weixin_39805195的博客-程序员ITS304

部分基础篇章Python语言基础/21.0引子/21.1工欲善其事,必先利其器(安装Python)/31.2学跑得先学走(语法基础)/91.3程序结构/111.3.1HelloWorld!/111.3.2运算符介绍/121.3.3顺序结构/141.3.4判断结构/171.3.5循环结构/181.3.6异常/201.4函数/241.4.1基本函数结构/241.4.2参数结构/251.4.3回调函数/...

深圳大学计算机与软件学院哪个校区,2021年深圳大学有几个校区,大一新生在哪个校区..._清溪-南柯的博客-程序员ITS304

很多深圳大学新生报到之前都有很多疑问,其中一个疑问就是深圳大学有几个校区,大一新生在哪个校区。本文主要为大家介绍关于2020年深圳大学的介绍,深圳大学招生各个专业目录、深圳大学新生在哪个校区的知识。一、深圳大学校区介绍深圳大学现有粤海、沧海、丽湖、罗湖四个校区,校园总面积2.72平方公里。学校教学资源丰富,科研设施齐备。校园总建筑面积146.9万平方米,教学、科研仪器设备总值28亿元。图书馆馆舍5...

python 如果没有该key值置为空,SECRET_KEY设置不能为空||可在Settings.py中找到_研究所的鹏鹏博士的博客-程序员ITS304

I tried to find this bug, but don't know how to solve it.I kept getting error message "The SECRET_KEY setting must not be empty." when executing populate_rango.pyI have checked on settings.py and the ...

Linux命令详解:md5sum 命令_morgan363的博客-程序员ITS304_md5命令

md5sum命令采用MD5报文摘要算法(128位)计算和检查文件的校验和。MD5算法常常被用来验证网络文件传输的完整性,防止文件被人篡改。MD5全称是报文摘要算法(Message-Digest Algorithm 5),此算法对任意长度的信息逐位进行计算,产生一个二进制长度为128位(十六进制长度就是32位)的“指纹”(或称“报文摘要”),不同的文件产生相 同的报文摘要的可能性是非常非常之小的。

关于h5中的fetch方法解读(小结)_mengzhengjie的博客-程序员ITS304

https://www.jb51.net/html5/586989.htmlhttps://segmentfault.com/a/1190000011973904Fetch概念fetch身为H5中的一个新对象,他的诞生,是为了取代ajax的存在而出现,主要目的仅仅只是为了结合ServiceWorkers,来达到以下优化:优化离线体验 保持可扩展性当然如果ServiceWorke...

推荐文章

热门文章

相关标签