两种Android打渠道包的方法

转自:http://blog.csdn.net/wei1583812/article/details/44463697

注:全文转自其他博主。

方式一: 

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时命令行传入的,不懂的往下看。
        String source_apk_path = "D:/apk/app.apk";
        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;
        }

        @Override
        public 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156

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
 
 
  • 1
  • 1

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

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

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

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

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

360
xiaomi
anzhi
baidu
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

运行命令后,你会发现在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;
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

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

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

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

方式二:

把渠道信息写入APK文件的末尾

这个方法不需要解压缩,直接向apk文件末尾处写数据就可以了。打包速度比上个方法还要快一些(因为少了解压缩和压缩的过程)。

原理探究

写:apk文件打包好了之后,向apk文件的最末位处写入开始写入渠道的标记,再把渠道写到标记之后。这样apk的渠道已经写好了。

读:程序在启动的时候,直接找到apk的存储位置,读流的方式找到之前写的标记位,标记位之后就是渠道了,拿到渠道之后对这个数据做持久化处理就可以了(放sp或者sqlite里都可以),这样就不用每次都读渠道号啦。

/*
 * Copyright (C) 2014 seven456@gmail.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.multichannel2;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.security.Key;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.ZipFile;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

/**
 * 多渠道打包工具;<br/>
 * 利用的是Zip文件“可以添加comment(注释)”的数据结构特点,在文件的末尾写入任意数据,而不用重新解压zip文件(apk文件就是zip文件格式);<br/>
 * 创建时间: 2014-12-16 18:56:29
 * @author zhangguojun
 * @version 1.1
 * @since JDK1.6 Android2.2
 */
public class MCPTool {
    /**
     * 数据结构体的签名标记
     */
    private static final String SIG = "MCPT";
    /**
     * 数据结构的版本号
     */
    private static final String VERSION_1_1 = "1.1";
    /**
     * 数据编码格式
     */
    private static final String CHARSET_NAME = "UTF-8";
    /**
     * 加密用的IvParameterSpec参数
     */
    private static final byte[] IV = new byte[] { 1, 3, 1, 4, 5, 2, 0, 1 };

    /**
     * 写入数据
     * @param path 文件路径
     * @param content 写入的内容
     * @param password 加密密钥
     * @throws Exception
     */
    private static void write(File path, String content, String password) throws Exception {
        write(path, content.getBytes(CHARSET_NAME), password);
    }

    /**
     * 写入数据(如:渠道号)
     * @param path 文件路径
     * @param content 写入的内容
     * @param password 加密密钥
     * @throws Exception
     */
    private static void write(File path, byte[] content, String password) throws Exception {
        ZipFile zipFile = new ZipFile(path);
        boolean isIncludeComment = zipFile.getComment() != null;
        zipFile.close();
        if (isIncludeComment) {
            throw new IllegalStateException("Zip comment is exists, Repeated write is not recommended.");
        }

        boolean isEncrypt = password != null && password.length() > 0;
        byte[] bytesContent = isEncrypt ? encrypt(password, content) : content;
        byte[] bytesVersion = VERSION_1_1.getBytes(CHARSET_NAME);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(bytesContent); // 写入内容;
        baos.write(short2Stream((short) bytesContent.length)); // 写入内容长度;
        baos.write(isEncrypt ? 1 : 0); // 写入是否加密标示;
        baos.write(bytesVersion); // 写入版本号;
        baos.write(short2Stream((short) bytesVersion.length)); // 写入版本号长度;
        baos.write(SIG.getBytes(CHARSET_NAME)); // 写入SIG标记;
        byte[] data = baos.toByteArray();
        baos.close();
        if (data.length > Short.MAX_VALUE) {
            throw new IllegalStateException("Zip comment length > 32767.");
        }

        // Zip文件末尾数据结构:{@see java.util.zip.ZipOutputStream.writeEND}
        RandomAccessFile raf = new RandomAccessFile(path, "rw");
        raf.seek(path.length() - 2); // comment长度是short类型
        raf.write(short2Stream((short) data.length)); // 重新写入comment长度,注意Android apk文件使用的是ByteOrder.LITTLE_ENDIAN(小端序);
        raf.write(data);
        raf.close();
    }

    /**
     * 读取数据
     * @param path 文件路径
     * @param password 解密密钥
     * @return 被该工具写入的数据(如:渠道号)
     * @throws Exception
     */
    private static byte[] read(File path, String password) throws Exception {
        byte[] bytesContent = null;
        byte[] bytesMagic = SIG.getBytes(CHARSET_NAME);
        byte[] bytes = new byte[bytesMagic.length];
        RandomAccessFile raf = new RandomAccessFile(path, "r");
        Object[] versions = getVersion(raf);
        long index = (long) versions[0];
        String version = (String) versions[1];
        if (VERSION_1_1.equals(version)) {
            bytes = new byte[1];
            index -= bytes.length;
            readFully(raf, index, bytes); // 读取内容长度;
            boolean isEncrypt = bytes[0] == 1;

            bytes = new byte[2];
            index -= bytes.length;
            readFully(raf, index, bytes); // 读取内容长度;
            int lengthContent = stream2Short(bytes, 0);

            bytesContent = new byte[lengthContent];
            index -= lengthContent;
            readFully(raf, index, bytesContent); // 读取内容;

            if (isEncrypt && password != null && password.length() > 0) {
                bytesContent = decrypt(password, bytesContent);
            }
        }
        raf.close();
        return bytesContent;
    }

