Android apk多渠道自動打包 - 不提供工具,只提供源碼

 在項目中用到了百度SDK統計,沒用過別的統計工具,只用了百度的感覺還不錯,最新版本新增了Fragment統計功能。應用上線三天,用各種流氓辦法下載安裝量已經超過了2800,但是留存率只有10%左右。主要原因還是產品同質化比較嚴重,沒有什麼亮點。

    用到統計工具基本上就會用到渠道,分渠道打包真是件很頭疼的事情,渠道一多了之後手動打包效率非常低,而且容易出錯。所以今天花了半天時間研究了一下多渠道自動打包的方法,這樣節省了不少時間,主要不會在打包的過程中出錯了!

    下面我就一步步的告訴大家怎麼自己寫一個多渠道打包工具,爲什麼我不提供一個寫好的給大家下載呢?因爲每個人的項目、編譯環境等等諸多因素都不相同,主要原因也是我很忙,沒有時間寫一個擴展性更好的工具,所以就在這裏講一講實現原理吧。希望有人可以看到這篇文章後寫個通用性更廣的打包工具出來。

 

    言歸正傳。

    apk打包有兩種方式ant & apktool,我看網上很多人都用ant的打包方式,但是研究了一下感覺有點小複雜,不是半天就能搞定的,所以換用apktool的方式實現自動打包。apktool是外國人寫的工具,很多反編譯軟件會用到它解包,也有一些山寨應用會用它解包打包,官方網址是http://code.google.com/p/android-apktool/,最新版本是1.5.2。apktool底層原理就是用sdk工具中的aapt實現的。

    我們需要用到的工具

    jdk        一般開發都有這個吧

    sdk      一般開發都有這個吧(主要用到裏面的aapt,我的路徑是:sdk\build-tools\android-4.2.2\aapt.exe)

    apktool  去官網下載(http://code.google.com/p/android-apktool/downloads/detail?name=apktool1.5.2.tar.bz2&can=2&q=)

 

    有了工具就可以開始寫代碼了,實現自動打包的原理是這樣的:

    1.先得到apk文件(我是用Eclipse生成的,可以從bin文件夾裏直接獲得,也可以打簽名包和未簽名包,只要有apk就行)

    2.用apktool 解包  (java -jar apktool.jar d -f -s xxx.apk),通過這個指令就會在apktool目錄下生成一個apk同名的文件夾,其中就包括我們要修改的AndroidManifest.xml

    3.寫代碼去修改AndroidManifest.xml中對應Channel_Id的地方

    4.用apktool 打包 (java -jar apktool.jar b xxx.ap xxx_us.apk),通過這個指令會生成一個未簽名的apk,注意,此指令需要依賴aapt,請在系統環境變量中引入aapt!

    5.用jdk的jarsigner工具給apk簽名(指令有很多,我用的是jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore abc.keystore -signedjar xxx_s.apk xxx_us.apk abc.keystore -storepass)

 

    好的,原理知道後,剩下的就非常簡單了,一步步去實現就可以了!

    爲了避免大家走彎路,我告訴大家一個方法。在寫剩下的代碼之前,請大家用apktool指令Run一遍解包、打包和簽名的一整套動作,如果可以順利跑下來,你後面寫的工具纔是有意義的。我在寫工具過程中遇到一些問題都是因爲這幾個指令都不能完全執行導致的,特別是因爲aapt和jarsigner沒有配置環境變量。


    正式開始了

    1.打開Eclipse新建Java工程,起一個自己喜歡的工程名字和包名。
    2.創建一個程序入口Main.java

[java] view plaincopy

  1. public class Main {  

  2.     public static void main(String[] args) {// 這裏用cmd傳入參數用  

  3.         System.out.println("====**====By H3c=====**======");  

  4.   

  5.         if (args.length != 3) {// 傳入3個參數 apk報名、簽名文件、簽名密碼  

  6.             System.out  

  7.                     .println("==ERROR==usage:java -jar rePack.jar apkName keyFile keyPasswd======");  

  8.             System.out  

  9.                     .println("==INFO==Example: java -jar rePack.jar test.apk android.keystore 123456======");  

  10.             return;  

  11.         }  

  12.   

  13.         String apk = args[0];  

  14.         String keyFile = args[1];  

  15.         String keyPasswd = args[2];  

  16.   

  17.         SplitApk sp = new SplitApk(apk, keyFile, keyPasswd);  

  18.         sp.mySplit();  

  19.     }  

  20. }<span style="color:#000099;">  

  21. </span>  

    3.創建工具類SplitApk.java

[java] view plaincopy

  1. import java.io.BufferedReader;  

  2. import java.io.File;  

  3. import java.io.FileReader;  

  4. import java.io.FileWriter;  

  5. import java.io.IOException;  

  6. import java.io.InputStreamReader;  

  7. import java.util.HashMap;  

  8. import java.util.Map;  

  9.   

  10. public class SplitApk {  

  11.     HashMap<String, String> qudao = new HashMap<String, String>();// 渠道號,渠道名  

  12.     String curPath;// 當前文件夾路徑  

  13.     String apkName;  

  14.     String keyFile;  

  15.     String keyPasswd;  

  16.   

  17.     public SplitApk(String apkName, String keyFile, String keyPasswd) {// 構造函數接受參數  

  18.         this.curPath = new File("").getAbsolutePath();  

  19.         this.apkName = apkName;  

  20.         this.keyFile = keyFile;  

  21.         this.keyPasswd = keyPasswd;  

  22.     }  

  23.   

  24.     public void mySplit() {  

  25.         getCannelFile();// 獲得自定義的渠道號  

  26.         modifyXudao();// 解包 - 打包 - 簽名  

  27.     }  

  28.   

  29.     /** 

  30.      * 獲得渠道號 

  31.      */  

  32.     private void getCannelFile() {  

  33.         File f = new File("channel.txt");// 讀取當前文件夾下的channel.txt  

  34.         if (f.exists() && f.isFile()) {  

  35.             BufferedReader br = null;  

  36.             FileReader fr = null;  

  37.             try {  

  38.                 fr = new FileReader(f);  

  39.                 br = new BufferedReader(fr);  

  40.                 String line = null;  

  41.                 while ((line = br.readLine()) != null) {  

  42.                     String[] array = line.split("\t");// 這裏是Tab分割  

  43.                     if (array.length == 2) {  

  44.                         qudao.put(array[0].trim(), array[1].trim());// 講渠道號和渠道名存入HashMap中  

  45.                     }  

  46.                 }  

  47.             } catch (Exception e) {  

  48.                 e.printStackTrace();  

  49.             } finally {  

  50.                 try {  

  51.                     if (fr != null) {  

  52.                         fr.close();  

  53.                     }  

  54.                     if (br != null) {  

  55.                         br.close();  

  56.                     }  

  57.                 } catch (IOException e) {  

  58.                     e.printStackTrace();  

  59.                 }  

  60.             }  

  61.             System.out.println("==INFO 1.==獲取渠道成功,一共有" + qudao.size()  

  62.                     + "個渠道======");  

  63.         } else {  

  64.             System.out.println("==ERROR==channel.txt文件不存在,請添加渠道文件======");  

  65.         }  

  66.     }  

  67.   

  68.     /** 

  69.      * apktool解壓apk,替換渠道值 

  70.      *  

  71.      * @throws Exception 

  72.      */  

  73.     private void modifyXudao() {  

  74.         // 解壓 /C 執行字符串指定的命令然後終斷  

  75.         String cmdUnpack = "cmd.exe /C java -jar apktool.jar d -f -s "  

  76.                 + apkName;  

  77.         runCmd(cmdUnpack);  

  78.         System.out.println("==INFO 2.==解壓apk成功,準備移動======");  

  79.   

  80.         // 備份AndroidManifest.xml  

  81.         // 獲取解壓的apk文件名  

  82.         String[] apkFilePath = apkName.split("\\\\");  

  83.         String shortApkName = apkFilePath[apkFilePath.length - 1];  

  84.         String dir = shortApkName.split(".apk")[0];  

  85.         File packDir = new File(dir);// 獲得解壓的apk目錄  

  86.   

  87.         String f_mani = packDir.getAbsolutePath() + "\\AndroidManifest.xml";  

  88.         String f_mani_bak = curPath + "\\AndroidManifest.xml";  

  89.         File manifest = new File(f_mani);  

  90.         File manifest_bak = new File(f_mani_bak);  

  91.   

  92.         // 拷貝文件 -- 此方法慎用,詳見http://xiaoych.iteye.com/blog/149328  

  93.         manifest.renameTo(manifest_bak);  

  94.   

  95.         for (int i = 0; i < 10; i++) {  

  96.             if (manifest_bak.exists()) {  

  97.                 break;  

  98.             }  

  99.             try {  

  100.                 Thread.sleep(1000);  

  101.             } catch (InterruptedException e) {  

  102.                 e.printStackTrace();  

  103.             }  

  104.         }  

  105.   

  106.         if (manifest_bak.exists()) {  

  107.             System.out.println("==INFO 3.==移動文件成功======");  

  108.         } else {  

  109.             System.out.println("==ERROR==移動文件失敗======");  

  110.         }  

  111.   

  112.         // 創建生成結果的目錄  

  113.         File f = new File("apk");  

  114.         if (!f.exists()) {  

  115.             f.mkdir();  

  116.         }  

  117.   

  118.         /* 

  119.          * 遍歷map,複製manifese進來,修改後打包,簽名,存儲在對應文件夾中 

  120.          */  

  121.         for (Map.Entry<String, String> entry : qudao.entrySet()) {  

  122.             String id = entry.getKey();  

  123.             System.out.println("==INFO 4.1. == 正在生成包: " + entry.getValue()  

  124.                     + " ======");  

  125.             BufferedReader br = null;  

  126.             FileReader fr = null;  

  127.             FileWriter fw = null;  

  128.             try {  

  129.                 fr = new FileReader(manifest_bak);  

  130.                 br = new BufferedReader(fr);  

  131.                 String line = null;  

  132.                 StringBuffer sb = new StringBuffer();  

  133.                 while ((line = br.readLine()) != null) {  

  134.                     if (line.contains("\"ads-2.0\"")) {  

  135.                         line = line.replaceAll("ads-2.0", id);  

  136.                     }  

  137.                     sb.append(line + "\n");  

  138.                 }  

  139.   

  140.                 // 寫回文件  

  141.                 fw = new FileWriter(f_mani);  

  142.                 fw.write(sb.toString());  

  143.             } catch (Exception e) {  

  144.                 e.printStackTrace();  

  145.             } finally {  

  146.                 try {  

  147.                     if (fr != null) {  

  148.                         fr.close();  

  149.                     }  

  150.                     if (br != null) {  

  151.                         br.close();  

  152.                     }  

  153.                     if (fw != null) {  

  154.                         fw.close();  

  155.                     }  

  156.                 } catch (IOException e) {  

  157.                     e.printStackTrace();  

  158.                 }  

  159.             }  

  160.   

  161.             System.out.println("==INFO 4.2. == 準備打包: " + entry.getValue()  

  162.                     + " ======");  

  163.   

  164.             // 打包 - 生成未簽名的包  

  165.             String unsignApk = id + "_" + dir + "_un.apk";  

  166.             String cmdPack = String.format(  

  167.                     "cmd.exe /C java -jar apktool.jar b %s %s", dir, unsignApk);  

  168.             runCmd(cmdPack);  

  169.   

  170.             System.out.println("==INFO 4.3. == 開始簽名: " + entry.getValue()  

  171.                     + " ======");  

  172.             // 簽名  

  173.             String signApk = "./apk/" + id + "_" + dir + ".apk";  

  174.             String cmdKey = String  

  175.                     .format("cmd.exe /C jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore %s -signedjar %s %s %s -storepass  %s",  

  176.                             keyFile, signApk, unsignApk, keyFile, keyPasswd);  

  177.             runCmd(cmdKey);  

  178.             System.out.println("==INFO 4.4. == 簽名成功: " + entry.getValue()  

  179.                     + " ======");  

  180.             // 刪除未簽名的包  

  181.             File unApk = new File(unsignApk);  

  182.             unApk.delete();  

  183.         }  

  184.   

  185.         // 刪除中途文件  

  186.         String cmdKey = String.format("cmd.exe /C rd /s/q %s", dir);  

  187.         runCmd(cmdKey);  

  188.         manifest_bak.delete();  

  189.   

  190.         System.out.println("==INFO 5 == 完成 ======");  

  191.     }  

  192.   

  193.     /** 

  194.      * 執行指令 

  195.      *  

  196.      * @param cmd 

  197.      */  

  198.     public void runCmd(String cmd) {  

  199.         Runtime rt = Runtime.getRuntime();  

  200.         BufferedReader br = null;  

  201.         InputStreamReader isr = null;  

  202.         try {  

  203.             Process p = rt.exec(cmd);  

  204.             // p.waitFor();  

  205.             isr = new InputStreamReader(p.getInputStream());  

  206.             br = new BufferedReader(isr);  

  207.             String msg = null;  

  208.             while ((msg = br.readLine()) != null) {  

  209.                 System.out.println(msg);  

  210.             }  

  211.         } catch (Exception e) {  

  212.             e.printStackTrace();  

  213.         } finally {  

  214.             try {  

  215.                 if (isr != null) {  

  216.                     isr.close();  

  217.                 }  

  218.                 if (br != null) {  

  219.                     br.close();  

  220.                 }  

  221.             } catch (IOException e) {  

  222.                 e.printStackTrace();  

  223.             }  

  224.         }  

  225.     }  

  226. }<span style="color:#000099;">  

  227. </span>  

    4.代碼寫好後就該生成jar包了:

    右擊工程選擇菜單中的Export - Java - Runnable JAR file,選擇導出路徑後就可以輸出jar了。

    5.最後一步,新建一個文件夾,放入剛編譯出的jar、apktool.jar和channel.txt,最好還有android.keystore

        channel.txt 格式如下,注意是tab分割,不是空格

[java] view plaincopy

  1. 123 外部推廣  

  2. 124 軟件盒子  

  3. 125  內部網頁top  

  4. 126  官方包  

    爲什麼這裏建議放入androdi.keystore,因爲如果引入外部的會報一個簽名不一致的錯誤。

    6.在cmd中進入這個文件夾後,輸入java -jar rePack.jar 文件名 android.keystore 簽名密碼,就可以自動換渠道打包了,如果中途出現問題,請自己檢查apktool解打包過程和jarsigner是否會報錯,去google上搜出錯原因。
    7.爲了更簡單,可以寫個批處理

[java] view plaincopy

  1. @echo off  

  2. set /p var=請拖入apk:  

  3. java -jar rePack.jar %var% android.keystore 55775577  

  4.   

  5. echo.&echo 請按任意鍵退出...&pause>nul  

  6. exit   


   全文完

 轉自http://blog.csdn.net/h3c4lenovo/article/details/10007039


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章