转载请注明出处:http://blog.csdn.net/droyon/article/details/8654023
apk项目是如何编译的?
总结:
我们可以通过mm showcommands命令查看编译过程。今天下载了小米的Notes,便签,没有Android.mk文件,自己加上了Androd.mk,将这个项目放在自己的电脑工程下编译。总结了一下编译过程,不当之处请指正。参考柯元旦android内核剖析。
接下来我们就按顺序看看apk编译过程中的流程。
1、首先编译项目下的资源文件,生成R.java文件。因为资源文件相对独立,不会跟java文件存在依赖,相反java文件要以来资源文件,因为我们在java文件中通过R.string.xx来引用具体某个资源。编译res资源文件,主要是aap进行编译。命令使用:
-J :指定R.java的输出目录
-M:指定AndroidManifest.xml的位置
-P:指定public_resources.xml的输出目录,这个文件记述了apk资源中属性的相关描述
-S:指定res目录的路径
-I:指定apk项目可能引用的其他资源,比如framework层下的资源。
比如短彩信中会引用framework下的资源
chips_dir := ../../../frameworks/ex/chips/res
res_dirs := $(chips_dir) res
LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
aapt package -z -m -J out/target/common/obj/APPS/MiuiNotes_intermediates/src -M packages/apps/Notes/AndroidManifest.xml -P out/target/common/obj/APPS/MiuiNotes_intermediates/public_resources.xml -S packages/apps/Notes/res
我们可以看到 -J指定了R.java的输出目录 -M,指定了AndroidManifest.xml文件的路径 -P指定了public_resource.xml的位置 -S:指定了res的编译目录。
执行完aapt命令,那么就会在相应的输出目录中存在一个R.java文件了。
2、编译aidl文件。
我们在上篇文章中展示了如何在Android.mk文件中添加aidl文件。要编译aidl文件需要aidl命令,aidl 参数 输入 输出。
3、包含java静态库,也就是jar包。
有的模块需要第三方支持,有时候第三方为了保护代码,会提供一个jar,没有源文件。编译系统定义了这部分规则。
for f in out/target/common/obj/JAVA_LIBRARIES/android-support-v4_intermediates/javalib.jar; do if [ ! -f $f ]; then echo Missing file $f; exit 1; fi; unzip -qo $f -d /out/target/common/obj/APPS/MiuiNotes_intermediates/classes; (cd /out/target/common/obj/APPS/MiuiNotes_intermediates/classes && rm -rf META-INF); done
jar文件会被解压缩,然后拷贝解压缩后的class文件到out下对应app的src目录下。
4、编译java源文件,生成jar包。
首先将找到所有的*.java文件,将他们编译生成class文件。
-bootclasspath:指定java运行库core.jar。core.jar中包含着Dalvik虚拟机运行时所需的java库。
-classpath:指定静态库,例如core.jar、framework.jar、ext.jar等。
-d:指明class的输出路径
javac -J-Xmx512M -target 1.5 -Xmaxerrs 9999999 -encoding UTF-8 -g -bootclasspath out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar -classpath /out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar:/out/target/common/obj/JAVA_LIBRARIES/core-junit_intermediates/classes.jar:/out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar:/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar:/out/target/common/obj/JAVA_LIBRARIES/framework2_intermediates/classes.jar:/out/target/common/obj/JAVA_LIBRARIES/android-support-v4_intermediates/javalib.jar -extdirs "" -d /out/target/common/obj/APPS/MiuiNotes_intermediates/classes
然后将这些所有的class文件打包成class.jar包,这些class文件应该就包含上一步jar包解压缩后产生的class文件。
jar -cf /out/target/common/obj/APPS/MiuiNotes_intermediates/classes-full-debug.jar -C /out/target/common/obj/APPS/MiuiNotes_intermediates/classes .
-cf:c代表创建一个新的jar包,f:指定jar包的名称。-C:指定jar包包含的文件,此处就是刚才生成的所有的class文件。
生成了class-full-debug.jar。
然后执行
acp -fp /out/target/common/obj/APPS/MiuiNotes_intermediates/classes-full-debug.jar /out/target/common/obj/APPS/MiuiNotes_intermediates/classes-jarjar.jar
acp -fp /out/target/common/obj/APPS/MiuiNotes_intermediates/classes-jarjar.jar /out/target/common/obj/APPS/MiuiNotes_intermediates/emma_out/lib/classes-jarjar.jar
acp -fp /out/target/common/obj/APPS/MiuiNotes_intermediates/emma_out/lib/classes-jarjar.jar /out/target/common/obj/APPS/MiuiNotes_intermediates/classes.jar
acp -fp /out/target/common/obj/APPS/MiuiNotes_intermediates/classes.jar /out/target/common/obj/APPS/MiuiNotes_intermediates/noproguard.classes.jar
复制刚才产生的classes-full-debug.jar ,生成四个文件,classes-jarjar.jar、classed-jarjar.jar、classes.jar、noproguard.classes.jar
5、将jar包转换为dex文件。
在android系统中构建了一个java虚拟机,这个虚拟机需要的是dex文件。在编译系统中我们可以使用dx工具,将jar文件转换为dex文件。
--dex:产生一个dex格式的文件
--output:指定输出路径
最后指定要转换为dex的jar文件。
/out/host/linux-x86/bin/dx -JXms16M -JXmx2048M --dex --output=/out/target/common/obj/APPS/MiuiNotes_intermediates/noproguard.classes-with-local.dex /out/target/common/obj/APPS/MiuiNotes_intermediates/noproguard.classes.jar
生成了noproguard.classes-with-local.dex文件,接下来
acp -fp /out/target/common/obj/APPS/MiuiNotes_intermediates/noproguard.classes-with-local.dex /out/target/common/obj/APPS/MiuiNotes_intermediates/noproguard.classes.dex
复制产生一个noproguard.classes.dex文件。
6、编译资源文件生成apk包
apk包,本质上是个zip压缩文件包。将它解压缩我们可以看到这个包中文件列表如下:
inflating: res/layout/activity_main.xml
inflating: res/menu/main.xml
inflating: AndroidManifest.xml
extracting: resources.arsc
extracting: res/drawable-hdpi/ic_launcher.png
extracting: res/drawable-mdpi/ic_launcher.png
extracting: res/drawable-xhdpi/ic_launcher.png
extracting: res/drawable-xxhdpi/ic_launcher.png
inflating: classes.dex
inflating: META-INF/MANIFEST.MF
inflating: META-INF/CERT.SF
inflating: META-INF/CERT.RSA
包含resource.arsc:包含了所有资源文件的id值和路径的映射关系
res目录:原封不动的复制了开发时的res目录
class.dex:java源文件达成jar包,然后dx工具将jar包转化为jar
AndroidManifest.xml:源文件的二进制文件。
编译apk时,aapt首先将资源文件等除class.dex文件之外的所有文件打包成apk文件,然后再将classes.dex文件添加到apk文件中。
aapt工具参与了编译资源文件,参与了将源文件打包成apk。编译资源时使用命令参数 -m -J,-m的含义是告诉aapt使用-J后面的路径为输出路径。在将资源文件jar包压缩成apk时,使用-F参数,后面指定输出路径。-u代表如果目标apk存在,更新包中的内容。
aapt package -u -z -c en_US,en_US,cs_CZ,da_DK,de_AT,de_CH,de_DE,de_LI,el_GR,en_AU,en_CA,en_GB,en_NZ,en_SG,es_ES,fr_CA,fr_CH,fr_BE,fr_FR,it_CH,it_IT,ja_JP,ko_KR,nb_NO,nl_BE,nl_NL,pl_PL,pt_PT,ru_RU,sv_SE,tr_TR,zh_CN,zh_HK,zh_TW,am_ET,hi_IN,ug_CN,en_US,fr_FR,it_IT,es_ES,de_DE,nl_NL,cs_CZ,pl_PL,ja_JP,zh_TW,zh_CN,ru_RU,ko_KR,nb_NO,es_US,da_DK,el_GR,tr_TR,pt_PT,pt_BR,rm_CH,sv_SE,bg_BG,ca_ES,en_GB,fi_FI,hi_IN,hr_HR,hu_HU,in_ID,iw_IL,lt_LT,lv_LV,ro_RO,sk_SK,sl_SI,sr_RS,uk_UA,vi_VN,tl_PH,ar_EG,fa_IR,th_TH,sw_TZ,ms_MY,af_ZA,zu_ZA,am_ET,hi_IN,ug_CN,mdpi,nodpi -M packages/apps/Notes/AndroidManifest.xml -S packages/apps/Notes/res -I /out/target/common/obj/APPS/framework-res_intermediates/package-export.apk --min-sdk-version 16 --target-sdk-version 16 --product default --version-code 16 --version-name 4.1.2-eng.hlwang.20130310.122659 -F /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk
7、将dex文件添加到apk包中。
由于apk本身就是个zip压缩包,因此将dex文件添加进去的,很容易。
_adtp_classes_dex=/out/target/common/obj/APPS/MiuiNotes_intermediates/classes.dex; cp /out/target/common/obj/APPS/MiuiNotes_intermediates/noproguard.classes.dex $_adtp_classes_dex && /out/host/linux-x86/bin/aapt add -k /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk $_adtp_classes_dex && rm -f $_adtp_classes_dex
首先定义class.dex文件,然后执行cp,拷贝刚才生成的noproguard.class.dex文件到class.dex中,然后调用aapt add -k命令,将class.dex添加到package.apk中,最后删除class.dex文件。
这样在out下的相应应用的目录中就产生了一个package.apk文件了。并且这个package.apk文件包含了一个完整apk所应该有的所有内容。
有时候我们的apk中需要JNI的支持,那么我们就需要将JNI所实现的库包含到apk中。我们通过在Android.mk文件中对变量LOCAL_JNI_SHARED_LIBRARIES进行赋值。在编译apk时,我们调用了 include $(BUILD_PACKAGE),这个变量在编译系统中会加载package.mk,在这个文件中,规则了加载LOCAL_JNI_SHARED_LIBARIES变量所对应的文件。
jni_shared_libraries := \
$(addprefix $($(my_prefix)OUT_INTERMEDIATE_LIBRARIES)/, \
$(addsuffix $(so_suffix), \
$(LOCAL_JNI_SHARED_LIBRARIES)))
这段代码会给要加载进去的库写上前缀路径,以及后缀.so。
然后在package.mk后面会判断这段代码是否为空,如果不为空,则将这个库文件拷贝到apk目录下的lib下。
java静态库在编译时不会包含到apk中,动态共享库会包含到apk中,详见:http://blog.csdn.net/hailushijie/article/details/8648665
ifneq ($(jni_shared_libraries),)
$(add-jni-shared-libs-to-package)
endif
关于add-jni-shared-libs-to-package变量的定义实在编译系统初始化时在definations.mk中定义的。
define add-jni-shared-libs-to-package
$(hide) rm -rf $(dir $@)lib
$(hide) mkdir -p $(dir $@)lib/$(PRIVATE_JNI_SHARED_LIBRARIES_ABI)
$(hide) cp $(PRIVATE_JNI_SHARED_LIBRARIES) $(dir $@)lib/$(PRIVATE_JNI_SHARED_LIBRARIES_ABI)
$(hide) (cd $(dir $@) && zip -r $(notdir $@) lib)
$(hide) rm -rf $(dir $@)lib
endef
以源码的方式包含库文件,package/inputmethods/PinyinIME
include $(BUILD_PACKAGE)
MY_PATH := $(LOCAL_PATH)
include $(MY_PATH)/jni/Android.mk
include $(MY_PATH)/lib/Android.mk
8、对apk文件进行签名
mv /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.unsigned
java -jar /out/host/linux-x86/framework/signapk.jar build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8 /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.unsigned /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.signed
mv /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.signed /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk
首先将上一步中的package.apk更名为package.apk.unsigned,然后调用signapk.jar、testkey.x509.pem,testkey.pk8给更名的package.apk.unsigned签名后生成package.apk.signed。
最后在把package.apk.signed更名为package.apk。
9、使用zipalign优化apk内部存储。
为了提高apk程序的加载速度,使用zipalign对apk进行边界对齐。
mv /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.signed /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk
mv /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.unaligned
/out/host/linux-x86/bin/zipalign -f 4 /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.unaligned /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.aligned
mv /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk.aligned /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk
过程和签名基本相似。
10、dex-preopt优化。
dalvik/tools/dex-preopt --dexopt=host/linux-x86/bin/dexopt --build-dir=/out --product-dir=target/product/generic/dex_bootjars --boot-dir=system/framework --boot-jars=core:core-junit:bouncycastle:ext:framework:framework2:android.policy:services:apache-xml --uniprocessor target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk target/product/generic/obj/APPS/MiuiNotes_intermediates/package.odex
package.apk作为输入文件,将其中的dex文件输出到package.odex中。
将class.dex文件从package.apk中移除/out/host/linux-x86/bin/aapt remove /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk classes.dex
这样package.apk中就不再含有class.dex文件了。
怎样设置才能不进行这一步
在我们执行Android.mk时,最后一步,call $BUILD_PACKAGE时,会检查WITH_DEXPREOPT的值是否为true,然后对LOCAL_DEX_PREOPT进行赋值。
ifneq (true,$(WITH_DEXPREOPT))
LOCAL_DEX_PREOPT :=
else
ifeq (,$(TARGET_BUILD_APPS))
ifneq (,$(LOCAL_SRC_FILES))
ifndef LOCAL_DEX_PREOPT
LOCAL_DEX_PREOPT := true
然后在base_rules.mk中
ifdef (true,$(LOCAL_DEX_PREOPT))
installed_odex := $(basename $(LOCAL_INSTALLED_MODULE)).odex
built_odex := $(basename $(LOCAL_BUILT_MODULE)).odex
$(installed_odex) : $(built_odex) | $(ACP)
@echo -e ${CL_CYN}"Install: $@"${CL_RST}
$(copy-file-to-target)
$(LOCAL_INSTALLED_MODULE) : $(installed_odex)
endif
这样apk在编译时就能进行dex-preopt优化了。
所以,我们可以更改WITH_DEXPREOPT的值为false,或者更改LOCAL_DEX_PREOPT的值。都ok。WITH_DEXPREOPT的值在BoardConfig.mk中定义,LOCAL_DEX_PREOPT的值在package.mk中定义。
网上有人说修改system.prop文件,增加
dalvik.vm.verify-bytecode = false不知道是否ok。
如果不进行这个步骤,那么我们的apk文件中将会包含所有的文件,包括class.dex,如果包括这步则apk内不含有class.dex文件,class.dex将会以应用名.odex文件存在。
11、odex文件
dalvik/tools/dex-preopt --dexopt=host/linux-x86/bin/dexopt --build-dir=/out --product-dir=target/product/generic/dex_bootjars --boot-dir=system/framework --boot-jars=core:core-junit:bouncycastle:ext:framework:framework2:android.policy:services:apache-xml --uniprocessor target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk target/product/generic/obj/APPS/MiuiNotes_intermediates/package.odex
odex可能为了加速apk的解析吧。
最后生成了package.odex。
acp -fp /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.odex /out/target/product/generic/system/app/MiuiNotes.odex
拷贝刚才生成的package.odex,生成MiuiNotes.odex。
12、拷贝package.apk生成目标apk
acp -fp /out/target/product/generic/obj/APPS/MiuiNotes_intermediates/package.apk /out/target/product/generic/system/app/MiuiNotes.apk
这样我们通过编译同时生成了MiuiNotes.apk以及MiuiNotes.odex文件。
我们的源文件class.dex包含在MiuiNotes.odex中,我们的MiuiNotes.apk中包括资源文件。
关注我的技术公众号,查看更多优质技术文章推送
微信扫一扫下方二维码即可关注: