分享一种最简单的Android打渠道包的方法

来源:互联网 发布:mysql truncate table 编辑:程序博客网 时间:2024/05/01 13:27

因为APK其实就是ZIP的格式,所以,解压apk后,会看到里面有个META-INF目录。

由于META-INF目录并不会影响到APK的签名和运行,所以我们可以在META-INF目录里添加一个空文件,

不同的渠道就添加不同的空文件,文件名代表不同的渠道。

代码是java写的:

[java] view plaincopy
  1. public class Tool {  
  2.   
  3.     private static final String CHANNEL_PREFIX = "/META-INF/";  
  4.     private static final String CHANNEL_PATH_MATCHER = "regex:/META-INF/mtchannel_[0-9a-zA-Z]{1,5}";  
  5.     private static String source_path;  
  6.     private static final String channel_file_name = "channel_list.txt";  
  7.     private static final String channel_flag = "channel_";  
  8.   
  9.     public static void main(String[] args) throws Exception {  
  10.         if (args.length <= 0) {  
  11.             System.out.println("请输入文件路径作为参数");  
  12.             return;  
  13.         }  
  14.   
  15.   
  16.         final String source_apk_path = args[0];//main方法传入的源apk的路径,是执行jar时命令行传入的,不懂的往下看。  
  17.         int last_index = source_apk_path.lastIndexOf("/") + 1;  
  18.         source_path = source_apk_path.substring(0, last_index);  
  19.         final String source_apk_name = source_apk_path.substring(last_index, source_apk_path.length());  
  20.   
  21.         System.out.println("包路径:" + source_path);  
  22.         System.out.println("文件名:" + source_apk_name);  
  23.   
  24.         ArrayList<String> channel_list = getChannelList(source_path + channel_file_name);  
  25.         final String last_name = ".apk";  
  26.         for (int i = 0; i < channel_list.size(); i++) {  
  27.             final String new_apk_path = source_path + source_apk_name.substring(0, source_apk_name.length() - last_name.length()) //  
  28.                     + "_" + channel_list.get(i) + last_name;  
  29.             copyFile(source_apk_path, new_apk_path);  
  30.             changeChannel(new_apk_path, channel_flag + channel_list.get(i));  
  31.         }  
  32.     }  
  33.   
  34.     /** 
  35.      * 修改渠道号,原理是在apk的META-INF下新建一个文件名为渠道号的文件 
  36.      */  
  37.     public static boolean changeChannel(final String zipFilename, final String channel) {  
  38.         try (FileSystem zipfs = createZipFileSystem(zipFilename, false)) {  
  39.   
  40.             final Path root = zipfs.getPath("/META-INF/");  
  41.             ChannelFileVisitor visitor = new ChannelFileVisitor();  
  42.             Files.walkFileTree(root, visitor);  
  43.   
  44.             Path existChannel = visitor.getChannelFile();  
  45.             Path newChannel = zipfs.getPath(CHANNEL_PREFIX + channel);  
  46.             if (existChannel != null) {  
  47.                 Files.move(existChannel, newChannel, StandardCopyOption.ATOMIC_MOVE);  
  48.             } else {  
  49.                 Files.createFile(newChannel);  
  50.             }  
  51.   
  52.             return true;  
  53.   
  54.         } catch (IOException e) {  
  55.             System.out.println("添加渠道号失败:" + channel);  
  56.             e.printStackTrace();  
  57.         }  
  58.   
  59.         return false;  
  60.     }  
  61.   
  62.     private static FileSystem createZipFileSystem(String zipFilename, boolean create) throws IOException {  
  63.         final Path path = Paths.get(zipFilename);  
  64.         final URI uri = URI.create("jar:file:" + path.toUri().getPath());  
  65.   
  66.         final Map<String, String> env = new HashMap<>();  
  67.         if (create) {  
  68.             env.put("create""true");  
  69.         }  
  70.         return FileSystems.newFileSystem(uri, env);  
  71.     }  
  72.   
  73.     private static class ChannelFileVisitor extends SimpleFileVisitor<Path> {  
  74.         private Path channelFile;  
  75.         private PathMatcher matcher = FileSystems.getDefault().getPathMatcher(CHANNEL_PATH_MATCHER);  
  76.   
  77.         public Path getChannelFile() {  
  78.             return channelFile;  
  79.         }  
  80.   
  81.         @Override  
  82.         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {  
  83.             if (matcher.matches(file)) {  
  84.                 channelFile = file;  
  85.                 return FileVisitResult.TERMINATE;  
  86.             } else {  
  87.                 return FileVisitResult.CONTINUE;  
  88.             }  
  89.         }  
  90.     }  
  91.   
  92.     /** 得到渠道列表 */  
  93.     private static ArrayList<String> getChannelList(String filePath) {  
  94.         ArrayList<String> channel_list = new ArrayList<String>();  
  95.   
  96.         try {  
  97.             String encoding = "UTF-8";  
  98.             File file = new File(filePath);  
  99.             if (file.isFile() && file.exists()) { // 判断文件是否存在  
  100.                 InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);// 考虑到编码格式  
  101.                 BufferedReader bufferedReader = new BufferedReader(read);  
  102.                 String lineTxt = null;  
  103.                 while ((lineTxt = bufferedReader.readLine()) != null) {  
  104.                     // System.out.println(lineTxt);  
  105.                     if (lineTxt != null && lineTxt.length() > 0) {  
  106.                         channel_list.add(lineTxt);  
  107.                     }  
  108.                 }  
  109.                 read.close();  
  110.             } else {  
  111.                 System.out.println("找不到指定的文件");  
  112.             }  
  113.         } catch (Exception e) {  
  114.             System.out.println("读取文件内容出错");  
  115.             e.printStackTrace();  
  116.         }  
  117.   
  118.         return channel_list;  
  119.     }  
  120.   
  121.     /** 复制文件 */  
  122.     private static void copyFile(final String source_file_path, final String target_file_path) throws IOException {  
  123.   
  124.         File sourceFile = new File(source_file_path);  
  125.         File targetFile = new File(target_file_path);  
  126.   
  127.         BufferedInputStream inBuff = null;  
  128.         BufferedOutputStream outBuff = null;  
  129.         try {  
  130.             // 新建文件输入流并对它进行缓冲  
  131.             inBuff = new BufferedInputStream(new FileInputStream(sourceFile));  
  132.   
  133.             // 新建文件输出流并对它进行缓冲  
  134.             outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));  
  135.   
  136.             // 缓冲数组  
  137.             byte[] b = new byte[1024 * 5];  
  138.             int len;  
  139.             while ((len = inBuff.read(b)) != -1) {  
  140.                 outBuff.write(b, 0, len);  
  141.             }  
  142.             // 刷新此缓冲的输出流  
  143.             outBuff.flush();  
  144.         } catch (Exception e) {  
  145.             System.out.println("复制文件失败:" + target_file_path);  
  146.             e.printStackTrace();  
  147.         } finally {  
  148.             // 关闭流  
  149.             if (inBuff != null)  
  150.                 inBuff.close();  
  151.             if (outBuff != null)  
  152.                 outBuff.close();  
  153.         }  
  154.     }  
  155. }  


