Last active
March 5, 2022 11:24
-
-
Save panpf/4f04be745a921ff91b0ad8b95297f53d to your computer and use it in GitHub Desktop.
一种通过ZIP备注实现APK多渠道的方法
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.*; | |
import java.util.LinkedList; | |
import java.util.List; | |
public class BatchConfiguration { | |
public int version = -1; | |
public List<MCPComment> commentList; | |
private BatchConfiguration() { | |
} | |
public void addComment(MCPComment comment) { | |
if (comment.name == null || "".equals(comment.name.trim())) { | |
throw new IllegalArgumentException("comment name is null"); | |
} | |
if (comment.attrList == null) { | |
throw new IllegalArgumentException("comment " + comment.name + " attrList is null"); | |
} | |
if (commentList == null) { | |
commentList = new LinkedList<>(); | |
} | |
commentList.add(comment); | |
} | |
@Override | |
public String toString() { | |
StringBuilder builder = new StringBuilder(); | |
builder.append(version); | |
if (commentList != null && commentList.size() > 0) { | |
for (MCPComment comment : commentList) { | |
builder.append("\n"); | |
builder.append("\n"); | |
builder.append(comment.name); | |
if (comment.attrList != null && comment.attrList.size() > 0) { | |
for (MCPComment.Attr attr : comment.attrList) { | |
builder.append("\n"); | |
builder.append(attr.key).append("=").append(attr.value); | |
} | |
} | |
} | |
} | |
return builder.toString(); | |
} | |
public static BatchConfiguration readFromFile(File file) throws Exception { | |
BufferedReader reader = new BufferedReader(new FileReader(file)); | |
int lineNumber = 0; | |
int commentLineNumber = 0; | |
BatchConfiguration batchConfiguration = new BatchConfiguration(); | |
MCPComment currentComment = null; | |
try { | |
while (true) { | |
String lineContent = reader.readLine(); | |
if (lineContent == null) { | |
break; | |
} | |
lineContent = lineContent.trim(); | |
lineNumber++; | |
if (lineNumber == 1) { | |
// 第一行是配置文件版本号 | |
try { | |
batchConfiguration.version = Integer.valueOf(lineContent); | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("Configuration file is only a pure digital version number: " + lineContent + ". line: " + lineNumber); | |
} | |
} else if ("".equals(lineContent)) { | |
// 空行是Comment分割线,要把上一个Comment加入列表 | |
if (currentComment != null) { | |
if (currentComment.name == null || "".equals(currentComment.name.trim())) { | |
throw new IllegalArgumentException("Comment name is empty: " + currentComment.toString()); | |
} | |
if (currentComment.attrList == null) { | |
throw new IllegalArgumentException("Comment no attribute: " + currentComment.toString()); | |
} | |
batchConfiguration.addComment(currentComment); | |
currentComment = null; | |
} | |
} else { | |
// 到这里就是Comment的内容了,要创建备注对象并初始化备注行号 | |
if (currentComment == null) { | |
currentComment = new MCPComment(); | |
commentLineNumber = 0; | |
} | |
// 备注行号加1,备注的第一行是备注名称、备注版本以及密码 | |
commentLineNumber++; | |
if (commentLineNumber == 1) { | |
String[] elements = lineContent.split(" "); | |
if (elements.length >= 3) { | |
currentComment.name = elements[0]; | |
String commentVersion = elements[1]; | |
try { | |
currentComment.version = Integer.valueOf(commentVersion); | |
} catch (NumberFormatException e) { | |
throw new IllegalArgumentException("Comment version only digital: " + lineContent + ". line: " + lineNumber); | |
} | |
currentComment.password = elements[2]; | |
} else { | |
throw new IllegalArgumentException("Comment name error format: " + lineContent + ". line: " + lineNumber); | |
} | |
} else { | |
int index = lineContent.indexOf('='); | |
if(index == -1){ | |
throw new IllegalArgumentException("Comments attribute can only be keys to format: " + lineContent + ". line: " + lineNumber); | |
} | |
String attrKey = lineContent.substring(0, index).trim(); | |
if ("".equals(attrKey)) { | |
throw new IllegalArgumentException("Comments Attribute key cannot be empty: " + lineContent + ". line: " + lineNumber); | |
} | |
String attrValue = lineContent.substring(index + 1).trim(); | |
if ("".equals(attrValue)) { | |
throw new IllegalArgumentException("Comments Attribute value cannot be empty: " + lineContent + ". line: " + lineNumber); | |
} | |
currentComment.addAttr(new MCPComment.Attr(attrKey, attrValue)); | |
} | |
} | |
} | |
if (currentComment != null) { | |
batchConfiguration.addComment(currentComment); | |
} | |
return batchConfiguration; | |
} finally { | |
close(reader); | |
} | |
} | |
private static void close(Closeable closeable) { | |
if (closeable == null) { | |
return; | |
} | |
if (closeable instanceof OutputStream) { | |
try { | |
((OutputStream) closeable).flush(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
try { | |
closeable.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.json.JSONException; | |
import java.io.File; | |
public class BatchPackager { | |
public static void main(String[] args) { | |
String configFilePath = args != null && args.length >= 1 ? args[0] : null; | |
if (configFilePath == null) { | |
System.out.println("请输入配置文件路径"); | |
return; | |
} | |
File configFile = new File(configFilePath); | |
if(!configFile.exists()){ | |
System.out.println("找不到配置文件:" + configFile.getPath()); | |
return; | |
} | |
String apkFilePath = args.length >= 2 ? args[1] : null; | |
if (apkFilePath == null) { | |
System.out.println("请输入原始APK文件路径"); | |
return; | |
} | |
File apkFile = new File(apkFilePath); | |
if(!apkFile.exists()){ | |
System.out.println("找不到APK文件:" + apkFile.getPath()); | |
return; | |
} | |
try { | |
build(apkFile, configFile); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
public static void build(File originApkFile, File configurationFile) throws Exception { | |
long startTime = System.currentTimeMillis(); | |
BatchConfiguration batchConfiguration = BatchConfiguration.readFromFile(configurationFile); | |
if (batchConfiguration.commentList == null || batchConfiguration.commentList.size() == 0) { | |
throw new IllegalArgumentException("Not found comment info in configuration file: " + configurationFile.getPath()); | |
} | |
String apkFileName = originApkFile.getName(); | |
int dotIndex = apkFileName.lastIndexOf('.'); | |
String prefix = apkFileName.substring(0, dotIndex); | |
String suffix = apkFileName.substring(dotIndex); | |
File outDir = new File(originApkFile.getParent()); | |
for (MCPComment comment : batchConfiguration.commentList) { | |
File newApkFile = new File(outDir, prefix + "-" + comment.name + suffix); | |
if (newApkFile.exists()) { | |
if (!newApkFile.delete()) { | |
throw new IllegalStateException("Unable to delete file: " + newApkFile.getPath()); | |
} | |
} | |
MCPTool.nioTransferCopy(originApkFile, newApkFile); | |
String commentString = comment.getCommentString(); | |
MCPTool.write(newApkFile, commentString, comment.password); | |
System.out.println(newApkFile.getPath() + ": " + commentString); | |
} | |
long useTime = System.currentTimeMillis() - startTime; | |
System.out.println(); | |
System.out.println("Batch package finished, " + batchConfiguration.commentList.size() + " packages total time: " + (useTime / 1000) + " secs"); | |
} | |
public static void read(File dir, String password) { | |
File[] childFiles = dir.listFiles(); | |
if (childFiles == null || childFiles.length == 0) { | |
return; | |
} | |
for (File childFile : childFiles) { | |
String commentString = MCPTool.readContent(childFile, password); | |
if (commentString != null) { | |
MCPComment comment; | |
try { | |
comment = MCPComment.parse(commentString); | |
} catch (JSONException e) { | |
e.printStackTrace(); | |
continue; | |
} | |
if (comment != null) { | |
System.out.println(childFile.getName() + "\n" + comment.getShowString()); | |
} else { | |
System.out.println("parse comment info failed from " + childFile.getName() + " (" + commentString + ")"); | |
} | |
} else { | |
System.out.println("not found comment info from " + childFile.getName()); | |
} | |
System.out.print("\n"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.json.JSONException; | |
import org.json.JSONObject; | |
import java.util.Iterator; | |
import java.util.LinkedList; | |
import java.util.List; | |
public class MCPComment { | |
public String name; | |
public int version; | |
public String password; | |
public List<Attr> attrList; | |
public void addAttr(Attr attr) { | |
if (attr.key == null || "".equals(attr.key)) { | |
throw new IllegalArgumentException("attr key is null or empty"); | |
} | |
if (attr.value == null || "".equals(attr.value)) { | |
throw new IllegalArgumentException("attr value is null or empty"); | |
} | |
if (attrList == null) { | |
attrList = new LinkedList<>(); | |
} | |
attrList.add(attr); | |
} | |
public String getCommentString() { | |
if (attrList != null && attrList.size() > 0) { | |
StringBuilder builder = new StringBuilder(); | |
builder.append("{"); | |
// 版本 | |
builder.append("\"").append("version").append("\""); | |
builder.append(":").append(version); | |
// 属性 | |
for (Attr attr : attrList) { | |
if (builder.length() > 1) { | |
builder.append(","); | |
} | |
builder.append("\"").append(attr.key).append("\""); | |
builder.append(":"); | |
builder.append("\"").append(attr.value).append("\""); | |
} | |
builder.append("}"); | |
return builder.toString(); | |
} else { | |
return null; | |
} | |
} | |
@Override | |
public String toString() { | |
StringBuilder builder = new StringBuilder(); | |
builder.append(name).append(" ").append(version).append(" ").append(password); | |
if (attrList != null && attrList.size() > 0) { | |
for (Attr attr : attrList) { | |
if (builder.length() > 0) { | |
builder.append("\n"); | |
} | |
builder.append(attr.key).append("=").append(attr.value); | |
} | |
} | |
return builder.toString(); | |
} | |
public String getShowString() { | |
StringBuilder builder = new StringBuilder(); | |
builder.append("version").append("=").append(version); | |
if (attrList != null && attrList.size() > 0) { | |
for (Attr attr : attrList) { | |
if (builder.length() > 0) { | |
builder.append("\n"); | |
} | |
builder.append(attr.key).append("=").append(attr.value); | |
} | |
} | |
return builder.toString(); | |
} | |
public static class Attr { | |
public String key; | |
public String value; | |
public Attr(String key, String value) { | |
this.key = key; | |
this.value = value; | |
} | |
} | |
public static MCPComment parse(String commentJson) throws JSONException { | |
JSONObject jsonObject = new JSONObject(commentJson); | |
if (jsonObject.length() <= 0) { | |
return null; | |
} | |
MCPComment comment = new MCPComment(); | |
Iterator<String> keyIterator = jsonObject.keys(); | |
while (keyIterator.hasNext()) { | |
String key = keyIterator.next(); | |
String value = jsonObject.getString(key); | |
if ("version".equals(key)) { | |
comment.version = Integer.valueOf(value); | |
} else { | |
if (comment.attrList == null) { | |
comment.attrList = new LinkedList<>(); | |
} | |
comment.attrList.add(new Attr(key, jsonObject.getString(key))); | |
} | |
} | |
return comment; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 seven456@gmail.com | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import javax.crypto.Cipher; | |
import javax.crypto.SecretKeyFactory; | |
import javax.crypto.spec.DESKeySpec; | |
import javax.crypto.spec.IvParameterSpec; | |
import java.io.*; | |
import java.nio.ByteBuffer; | |
import java.nio.ByteOrder; | |
import java.nio.channels.FileChannel; | |
import java.security.Key; | |
import java.util.Arrays; | |
import java.util.LinkedHashMap; | |
import java.util.Map; | |
import java.util.zip.ZipFile; | |
/** | |
* 多渠道打包工具;<br/> | |
* 利用的是Zip文件“可以添加comment(注释)”的数据结构特点,在文件的末尾写入任意数据,而不用重新解压zip文件(apk文件就是zip文件格式);<br/> | |
* 创建时间: 2014-12-16 18:56:29 | |
* @author zhangguojun | |
* @version 1.1 | |
* @since JDK1.7 Android2.2 | |
*/ | |
public class MCPTool { | |
/** | |
* 数据结构体的签名标记 | |
*/ | |
private static final String SIG = "MCPT"; | |
/** | |
* 数据结构的版本号 | |
*/ | |
private static final String VERSION_1_1 = "1.1"; | |
/** | |
* 数据编码格式 | |
*/ | |
private static final String CHARSET_NAME = "UTF-8"; | |
/** | |
* 加密用的IvParameterSpec参数 | |
*/ | |
private static final byte[] IV = new byte[] { 1, 3, 1, 4, 5, 2, 0, 1 }; | |
/** | |
* 写入数据 | |
* @param path 文件路径 | |
* @param content 写入的内容 | |
* @param password 加密密钥 | |
* @throws Exception | |
*/ | |
public static void write(File path, String content, String password) throws Exception { | |
write(path, content.getBytes(CHARSET_NAME), password); | |
} | |
/** | |
* 写入数据(如:渠道号) | |
* @param path 文件路径 | |
* @param content 写入的内容 | |
* @param password 加密密钥 | |
* @throws Exception | |
*/ | |
private static void write(File path, byte[] content, String password) throws Exception { | |
ZipFile zipFile = new ZipFile(path); | |
boolean isIncludeComment = zipFile.getComment() != null; | |
zipFile.close(); | |
if (isIncludeComment) { | |
throw new IllegalStateException("Zip comment is exists, Repeated write is not recommended."); | |
} | |
boolean isEncrypt = password != null && password.length() > 0; | |
byte[] bytesContent = isEncrypt ? encrypt(password, content) : content; | |
byte[] bytesVersion = VERSION_1_1.getBytes(CHARSET_NAME); | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
baos.write(bytesContent); // 写入内容; | |
baos.write(short2Stream((short) bytesContent.length)); // 写入内容长度; | |
baos.write(isEncrypt ? 1 : 0); // 写入是否加密标示; | |
baos.write(bytesVersion); // 写入版本号; | |
baos.write(short2Stream((short) bytesVersion.length)); // 写入版本号长度; | |
baos.write(SIG.getBytes(CHARSET_NAME)); // 写入SIG标记; | |
byte[] data = baos.toByteArray(); | |
baos.close(); | |
if (data.length > Short.MAX_VALUE) { | |
throw new IllegalStateException("Zip comment length > 32767."); | |
} | |
// Zip文件末尾数据结构:{@see java.util.zip.ZipOutputStream.writeEND} | |
RandomAccessFile raf = new RandomAccessFile(path, "rw"); | |
raf.seek(path.length() - 2); // comment长度是short类型 | |
raf.write(short2Stream((short) data.length)); // 重新写入comment长度,注意Android apk文件使用的是ByteOrder.LITTLE_ENDIAN(小端序); | |
raf.write(data); | |
raf.close(); | |
} | |
/** | |
* 读取数据 | |
* @param path 文件路径 | |
* @param password 解密密钥 | |
* @return 被该工具写入的数据(如:渠道号) | |
* @throws Exception | |
*/ | |
private static byte[] read(File path, String password) throws Exception { | |
byte[] bytesContent = null; | |
byte[] bytes; | |
RandomAccessFile raf = new RandomAccessFile(path, "r"); | |
Object[] versions = getVersion(raf); | |
long index = (long) versions[0]; | |
String version = (String) versions[1]; | |
if (VERSION_1_1.equals(version)) { | |
bytes = new byte[1]; | |
index -= bytes.length; | |
readFully(raf, index, bytes); // 读取内容长度; | |
boolean isEncrypt = bytes[0] == 1; | |
bytes = new byte[2]; | |
index -= bytes.length; | |
readFully(raf, index, bytes); // 读取内容长度; | |
int lengthContent = stream2Short(bytes, 0); | |
bytesContent = new byte[lengthContent]; | |
index -= lengthContent; | |
readFully(raf, index, bytesContent); // 读取内容; | |
if (isEncrypt && password != null && password.length() > 0) { | |
bytesContent = decrypt(password, bytesContent); | |
} | |
} | |
raf.close(); | |
return bytesContent; | |
} | |
/** | |
* 读取数据结构的版本号 | |
* @param raf RandomAccessFile | |
* @return 数组对象,[0] randomAccessFile.seek的index,[1] 数据结构的版本号 | |
* @throws IOException | |
*/ | |
private static Object[] getVersion(RandomAccessFile raf) throws IOException { | |
String version = null; | |
byte[] bytesMagic = SIG.getBytes(CHARSET_NAME); | |
byte[] bytes = new byte[bytesMagic.length]; | |
long index = raf.length(); | |
index -= bytesMagic.length; | |
readFully(raf, index, bytes); // 读取SIG标记; | |
if (Arrays.equals(bytes, bytesMagic)) { | |
bytes = new byte[2]; | |
index -= bytes.length; | |
readFully(raf, index, bytes); // 读取版本号长度; | |
int lengthVersion = stream2Short(bytes, 0); | |
index -= lengthVersion; | |
byte[] bytesVersion = new byte[lengthVersion]; | |
readFully(raf, index, bytesVersion); // 读取内容; | |
version = new String(bytesVersion, CHARSET_NAME); | |
} | |
return new Object[] { index, version }; | |
} | |
/** | |
* RandomAccessFile seek and readFully | |
* @param raf | |
* @param index | |
* @param buffer | |
* @throws IOException | |
*/ | |
private static void readFully(RandomAccessFile raf, long index, byte[] buffer) throws IOException { | |
raf.seek(index); | |
raf.readFully(buffer); | |
} | |
/** | |
* 读取数据(如:渠道号) | |
* @param path 文件路径 | |
* @param password 解密密钥 | |
* @return 被该工具写入的数据(如:渠道号) | |
*/ | |
public static String readContent(File path, String password) { | |
try { | |
return new String(read(path, password), CHARSET_NAME); | |
} catch (Exception ignore) { | |
} | |
return null; | |
} | |
/** | |
* Android平台读取渠道号 | |
* @param context Android中的android.content.Context对象 | |
* @param mcptoolPassword mcptool解密密钥 | |
* @param defValue 读取不到时用该值作为默认值 | |
* @return | |
*/ | |
public static String getChannelId(Object context, String mcptoolPassword, String defValue) { | |
String content = MCPTool.readContent(new File(getPackageCodePath(context)), mcptoolPassword); | |
return content == null || content.length() == 0 ? defValue : content; | |
} | |
/** | |
* 获取已安装apk文件的存储路径(这里使用反射,因为MCPTool项目本身不需要导入Android的运行库) | |
* @param context Android中的Context对象 | |
* @return | |
*/ | |
private static String getPackageCodePath(Object context) { | |
try { | |
return (String) context.getClass().getMethod("getPackageCodePath").invoke(context); | |
} catch (Exception ignore) { | |
} | |
return null; | |
} | |
/** | |
* 加密 | |
* @param password | |
* @param content | |
* @return | |
* @throws Exception | |
*/ | |
private static byte[] encrypt(String password, byte[] content) throws Exception { | |
return cipher(Cipher.ENCRYPT_MODE, password, content); | |
} | |
/** | |
* 解密 | |
* @param password | |
* @param content | |
* @return | |
* @throws Exception | |
*/ | |
private static byte[] decrypt(String password, byte[] content) throws Exception { | |
return cipher(Cipher.DECRYPT_MODE, password, content); | |
} | |
/** | |
* 加解密 | |
* @param cipherMode | |
* @param password | |
* @param content | |
* @return | |
* @throws Exception | |
*/ | |
private static byte[] cipher(int cipherMode, String password, byte[] content) throws Exception { | |
DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET_NAME)); | |
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); | |
Key secretKey = keyFactory.generateSecret(dks); | |
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); | |
IvParameterSpec spec = new IvParameterSpec(IV); | |
cipher.init(cipherMode, secretKey, spec); | |
return cipher.doFinal(content); | |
} | |
/** | |
* short转换成字节数组(小端序) | |
* @param data | |
* @return | |
*/ | |
private static short stream2Short(byte[] stream, int offset) { | |
ByteBuffer buffer = ByteBuffer.allocate(2); | |
buffer.order(ByteOrder.LITTLE_ENDIAN); | |
buffer.put(stream[offset]); | |
buffer.put(stream[offset + 1]); | |
return buffer.getShort(0); | |
} | |
/** | |
* 字节数组转换成short(小端序) | |
* @param stream | |
* @param offset | |
* @return | |
*/ | |
private static byte[] short2Stream(short data) { | |
ByteBuffer buffer = ByteBuffer.allocate(2); | |
buffer.order(ByteOrder.LITTLE_ENDIAN); | |
buffer.putShort(data); | |
buffer.flip(); | |
return buffer.array(); | |
} | |
/** | |
* nio高速拷贝文件 | |
* @param source | |
* @param target | |
* @return | |
* @throws IOException | |
*/ | |
public static boolean nioTransferCopy(File source, File target) throws IOException { | |
FileChannel in = null; | |
FileChannel out = null; | |
FileInputStream inStream = null; | |
FileOutputStream outStream = null; | |
try { | |
File parent = target.getParentFile(); | |
if (!parent.exists()) { | |
parent.mkdirs(); | |
} | |
inStream = new FileInputStream(source); | |
outStream = new FileOutputStream(target); | |
in = inStream.getChannel(); | |
out = outStream.getChannel(); | |
return in.transferTo(0, in.size(), out) == in.size(); | |
} finally { | |
close(inStream); | |
close(in); | |
close(outStream); | |
close(out); | |
} | |
} | |
/** | |
* 关闭数据流 | |
* @param closeable | |
*/ | |
private static void close(Closeable closeable) { | |
if (closeable != null) { | |
try { | |
closeable.close(); | |
} catch (IOException ignore) { | |
} | |
} | |
} | |
// /** | |
// * 简单测试代码段 | |
// * @param args | |
// * @throws Exception | |
// */ | |
// public static void test() throws Exception { | |
// String content = "abc"; | |
// String password = "123456789"; | |
// System.out.println("content = " + content); | |
// String contentE = new String(encrypt(password, content.getBytes(CHARSET_NAME)), CHARSET_NAME); | |
// System.out.println("contentE = " + contentE); | |
// String contentD = new String(decrypt(password, contentE.getBytes(CHARSET_NAME)), CHARSET_NAME); | |
// System.out.println("contentD = " + contentD); | |
// | |
// } | |
/** | |
* jar命令行的入口方法 | |
* @param args | |
* @throws Exception | |
*/ | |
public static void main(String[] args) throws Exception { | |
// 写入渠道号 | |
// args = "-path D:/111.apk -outdir D:/111/ -contents googleplay;m360; -password 12345678".split(" "); | |
// 查看工具程序版本号 | |
// args = "-version".split(" "); | |
// 读取渠道号 | |
// args = "-path D:/111_m360.apk -password 12345678".split(" "); | |
long time = System.currentTimeMillis(); | |
String cmdPath = "-path"; | |
String cmdOutdir = "-outdir"; | |
String cmdContents = "-contents"; | |
String cmdPassword = "-password"; | |
String cmdVersion = "-version"; | |
String help = "用法:java -jar MCPTool.jar [" + cmdPath + "] [arg0] [" + cmdOutdir + "] [arg1] [" + cmdContents + "] [arg2] [" + cmdPassword + "] [arg3]" | |
+ "\n" + cmdPath + " APK文件路径" | |
+ "\n" + cmdOutdir + " 输出路径(可选),默认输出到APK文件同一级目录" | |
+ "\n" + cmdContents + " 写入内容集合,多个内容之间用“;”分割(linux平台请在“;”前加“\\”转义符),如:googleplay;m360; 当没有" + cmdContents + "”参数时输出已有文件中的contents" | |
+ "\n" + cmdPassword + " 加密密钥(可选),长度8位以上,如果没有该参数,不加密" | |
+ "\n" + cmdVersion + " 显示MCPTool版本号" | |
+ "\n例如:" | |
+ "\n写入:java -jar MCPTool.jar -path D:/test.apk -outdir ./ -contents googleplay;m360; -password 12345678" | |
+ "\n读取:java -jar MCPTool.jar -path D:/test.apk -password 12345678"; | |
if (args.length == 0 || args[0] == null || args[0].trim().length() == 0) { | |
System.out.println(help); | |
} else { | |
if (args.length > 0) { | |
if (args.length == 1 && cmdVersion.equals(args[0])) { | |
System.out.println("version: " + VERSION_1_1); | |
} else { | |
Map<String, String> argsMap = new LinkedHashMap<String, String>(); | |
for (int i = 0; i < args.length; i += 2) { | |
if (i + 1 < args.length) { | |
if (args[i + 1].startsWith("-")) { | |
throw new IllegalStateException("args is error, help: \n" + help); | |
} else { | |
argsMap.put(args[i], args[i + 1]); | |
} | |
} | |
} | |
System.out.println("argsMap = " + argsMap); | |
File path = argsMap.containsKey(cmdPath) ? new File(argsMap.get(cmdPath)) : null; | |
String parent = path == null? null : (path.getParent() == null ? "./" : path.getParent()); | |
File outdir = parent == null ? null : new File(argsMap.containsKey(cmdOutdir) ? argsMap.get(cmdOutdir) : parent); | |
String[] contents = argsMap.containsKey(cmdContents) ? argsMap.get(cmdContents).split(";") : null; | |
String password = argsMap.get(cmdPassword); | |
if (path != null) { | |
System.out.println("path: " + path); | |
System.out.println("outdir: " + outdir); | |
if (contents != null && contents.length > 0) { | |
System.out.println("contents: " + Arrays.toString(contents)); | |
} | |
System.out.println("password: " + password); | |
if (contents == null || contents.length == 0) { // 读取数据; | |
System.out.println("content: " + readContent(path, password)); | |
} else { // 写入数据; | |
String fileName = path.getName(); | |
int dot = fileName.lastIndexOf("."); | |
String prefix = fileName.substring(0, dot); | |
String suffix = fileName.substring(dot); | |
for (String content : contents) { | |
File target = new File(outdir, prefix + "_" + content + suffix); | |
if (nioTransferCopy(path, target)) { | |
write(target, content, password); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
System.out.println("time:" + (System.currentTimeMillis() - time)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 | |
Test1 1 meispassword | |
UmengChannel=google_Play | |
custom1=1 | |
Test2 1 meispassword | |
UmengChannel=yingyongbao | |
custom1=1 | |
Test3 1 meispassword | |
UmengChannel=yingyongbao | |
custom1=1 | |
Test4 1 meispassword | |
UmengChannel=yingyongbao | |
custom1=1 | |
Test5 1 meispassword | |
UmengChannel=yingyongbao | |
custom1=1 | |
Test6 1 meispassword | |
UmengChannel=yingyongbao | |
custom1=1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment