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

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

先看多渠道打包成果。


Android开发之旅-实用工具之多渠道打包工具_第1张图片
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 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 env = new HashMap<>();
        if (create) {
            env.put("create", "true");
        }
        return FileSystems.newFileSystem(uri, env);
    }

    private static class ChannelFileVisitor extends SimpleFileVisitor {
        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 getChannelList(String filePath) {
        ArrayList channel_list = new ArrayList();
        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](http://upload-images.jianshu.io/upload_images/1914079-883436dd373c3f9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我们看看内容channel文件内容:
![channel文件内容](http://upload-images.jianshu.io/upload_images/1914079-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](http://upload-images.jianshu.io/upload_images/1914079-efba74f39fc7a79c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


至此,多渠道打包完毕,万圣节不快乐....

你可能感兴趣的:(Android开发之旅-实用工具之多渠道打包工具)