做Android开发一转眼就四年了,以前是用ant打包的,习惯了也没觉得慢。

今年年初加入了新公司,新公司用的是Android studio开发,用的是gradle构建项目。

由于gradle构建每次都是重新编译项目,所以打包时就特别慢了,16个渠道包要打一个小时吧。

然后我们的项目负责人就交给我一个任务,研究下有什么快的打包方法,

并发给我一篇参考文章:http://tech.meituan.com/mt-apk-packaging.html

我一边写代码一边测试,终于找到了一种很快的打渠道包的方法。

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

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

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

代码是java写的:

public class Tool {private static final String CHANNEL_PREFIX = "/META-INF/";private static final String CHANNEL_PATH_MATCHER = "regex:/META-INF/mtchannel_[0-9a-zA-Z]{1,5}";private static String source_path;private static final String channel_file_name = "channel_list.txt";private static final String channel_flag = "channel_";public static void main(String[] args) throws Exception {if (args.length <= 0) {System.out.println("请输入文件路径作为参数");return;}final String source_apk_path = args[0];//main方法传入的源apk的路径,是执行jar时命令行传入的,不懂的往下看。int last_index = source_apk_path.lastIndexOf("/") + 1;source_path = source_apk_path.substring(0, last_index);final String source_apk_name = source_apk_path.substring(last_index, source_apk_path.length());System.out.println("包路径:" + source_path);System.out.println("文件名:" + source_apk_name);ArrayList<String> channel_list = getChannelList(source_path + channel_file_name);final String last_name = ".apk";for (int i = 0; i < channel_list.size(); i++) {final String new_apk_path = source_path + source_apk_name.substring(0, source_apk_name.length() - last_name.length()) //+ "_" + channel_list.get(i) + last_name;copyFile(source_apk_path, new_apk_path);changeChannel(new_apk_path, channel_flag + channel_list.get(i));}}/** * 修改渠道号,原理是在apk的META-INF下新建一个文件名为渠道号的文件 */public static boolean changeChannel(final String zipFilename, final String channel) {try (FileSystem zipfs = createZipFileSystem(zipFilename, false)) {final Path root = zipfs.getPath("/META-INF/");ChannelFileVisitor visitor = new ChannelFileVisitor();Files.walkFileTree(root, visitor);Path existChannel = visitor.getChannelFile();Path newChannel = zipfs.getPath(CHANNEL_PREFIX + channel);if (existChannel != null) {Files.move(existChannel, newChannel, StandardCopyOption.ATOMIC_MOVE);} else {Files.createFile(newChannel);}return true;} catch (IOException e) {System.out.println("添加渠道号失败:" + channel);e.printStackTrace();}return false;}private static FileSystem createZipFileSystem(String zipFilename, boolean create) throws IOException {final Path path = Paths.get(zipFilename);final URI uri = URI.create("jar:file:" + path.toUri().getPath());final Map<String, String> env = new HashMap<>();if (create) {env.put("create", "true");}return FileSystems.newFileSystem(uri, env);}private static class ChannelFileVisitor extends SimpleFileVisitor<Path> {private Path channelFile;private PathMatcher matcher = FileSystems.getDefault().getPathMatcher(CHANNEL_PATH_MATCHER);public Path getChannelFile() {return channelFile;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {if (matcher.matches(file)) {channelFile = file;return FileVisitResult.TERMINATE;} else {return FileVisitResult.CONTINUE;}}}/** 得到渠道列表 */private static ArrayList<String> getChannelList(String filePath) {ArrayList<String> channel_list = new ArrayList<String>();try {String encoding = "UTF-8";File file = new File(filePath);if (file.isFile() && file.exists()) { // 判断文件是否存在InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);// 考虑到编码格式BufferedReader bufferedReader = new BufferedReader(read);String lineTxt = null;while ((lineTxt = bufferedReader.readLine()) != null) {// System.out.println(lineTxt);if (lineTxt != null && lineTxt.length() > 0) {channel_list.add(lineTxt);}}read.close();} else {System.out.println("找不到指定的文件");}} catch (Exception e) {System.out.println("读取文件内容出错");e.printStackTrace();}return channel_list;}/** 复制文件 */private static void copyFile(final String source_file_path, final String target_file_path) throws IOException {File sourceFile = new File(source_file_path);File targetFile = new File(target_file_path);BufferedInputStream inBuff = null;BufferedOutputStream outBuff = null;try {// 新建文件输入流并对它进行缓冲inBuff = new BufferedInputStream(new FileInputStream(sourceFile));// 新建文件输出流并对它进行缓冲outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));// 缓冲数组byte[] b = new byte[1024 * 5];int len;while ((len = inBuff.read(b)) != -1) {outBuff.write(b, 0, len);}// 刷新此缓冲的输出流outBuff.flush();} catch (Exception e) {System.out.println("复制文件失败:" + target_file_path);e.printStackTrace();} finally {// 关闭流if (inBuff != null)inBuff.close();if (outBuff != null)outBuff.close();}}}


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

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

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

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

然后在命令行输入:

java -jar /Users/company/Documents/apk/apktool.jar /Users/company/Documents/apk/test.apk

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

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

  

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

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

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

360xiaomianzhibaidu

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

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


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

private static String channel = null;    public static String getChannel(Context context) {        if (channel != null) {            return channel;        }        final String start_flag = "META-INF/channel_";        ApplicationInfo appinfo = context.getApplicationInfo();        String sourceDir = appinfo.sourceDir;        ZipFile zipfile = null;        try {            zipfile = new ZipFile(sourceDir);            Enumeration<?> entries = zipfile.entries();            while (entries.hasMoreElements()) {                ZipEntry entry = ((ZipEntry) entries.nextElement());                String entryName = entry.getName();                if (entryName.contains(start_flag)) {                    channel = entryName.replace(start_flag, "");                    break;                }            }        } catch (IOException e) {            e.printStackTrace();        } finally {            if (zipfile != null) {                try {                    zipfile.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }        if (channel == null || channel.length() <= 0) {            channel = "guanwang";//读不到渠道号就默认是官方渠道        }        return channel;    }


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

好了,结束了,有问题留言。


更多相关文章

  1. 【Android自学笔记】对应资源文件夹中的图标尺寸
  2. Android 查看/data/data文件夹并取回文件
  3. Android中解析与创建XML文件
  4. Android中读取properties文件2
  5. Android下载 文件(APP) 并且静默安装
  6. Android文件读写权限
  7. android 文件选择
  8. Android 实现文件的下载

随机推荐

  1. Android(安卓)studio 下载安装
  2. Activity 组件的启动流程
  3. Android 学习笔记——利用JNI技术在Andro
  4. Android HAL
  5. Android中运行Tensorflow程序2-编写自己
  6. Android Content Providers(二)——Contact
  7. WebView的使用之Android与JS通过WebView
  8. 【Android】背景知识
  9. Android中的shape中的属性大全
  10. gif in android