    /**
     * 读取数据结构的版本号
     * @param raf RandomAccessFile
     * @return 数组对象,[0] randomAccessFile.seek的index,[1] 数据结构的版本号
     * @throws IOException
     */
    private static Object[] getVersion(RandomAccessFile raf) throws IOException {
        String version = null;
        byte[] bytesMagic = SIG.getBytes(CHARSET_NAME);
        byte[] bytes = new byte[bytesMagic.length];
        long index = raf.length();
        index -= bytesMagic.length;
        readFully(raf, index, bytes); // 读取SIG标记;
        if (Arrays.equals(bytes, bytesMagic)) {
            bytes = new byte[2];
            index -= bytes.length;
            readFully(raf, index, bytes); // 读取版本号长度;
            int lengthVersion = stream2Short(bytes, 0);
            index -= lengthVersion;
            byte[] bytesVersion = new byte[lengthVersion];
            readFully(raf, index, bytesVersion); // 读取内容;
            version = new String(bytesVersion, CHARSET_NAME);
        }
        return new Object[] { index, version };
    }

    /**
     * RandomAccessFile seek and readFully
     * @param raf
     * @param index
     * @param buffer
     * @throws IOException
     */
    private static void readFully(RandomAccessFile raf, long index, byte[] buffer) throws IOException {
        raf.seek(index);
        raf.readFully(buffer);
    }

    /**
     * 读取数据(如:渠道号)
     * @param path 文件路径
     * @param password 解密密钥
     * @return 被该工具写入的数据(如:渠道号)
     */
    public static String readContent(File path, String password) {
        try {
            return new String(read(path, password), CHARSET_NAME);
        } catch (Exception ignore) {
        }
        return null;
    }

    /**
     * Android平台读取渠道号
     * @param context Android中的android.content.Context对象
     * @param mcptoolPassword mcptool解密密钥
     * @param defValue 读取不到时用该值作为默认值
     * @return
     */
    public static String getChannelId(Object context, String mcptoolPassword, String defValue) {
        String content = MCPTool.readContent(new File(getPackageCodePath(context)), mcptoolPassword);
        return content == null || content.length() == 0 ? defValue : content;
    }

    /**
     * 获取已安装apk文件的存储路径(这里使用反射,因为MCPTool项目本身不需要导入Android的运行库)
     * @param context Android中的Context对象
     * @return
     */
    private static String getPackageCodePath(Object context) {
        try {
            return (String) context.getClass().getMethod("getPackageCodePath").invoke(context);
        } catch (Exception ignore) {
        }
        return null;
    }

    /**
     * 加密
     * @param password
     * @param content
     * @return
     * @throws Exception
     */
    private static byte[] encrypt(String password, byte[] content) throws Exception {
        return cipher(Cipher.ENCRYPT_MODE, password, content);
    }

    /**
     * 解密
     * @param password
     * @param content
     * @return
     * @throws Exception
     */
    private static byte[] decrypt(String password, byte[] content) throws Exception {
        return cipher(Cipher.DECRYPT_MODE, password, content);
    }

    /**
     * 加解密
     * @param cipherMode
     * @param password
     * @param content
     * @return
     * @throws Exception
     */
    private static byte[] cipher(int cipherMode, String password, byte[] content) throws Exception {
        DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET_NAME));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        Key secretKey = keyFactory.generateSecret(dks);
        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        IvParameterSpec spec = new IvParameterSpec(IV);
        cipher.init(cipherMode, secretKey, spec);
        return cipher.doFinal(content);
    }

    /**
     * short转换成字节数组(小端序)
     * @param stream
     * @param offset
     * @return
     */
    private static short stream2Short(byte[] stream, int offset) {
        ByteBuffer buffer = ByteBuffer.allocate(2);
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.put(stream[offset]);
        buffer.put(stream[offset + 1]);
        return buffer.getShort(0);
    }

    /**
     * 字节数组转换成short(小端序)
     * @param data
     * @return
     */
    private static byte[] short2Stream(short data) {
        ByteBuffer buffer = ByteBuffer.allocate(2);
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.putShort(data);
        buffer.flip();
        return buffer.array();
    }

    /**
     * nio高速拷贝文件
     * @param source
     * @param target
     * @return
     * @throws IOException
     */
    private static boolean nioTransferCopy(File source, File target) throws IOException {
        FileChannel in = null;
        FileChannel out = null;
        FileInputStream inStream = null;
        FileOutputStream outStream = null;
        try {
            File parent = target.getParentFile();
            if (!parent.exists()) {
                parent.mkdirs();
            }
            inStream = new FileInputStream(source);
            outStream = new FileOutputStream(target);
            in = inStream.getChannel();
            out = outStream.getChannel();
            return in.transferTo(0, in.size(), out) == in.size();
        } finally {
            close(inStream);
            close(in);
            close(outStream);
            close(out);
        }
    }

    /**
     * 关闭数据流
     * @param closeable
     */
    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException ignore) {
            }
        }
    }

