安卓多渠道打包工具

新公司是做手游的,每出一款游戏包都要多渠道打包,网上找了渠道打包的原理,自己封装的代码(大多是copy),共享出来供大家参考
多线程实现打包,可以实时获取已完成的数量

欢迎各位大牛们提出指导意见

------------------------------------------------------------------------------------------------------------

import java.util.concurrent.*;

/**
 * 线程管理类
 *
 * @author ericyuan
 * @date 2018/11/9
 */
public class ThreadManager {

    private static ThreadPoolProxy instance;

    public static ThreadPoolProxy getInstance() {
        if (null == instance) {
            synchronized (ThreadManager.class) {
                if (null == instance) {
                    // 获取处理器数量
                    int cpuNum = Runtime.getRuntime().availableProcessors();
                    // 根据CPU的数量计算出合理的线程并发数
                    int threadNum = cpuNum * 2 + 1;
                    instance = new ThreadPoolProxy(threadNum - 1, threadNum, Integer.MAX_VALUE);
                }
            }
        }
        return instance;
    }

    public static class ThreadPoolProxy {
        /**
         * 线程池
         */
        private ThreadPoolExecutor executor;
        /**
         * 核心线程数
         */
        private int corePoolSize;
        /**
         * 最大线程数
         */
        private int maximumPoolSize;
        /**
         * 闲置线程存活时间
         */
        private long keepAliveTime;

        public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.keepAliveTime = keepAliveTime;
        }

        public Future submit(Callable task) {
            if (null == task) {
                return null;
            }
            if (null == executor) {
                initThreadPoolExecutor();
            }
            return executor.submit(task);
        }

        /**
         * 初始化
         */
        private void initThreadPoolExecutor() {
            this.executor = new ThreadPoolExecutor(
                    corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    // 单位:毫秒
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingDeque<>(Integer.MAX_VALUE));
        }
    }
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 安卓安装包多渠道打包
 *
 * @author ericyuan
 * @date 2018/11/8
 */
public class BatchPackage {

    private static LinkedBlockingQueue queue = new LinkedBlockingQueue<>();

    private List> taskResult;

    public void offerChannel(String channel) {
        queue.offer(channel);
    }

    /**
     * 开始打包
     */
    public void start(String master, String targetDir, String prefix) {
        File masterFile = new File(master);
        if (!masterFile.exists()) {
            throw new ApkChannelExcception(String.format("母包文件不存在[%s]", master));
        }
        File targetDirectory = new File(targetDir);
        if (!targetDirectory.exists()) {
            targetDirectory.mkdirs();
        }
        taskResult = new ArrayList<>();
        try {
            String channel;
            while (true) {
                channel = queue.poll(1, TimeUnit.SECONDS);
                System.out.println("读取的channel: " + channel);
                if (null == channel) {
                    System.out.println("队列中的消息已读取完毕");
                    break;
                }
                Future result = ThreadManager.getInstance().submit(new ApkChannel(master, targetDir, channel, prefix));
                taskResult.add(result);
            }

        } catch (Exception e) {
            throw new ApkChannelExcception(e);
        }
    }

    List doneList = new ArrayList<>();

    public List getTaskResult() {
        if (null != taskResult && !taskResult.isEmpty()) {
            for (Future task : taskResult) {
                try {
                    if (task.isDone()) {
                        if (!doneList.contains(task.get())) {
                            doneList.add(task.get());
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return doneList;
    }

    public void clear() {
        if (null != taskResult) {
            taskResult.clear();
            taskResult = null;
        }
        if(null != doneList){
            doneList.clear();
            doneList = null;
        }
    }

    class ApkChannel implements Callable {
        private String masterFile;
        private String targetDirectory;
        private String channel;
        private String prefix;

        public ApkChannel(String masterFile, String targetDirectory, String channel, String prefix) {
            this.masterFile = masterFile;
            this.targetDirectory = targetDirectory;
            this.channel = channel;
            this.prefix = prefix;
        }

        @Override
        public String call() throws Exception {
            System.out.println(Thread.currentThread().getName() + ": 开始处理。。。");
            ZipFile sourceZip = new ZipFile(masterFile);
            // 复制母包文件到目标渠道目录下,根据目标渠道文件名生成文件
            String targetApkName = getTargetApkName();
            FileOutputStream fos = new FileOutputStream(targetApkName);
            ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos);
            Enumeration entries = sourceZip.getEntries();
            while (entries.hasMoreElements()) {
                ZipArchiveEntry entry = entries.nextElement();
                zos.putArchiveEntry(entry);
                int length;
                byte[] buffer = new byte[1024];
                InputStream is = sourceZip.getInputStream(entry);
                while ((length = is.read(buffer)) != -1) {
                    zos.write(buffer, 0, length);
                }
                is.close();
                buffer = null;
            }
            // 拼接渠道识别字符串:META-INF/channel_{channel}。(channel:为读取配置的渠道号)
            zos.putArchiveEntry(new ZipArchiveEntry(prefix.concat(channel)));
            zos.closeArchiveEntry();
            zos.close();
            sourceZip.close();
            System.out.println(Thread.currentThread().getName() + ": 结束处理。。。");
            return targetApkName;
        }

        /**
         * 获取目标渠道包文件的完整(绝对)路径
         *
         * @return
         */
        private String getTargetApkName() {
            String ext = getSuffix(masterFile);
            return targetDirectory.concat(channel).concat(ext);
        }

        private String getSuffix(String fileName) {
            if (null == fileName || fileName.length() == 0) {
                return "";
            }
            String dot = ".";
            if (fileName.lastIndexOf(dot) < 0) {
                return "";
            }
            // 反向肯定预查。从右向左搜符号.和.后面的字符串
            return fileName.replaceAll("^.+(?<=\\.)(.+)$", ".$1");
        }
    }
}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

public class ApkChannelExcception extends RuntimeException {

    public ApkChannelExcception(String message) {
        super(message);
    }

    public ApkChannelExcception(Throwable cause) {
        super(cause);
    }
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

public class FilePack {

    public static void main(String[] args) {
        long s = System.currentTimeMillis();
        // 获取母包apk文件
        String sourceApk = "D:/apk/app.apk";
        String targetDir = "D:/apk/target/";
        // 拼接渠道识别字符串:META-INF/uuchannel_{channel}。(channel:为读取配置的渠道号)
        String channelFileName = "META-INF/uuchannel_";
        String formatChannel = "test%dchannel";
        int left = 1, right = 8;
        BatchPackage bpk = new BatchPackage();
        for (int i = left; i <= right; i++) {
            String channel = String.format(formatChannel, i);
            bpk.offerChannel(channel);
        }
        bpk.start(sourceApk, targetDir, channelFileName);
        int count = right - left + 1;
        while (true) {
            System.out.println("现在完成打包文件数: [" + bpk.getTaskResult().size() + "]/" + count);
            if (bpk.getTaskResult().size() == count) {
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (String path : bpk.getTaskResult()) {
            System.out.println("渠道包路径: = [" + path + "]");
        }
        bpk.clear();
        long l = System.currentTimeMillis() - s;
        System.out.println("用时:[" + l / 1000 + "s]");
        System.exit(0);
    }
}

你可能感兴趣的:(经验总结)