by Amour.wang
通常我们都是在电脑上开发android应用,但是有些情况下不方便带电脑,又想临时修改一些参数重新生成apk,于是就发现了一个神器 AIDE(http://www.android-ide.com/)。有免费的版本基本够用了,还有高级版需要$.
然而本篇文章并不是要介绍AIDE,出于程序猿一贯的好奇心,于是决定研究一下这当中的奥秘。本篇文章主要就是介绍apk 打包的流程及如何在手机上生成一个apk 的过程。
一、apk 的打包过程分析
既然是要生成Apk 那就得来了解一下APK 的生成过程,以往我们都是在eclipse 中点一下run 然后就可以在bin 文件夹下找到xxx.apk. 再高级一点的使用ant 编译,或者谷歌家大力推崇的 android studio 中的gradle build,都大致是这样。
但是没有过程啊,于是开始研究这个过程,基本可以分为如下几步
从这个图就可以很明确的看出蓝色的小方块就是我们需要的工具了,白色的小方块是输入,浅蓝色的小方块是中间产物。有了这个流程不管在什么平台上只要找到相应的工具都可以完成apk 的编译打包过程了,目前在 windows,OSX,linux 上网上都已经有很多人有分享了相关的资料的,这边我就不再重复了。下面重点来了,这个目前在网上很少有相关的资料,所以特别整理一下分享给大家。
一、在手机上完成APK 的编译打包。
按照上述的流程分为如下几个步骤,待我一一详细介绍
1. aapt
google 官方有提供windows 下的aapt.exe 和 linux/mac 的aapt,可是这个aapt 是X86 架构的,在arm 下无法使用,于是想到可以自己编译一个arm 版的。
于是开始各种折腾,aapt 谷歌是有提供官方源码在frameworks/base/tools/aapt/下,又是各种折腾,最后还是直接用别人生成的的比较好用(以后有时间再折腾,还得看下aapt 的源码更深入了解apk 资源的编译原理,这个包含的内容较多就不在这篇详细介绍了)。
Aapt 的基本用法
Aapt package
-f overwrite file
-m make package directoriesunder location specified by –J
[-S resource-sources [-S resource-sources ...]] \
-J specify where to outputR.java resource constant definitions
-M specify full path toAndroidManifest.xml to include in zip
-I add an existing package tobase include set
-F specify the apk file tooutput
在使用之前必须先把aapt 拷贝到自己应用的目录下,再chmod 为可执行,
String[] chmod = {"chmod", "744", aaptLoc.getAbsolutePath()}; Process chmodProcess = Runtime.getRuntime().exec(chmod);
private boolean runaapt(File aaptLoc, File androidJarLoc,String actName) {
try {
String[] args = {
aaptLoc.getAbsolutePath(), //Thelocation of AAPT
"package", "-v", "-f", "-m",
"-S", buildFolder.getAbsolutePath() + "/res/",
"-J", genFolder.getAbsolutePath(),
"-A", assetsFolder.getAbsolutePath(),
"-M", buildFolder.getAbsolutePath() + "/AndroidManifest.xml",
"-I", androidJarLoc.getAbsolutePath(),
"-F", binFolder.getAbsolutePath() + "/" + actName + ".apk.res"/
};
Process aaptProcess = Runtime.getRuntime().exec(args);
int code =aaptProcess.waitFor();
if (code!= 0){
System.err.println("AAPTexited with error code " +code);
copyStream(aaptProcess.getErrorStream(),System.out);
return false;
}
return true;
} catch (IOExceptione) {
System.out.println("AAPT failed");
e.printStackTrace();
return false;
} catch (InterruptedException e) {
System.out.println("AAPT failed");
e.printStackTrace();
return false;
}
}
2. aidl
源码在frameworks/base/tools/aidl/,这个跟aapt 差不多,依葫芦画瓢,这边就不再详细描述。
3. javac compiler
把java 变成class 文件,这个比较简单了,从SDK tools里面抠出来,直接就用了 ecj.jar
private boolean ecj(File androidJarLoc, String actName, String mainActivityLoc) { { System.out.println("Compiling with ECJ..."); Main main = new Main(new PrintWriter(System.out), new PrintWriter(System.err), false, null, null); String[] args = { ("-verbose"), "-extdirs", libsFolder.getAbsolutePath(), "-bootclasspath", androidJarLoc.getAbsolutePath(), "-classpath", srcFolder.getAbsolutePath() + ":" + genFolder.getAbsolutePath() + ":" + libsFolder.getAbsolutePath(), "-1.6", "-target", "1.6", "-proc:none", "-d", binFolder.getAbsolutePath() + "/classes/", srcFolder.getAbsolutePath() + "/" + mainActivityLoc + "/" + actName + ".java", }; System.out.println("Compiling: " + srcFolder.getAbsolutePath() + "/" + mainActivityLoc + "/" + actName + ".java"); if (main.compile(args)) { System.out.println(); return true; } else { System.out.println(); System.out.println("Compilation with ECJ failed"); return false; } } }
4.dex
这个也是一样从SDK buildtools 里面抠出来 dx.jar,这些个jar的用法参数都是参考各路大神,时间有限也没再多去研究
private boolean dexer(int cores) { try { System.out.println("Dexing with DX Dexer..."); String[] args; args = new String[]{ "--verbose", "--num-threads=" + cores, "--output=" + binFolder.getAbsolutePath() + "/classes.dex", // binFolder.getAbsolutePath() + "/classes/" }; com.android.dx.command.dexer.Main.Arguments dexArgs = new com.android.dx.command.dexer.Main.Arguments(); dexArgs.parse(args); int resultCode = com.android.dx.command.dexer.Main.run(dexArgs); if (resultCode != 0) { System.err.println("DX Dexer result code: " + resultCode); return false; } return true; } catch (Exception e) { System.out.println("DX Dexer failed"); e.printStackTrace(); return false; } }
5. apkbuilder
一样的套路在 SDK 的tools 目录下 sdklib.jar,执行完这步,就已经生成了一个未签名的APK
private boolean buildapk(String sketchName, boolean verbose) { try { System.out.println("Building APK file with APKBuilder..."); com.android.sdklib.build.ApkBuilder builder = new com.android.sdklib.build.ApkBuilder(new File(binFolder.getAbsolutePath() + "/" + sketchName + ".apk.unsigned"), new File(binFolder.getAbsolutePath() + "/" + sketchName + ".apk.res"), new File(binFolder.getAbsolutePath() + "/classes.dex"), null, (verbose ? System.out : null) ); builder.addSourceFolder(srcFolder); builder.sealApk(); return true; } catch (Exception e) { e.printStackTrace(); System.out.println("APKBuilder failed"); return false; } }
6. sign
这个eclipse中是调用java 来对apk 进行签名的没有现成的jar包可以用。从谷歌家拿 https://code.google.com/archive/p/zip-signer/,好了一切顺利的话(通常这种情况发生的概率跟中500万差不多),你就可以看到你在手机上生成的apk了
private boolean signApk(String actName) { String mode = "testkey"; String inFilename = binFolder.getAbsolutePath() + "/" + actName + ".apk.unsigned"; String outFilename = binFolder.getAbsolutePath() + "/" + actName + ".apk"; ZipSigner signer; try { signer = new ZipSigner(); signer.setKeymode(mode); signer.signZip(inFilename, outFilename); return true; } catch (Exception e) { e.printStackTrace(); } return false; }
二、小结
遗留的一些已知的问题:
1. aidl 没有处理,这个应该跟aapt 类似,不过过程太折腾了,暂时没有时间弄
2. 多个dex 合并,这个部分没有处理,但是在SDK里面有看到对应的jar包,这部分应该还好
3. JNI 编译,这个估计只有大神才可以做到
当中还有很多未知的问题,因个人精力所限,暂时也不能一一查明。
引用查询资料出处
http://www.cnblogs.com/royi123/p/3576746.html
http://1025250620.iteye.com/blog/1974214
https://code.google.com/archive/p/zip-signer/