1、新建一个java工程,把上面的代码复制进去。

2、对着这个类点右键,选择Export-java-Runnable JAR file

3、在Launch configuration中,选择你所要导出的类(如果这里不能选择,那么你要run一下你的工程,run成功了才能选择你要导为jar的类),

假设导出的jar的名字是apktool.jar

然后在命令行输入:

[plain] view plaincopy
  1. java -jar /Users/company/Documents/apk/apktool.jar /Users/company/Documents/apk/test.apk  

这里我建议直接cd到目录下再写命令,不然可能会出错,为啥?因为我错了。。。

我用的mac电脑,路径和windows不一样,上面的路径都是拖拽进命令行的。

[plain] view plaincopy
  1. /Users/company/Documents/apk/apktool.jar 表示jar包所在路径;  
[plain] view plaincopy
  1. /Users/company/Documents/apk/test.apk表示你源apk路径,这个是作为命令行参数传入main方法的。  

test.apk就是你已经打包成功的一个apk,就是源apk,在你源apk的基础上生成渠道包。

channel_list.text一定要和这个源apk在同一个目录下。

比如channel_list.text里面的数据结构如下:

[plain] view plaincopy
  1. 360  
  2. xiaomi  
  3. anzhi  
  4. baidu  

运行命令后,你会发现在channel_list.txt和源apk目录下,会生成你想要的渠道包。

你可以把扩展名改为.zip,然后解压看看是否在META-INF目录下生成你想要的渠道名文件。


最后,就是读取这个渠道标识了,代码是写在Android工程里的,代码如下:

[java] view plaincopy
  1. private static String channel = null;      
  2.   
  3. public static String getChannel(Context context) {  
  4.         if (channel != null) {  
  5.             return channel;  
  6.         }  
  7.   
  8.         final String start_flag = "META-INF/channel_";  
  9.         ApplicationInfo appinfo = context.getApplicationInfo();  
  10.         String sourceDir = appinfo.sourceDir;  
  11.         ZipFile zipfile = null;  
  12.         try {  
  13.             zipfile = new ZipFile(sourceDir);  
  14.             Enumeration<?> entries = zipfile.entries();  
  15.             while (entries.hasMoreElements()) {  
  16.                 ZipEntry entry = ((ZipEntry) entries.nextElement());  
  17.                 String entryName = entry.getName();  
  18.                 if (entryName.contains(start_flag)) {  
  19.                     channel = entryName.replace(start_flag, "");  
  20.                     break;  
  21.                 }  
  22.             }  
  23.         } catch (IOException e) {  
  24.             e.printStackTrace();  
  25.         } finally {  
  26.             if (zipfile != null) {  
  27.                 try {  
  28.                     zipfile.close();  
  29.                 } catch (IOException e) {  
  30.                     e.printStackTrace();  
  31.                 }  
  32.             }  
  33.         }  
  34.   
  35.         if (channel == null || channel.length() <= 0) {  
  36.             channel = "guanwang";//读不到渠道号就默认是官方渠道  
  37.         }  
  38.         return channel;  
  39.     }  


如果你用的友盟统计,可以在主Activity里这么写:AnalyticsConfig.setChannel("获取到的渠道");


0 0
原创粉丝点击