Android编译系统分析系列文章已经是第六篇了,随着时间的流逝,随着工作中接触编译系统越来越多,对Android编译系统的理解也在一点点加深,这是令人欣慰的地方。最近遇到了系统apk签名的问题,于是专门分析了下签名部分的逻辑,将分析结果做个记录。
下面是其他五篇分析Android编译系统的文章,感兴趣的可以看下:
android编译系统分析(一)-source build/envsetup.sh与lunch
Android编译系统(二)-mm编译单个模块
android编译系统分析(三)-make
android编译系统(四)-实战:新增一个产品
Android编译系统分析(五)-system.img的生成过程

我们知道,当我们执行mm或者其他命令编译一个模块的时候,会在out\target\product\xxx\obj\APPS\xxxx生成三个apk,它们分别是:package.apk,package.apk.unaligned,package.apk.unsigned。其中package.apk应当是已经签过名的apk,package.apk.unsigned是没有签过名的apk,package.apk.unaligned是指没有使用zipalign工具优化过的apk,zipalign优化过的apk具有更高的效率。然而,按理讲package.apk是签过名的apk,可是,当我拿着这个apk来安装的时候,提示安装失败,重新使用自己的签名文件(系统也是使用该签名文件的,但不是build系统中默认的签名文件)进行签名后又可以安装,这是为什么呢?
package.apk确实是签过名的,可是当我们执行mm命令编译的时候,它是使用默认的系统签名文件签名的,如果一个公司使用它自己的签名文件给系统所有apk签名,就需要修改默认的系统签名文件的路径,改为自己的签名文件的路径,可是怎么改呢?
在我前面的博客: Android编译系统分析二:mm编译单个模块 一文中,我们分析了编译一个模块的具体过程,如果对编译一个模块不太清楚可参考下。当我们编译一个模块的时候,在package_internal.mk文件中,会有签名的相关逻辑:

# Pick a key to sign the package with. If this package hasn't specified# an explicit certificate, use the default.# Secure release builds will have their packages signed after the fact,# so it's ok for these private keys to be in the clear.ifeq ($(LOCAL_CERTIFICATE),)    LOCAL_CERTIFICATE := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)endififeq ($(LOCAL_CERTIFICATE),EXTERNAL)  # The special value "EXTERNAL" means that we will sign it with the  # default devkey, apply predexopt, but then expect the final .apk  # (after dexopting) to be signed by an outside tool.  LOCAL_CERTIFICATE := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)  PACKAGES.$(LOCAL_PACKAGE_NAME).EXTERNAL_KEY := 1endif# If this is not an absolute certificate, assign it to a generic one.ifeq ($(dir $(strip $(LOCAL_CERTIFICATE))),./)    LOCAL_CERTIFICATE := $(dir $(DEFAULT_SYSTEM_DEV_CERTIFICATE))$(LOCAL_CERTIFICATE)endifprivate_key := $(LOCAL_CERTIFICATE).pk8certificate := $(LOCAL_CERTIFICATE).x509.pem$(LOCAL_BUILT_MODULE): $(private_key) $(certificate) $(SIGNAPK_JAR)$(LOCAL_BUILT_MODULE): PRIVATE_PRIVATE_KEY := $(private_key)$(LOCAL_BUILT_MODULE): PRIVATE_CERTIFICATE := $(certificate)PACKAGES.$(LOCAL_PACKAGE_NAME).PRIVATE_KEY := $(private_key)PACKAGES.$(LOCAL_PACKAGE_NAME).CERTIFICATE := $(certificate)$(LOCAL_BUILT_MODULE): PRIVATE_ADDITIONAL_CERTIFICATES := $(foreach c,\    $(LOCAL_ADDITIONAL_CERTIFICATES), $(c).x509.pem $(c).pk8)

从中我们可以看到签名文件的位置取决于DEFAULT_SYSTEM_DEV_CERTIFICATE变量的值,这个值的默认的定义是在build/core/config.mk中,在查看这个值之前,我们看下这段代码,发现最终的签名文件是存放在PRIVATE_ADDITIONAL_CERTIFICATES变量中的:

$(LOCAL_BUILT_MODULE): PRIVATE_ADDITIONAL_CERTIFICATES := $(foreach c,\    $(LOCAL_ADDITIONAL_CERTIFICATES), $(c).x509.pem $(c).pk8)

这点,我们在追寻具体的签名过程的时候会用到。好了,继续回到build/core/config.mk中,看下DEFAULT_SYSTEM_DEV_CERTIFICATE的值:

# The default key if not set as LOCAL_CERTIFICATEifdef PRODUCT_DEFAULT_DEV_CERTIFICATE  DEFAULT_SYSTEM_DEV_CERTIFICATE := $(PRODUCT_DEFAULT_DEV_CERTIFICATE)else  DEFAULT_SYSTEM_DEV_CERTIFICATE := build/target/product/security/testkeyendif

他又取决于PRODUCT_DEFAULT_DEV_CERTIFICATE变量的值,如果这个变量的值没有定义的话,DEFAULT_SYSTEM_DEV_CERTIFICATE 就是build/target/product/security/testkey了。因此我们要使用自己的签名文件的话,就需要重新定义PRODUCT_DEFAULT_DEV_CERTIFICATE变量的值,这个值从名字就可以看出来它是与具体产品相关的,很过产品会在device/xxx/xxx下的device.mk中配置该值。
比如,假设我们有这样的一个device.mk文件,我们可以这样配置:

ifdef PRODUCT_RELEASEKEY_PATH PRODUCT_DEFAULT_DEV_CERTIFICATE := $(PRODUCT_RELEASEKEY_PATH) endif

