Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 223|回复: 0

[默认分类] Android apk多渠道自动打包 - 不提供工具,只提供源码

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2016-1-2 10:26:12 | 显示全部楼层 |阅读模式


         在项目中用到了百度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

    1. public class Main {
    2.     public static void main(String[] args) {// 这里用cmd传入参数用
    3.         System.out.println("====**====By H3c=====**======");
    4.         if (args.length != 3) {// 传入3个参数 apk报名、签名文件、签名密码
    5.             System.out
    6.                     .println("==ERROR==usage:java -jar rePack.jar apkName keyFile keyPasswd======");
    7.             System.out
    8.                     .println("==INFO==Example: java -jar rePack.jar test.apk android.keystore 123456======");
    9.             return;
    10.         }
    11.         String apk = args[0];
    12.         String keyFile = args[1];
    13.         String keyPasswd = args[2];
    14.         SplitApk sp = new SplitApk(apk, keyFile, keyPasswd);
    15.         sp.mySplit();
    16.     }
    17. }
    复制代码

          3.创建工具类SplitApk.java
      

    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. public class SplitApk {
    10.     HashMap<String, String> qudao = new HashMap<String, String>();// 渠道号,渠道名
    11.     String curPath;// 当前文件夹路径
    12.     String apkName;
    13.     String keyFile;
    14.     String keyPasswd;
    15.     public SplitApk(String apkName, String keyFile, String keyPasswd) {// 构造函数接受参数
    16.         this.curPath = new File("").getAbsolutePath();
    17.         this.apkName = apkName;
    18.         this.keyFile = keyFile;
    19.         this.keyPasswd = keyPasswd;
    20.     }
    21.     public void mySplit() {
    22.         getCannelFile();// 获得自定义的渠道号
    23.         modifyXudao();// 解包 - 打包 - 签名
    24.     }
    25.     /**
    26.      * 获得渠道号
    27.      */
    28.     private void getCannelFile() {
    29.         File f = new File("channel.txt");// 读取当前文件夹下的channel.txt
    30.         if (f.exists() && f.isFile()) {
    31.             BufferedReader br = null;
    32.             FileReader fr = null;
    33.             try {
    34.                 fr = new FileReader(f);
    35.                 br = new BufferedReader(fr);
    36.                 String line = null;
    37.                 while ((line = br.readLine()) != null) {
    38.                     String[] array = line.split("\t");// 这里是Tab分割
    39.                     if (array.length == 2) {
    40.                         qudao.put(array[0].trim(), array[1].trim());// 讲渠道号和渠道名存入HashMap中
    41.                     }
    42.                 }
    43.             } catch (Exception e) {
    44.                 e.printStackTrace();
    45.             } finally {
    46.                 try {
    47.                     if (fr != null) {
    48.                         fr.close();
    49.                     }
    50.                     if (br != null) {
    51.                         br.close();
    52.                     }
    53.                 } catch (IOException e) {
    54.                     e.printStackTrace();
    55.                 }
    56.             }
    57.             System.out.println("==INFO 1.==获取渠道成功,一共有" + qudao.size()
    58.                     + "个渠道======");
    59.         } else {
    60.             System.out.println("==ERROR==channel.txt文件不存在,请添加渠道文件======");
    61.         }
    62.     }
    63.     /**
    64.      * apktool解压apk,替换渠道值
    65.      *
    66.      * @throws Exception
    67.      */
    68.     private void modifyXudao() {
    69.         // 解压 /C 执行字符串指定的命令然后终断
    70.         String cmdUnpack = "cmd.exe /C java -jar apktool.jar d -f -s "
    71.                 + apkName;
    72.         runCmd(cmdUnpack);
    73.         System.out.println("==INFO 2.==解压apk成功,准备移动======");
    74.         // 备份AndroidManifest.xml
    75.         // 获取解压的apk文件名
    76.         String[] apkFilePath = apkName.split("\\\");
    77.         String shortApkName = apkFilePath[apkFilePath.length - 1];
    78.         String dir = shortApkName.split(".apk")[0];
    79.         File packDir = new File(dir);// 获得解压的apk目录
    80.         String f_mani = packDir.getAbsolutePath() + "\\AndroidManifest.xml";
    81.         String f_mani_bak = curPath + "\\AndroidManifest.xml";
    82.         File manifest = new File(f_mani);
    83.         File manifest_bak = new File(f_mani_bak);
    84.         // 拷贝文件 -- 此方法慎用,详见http://xiaoych.iteye.com/blog/149328
    85.         manifest.renameTo(manifest_bak);
    86.         for (int i = 0; i < 10; i++) {
    87.             if (manifest_bak.exists()) {
    88.                 break;
    89.             }
    90.             try {
    91.                 Thread.sleep(1000);
    92.             } catch (InterruptedException e) {
    93.                 e.printStackTrace();
    94.             }
    95.         }
    96.         if (manifest_bak.exists()) {
    97.             System.out.println("==INFO 3.==移动文件成功======");
    98.         } else {
    99.             System.out.println("==ERROR==移动文件失败======");
    100.         }
    101.         // 创建生成结果的目录
    102.         File f = new File("apk");
    103.         if (!f.exists()) {
    104.             f.mkdir();
    105.         }
    106.         /*
    107.          * 遍历map,复制manifese进来,修改后打包,签名,存储在对应文件夹中
    108.          */
    109.         for (Map.Entry<String, String> entry : qudao.entrySet()) {
    110.             String id = entry.getKey();
    111.             System.out.println("==INFO 4.1. == 正在生成包: " + entry.getValue()
    112.                     + " ======");
    113.             BufferedReader br = null;
    114.             FileReader fr = null;
    115.             FileWriter fw = null;
    116.             try {
    117.                 fr = new FileReader(manifest_bak);
    118.                 br = new BufferedReader(fr);
    119.                 String line = null;
    120.                 StringBuffer sb = new StringBuffer();
    121.                 while ((line = br.readLine()) != null) {
    122.                     if (line.contains(""ads-2.0"")) {
    123.                         line = line.replaceAll("ads-2.0", id);
    124.                     }
    125.                     sb.append(line + "\n");
    126.                 }
    127.                 // 写回文件
    128.                 fw = new FileWriter(f_mani);
    129.                 fw.write(sb.toString());
    130.             } catch (Exception e) {
    131.                 e.printStackTrace();
    132.             } finally {
    133.                 try {
    134.                     if (fr != null) {
    135.                         fr.close();
    136.                     }
    137.                     if (br != null) {
    138.                         br.close();
    139.                     }
    140.                     if (fw != null) {
    141.                         fw.close();
    142.                     }
    143.                 } catch (IOException e) {
    144.                     e.printStackTrace();
    145.                 }
    146.             }
    147.             System.out.println("==INFO 4.2. == 准备打包: " + entry.getValue()
    148.                     + " ======");
    149.             // 打包 - 生成未签名的包
    150.             String unsignApk = id + "_" + dir + "_un.apk";
    151.             String cmdPack = String.format(
    152.                     "cmd.exe /C java -jar apktool.jar b %s %s", dir, unsignApk);
    153.             runCmd(cmdPack);
    154.             System.out.println("==INFO 4.3. == 开始签名: " + entry.getValue()
    155.                     + " ======");
    156.             // 签名
    157.             String signApk = "./apk/" + id + "_" + dir + ".apk";
    158.             String cmdKey = String
    159.                     .format("cmd.exe /C jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore %s -signedjar %s %s %s -storepass  %s",
    160.                             keyFile, signApk, unsignApk, keyFile, keyPasswd);
    161.             runCmd(cmdKey);
    162.             System.out.println("==INFO 4.4. == 签名成功: " + entry.getValue()
    163.                     + " ======");
    164.             // 删除未签名的包
    165.             File unApk = new File(unsignApk);
    166.             unApk.delete();
    167.         }
    168.         // 删除中途文件
    169.         String cmdKey = String.format("cmd.exe /C rd /s/q %s", dir);
    170.         runCmd(cmdKey);
    171.         manifest_bak.delete();
    172.         System.out.println("==INFO 5 == 完成 ======");
    173.     }
    174.     /**
    175.      * 执行指令
    176.      *
    177.      * @param cmd
    178.      */
    179.     public void runCmd(String cmd) {
    180.         Runtime rt = Runtime.getRuntime();
    181.         BufferedReader br = null;
    182.         InputStreamReader isr = null;
    183.         try {
    184.             Process p = rt.exec(cmd);
    185.             // p.waitFor();
    186.             isr = new InputStreamReader(p.getInputStream());
    187.             br = new BufferedReader(isr);
    188.             String msg = null;
    189.             while ((msg = br.readLine()) != null) {
    190.                 System.out.println(msg);
    191.             }
    192.         } catch (Exception e) {
    193.             e.printStackTrace();
    194.         } finally {
    195.             try {
    196.                 if (isr != null) {
    197.                     isr.close();
    198.                 }
    199.                 if (br != null) {
    200.                     br.close();
    201.                 }
    202.             } catch (IOException e) {
    203.                 e.printStackTrace();
    204.             }
    205.         }
    206.     }
    207. }
    复制代码

          4.代码写好后就该生成jar包了:
      

          右击工程选择菜单中的Export - Java - Runnable JAR file,选择导出路径后就可以输出jar了。
      
         5.最后一步,新建一个文件夹,放入刚编译出的jar、apktool.jar和channel.txt,最好还有android.keystore

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

    1. 123 外部推广
    2. 124 软件盒子
    3. 125  内部网页top
    4. 126  官方包
    复制代码
       为什么这里建议放入androdi.keystore,因为如果引入外部的会报一个签名不一致的错误。
      
         6.在cmd中进入这个文件夹后,输入java -jar rePack.jar 文件名 android.keystore 签名密码,就可以自动换渠道打包了,如果中途出现问题,请自己检查apktool解打包过程和jarsigner是否会报错,去Google上搜出错原因。
         7.为了更简单,可以写个批处理

    1. @echo off
    2. set /p var=请拖入apk:
    3. java -jar rePack.jar %var% android.keystore 55775577
    4. echo.&echo 请按任意键退出...&pause>nul
    5. exit
    复制代码
      

         全文完


       
       
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-23 19:00 , Processed in 0.368738 second(s), 46 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表