手工构建Android应用

本文中所使用的工具信息:
系统为Mac 10.12版本。
Android build-tools版本:22.0.1
Android platforms版本:22
JDK版本:1.7
测试的手机系统:Android 5.1

请尊重博主劳动成果,转载请标明原文链接。

Android应用构建流程

以前使用Eclipse,现在使用Android Studio开发,点击“run”,等待一会儿,便能输出Apk文件。应用构建的基本流程,自己大概明白,但是没有使用过命令行来构建Apk。现在使用命令构建,并记录相关流程。

先看看Android开发文档上的两张图片。

(一)Android工程构建简化图
手工构建Android应用 - 阿里云

(二)Android打包流程图
手工构建Android应用 - 阿里云

Apk文件内部可以分为4个部分:
dex文件:有一个或多个,编译所产生的class文件都被转换打包到dex。
资源包resources.arsc:以表的形式记录应用的资源文件信息。
未编译的文件:签名信息相关的文件,以及其它写入信息。
AndroidManifest.xml文件:记录版本信息,应用所需要的权限,四大组件等基本信息。

构建Apk的步骤:
1,通过appt生产R.java和资源包resources.arsc。如果有aidl文件则通过aidl工具生成对应的java文件;
2,通过jdk编译工程中的java文件;
3,将工程编译的class和引入的jar文件,通过dx工具转换生成dex文件;
4,使用ApkBuilder工具将dex文件和资源文件合并为apk文件;
5,使用jarsigner命令和keystore对apk进行签名;
6,使用zipalign命令优化签名后的apk文件。如果为debug包,则不需要进行这一步。

手动创建一个Android工程,使用命令构建一个可安装的apk。

一 创建Android工程

手工创建一个简单的PluginDemo工程,目录和文件如下:

PluginDemo|____AndroidManifest.xml|____build| |____out|____gen|____res| |____drawable| | |____p_icon_play.png| |____layout| | |____plugin_item.xml| |____mipmap| | |____p_icon_lock.png| |____values| | |____string.xml| | |____styles.xml|____src| |____com| | |____plugin| | | |____test| | | | |____PluginActivity.java 二 生产R.java和resoures.ap_

生成R.java和资源文件所使用的aapt基本命令:
-f 如果编译生成的文件存在,则强制覆盖
-m 让生成的文件存放到-J指定的目录
-M 指定AndroidManifest.xml文件的路径
-J 指定R.java文件存放的目录
-A 指定asset source目录
-S 指定res资源目录
-F 指定生成的资源包文件路径
-I 指定编译使用的版本平台的android.jar路径

更多相关命令,可以在控制台执行appt,查看打印出来的Usag信息。

开启终端(Window系统则是控制台),进入到PluginDemo工程目录。使用aapt命令生成R.java文件和生成资源包文件。

../../../devTools/android/android-sdk-macosx/build-tools/22.0.1/aapt package -f -m -J gen -S res -M AndroidManifest.xml -I ../../../devTools/android/android-sdk-macosx/platforms/android-22/android.jar -F build/out/resources.ap_ 三 编译java文件

使用javac编译java文件

javac -encoding GBK -bootclasspath ../../../devTools/android/android-sdk-macosx/platforms/android-22/android.jar -d ./build/out ./gen/com/plugin/test/R.java

执行javac -help,查看相关命令:

用法: javac <options> <source files>其中, 可能的选项包括: -g 生成所有调试信息 -g:none 不生成任何调试信息 -g:{lines,vars,source} 只生成某些调试信息 -nowarn 不生成任何警告 -verbose 输出有关编译器正在执行的操作的消息 -deprecation 输出使用已过时的 API 的源位置 -classpath <路径> 指定查找用户类文件和注释处理程序的位置 -cp <路径> 指定查找用户类文件和注释处理程序的位置 -sourcepath <路径> 指定查找输入源文件的位置 -bootclasspath <路径> 覆盖引导类文件的位置 -extdirs <目录> 覆盖所安装扩展的位置 -endorseddirs <目录> 覆盖签名的标准路径的位置 -proc:{none,only} 控制是否执行注释处理和/或编译。 -processor <class1>[,<class2>,<class3>…] 要运行的注释处理程序的名称; 绕过默认的搜索进程 -processorpath <路径> 指定查找注释处理程序的位置 -d <目录> 指定放置生成的类文件的位置 -s <目录> 指定放置生成的源文件的位置 -implicit:{none,class} 指定是否为隐式引用文件生成类文件 -encoding <编码> 指定源文件使用的字符编码 -source <发行版> 提供与指定发行版的源兼容性 -target <发行版> 生成特定 VM 版本的类文件 -version 版本信息 -help 输出标准选项的提要 -A关键字[=值] 传递给注释处理程序的选项 -X 输出非标准选项的提要 -J<标记> 直接将 <标记> 传递给运行时系统 -Werror 出现警告时终止编译 @<文件名> 从文件读取选项和文件名 四 将class文件转换为dex文件

