美文网首页Android知识Android开发经验谈
Android开发之旅-实用工具之多渠道打包工具

Android开发之旅-实用工具之多渠道打包工具

作者: 学海摆渡人 | 来源:发表于2016-10-31 18:55 被阅读205次

    AndroidStudio实现多渠道打包速度慢,且公司渠道多大54个(后面还会追加/(ㄒoㄒ)/~~),一到上线就需要各个渠道的apk,着实是慢,现在用自己编写的jar包来实现多渠道打包。

    先看多渠道打包成果。


    image.png

    现在我们来实现多渠道的jar包吧。
    1、首先新建一个Java Application,创建一个PackagingTool 工具类,添加如下代码。

    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.URI;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.FileSystem;
    import java.nio.file.FileSystems;
    import java.nio.file.FileVisitResult;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.PathMatcher;
    import java.nio.file.Paths;
    import java.nio.file.SimpleFileVisitor;
    import java.nio.file.StandardCopyOption;
    import java.nio.file.StandardOpenOption;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Map;
    
    public class PackagingTool {
    
        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 final String CHANNEL_LIST_TXT = "channel_list";
        private static String source_path;
    
        public static void main(String[] args) throws Exception {
            if (args.length < 3) {
                System.out.println("参数不足,请重新输入...");
                return;
            }
            final String source_apk_path = args[0];
            System.out.println("接收第一个参数源apk:"+args[0]);
            
            int last_index = source_apk_path.lastIndexOf("/") + 1;
            source_path = source_apk_path.substring(0, last_index);
            System.out.println("源apk所在路径:" + source_path);
            
            final String source_apk_name = source_apk_path.substring(last_index,
                    source_apk_path.length());
            System.out.println("源apk名称:" + source_apk_name);
            
            final String last_name = ".apk";
            
            String channelName = args[1];
            System.out.println("接收到的渠道名称:" + channelName);
            
            String channelVersion = args[2];
            System.out.println("接收到的渠道版本:" + channelVersion);
            
            //如果接收到的是all(就是打所有渠道的apk)
            if ("all".equals(channelName)) {
                System.out.println("开始生成所有渠道包...");
    
                ArrayList<String> allChannels = getChannelList(source_path+CHANNEL_LIST_TXT);
                if (allChannels==null||allChannels.size()==0) {
                    System.out.println("生成所有渠道包失败,渠道信息为空");
                    return;
                }
                ChannelJson channelJson = null;
                for(String str:allChannels){
                    
                    if ("hsz".equals(str)) {
                        System.out.println("无需生成红手指渠道apk...");
                        continue;
                    }else{
                        channelJson = new ChannelJson();
                        channelJson.setChannelName(str);
                        channelJson.setChannelID("com.redfinger.app."+str);
                        channelJson.setChannelVersion(channelVersion);
                        
                        String new_apk_path = source_path
                                + source_apk_name.substring(0, source_apk_name.length()
                                        - last_name.length()) + "_channel_" + str +"_v"+channelVersion+ last_name;
                        copyFile(source_apk_path, new_apk_path);
                        changeChannel(new_apk_path, "channel", channelJson.toString());
                        System.out.println("生成渠道包成功,渠道:"+str+"...");
                    }
                }
                
            }else if("hsz".equals(channelName)){
                System.out.println("无需生成红手指渠道apk...");
                return;
            }else{
                String new_apk_path = source_path
                        + source_apk_name.substring(0, source_apk_name.length()
                                - last_name.length()) + "_channel_" + args[1] +"_v"+channelVersion+ last_name;
                System.out.println("将生成渠道包,名称为:" + new_apk_path);
                
                System.out.println("开始生产渠道apk:"+new_apk_path+"...");
                
                copyFile(source_apk_path, new_apk_path);
                
                System.out.println("完成生成渠道apk:"+new_apk_path+"...");
                
                ChannelJson channelJson = new ChannelJson();
                channelJson.setChannelName(channelName);
                channelJson.setChannelID("com.redfinger.app."+channelName);
                channelJson.setChannelVersion(channelVersion);
                System.out.println("生成的Json数据内容:"+channelJson.toString());
                
                System.out.println("开始打入渠道信息json:"+channelJson.toString());
                changeChannel(new_apk_path, "channel", channelJson.toString());
            }
            System.out.println("生成渠道包完毕...");
        }
    
        public static class ChannelJson{
            private String channelName="hsz";
            private String channelID="com.redfinger.app";
            private String channelVersion="2.1.15";
            public String getChannelName() {
                return channelName;
            }
            public void setChannelName(String channelName) {
                this.channelName = channelName;
            }
            public String getChannelID() {
                return channelID;
            }
            public void setChannelID(String channelID) {
                this.channelID = channelID;
            }
            public String getChannelVersion() {
                return channelVersion;
            }
            public void setChannelVersion(String channelVersion) {
                this.channelVersion = channelVersion;
            }
            
            public String toString(){
                StringBuffer buffer = new StringBuffer();
                buffer.append("{");
                buffer.append("\"channelName"+"\":"+"\""+getChannelName()+"\",");
                buffer.append("\"channelID"+"\":"+"\""+getChannelID()+"\",");
                buffer.append("\"channelVersion"+"\":"+"\""+getChannelVersion()+"\"");
                buffer.append("}");
                return buffer.toString();
            }
        }
        
        /**
         * 修改渠道号,原理是在apk的META-INF下新建一个文件名为渠道号的文件
         */
        public static boolean changeChannel(final String zipFilename,
                String channel, String jsonstr) {
            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 {
                    Path path = Files.createFile(newChannel);
                    if (path != null) {
                        BufferedWriter writer = Files.newBufferedWriter(path,
                                StandardCharsets.UTF_8, StandardOpenOption.APPEND); // 追加
                        writer.write(jsonstr);
                        writer.close();
                    }
                }
                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();
            }
        }
    }```
    先run下这个Java Application工程,右键项目选择export->java->Runnable jar file->导出到D盘中,然后在D:盘根目录下生成文件channelList文件,添加渠道列表内容。
    

    youku
    yxfw
    jdy
    jbjl
    sy
    zfwl
    nmzs
    xxfz
    hnyx
    mmy
    wy
    azyxlt
    ayx
    bsj
    jy
    vip_tg

    将自己原apk文件放置于如上channelList文件相同目录。我这里放到D盘。CMD窗口运行D:\>java -jar apkTool.jar RedFingerClient.apk all 2.1.15就能打出所有渠道包。生成一个渠道就需将all 修改成渠道名称即可。
    

    D:>java -jar apkTool.jar RedFingerClient.apk all 2.1.15
    接收第一个参数源apk:RedFingerClient.apk
    源apk所在路径:
    源apk名称:RedFingerClient.apk
    接收到的渠道名称:all
    接收到的渠道版本:2.1.15
    开始生成所有渠道包...
    无需生成红手指渠道apk...
    生成渠道包成功,渠道:hsz_uat...
    生成渠道包成功,渠道:youku...
    生成渠道包成功,渠道:yxfw...
    生成渠道包成功,渠道:jdy...
    生成渠道包成功,渠道:jbjl...
    生成渠道包成功,渠道:sy...
    生成渠道包成功,渠道:zfwl...
    生成渠道包成功,渠道:nmzs...
    生成渠道包成功,渠道:xxfz...
    生成渠道包成功,渠道:hnyx...
    生成渠道包成功,渠道:mmy...
    生成渠道包成功,渠道:wy...
    生成渠道包成功,渠道:azyxlt...
    生成渠道包成功,渠道:ayx...
    生成渠道包成功,渠道:bsj...
    生成渠道包成功,渠道:jy...
    生成渠道包成功,渠道:vip_tg...
    生成渠道包完毕...

    现在就可以在新生产的渠道apk中查看重要的渠道文件了。
    我这里把apk文件格式改成.zip格式方便查看。
    ![image.png](https://img.haomeiwen.com/i1914079/883436dd373c3f9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    我们看看内容channel文件内容:
    ![channel文件内容](https://img.haomeiwen.com/i1914079/64b9c5415d03ce34.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    现在接下来就需通过apk文件去获取渠道名称和渠道ID了,详细代码如下:
    

    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Enumeration;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipFile;

    import org.json.JSONException;
    import org.json.JSONObject;

    import android.content.Context;
    import android.content.pm.ApplicationInfo;
    import android.util.Log;

    public class ChannelUtil {

    private String channelName = null;
    private String channelId = null;
    private String channelVersion = null;
    
    private Context context;
    private static ChannelUtil channelUtil;
    
    /**
     * 获取渠道名称
     * @return
     */
    public String getChannelName() {
        return channelName;
    }
    
    /**
     * 获取渠道ID
     * @return
     */
    public String getChannelId() {
        return channelId;
    }
    
    /**
     * 获取渠道版本
     * @return
     */
    public String getChannelVersion() {
        return channelVersion;
    }
    
    public static ChannelUtil getInstant(Context context) {
        if (channelUtil==null) {
            channelUtil = new ChannelUtil(context);
        }
        return channelUtil;
    }
    
    private ChannelUtil(Context context) {
        this.context = context;
    }
    
    public ChannelUtil init() {
        final String start_flag = "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)&&!entry.isDirectory()) {
                    InputStream inputStream = null;
                    try {
                        inputStream = new BufferedInputStream(zipfile.getInputStream(entry));
                    } catch (IOException e) {
                        e.printStackTrace();
                        Log.e("ChannelUtil", "Read channel failure");
                    }
                    if (inputStream!=null) {
                        String jsonStr = getInputStreamTxt(inputStream);
                        JSONObject jsonObject = new JSONObject(jsonStr);
                        channelName = jsonObject.getString("channelName");
                        channelId = jsonObject.getString("channelID");
                        channelVersion = jsonObject.getString("channelVersion");
                    }
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        if (channelName==null||channelId==null||channelVersion==null) {
            channelName = "com.redfinger.app";
            channelId = "com.redfinger.app";
            channelVersion = "3.1.14";
        }
        return this;
    }
    
    /**
     * 读取传入输入流的内容
     * @param iStream
     * @return 内容
     * @throws IOException 会跑出IO异常
     */
    public String getInputStreamTxt(InputStream iStream) throws IOException{
        StringBuffer out = new StringBuffer();
        byte[] b = new byte[1024];
        int n;
        while ((n = iStream.read(b)) != -1) {
            out.append(new String(b, 0, n));
        }
        return out.toString();
    }
    

    }

    //获取渠道名称和渠道ID
    ChannelUtil util = ChannelUtil.getInstant(this);
    TextView tv = (TextView) findViewById(R.id.channel);
    tv.setText("渠道名:"+util.getChannelName()+"\n"+"渠道ID:"+util.getChannelID());

    ![image.png](https://img.haomeiwen.com/i1914079/efba74f39fc7a79c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    至此,多渠道打包完毕,万圣节不快乐....

    相关文章

      网友评论

        本文标题:Android开发之旅-实用工具之多渠道打包工具

        本文链接:https://www.haomeiwen.com/subject/ttpputtx.html