Android apk多渠道自動打包 - 不提供工具,只提供源碼分類:
Android App2013-08-16 18:01 1392人閱讀
評論(4)
舉報目錄
(?)[-]我們需要用到的工具有了工具就可以開始寫代碼了實現(xiàn)自動打包的原理是這樣的正式開始了打開Eclipse新建Java工程起一個自己喜歡的工程名字和包名 2創(chuàng)建一個程序入口Mainjava創(chuàng)建工具類SplitApkjava代碼寫好后就該生成jar包了最后一步新建一個文件夾放入剛編譯出的jarapktooljar和channeltxt最好還有androidkeystore在項目中用到了百度SDK統(tǒng)計,沒用過別的統(tǒng)計工具,只用了百度的感覺還不錯,最新版本新增了Fragment統(tǒng)計功能。應(yīng)用上線三天,用各種流氓辦法下載安裝量已經(jīng)超過了2800,但是留存率只有10%左右。主要原因還是產(chǎn)品同質(zhì)化比較嚴重,沒有什么亮點。
用到統(tǒng)計工具基本上就會用到渠道,分渠道打包真是件很頭疼的事情,渠道一多了之后手動打包效率非常低,而且容易出錯。所以今天花了半天時間研究了一下多渠道自動打包的方法,這樣節(jié)省了不少時間,主要不會在打包的過程中出錯了!
下面我就一步步的告訴大家怎么自己寫一個多渠道打包工具,為什么我不提供一個寫好的給大家下載呢?因為每個人的項目、編譯環(huán)境等等諸多因素都不相同,主要原因也是我很忙,沒有時間寫一個擴展性更好的工具,所以就在這里講一講實現(xiàn)原理吧。希望有人可以看到這篇文章后寫個通用性更廣的打包工具出來。
言歸正傳。
apk打包有兩種方式ant & apktool,我看網(wǎng)上很多人都用ant的打包方式,但是研究了一下感覺有點小復(fù)雜,不是半天就能搞定的,所以換用apktool的方式實現(xiàn)自動打包。apktool是外國人寫的工具,很多反編譯軟件會用到它解包,也有一些山寨應(yīng)用會用它解包打包,官方網(wǎng)址是
http://code.google.com/p/android-apktool/,最新版本是1.5.2。apktool底層原理就是用sdk工具中的aapt實現(xiàn)的。
我們需要用到的工具
jdk 一般開發(fā)都有這個吧
sdk 一般開發(fā)都有這個吧(主要用到里面的aapt,我的路徑是:sdk\build-tools\android-4.2.2\aapt.exe)
apktool 去官網(wǎng)下載(
http://code.google.com/p/android-apktool/downloads/detail?name=apktool1.5.2.tar.bz2&can=2&q=)
有了工具就可以開始寫代碼了,實現(xiàn)自動打包的原理是這樣的:
1.先得到apk文件(我是用Eclipse生成的,可以從bin文件夾里直接獲得,也可以打簽名包和未簽名包,只要有apk就行)
2.用apktool 解包 (java -jar apktool.jar d -f -s xxx.apk),通過這個指令就會在apktool目錄下生成一個apk同名的文件夾,其中就包括我們要修改的AndroidManifest.xml
3.寫代碼去修改AndroidManifest.xml中對應(yīng)Channel_Id的地方
4.用apktool 打包 (java -jar apktool.jar b xxx.ap xxx_us.apk),通過這個指令會生成一個未簽名的apk,注意,此指令需要依賴aapt,請在系統(tǒng)環(huán)境變量中引入aapt!
5.用jdk的jarsigner工具給apk簽名(指令有很多,我用的是jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore abc.keystore -signedjar xxx_s.apk xxx_us.apk abc.keystore -storepass)
好的,原理知道后,剩下的就非常簡單了,一步步去實現(xiàn)就可以了!
為了避免大家走彎路,我告訴大家一個方法。在寫剩下的代碼之前,請大家用apktool指令Run一遍解包、打包和簽名的一整套動作,如果可以順利跑下來,你后面寫的工具才是有意義的。我在寫工具過程中遇到一些問題都是因為這幾個指令都不能完全執(zhí)行導(dǎo)致的,特別是因為aapt和jarsigner沒有配置環(huán)境變量。
正式開始了
1.打開Eclipse新建Java工程,起一個自己喜歡的工程名字和包名。
2.創(chuàng)建一個程序入口Main.java
[java]
view plaincopypublic class Main {
public static void main(String[] args) {// 這里用cmd傳入?yún)?shù)用
System.out.println("====**====By H3c=====**======");
if (args.length != 3) {// 傳入3個參數(shù) apk報名、簽名文件、簽名密碼
System.out
.println("==ERROR==usage:java -jar rePack.jar apkName keyFile keyPasswd======");
System.out
.println("==INFO==Example: java -jar rePack.jar test.apk android.keystore 123456======");
return;
}
String apk = args[0];
String keyFile = args[1];
String keyPasswd = args[2];
SplitApk sp = new SplitApk(apk, keyFile, keyPasswd);
sp.mySplit();
}
}<span style="color:#000099;">
</span>
3.創(chuàng)建工具類SplitApk.java
[java]
view plaincopyimport java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
public class SplitApk {
HashMap<String, String> qudao = new HashMap<String, String>();// 渠道號,渠道名
String curPath;// 當前文件夾路徑
String apkName;
String keyFile;
String keyPasswd;
public SplitApk(String apkName, String keyFile, String keyPasswd) {// 構(gòu)造函數(shù)接受參數(shù)
this.curPath = new File("").getAbsolutePath();
this.apkName = apkName;
this.keyFile = keyFile;
this.keyPasswd = keyPasswd;
}
public void mySplit() {
getCannelFile();// 獲得自定義的渠道號
modifyXudao();// 解包 - 打包 - 簽名
}
/**
* 獲得渠道號
*/
private void getCannelFile() {
File f = new File("channel.txt");// 讀取當前文件夾下的channel.txt
if (f.exists() && f.isFile()) {
BufferedReader br = null;
FileReader fr = null;
try {
fr = new FileReader(f);
br = new BufferedReader(fr);
String line = null;
while ((line = br.readLine()) != null) {
String[] array = line.split("\t");// 這里是Tab分割
if (array.length == 2) {
qudao.put(array[0].trim(), array[1].trim());// 講渠道號和渠道名存入HashMap中
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fr != null) {
fr.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("==INFO 1.==獲取渠道成功,一共有" + qudao.size()
+ "個渠道======");
} else {
System.out.println("==ERROR==channel.txt文件不存在,請?zhí)砑忧牢募?=====");
}
}
/**
* apktool解壓apk,替換渠道值
*
* @throws Exception
*/
private void modifyXudao() {
// 解壓 /C 執(zhí)行字符串指定的命令然后終斷
String cmdUnpack = "cmd.exe /C java -jar apktool.jar d -f -s "
+ apkName;
runCmd(cmdUnpack);
System.out.println("==INFO 2.==解壓apk成功,準備移動======");
// 備份AndroidManifest.xml
// 獲取解壓的apk文件名
String[] apkFilePath = apkName.split("\\\\");
String shortApkName = apkFilePath[apkFilePath.length - 1];
String dir = shortApkName.split(".apk")[0];
File packDir = new File(dir);// 獲得解壓的apk目錄
String f_mani = packDir.getAbsolutePath() + "\\AndroidManifest.xml";
String f_mani_bak = curPath + "\\AndroidManifest.xml";
File manifest = new File(f_mani);
File manifest_bak = new File(f_mani_bak);
// 拷貝文件 -- 此方法慎用,詳見http://xiaoych.iteye.com/blog/149328
manifest.renameTo(manifest_bak);
for (int i = 0; i < 10; i++) {
if (manifest_bak.exists()) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (manifest_bak.exists()) {
System.out.println("==INFO 3.==移動文件成功======");
} else {
System.out.println("==ERROR==移動文件失敗======");
}
// 創(chuàng)建生成結(jié)果的目錄
File f = new File("apk");
if (!f.exists()) {
f.mkdir();
}
/*
* 遍歷map,復(fù)制manifese進來,修改后打包,簽名,存儲在對應(yīng)文件夾中
*/
for (Map.Entry<String, String> entry : qudao.entrySet()) {
String id = entry.getKey();
System.out.println("==INFO 4.1. == 正在生成包: " + entry.getValue()
+ " ======");
BufferedReader br = null;
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(manifest_bak);
br = new BufferedReader(fr);
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = br.readLine()) != null) {
if (line.contains("\"ads-2.0\"")) {
line = line.replaceAll("ads-2.0", id);
}
sb.append(line + "\n");
}
// 寫回文件
fw = new FileWriter(f_mani);
fw.write(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fr != null) {
fr.close();
}
if (br != null) {
br.close();
}
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("==INFO 4.2. == 準備打包: " + entry.getValue()
+ " ======");
// 打包 - 生成未簽名的包
String unsignApk = id + "_" + dir + "_un.apk";
String cmdPack = String.format(
"cmd.exe /C java -jar apktool.jar b %s %s", dir, unsignApk);
runCmd(cmdPack);
System.out.println("==INFO 4.3. == 開始簽名: " + entry.getValue()
+ " ======");
// 簽名
String signApk = "./apk/" + id + "_" + dir + ".apk";
String cmdKey = String
.format("cmd.exe /C jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore %s -signedjar %s %s %s -storepass %s",
keyFile, signApk, unsignApk, keyFile, keyPasswd);
runCmd(cmdKey);
System.out.println("==INFO 4.4. == 簽名成功: " + entry.getValue()
+ " ======");
// 刪除未簽名的包
File unApk = new File(unsignApk);
unApk.delete();
}
// 刪除中途文件
String cmdKey = String.format("cmd.exe /C rd /s/q %s", dir);
runCmd(cmdKey);
manifest_bak.delete();
System.out.println("==INFO 5 == 完成 ======");
}
/**
* 執(zhí)行指令
*
* @param cmd
*/
public void runCmd(String cmd) {
Runtime rt = Runtime.getRuntime();
BufferedReader br = null;
InputStreamReader isr = null;
try {
Process p = rt.exec(cmd);
// p.waitFor();
isr = new InputStreamReader(p.getInputStream());
br = new BufferedReader(isr);
String msg = null;
while ((msg = br.readLine()) != null) {
System.out.println(msg);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (isr != null) {
isr.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}<span style="color:#000099;">
</span>
4.代碼寫好后就該生成jar包了:
右擊工程選擇菜單中的Export - Java - Runnable JAR file,選擇導(dǎo)出路徑后就可以輸出jar了。
5.最后一步,新建一個文件夾,放入剛編譯出的jar、apktool.jar和channel.txt,最好還有android.keystore
channel.txt 格式如下,注意是tab分割,不是空格
[java]
view plaincopy123 外部推廣
124 軟件盒子
125 內(nèi)部網(wǎng)頁top
126 官方包
為什么這里建議放入androdi.keystore,因為如果引入外部的會報一個簽名不一致的錯誤。
6.在cmd中進入這個文件夾后,輸入java -jar rePack.jar 文件名 android.keystore 簽名密碼,就可以自動換渠道打包了,如果中途出現(xiàn)問題,請自己檢查apktool解打包過程和jarsigner是否會報錯,去google上搜出錯原因。
7.為了更簡單,可以寫個批處理
[java]
view plaincopy@echo off
set /p var=請拖入apk:
java -jar rePack.jar %var% android.keystore 55775577
echo.&echo 請按任意鍵退出...&pause>nul
exit
全文完