将编译生成的class文件转换成dex文件

../../../devTools/android/android-sdk-macosx/build-tools/22.0.1/dx –dex –output=build/out/classes.dex build/out/

由于系统默认的JDK版本为1.8,所以在使用22.0.1的dx工具生成dex文件时报错了。

UNEXPECTED TOP-LEVEL EXCEPTION:com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472) at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406) at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388) at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251) at com.android.dx.command.dexer.Main.processClass(Main.java:704) at com.android.dx.command.dexer.Main.processFileBytes(Main.java:673) at com.android.dx.command.dexer.Main.access$300(Main.java:83) at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:170) at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158) at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158) at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158) at com.android.dx.cf.direct.ClassPathOpener.processDirectory(ClassPathOpener.java:229) at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:158) at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144) at com.android.dx.command.dexer.Main.processOne(Main.java:632) at com.android.dx.command.dexer.Main.processAllFiles(Main.java:510) at com.android.dx.command.dexer.Main.runMonoDex(Main.java:280) at com.android.dx.command.dexer.Main.run(Main.java:246) at com.android.dx.command.dexer.Main.main(Main.java:215) at com.android.dx.command.Main.main(Main.java:106)…while parsing com/plugin/test/PluginActivity.class

这是因为22.0.1版本的dx工具无法识别JDK 1.8编译的class文件中的一些内容。将JDK版本切换到1.7版本,重新编译。然后执行dx打包命令,可以正常生产dex文件。

dx基本命令:
–dex 指定生成dex文件
–output=<file> file为生成的dex文件的路径
[<file>.class | <file>.{zip,jar,apk} | <directory>] 指定class或jar文件路径

更多dx命令,可以使用dx -help查看。

五 合并dex和资源文件为apk

使用ApkBuilder将资源包和dex文件合并为apk文件

java -cp ../../../devTools/android/android-sdk-macosx/tools/lib/sdklib.jar com.android.sdklib.build.ApkBuilderMain build/out/test.apk -u -v -z build/out/resources.ap_ -f build/out/classes.dex

-cp为java命令,用来指定目录和 zip/jar 文件的类搜索路径。后面的-u -v -z -f为ApkBuilder的命令。

ApkBuilder基本命令:

A command line tool to package an Android application from various sources.Usage: apkbuilder <out archive> [-v][-u][-storetype STORE_TYPE] [-z inputzip] [-f inputfile] [-rf input-folder] [-rj -input-path] -v Verbose. -d Debug Mode: Includes debug files in the APK file. -u Creates an unsigned package. -storetype Forces the KeyStore type. If omitted the default is used. -z Followed by the path to a zip archive. Adds the content of the application package. -f Followed by the path to a file. Adds the file to the application package. -rf Followed by the path to a source folder. Adds the java resources found in that folder to the application package, while keeping their path relative to the source folder. -rj Followed by the path to a jar file or a folder containing jar files. Adds the java resources found in the jar file(s) to the application package. -nf Followed by the root folder containing native libraries to include in the application package.

-rf用来绑定源码,-rj将指定的jar文件中的资源文件添加到应用包,-nf将so文件添加到应用包中。

六 对apk签名

如果没有keystore,或者想生成一个,可以使用keytool命令生成keystore文件。
生成一个别名为test,有效期为20000天,密码为123456,文件名称为test.keystore的签名文件。命令和步骤如下:

