新公司是做手游的,每出一款游戏包都要多渠道打包,网上找了渠道打包的原理,自己封装的代码(大多是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);
}
}