如何在手機上打包生成APK
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://1025250620.iteye.com/blog/1974214
https://code.google.com/archive/p/zip-signer/