APK打包安装过程

LintCode 每日一题https://github.com/Jensenczx/...java

序言

写的越多发现的问题也就越多,不能否认,以前的博客如今看来有些东西本身理解的仍是颇有出入的,在后续过程当中,也是须要本身进一步的去改进。前几篇写了Android手机启动流程还有Binder,以及服务的注册和使用问题,如今要写的是一个手机安装包的生成过程和手机如何安装一个安装包的。android

APK打包过程

图片描述
从上图咱们能够看出APK生成和安装的一个大体的流程,编译,打包生成APK以后再签名,而后能够安装到咱们的设备上,上图是一个总体的归纳,咱们接下来须要对其进行一个更深刻的学习。git

重点在于apk的生成过程,整个打包的过程,能够归结为下图。
图片描述github

第1步:aapt打包资源文件,生成R.java和编译后的资源(二进制文件)

  1. 检查AndroidManifest.xml,主要作一些检查并使用parsePackage初始化并设置一些attribute,好比package, minSdkVersion, uses-sdk。服务器

  2. 添加被引用资源包
    使用table.addIncludedResources(bundle, assets)添加被引用资源包,好比系统的那些android:命名空间下的资源。app

  3. 收集资源文件,处理overlay(重叠包,若是指定的重叠包有和当前编译包重名的资源,则使用重叠包的):socket

  4. 将收集到的资源文件加到资源表(ResourceTable)
    对res目录下的各个资源子目录进行处理,函数为makeFileResources:makeFileResources会对资源文件名作合法性检查,并将其添加到ResourceTable内。ide

  5. 编译values资源并添加到资源表,在上一步添加过程当中,其实并无对values资源进行处理,由于values比较特殊,须要通过编译以后,才能添加到资源表中。函数

  6. 给bag资源分配id,在继续编译其余资源以前,咱们须要先给bag资源(attrs,好比orientation这种属性的取值范围定义的子元素)分配id,由于其余资源可能对它们有引用。学习

  7. 编译xml资源文件,最后咱们终于能够编译xml文件了,由于咱们已经为它准备好了一切可能引用到的东西(value, drawable等)。程序会对layouts, anims, animators等逐一调用ResourceTable.cpp的,
    进行编译,内部流程又能够分为:解析xml文件,赋予属性名称资源id,解析属性值,扁平化为二进制文件。

  8. 编译AndroidManifest.xml文,拿到AndroidManifest.xml文件,清空原来的数据,从新解,处理package name重载,把各类相对路径的名字改成绝对路径,编译manifest xml文件,生成最终资源表.
    9.生成R.java文件

生成咱们解压后看到的那个resources.arsc:

第2步:aidl

aidl,全名Android Interface Definition Language,即Android接口定义语言。

输入:aidl后缀的文件。
输出:可用于进程通讯的C/S端java代码,位于build/generated/source/aidl。

第3步:Java源码编译

咱们有了R.java和aidl生成的Java文件,再加上工程的源代码,如今可使用javac进行正常的java编译生成class文件了。

输入:java source的文件夹(另外还包括了build/generated下的:R.java, aidl生成的java文件,以及BuildConfig.java)。
输出:对于gradle编译,能够在build/intermediates/classes里,看到输出的class文件。

源码编译以后,咱们可能还会对其进行代码的混淆,混淆的做用是增长反编译的难度,同时也将一些代码的命名进行了缩短,减小代码占用的空间。混淆完成以后,会生成一个混淆先后的映射表,这个是用来在反应咱们的应用执行的时候的一些堆栈信息,能够将混淆后的信息转化为咱们混淆前实际代码中的内容。

第4步:dex

调用dx.bat将全部的class文件(上一步生成的以及第三方库的)转化为classes.dex文件,dx会将class转换为Dalvik字节码,生成常量池,消除冗余数据等。

第5步:apkbuilder

打包生成APK文件。旧的apkbuilder脚本已经废弃,如今都已经经过sdklib.jar的ApkBuilder类进行打包了。输入为咱们以前生成的包含resources.arcs的.ap_文件,上一步生成的dex文件,以及其余资源如jni、jar包内的资源。

大体步骤为
以包含resources.arcs的.ap_文件为基础,new一个ApkBuilder,设置debugMode
apkBuilder.addZipFile(f);
apkBuilder.addSourceFolder(f);
apkBuilder.addResourcesFromJar(f);
apkBuilder.addNativeLibraries(nativeFileList);
apkBuilder.sealApk(); // 关闭apk文件
generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath());

第6步:Jarsigner

对apk文件进行签名。APK须要签名才能在设备上进行安装
不少时候咱们在逆向改完后,会由于没有签名文件致使最后的apk没法正常使用,又细分为本地验证和服务器验证。

第7步:zipalign

调用buildtoolszipalign,对签名后的apk文件进行对齐处理,使apk中全部资源文件距离文件起始偏移为4字节的整数倍,从而在经过内存映射访问apk文件时会更快。同时也减小了在设备上运行时的内存消耗。
这样咱们的最终apk就生成完毕了。

安装

安装方式
系统程序安装,开机时安装,没有安装界面。
第一步,将apk文件解压复制到程序目录下(/data/app/);第二步,为应用建立数据目录(/data/data/package name/)、提取dex文件到指定目录(/data/dalvik-cache/)、修改系统包管理信息。
由开机时启动的PackageManagerService服务完成,会在启动时扫描/system/app, vender/app, /data/app, /data/app-private并安装。
PackageInstallerActivity
当Android系统请求安装apk程序时,会启动这个Activity,并经过Intent读取传来的apk信息。下面是apk安装的具体过程。

  1. 解析过程会首先读取AndroidManifest.xml获取程序包名以构建Package对象,而后再处理manifest的其余标签包括四大组件,并把信息全都存到Package对象里面。

  2. 首先检测该程序是否已安装,是则弹框提示是否替换程序,不然直接调用startInstallConfirm(),作UI初始化和事件绑定,因而当咱们点击安装的时候则会触发onClick下的OK按钮事件:

  3. 不管是替换仍是新安装,都会调用scanPackageLI(),而后跑去scanPackageDirtyLI,它会判断是否为系统程序,解析apk程序包,检查依赖库,验证签名,检查sharedUser签名、权限冲突、ContentProvider冲突,更新native库目录文件(检测abi),进行dexopt,杀掉现有进程(仅对覆盖安装的场景)等等,最后调用createDataDirsLI()进行实际安装:
    4.执行完毕后,经过socket回传结果,而PackageInstaller根据返回结果作对应处理并显示给用户,至此为止,整个apk安装过程结束。

包名签名相关
android系统使用包名(package name)来断定应用程序的同一性,可是因为包名能够由开发者自由设置,为了保护应用程序不被其余开发者开发的同包名应用覆盖,用于发布的Android应用程序须要加上开发者签名。在应用程序被升级的时候,Android系统将会验证被升级的应用程序包与升级后的应用程序包是否使用了一样的开发者签名,若是一致,该应用程序能够被升级;若是不一致,那么将被视为非同一开发者开发的应用程序,用户须要先卸载已经安装的应用而后再安装新应用,在卸载的过程当中,应用在android系统中所保存的设置信息(SavedPreferences)将被删除,以保护应用本地保存的资料不被盗取。综上,应用是否能够覆盖的方式是对于包名的判断,而后是对于该APK签名的判断。

总结

上述为本身在看了两个博客后,根据博客内容,本身梳理,回顾的一个过程。省略了其中的代码细节。