然后把PRODUCT_RELEASEKEY_PATH值的配置写道一个函数中:

function use_release_key(){    echo "use relase key!!"    if [ ! "$TARGET_PRODUCT" ] ;then        echo Please do \"source env.sh\" and \"lunch\" first!        return 1    else        # find android root path        local T=$(gettop)        if [ ! "$T" ]; then            echo "Couldn't locate the top of the tree. Try setting TOP."            return 1        fi        #local device_name=`echo $TARGET_PRODUCT | sed -e "s/full_//"`         export PRODUCT_RELEASEKEY_PATH=$T/device/xxx/xxx/security/releasekey        echo -e "release key path:\n \e[31m${PRODUCT_RELEASEKEY_PATH}\e[0m"        echo " using product release key ${PRODUCT_RELEASEKEY_PATH}"    fi}

注意,函数中的xxx要改成对应的值哦。
把这个函数放到build/envsetup.sh 文件中,这样,当我们source build/envsetup.sh文件以后,这个函数就可以使用了,然后我们执行use_release_key,这样我们就把系统默认的签名文件改为使用我们自己的签名文件了,然后执行make或者执行mm编译的结束后,对应的apk就会使用我们提供的签名文件签名了。

那么具体Android编译系统中是怎么签名的呢?

在之前我们说签名文件的位置最终是存放在了PRIVATE_ADDITIONAL_CERTIFICATES变量中,我们搜索这个变量发现签名的逻辑是在

# Sign a package using the specified key/cert.#define sign-package$(hide) mv $@ $@.unsigned$(hide) java -jar $(SIGNAPK_JAR) \    $(PRIVATE_CERTIFICATE) $(PRIVATE_PRIVATE_KEY) \    $(PRIVATE_ADDITIONAL_CERTIFICATES) $@.unsigned $@.signed$(hide) mv $@.signed $@endef

这断代码告诉了我们系统是怎么签名的,比如我们有一个hello.apk文件,我们要把它签名成hellosign.apk,把这些变量展开就是:
java -jar signapk.jar platform.x509.pem platform.pk8 hello.apk hellosign.apk

那么到底在什么地方使用这段代码来给apk签名呢?通过搜索我们发现,位置就在package_internal.mk文件中:

$(LOCAL_BUILT_MODULE): $(all_res_assets) $(jni_shared_libraries) $(full_android_manifest)    @echo "target Package: $(PRIVATE_MODULE) ($@)"ifdef LOCAL_JACK_ENABLED    $(create-empty-package)else    $(if $(PRIVATE_SOURCE_ARCHIVE),\      $(call initialize-package-file,$(PRIVATE_SOURCE_ARCHIVE),$@),\      $(create-empty-package))endif    $(add-assets-to-package)ifneq ($(jni_shared_libraries),)    $(add-jni-shared-libs-to-package)endififeq ($(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,$@))else    $(add-dex-to-package)endififdef LOCAL_JACK_ENABLED    $(add-carried-jack-resources)endififdef LOCAL_DEX_PREOPTifneq (nostripping,$(LOCAL_DEX_PREOPT))    $(call dexpreopt-remove-classes.dex,$@)endifendif    $(sign-package)    @# Alignment must happen after all other zip operations.    $(align-package)

生成完一个apk后就会给他签名。
我们所困惑的zipalign这个时候也出现了,我们看下它的是怎么做的:

define align-package$(hide) mv $@ $@.unaligned$(hide) $(ZIPALIGN) \    -f \    $(ZIPALIGN_PAGE_ALIGN_FLAGS) \    4 \    $@.unaligned $@.aligned$(hide) mv $@.aligned $@endef

这里使用了zipalign工具。这个工具的作用我不太清楚,百度一下发现它的作用如下:
在Android SDK中包含了一个工具名为Zipalign,它可以优化你的APK程序包,我们都知道APK的MIME其实就是一个Zip压缩文件,通过Zipalign可以让你的应用程序运行更快,Android123猜测从原理上来讲应该是优化Zip文件的解压速度,毕竟这个工具的文件名为zip对齐。

在Android平台中,数据文件存储在apk文件中,可以多进程的访问,如果你开发过Win32可能知道程序的粒度对齐问题,不错虽然不是PE格式的文件,在Zip中一样,资源的访问可以通过更好的对其优化,而zipalign使用了4字节的边界对齐方式来影射内存,通过空间换时间的方式提高执行效率。
这样我们就知道这三个apk:package.apk,package.apk.unaligned,package.apk.unsigned他们分别生成的过程和时机了。

更多相关文章

  1. 开发可统计单词个数的Android驱动程序(3)
  2. Android的存储----重新认识Android(9)
  3. 第一章 JAVA入门(Android安全模型)
  4. Android(安卓)NDK开发(四) 将FFmpeg移植到Android平台
  5. Android(安卓)Lottie动画实战踩坑
  6. 【转】关于Android的.so文件你所需要知道的(二)
  7. Android(安卓)Studio目录结构浅析
  8. Android--数据持久化之内部存储、Sdcard存储
  9. 全面解析Activity的生命周期

随机推荐

  1. Android(安卓)Layout机制研究
  2. Android(安卓)studio Error: Please sele
  3. Android(安卓)Framework 之HelloWorld(二)
  4. 使用宝塔面板上线项目
  5. 抖音seo源码,抖音seo优化系统源码技术搭建
  6. 新手使用APICloud可视化开发搭建商城主页
  7. 使用APICloud实现文档下载和预览功能
  8. 如何用ps绘制插画肌理?ps肌理插画教程
  9. 本地可视化工具连接阿里云centOS服务器的
  10. VSCode实用插件