Android-用apktool實現多渠道自動打包


因為項目當中需要對apk的AndroidManifest.xml文件當中的meta-data中的數據進行更新跟替換,如果用其他方式打包的話非常麻煩,然后在網上找了一個教程實現一段代碼就可以自動打包,簡單而且粗暴。這個是原文:http://blog.csdn.net/h3c4lenovo/article/details/10007039。我做了一些修改跟補充。

需要用到的環境:jdk,sdk,還有apktool。因為我的電腦上已經有配置jdk跟sdk了。如果沒有配置的話自行搜索。然后apktool這個工具的話可以可以點擊這里下載

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

    1.先得到apk文件(可以打簽名包和未簽名包,只要有apk就行)

    2.用apktool 解包  (java -jar apktool.jar d  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工具解壓后里面有三個文件:


反編譯:把你需要反編譯的apk文件跟這三個文件放在一起,然后運行cmd,進入當前這個文件夾,在控制台輸入:java -jar apktool.jar d XXX.apk(xxx.apk你放入的apk文件名)然后會在你的當前文件夾下生成一個以apk的文件名。




打包:在控制台中輸入java -jar apktool.jar b MyAndroidTest -o XXX.apk(打包后要命名的名稱) -o表示新生成的apk文件放在當前文件夾。



簽名:把簽名的.keystore文件放到當前的文件夾當中,然后在控制台中輸入jarsigner -digestalg SHA1 -sigalg MD5withRSA -tsa https://timestamp.geotrust.com/tsa -verbose -keystore XXX.keystore(你放入的.keystore文件) -signedjar XXXsign.apk(簽名成功后apk文件名) XXX.apk(簽名前的apk名稱) XXX.keystore(你放入的.keystore文件) -storepass XXX(你放入的.keystore對應的密碼)



這三個步驟如果都能順利完成的話那就沒有什么問題了。就可以寫代碼來完成這些反編譯,打包還有簽名的這些操作了。
打包的代碼如下:
public class AutoPack {

public static void main(String[] args) {
System.out.println("====**====By H3c=====**======");

if (args.length != 3) {// 傳入3個參數 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];

System.out.println("apk名稱:"+apk);
SplitApk sp = new SplitApk(apk, keyFile, keyPasswd);
sp.mySplit();
}
}

SplitApk.java文件如下:
import 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.ArrayList;

public class SplitApk {
ArrayList<String> channel = new ArrayList<String>();//渠道號
String curPath;// 當前文件夾路徑
String apkName; //包名
String keyFile; //簽名文件
String keyPasswd; //簽名文件密碼

public SplitApk(String apkName, String keyFile, String keyPasswd) {// 構造函數接受參數
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分割
channel.add(array[0]);
}
} 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.==獲取渠道成功,一共有" + channel.size()
+ "個渠道======");
} else {
System.out.println("==ERROR==channel.txt文件不存在,請添加渠道文件======");
}
}

/**
* apktool解壓apk,替換渠道值
*
* @throws Exception
*/
private void modifyXudao() {
// 解壓 /C 執行字符串指定的命令然后終斷
String cmdUnpack = "cmd.exe /C java -jar apktool.jar d -f -s "
+ apkName;
runCmd(cmdUnpack); //執行指令 cmd指令
System.out.println("==INFO 2.==解壓apk成功,准備移動======");

// 備份AndroidManifest.xml
// 獲取解壓的apk文件名
String[] apkFilePath = apkName.split("\\\\");
String shortApkName = apkFilePath[apkFilePath.length - 1];
System.out.println("shortApkName = "+shortApkName);
String dir = shortApkName.split(".apk")[0];
System.err.println("dir = "+dir);
File packDir = new File(dir);//獲得解壓的apk目錄

String f_mani = packDir.getAbsolutePath() + "\\AndroidManifest.xml";
String f_mani_bak = curPath + "\\AndroidManifest.xml";
//在當前文件夾下新建一個AndroidManifest.xml文件並把解壓的apk文件里面的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==移動文件失敗======");
}

// 創建生成結果的目錄
File f = new File("apk");
if (!f.exists()) {
f.mkdir();
}

/*
* 遍歷map,復制manifese進來,修改后打包,簽名,存儲在對應文件夾中
*/
for (int i = 0; i < channel.size(); i++) {
String id = channel.get(i);
System.out.println("==INFO 4.1. == 正在生成包: " + id
+ " ======");
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) { //修改AndroidManifest.xml的meta-data字段
if (line.contains("deacon_id")) {
line = line.replaceAll("deacon_id", id);
System.out.println("替換為渠道號"+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. == 准備打包: " + id
+ " ======");

// 打包 - 生成未簽名的包
String unsignApk = id + "_" + dir + "_un.apk";
String cmdPack = String.format(
"cmd.exe /C java -jar apktool.jar b %s -o %s", dir, unsignApk);
runCmd(cmdPack);

System.out.println("==INFO 4.3. == 開始簽名: " + id
+ " ======");
// 簽名
String signApk = "./apk/" + id + "_" + dir + ".apk";
String cmdKey = String
.format("cmd.exe /C jarsigner -digestalg SHA1 -sigalg MD5withRSA -tsa https://timestamp.geotrust.com/tsa -verbose -keystore %s -signedjar %s %s %s -storepass %s",
keyFile, signApk, unsignApk, keyFile, keyPasswd);
runCmd(cmdKey);
System.out.println("==INFO 4.4. == 簽名成功: " + id
+ " ======");
// 刪除未簽名的包
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 == 完成 ======");
}

/**
* 執行指令 cmd指令
*
* @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();
System.out.println("執行cmd命令出錯");
} finally {
try {
if (isr != null) {
isr.close();
}
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

1、然后生成jar包,右擊工程選擇菜單中的Export - Java - Runnable JAR file,選擇導出路徑后就可以輸出jar了。
2、把輸出的jar包也放到當前文件夾下,並解壓打開jar中META-INF文件夾下的MANIFEST.MF文件,在這個MANIFEST.MF中增加入口函數,也就是你的main函數的類名。這個類名要寫全路徑。(藍色的那一行是自己添加進去的) 3、在當前文件夾新建一個名為channel.txt的文件,在里面填上你需要打包的渠道號,然后用換行符隔開。(這里面有1-5,5個渠道號) 完整的文件夾內容如下:
4、然后打開cmd進入到當前的文件夾當中,在控制台中輸入:java -jar XXX.jar(你自己導出的jar包) XXX.apk(你要打包的apk) XXX.keystore(你的簽名文件) XXX(簽名文件的密碼)結果如下:
打包成功的話會在當前文件夾中生成一個apk文件夾,打包成功的簽名文件都會放在這個文件夾里面:

(看過上面的代碼就知道,你可以修改獲取渠道號的文件名,可以修改mate-data中的替換的字段,反正可以改成滿足自己需求的樣子)


然后為了可以更簡單省事,可以寫一個批處理文件,這樣就點擊一下批處理文件就可以自動實現打包。而且一行代碼都不用寫。。

批處理文件:1、在當前文件夾下新建一個txt文件,然在文件中添加如下代碼:

@echo off  
set /p var=請拖入apk:
java -jar AutoPack.jar %var% game.keystore 123456

echo.&echo 請按任意鍵退出...&pause>nul
exit
2、把txt文件的后綴名改成bat,然后在打包的時候只要替換文件當中的apk文件,再點擊這個.bat文件。這個時候會出現一個控制台,你只要把你要打包的apk文件拖到控制台上然后確定,就可以幫你完成所有的操作了。結果如下:





注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com