xxx:PluginDemo liu1359041$ keytool -genkey -alias test -validity 20000 -keystore test.keystore输入密钥库口令: 再次输入新口令: 您的名字与姓氏是什么? [Unknown]: test您的组织单位名称是什么? [Unknown]: test您的组织名称是什么? [Unknown]: test您所在的城市或区域名称是什么? [Unknown]: test您所在的省/市/自治区名称是什么? [Unknown]: test该单位的双字母国家/地区代码是什么? [Unknown]: cnCN=test, OU=test, O=test, L=test, ST=test, C=cn是否正确? [否]: y输入 <test> 的密钥口令 (如果和密钥库口令相同, 按回车): 再次输入新口令:

执行完命令,便会在PluginDemo目录里生成一个test.keystore的签名文件。

使用test.keystore对apk进行签名

jarsigner -verbose -keystore test.keystore -storepass 123456 -keypass 123456 -signedjar build/out/test_s.apk build/out/test.apk test

输出的日志信息:

正在添加: META-INF/MANIFEST.MF 正在添加: META-INF/TEST.SF 正在添加: META-INF/TEST.DSA 正在签名: AndroidManifest.xml 正在签名: res/drawable/p_icon_play.png 正在签名: res/layout/plugin_item.xml 正在签名: res/mipmap/p_icon_lock.png 正在签名: resources.arsc 正在签名: classes.dexjar 已签名。警告: 未提供 -tsa 或 -tsacert, 此 jar 没有时间戳。如果没有时间戳, 则在签名者证书的到期日期 (2072-05-07) 或以后的任何撤销日期之后, 用户可能无法验证此 jar

对apk签名成功。

SHA-256算法在Android 4.2及以版本报错问题。原因是JDK 1.6以前默认采用SHA1算法,而1.6及以后版本则采用了更安全的SHA-256算法,所以在JDK 1.6+环境使用jarsigner签名,默认使用了SHA-256算法。因为未做兼容,在Android 4.2及以下的系统会出现签名验证失败问题。可以在jarsigner命令后追加-sigalg SHA1withRSA -digestalg SHA1命令,强制使用SHA1算法。

这个问题在使用Android 4.0的模拟器测试动态加载签名的jar文件时出现过。

七 优化apk

使用zipalign对apk进行优化,减少包的体积

../../../devTools/android/android-sdk-macosx/build-tools/22.0.1/zipalign -f 4 build/out/test.apk build/out/test_opt.apk

zipalign用法和命令:

Usage: zipalign [-f] [-v] [-z] <align> infile.zip outfile.zip zipalign -c [-v] <align> infile.zip <align>: alignment in bytes, e.g. ‘4’ provides 32-bit alignment -c: check alignment only (does not modify file) -f: overwrite existing outfile.zip -v: verbose output -z: recompress using Zopfli

执行完以上命令,再查看PluginDemo工程目录和文件如下:

PluginDemo|____AndroidManifest.xml|____build| |____out| | |____classes.dex| | |____com| | | |____plugin| | | | |____test| | | | | |____PluginActivity.class| | | | | |____R$attr.class| | | | | |____R$drawable.class| | | | | |____R$id.class| | | | | |____R$layout.class| | | | | |____R$mipmap.class| | | | | |____R$string.class| | | | | |____R$style.class| | | | | |____R.class| | |____resources.ap_| | |____test.apk| | |____test_opt.apk| | |____test_s.apk|____gen| |____com| | |____plugin| | | |____test| | | | |____R.java|____res| |____drawable| | |____p_icon_play.png| |____layout| | |____plugin_item.xml| |____mipmap| | |____p_icon_lock.png| |____values| | |____string.xml| | |____styles.xml|____src| |____com| | |____plugin| | | |____test| | | | |____PluginActivity.java|____test.keystore

构建过程中生成了不少的文件。最终能够运行的是test_opt.apk(经过优化的apk)和test_s.apk这两个文件。

安装运行test_opt.apk,如下图:
手工构建Android应用 - 阿里云
手工创建的工程和命令构建的Apk正常运行。虽然系统是5.1,由于没有配置Activity的主题和继承framework中的Actvity,而不是v4或v7包的,所以显示的像非常老旧2.3系统上的应用。

参考文章

Android官方开发文档

手动命令行编译 APK
https://juejin.im/entry/57cb841e2e958a0068dcf679

Android 打包过程
http://www.jianshu.com/p/7c288a17cda8