AndroidStudio实现多渠道打包速度慢,且公司渠道多大54个(后面还会追加/(ㄒoㄒ)/~~),一到上线就需要各个渠道的apk,着实是慢,现在用自己编写的jar包来实现多渠道打包。
先看多渠道打包成果。
现在我们来实现多渠道的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](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)
至此,多渠道打包完毕,万圣节不快乐....