1. 程式人生 > >如何在手機上打包生成APK

如何在手機上打包生成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/