//  /**
//   * 简单测试代码段
//   * @param args
//   * @throws Exception
//   */
//  public static void test() throws Exception {
//      String content = "abc";
//      String password = "123456789";
//      System.out.println("content = " + content);
//      String contentE = new String(encrypt(password, content.getBytes(CHARSET_NAME)), CHARSET_NAME);
//      System.out.println("contentE = " + contentE);
//      String contentD = new String(decrypt(password, contentE.getBytes(CHARSET_NAME)), CHARSET_NAME);
//      System.out.println("contentD = " + contentD);
//      
//  }

    /**
     * jar命令行的入口方法
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        args = "-path D:/apk/app.apk -outdir D:/apk/new/ -contents xiaomi;360; -password 12345678".split(" ");//写入
//      args = "-version".split(" ");
//      args = "-path D:/apk/app_360.apk -password 12345678".split(" ");//读取

        long time = System.currentTimeMillis();
        String cmdPath  = "-path";
        String cmdOutdir  = "-outdir";
        String cmdContents  = "-contents";
        String cmdPassword  = "-password";
        String cmdVersion  = "-version";
        String help = "用法:java -jar MCPTool.jar [" + cmdPath + "] [arg0] [" + cmdOutdir + "] [arg1] [" + cmdContents + "] [arg2] [" + cmdPassword + "] [arg3]"
                + "\n" + cmdPath + "        APK文件路径"
                + "\n" + cmdOutdir + "      输出路径(可选),默认输出到APK文件同一级目录"
                + "\n" + cmdContents + "    写入内容集合,多个内容之间用“;”分割(linux平台请在“;”前加“\\”转义符),如:xiaomi;360; 当没有" + cmdContents + "”参数时输出已有文件中的contents"
                + "\n" + cmdPassword + "    加密密钥(可选),长度8位以上,如果没有该参数,不加密"
                + "\n" + cmdVersion + " 显示MCPTool版本号"
                + "\n例如:"
                + "\n写入:java -jar MCPTool.jar -path D:/test.apk -outdir ./ -contents googleplay;m360; -password 12345678"
                + "\n读取:java -jar MCPTool.jar -path D:/test.apk -password 12345678";

        if (args.length == 0 || args[0] == null || args[0].trim().length() == 0) {
            System.out.println(help);
        } else {
            if (args.length > 0) {
                if (args.length == 1 && cmdVersion.equals(args[0])) {
                    System.out.println("version: " + VERSION_1_1);
                } else {
                    Map<String, String> argsMap = new LinkedHashMap<String, String>();
                    for (int i = 0; i < args.length; i += 2) {
                        if (i + 1 < args.length) {
                            if (args[i + 1].startsWith("-")) {
                                throw new IllegalStateException("args is error, help: \n" + help);
                            } else {
                                argsMap.put(args[i], args[i + 1]);
                            }
                        }
                    }
                    System.out.println("argsMap = " + argsMap);
                    File path = argsMap.containsKey(cmdPath) ? new File(argsMap.get(cmdPath)) : null;
                    String parent = path == null? null : (path.getParent() == null ? "./" : path.getParent());
                    File outdir = parent == null ? null : new File(argsMap.containsKey(cmdOutdir) ? argsMap.get(cmdOutdir) : parent);
                    String[] contents = argsMap.containsKey(cmdContents) ? argsMap.get(cmdContents).split(";") : null;
                    String password = argsMap.get(cmdPassword);
                    if (path != null) {
                        System.out.println("path: " + path);
                        System.out.println("outdir: " + outdir);
                        if (contents != null && contents.length > 0) {
                            System.out.println("contents: " + Arrays.toString(contents));
                        }
                        System.out.println("password: " + password);
                        if (contents == null || contents.length == 0) { // 读取数据;
                            System.out.println("content: " + readContent(path, password));
                        } else { // 写入数据;
                            String fileName = path.getName();
                            int dot = fileName.lastIndexOf(".");
                            String prefix = fileName.substring(0, dot);
                            String suffix = fileName.substring(dot);
                            for (String content : contents) {
                                File target = new File(outdir, prefix + "_" + content + suffix);
                                if (nioTransferCopy(path, target)) {
                                    write(target, content, password);
                                }
                            }
                        }
                    }
                }
            }
        }
        System.out.println("time:" + (System.currentTimeMillis() - time));
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432

上面MCPTool工具类,直接运行main(),只需改变args的值即可实现:写入(生成多个渠道包,并把渠道号写入清单文件)、读取(读出写入的渠道号)

调取读取的方法如下:

public class Test {
    public static void main(String[] args) throws Exception {
        String channel = MCPTool.readContent(new File("D:/apk/app_360.apk"), "12345678");
        System.out.print(channel);
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值