編輯:關於Android編程
想自己做個apk,還在為素材而苦惱嗎?
看到優秀的apk設計,還在為怎麼看到別人的實現代碼而苦惱嗎?
看著AndroidStudio
多渠道打包那麼爽,而自己坑爹的還在用Eclipse,始終搞不定多渠道打包而苦惱嗎?
現在這些統統都不是問題,以前全要10塊8塊的,現在全部都要2塊。。
看一下本篇博客的大綱:
使用apktool
反編譯apk 使用apktool
重新編譯打包apk 使用jdk中的jarsigner
對新打包的apk進行簽名。 編寫程序實現Eclipse 的 Android 多渠道打包。 獲取渠道值
在開始之前,上傳一個目錄圖,便於下面的進行
apktool 是 Android apk 的一個編譯和反編譯工具,他是一個jar 包。使用它肯定要先去下載該jar包。可以去官網下載,這裡提供一個csdn 的下載地址 apktool v2.1.1
在使用之前需要配置jdk 環境 ,這個應該搞android 的都配置了。。
下面開始反編譯:
關鍵命令
java -jar apktool.jar d -f -s apkName
跳轉到我們編譯的目錄,最好將需要編譯的文件和apktool
放在一起。
使用命令
java -jar apktool.jar d -f -s test.apk
vcv509DXytS0zsS8/ra8xNy08r+qo6yy6b+0oaM8L3A+DQo8aDMgaWQ9"apktool-編譯apk">apktool 編譯apk
反編譯之後,我們就用反編譯後的文件在進行重新編譯。有人會說了,你咋這麼蛋疼,一會你就明白了。
命令:
java -jar apktool.jar b 需要編譯的文件 編譯後的名字
開始操作
仍然在當前目錄下,執行如下命令
java -jar apktool.jar b test name.apk
這時候會在需要編譯文件的目錄裡多出一個dist目錄,裡面包含的就是我們編譯好的文件。
我的目錄如下
E:\apk_build\test\dist
不知道為什麼,定義的編譯後的名字沒有作用,和我們原apk同名。
這裡就需要使用 JDK 中的簽名工具jarsigner
.具體目錄位置為
C:\Program Files\Java\jdk1.8.0_91\bin
當然如果配置了JDK環境變量,就不需要寫全路徑了。
命令:
jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore 簽名文件 -storepass 倉庫密碼 -keypass 口令密碼 -signedjar 簽名後的文件 待簽名的文件 口令名
開始操作:
最好將簽名文件也放到當前目錄:
jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore alex.keystore -storepass 123456 -keypass 111111 -signedjar test_signer.apk E:\apk_build\test\dist\test.apk test
這一段很長,就不貼全了,貼個最後簽名好的結果
前面鋪墊了這麼多,終於開始搞大頭了。
先說一下步驟:
定義txt文件,保存不同的渠道信息。 程序讀取渠道信息。 對apk 反編譯 。 替換清單文件中關於渠道包的關鍵字段。 重新編譯打包。 簽名。是不是一氣呵成。下面開始搞,將以友盟多渠道打包舉例(流程過程中只貼部分代碼,最後的工具類會貼到博客最後):
先看一下初始的文件目錄,稍後會將該工程共享到github。
定義txt 文件,保存不同的渠道信息,名字不要改,代碼中使用的就是這個名字,改了會出問題。
baidu
yingyongbao
wandoujia
注意: 每一個渠道單獨一行。
程序讀取渠道信息
/**
* 獲取渠道字段
*/
private void getCannelFile() {
File file = new File("channel.txt");
// 如果文件不存在,則提示
if (file.exists() && file.isFile()) {
BufferedReader br = null;
FileReader fr = null;
try {
// 獲取到渠道的輸入流
br = new BufferedReader(new FileReader(file));
String line = null;
while ((line = br.readLine()) != null) {
// 獲取到渠道
channelList.add(line.trim());
}
System.out.println(channelList);
} 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("*********獲取渠道成功 ***********");
} else {
System.err
.println("*********error: channel.txt文件不存在,請添加渠道文件***********");
}
}
獲取到的渠道保存到一個集合中存儲,後面用。
對apk 反編譯使用apk 反編譯,需要用到控制台命令,所以在此編譯了一個類,用以調用控制台。
/**
* 執行控制台指令
*
* @param cmd
*/
public void runCmd(String cmd) {
Runtime rt = Runtime.getRuntime();
BufferedReader br = null;
InputStreamReader isr = null;
try {
// 執行
Process p = rt.exec(cmd);
// 獲取對應流,一遍打印控制台輸出的信息
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();
}
}
}
指令很簡單,就和我們之前編譯的apk 幾乎相似
// 1, 將該App 反編譯
String cmdUnpack = "cmd.exe /C java -jar apktool.jar d -f -s "
+ apkName;
runCmd(cmdUnpack);
apkName
是待編譯的apk。
// 2, 移動清單文件,作為備份
// 獲取編譯後後的目錄名 和目錄文件
String decodeDir = apkName.split(".apk")[0];
File decodeDirFile = new File(decodeDir);
// 獲取清單文件
String maniPath = decodeDirFile.getAbsolutePath()
+ "\\AndroidManifest.xml";
// 獲取備份清單文件目錄 工程根目錄
String maniPathSave = basePath + "\\AndroidManifest_back.xml";
// 備份清單文件
new File(maniPath).renameTo(new File(maniPathSave));
System.out.println("*********備份清單文件 ***********");
備份完清單文件之後,就開始對每一個渠道執行 替換清單文件 -> 重新編譯打包 -> 重新簽名。
替換清單文件,在此單獨寫了一個方法
/**
* 修改渠道值
*
* @param sourcePath
* 備份清單文件地址
* @param targetPath
* 目標清單文件地址
* @param channelStr
* 要求該的渠道值
*/
public void updateChannel(String sourcePath, String targetPath,
String channelStr) {
BufferedReader br = null;
FileReader fr = null;
FileWriter fw = null;
try {
// 從備份中讀取內容
fr = new FileReader(sourcePath);
br = new BufferedReader(fr);
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = br.readLine()) != null) {
// 如果某一行存在qwertyy 則替換該值
if (line.contains("qwertyy")) {
line = line.replaceAll("qwertyy", channelStr);
}
sb.append(line + "\n");
}
// 寫到目標清單文件
fw = new FileWriter(targetPath);
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();
}
}
}
說一下思路: 先從備份的清單文件中將數據按行讀取出來,如果某一行定義了我們的標示,則替換標示為我們的指定渠道值,這樣我們就獲取到了一個即將打包的清單文件字符串。將其寫入到需要打包的目錄中。
在此定義的標示為qwertyy
,應該不會有重復值,如果有,可以自己改,但此值要與清單文件中的對應,例如我測試包中的清單文件的標示為:
// 重新打包
String cmdPack = String.format(
"cmd.exe /C java -jar apktool.jar b %s %s", decodeDir,
apkName);
runCmd(cmdPack);
重新簽名
// 簽名文件地址
String keyStorePath = basePath + "\\" + keystoreName;
// 未簽名的apk 地址
String unsign_apk_path = decodeDir + "\\dist\\" + apkName;
// 簽名後的apk
String sign_apk_path = basePath + "\\" + channelList.get(i)
+ ".apk";
String signCmd = jarsignerPath
+ " -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore "
+ keyStorePath + " -storepass " + storepass + " -keypass "
+ keypass + " -signedjar " + sign_apk_path + " "
+ unsign_apk_path + " " + keyName;
runCmd(signCmd);
signCmd
有一點特殊,應為我無法再代碼中獲取到jarsigner
命令,所以走了一個迂回的方式。
jarsignerPath
的值為:C:\\Program Files\\Java\\jdk1.8.0_91\\bin\\jarsigner.exe
,其實就是我本地jdk中該程序的地址。
最後看一下整體的類:
public class SplitApk {
HashMap qudao = new HashMap();// 渠道號,渠道名
ArrayList channelList = new ArrayList<>();
String basePath;// 當前文件夾路徑
// apk 名
private String apkName;
// 秘鑰文件名
private String keystoreName ;
//倉庫密碼
private String storepass ;
// 口令密碼
private String keypass ;
// 口令名
private String keyName ;
// 簽名工具地址(全路徑)
private String jarsignerPath ;
public SplitApk(String apkName, String keystoreName, String storepass,
String keyName, String keypass, String jarsignerPath) {
this.apkName = apkName;
this.keystoreName = keystoreName;
this.storepass = storepass;
this.keypass = keypass;
this.keyName = keyName;
this.jarsignerPath = jarsignerPath;
this.basePath = new File("").getAbsolutePath();
}
public void mySplit() {
getCannelFile();// 獲得自定義的渠道號
modifyChannel(); // 開始打包
}
/**
* 修改渠道字段
*/
public void modifyChannel() {
// 1, 將該App 反編譯
String cmdUnpack = "cmd.exe /C java -jar apktool.jar d -f -s "
+ apkName;
runCmd(cmdUnpack);
System.out.println("*********反編譯Apk 成功 ***********");
// 2, 移動清單文件,作為備份
// 獲取編譯後後的目錄名 和目錄文件
String decodeDir = apkName.split(".apk")[0];
//
File decodeDirFile = new File(decodeDir);
// 獲取清單文件
String maniPath = decodeDirFile.getAbsolutePath()
+ "\\AndroidManifest.xml";
// 獲取備份清單文件目錄 工程根目錄
String maniPathSave = basePath + "\\AndroidManifest_back.xml";
// 備份清單文件
new File(maniPath).renameTo(new File(maniPathSave));
System.out.println("*********備份清單文件 ***********");
for (int i = 0; i < channelList.size(); i++) {
System.out.println("*********開始搞----"+channelList.get(i)+" ***********");
// 獲取備份文件的內容,修改渠道值,並保存到maniPath 中
updateChannel(maniPathSave, maniPath, channelList.get(i));
System.out.println("*********修改清單文件,替換清單文件成功 ***********");
// 重新打包
String cmdPack = String.format(
"cmd.exe /C java -jar apktool.jar b %s %s", decodeDir,
apkName);
runCmd(cmdPack);
System.out.println("*********4,打包成功,開始重新簽名 ***********");
// 簽名文件地址
String keyStorePath = basePath + "\\" + keystoreName;
// 未簽名的apk 地址
String unsign_apk_path = decodeDir + "\\dist\\" + apkName;
// 簽名後的apk
String sign_apk_path = basePath + "\\" + channelList.get(i)
+ ".apk";
String signCmd = jarsignerPath
+ " -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore "
+ keyStorePath + " -storepass " + storepass + " -keypass "
+ keypass + " -signedjar " + sign_apk_path + " "
+ unsign_apk_path + " " + keyName;
runCmd(signCmd);
System.out.println("*********5," + channelList.get(i)
+ "簽名成功***********");
}
}
/**
* 修改渠道值
*
* @param sourcePath
* 備份清單文件地址
* @param targetPath
* 目標清單文件地址
* @param channelStr
* 要求該的渠道值
*/
public void updateChannel(String sourcePath, String targetPath,
String channelStr) {
BufferedReader br = null;
FileReader fr = null;
FileWriter fw = null;
try {
// 從備份中讀取內容
fr = new FileReader(sourcePath);
br = new BufferedReader(fr);
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = br.readLine()) != null) {
// 如果某一行存在qwertyy 則替換該值
if (line.contains("qwertyy")) {
line = line.replaceAll("qwertyy", channelStr);
}
sb.append(line + "\n");
}
// 寫到目標清單文件
fw = new FileWriter(targetPath);
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();
}
}
}
/**
* 獲取渠道字段
*/
private void getCannelFile() {
File file = new File("channel.txt");
// 如果文件不存在,則提示
if (file.exists() && file.isFile()) {
BufferedReader br = null;
FileReader fr = null;
try {
// 獲取到渠道的輸入流
br = new BufferedReader(new FileReader(file));
String line = null;
while ((line = br.readLine()) != null) {
// 獲取到渠道
channelList.add(line.trim());
}
System.out.println(channelList);
} 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("*********獲取渠道成功 ***********");
} else {
System.err
.println("*********error: channel.txt文件不存在,請添加渠道文件***********");
}
}
/**
* 執行控制台指令
*
* @param cmd
*/
public void runCmd(String cmd) {
Runtime rt = Runtime.getRuntime();
BufferedReader br = null;
InputStreamReader isr = null;
try {
// 執行
Process p = rt.exec(cmd);
// 獲取對應流,一遍打印控制台輸出的信息
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();
}
}
}
}
工具類編寫好了,就看怎麼使用了
public static void main(String[] args) {// 這裡用cmd傳入參數用
// apk的名字
String apkName = "test.apk";
//簽名文件
String keystoreName = "alex.keystore";
// 初始密碼
String storepass = "123456";
// 名字
String keyName = "test";
// 密碼
String keypass = "111111";
// 簽名工具的地址 去jdk 中找 --- 需要修改
String jarsignerPath = "C:\\Program Files\\Java\\jdk1.8.0_91\\bin\\jarsigner.exe";
// 開始打包
new SplitApk(apkName,keystoreName,storepass,keyName,keypass,jarsignerPath).mySplit();
}
這樣就可以了,運行該方法之後,刷新工程,就可以看到在工程目錄下我們打好的包
可能會存在,如果我們不用友盟的話,肯定我們自己要獲取到標簽值,以便做操作。在這裡仍用友盟舉例:
在這裡貼出清單文件的application
的內容
Application
中獲取
ApplicationInfo appInfo;
try {
appInfo = this.getPackageManager().getApplicationInfo(
getPackageName(), PackageManager.GET_META_DATA);
msg = appInfo.metaData.getString("UMENG_CHANNEL");
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
OVER !!!!.
該工程源碼已經上傳到github,有需要者請移步。
簡介:BroadcastReceiver本質上就是一種全局的監聽器,用於監聽系統全局的廣播消息,因此它可以非常方便地實現系統中不同組件之間的通信。 特點:1.B
針對剛安裝好的Android studio, 比如import工程時,你會發現你的很多中文目錄顯示異常,如下圖: 為什麼會出現這個問題呢,其實原因很簡單,因為An
近日,遇到一個Dialog半透明背景消失的問題,背景需求是自定義Dialog實現警告提示框:// 初始化警告彈出框 alertDialog = new EmpAlertV
微信出了電腦版,在收到視頻後,視頻是保存在哪呢?微信電腦版視頻存在哪裡? 微信電腦版視頻文件夾位置就讓下載吧小編來告訴你吧 1、首先我們打開手機上的微信,