在项目中用到了百度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
- public class Main {
- public static void main(String[] args) {// 这里用cmd传入参数用
- 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];
- SplitApk sp = new SplitApk(apk, keyFile, keyPasswd);
- sp.mySplit();
- }
- }<span style="color:#000099;">
- </span>
3.创建工具类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.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) {// 构造函数接受参数
- 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文件不存在,请添加渠道文件======");
- }
- }
- /**
- * apktool解压apk,替换渠道值
- *
- * @throws Exception
- */
- private void modifyXudao() {
- // 解压 /C 执行字符串指定的命令然后终断
- 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==移动文件失败======");
- }
- // 创建生成结果的目录
- File f = new File("apk");
- if (!f.exists()) {
- f.mkdir();
- }
- /*
- * 遍历map,复制manifese进来,修改后打包,签名,存储在对应文件夹中
- */
- 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 == 完成 ======");
- }
- /**
- * 执行指令
- *
- * @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包了:
5.最后一步,新建一个文件夹,放入刚编译出的jar、apktool.jar和channel.txt,最好还有android.keystore
- 123 外部推广
- 124 软件盒子
- 125 内部网页top
- 126 官方包
6.在cmd中进入这个文件夹后,输入java -jar rePack.jar 文件名 android.keystore 签名密码,就可以自动换渠道打包了,如果中途出现问题,请自己检查apktool解打包过程和jarsigner是否会报错,去google上搜出错原因。
7.为了更简单,可以写个批处理
- @echo off
- set /p var=请拖入apk:
- java -jar rePack.jar %var% android.keystore 55775577
- echo.&echo 请按任意键退出...&pause>nul
- exit