分享一种最简单的Android打渠道包的方法
2015-12-15 13:54
525 查看
因为APK其实就是ZIP的格式,所以,解压apk后,会看到里面有个META-INF目录。
由于META-INF目录并不会影响到APK的签名和运行,所以我们可以在META-INF目录里添加一个空文件,
不同的渠道就添加不同的空文件,文件名代表不同的渠道。
代码是java写的:
[java] view
plaincopy
public class Tool {
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 String source_path;
private static final String channel_file_name = "channel_list.txt";
private static final String channel_flag = "channel_";
public static void main(String[] args) throws Exception {
if (args.length <= 0) {
System.out.println("请输入文件路径作为参数");
return;
}
final String source_apk_path = args[0];//main方法传入的源apk的路径,是执行jar时命令行传入的,不懂的往下看。
int last_index = source_apk_path.lastIndexOf("/") + 1;
source_path = source_apk_path.substring(0, last_index);
final String source_apk_name = source_apk_path.substring(last_index, source_apk_path.length());
System.out.println("包路径:" + source_path);
System.out.println("文件名:" + source_apk_name);
ArrayList<String> channel_list = getChannelList(source_path + channel_file_name);
final String last_name = ".apk";
for (int i = 0; i < channel_list.size(); i++) {
final String new_apk_path = source_path + source_apk_name.substring(0, source_apk_name.length() - last_name.length()) //
+ "_" + channel_list.get(i) + last_name;
copyFile(source_apk_path, new_apk_path);
changeChannel(new_apk_path, channel_flag + channel_list.get(i));
}
}
/**
* 修改渠道号,原理是在apk的META-INF下新建一个文件名为渠道号的文件
*/
public static boolean changeChannel(final String zipFilename, final String channel) {
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 {
Files.createFile(newChannel);
}
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();
}
}
}
1、新建一个java工程,把上面的代码复制进去。
2、对着这个类点右键,选择Export-java-Runnable JAR file
3、在Launch configuration中,选择你所要导出的类(如果这里不能选择,那么你要run一下你的工程,run成功了才能选择你要导为jar的类),
假设导出的jar的名字是apktool.jar
然后在命令行输入:
[plain] view
plaincopy
java -jar /Users/company/Documents/apk/apktool.jar /Users/company/Documents/apk/test.apk
这里我建议直接cd到目录下再写命令,不然可能会出错,为啥?因为我错了。。。
我用的mac电脑,路径和windows不一样,上面的路径都是拖拽进命令行的。
[plain] view
plaincopy
/Users/company/Documents/apk/apktool.jar 表示jar包所在路径;
[plain] view
plaincopy
/Users/company/Documents/apk/test.apk表示你源apk路径,这个是作为命令行参数传入main方法的。
test.apk就是你已经打包成功的一个apk,就是源apk,在你源apk的基础上生成渠道包。
channel_list.text一定要和这个源apk在同一个目录下。
比如channel_list.text里面的数据结构如下:
[plain] view
plaincopy
360
xiaomi
anzhi
baidu
运行命令后,你会发现在channel_list.txt和源apk目录下,会生成你想要的渠道包。
你可以把扩展名改为.zip,然后解压看看是否在META-INF目录下生成你想要的渠道名文件。
最后,就是读取这个渠道标识了,代码是写在Android工程里的,代码如下:
[java] view
plaincopy
private static String channel = null;
public static String getChannel(Context context) {
if (channel != null) {
return channel;
}
final String start_flag = "META-INF/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)) {
channel = entryName.replace(start_flag, "");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (channel == null || channel.length() <= 0) {
channel = "guanwang";//读不到渠道号就默认是官方渠道
}
return channel;
}
如果你用的友盟统计,可以在主Activity里这么写:AnalyticsConfig.setChannel("获取到的渠道");
由于META-INF目录并不会影响到APK的签名和运行,所以我们可以在META-INF目录里添加一个空文件,
不同的渠道就添加不同的空文件,文件名代表不同的渠道。
代码是java写的:
[java] view
plaincopy
public class Tool {
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 String source_path;
private static final String channel_file_name = "channel_list.txt";
private static final String channel_flag = "channel_";
public static void main(String[] args) throws Exception {
if (args.length <= 0) {
System.out.println("请输入文件路径作为参数");
return;
}
final String source_apk_path = args[0];//main方法传入的源apk的路径,是执行jar时命令行传入的,不懂的往下看。
int last_index = source_apk_path.lastIndexOf("/") + 1;
source_path = source_apk_path.substring(0, last_index);
final String source_apk_name = source_apk_path.substring(last_index, source_apk_path.length());
System.out.println("包路径:" + source_path);
System.out.println("文件名:" + source_apk_name);
ArrayList<String> channel_list = getChannelList(source_path + channel_file_name);
final String last_name = ".apk";
for (int i = 0; i < channel_list.size(); i++) {
final String new_apk_path = source_path + source_apk_name.substring(0, source_apk_name.length() - last_name.length()) //
+ "_" + channel_list.get(i) + last_name;
copyFile(source_apk_path, new_apk_path);
changeChannel(new_apk_path, channel_flag + channel_list.get(i));
}
}
/**
* 修改渠道号,原理是在apk的META-INF下新建一个文件名为渠道号的文件
*/
public static boolean changeChannel(final String zipFilename, final String channel) {
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 {
Files.createFile(newChannel);
}
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();
}
}
}
1、新建一个java工程,把上面的代码复制进去。
2、对着这个类点右键,选择Export-java-Runnable JAR file
3、在Launch configuration中,选择你所要导出的类(如果这里不能选择,那么你要run一下你的工程,run成功了才能选择你要导为jar的类),
假设导出的jar的名字是apktool.jar
然后在命令行输入:
[plain] view
plaincopy
java -jar /Users/company/Documents/apk/apktool.jar /Users/company/Documents/apk/test.apk
这里我建议直接cd到目录下再写命令,不然可能会出错,为啥?因为我错了。。。
我用的mac电脑,路径和windows不一样,上面的路径都是拖拽进命令行的。
[plain] view
plaincopy
/Users/company/Documents/apk/apktool.jar 表示jar包所在路径;
[plain] view
plaincopy
/Users/company/Documents/apk/test.apk表示你源apk路径,这个是作为命令行参数传入main方法的。
test.apk就是你已经打包成功的一个apk,就是源apk,在你源apk的基础上生成渠道包。
channel_list.text一定要和这个源apk在同一个目录下。
比如channel_list.text里面的数据结构如下:
[plain] view
plaincopy
360
xiaomi
anzhi
baidu
运行命令后,你会发现在channel_list.txt和源apk目录下,会生成你想要的渠道包。
你可以把扩展名改为.zip,然后解压看看是否在META-INF目录下生成你想要的渠道名文件。
最后,就是读取这个渠道标识了,代码是写在Android工程里的,代码如下:
[java] view
plaincopy
private static String channel = null;
public static String getChannel(Context context) {
if (channel != null) {
return channel;
}
final String start_flag = "META-INF/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)) {
channel = entryName.replace(start_flag, "");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (zipfile != null) {
try {
zipfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (channel == null || channel.length() <= 0) {
channel = "guanwang";//读不到渠道号就默认是官方渠道
}
return channel;
}
如果你用的友盟统计,可以在主Activity里这么写:AnalyticsConfig.setChannel("获取到的渠道");
相关文章推荐
- 【转】 Pro Android学习笔记(九一):了解Handler(5):组件生命
- 【转】 Pro Android学习笔记(九十):了解Handler(4):Worker线程
- 【转】 Pro Android学习笔记(八九):了解Handler(3):延迟执行小例子
- 【转】 Pro Android学习笔记(八八):了解Handler(2):什么是Handler
- 【转】 Pro Android学习笔记(八七):了解Handler(1):组件和线程
- 【转】 Pro Android学习笔记(八六):了解Package(5):使用lib
- 百度地图 Android SDK - 标注(Marker)的基本使用
- 【转】 Pro Android学习笔记(八五):了解Package(4):lib项目
- 【转】 Pro Android学习笔记(八四):了解Package(3):包间数据共享
- 【转】 Pro Android学习笔记(八三):了解Package(2):包签名过程
- 【转】 Pro Android学习笔记(八二):了解Package(1):包和进程
- 【转】 Pro Android学习笔记(八一):服务(6):复杂数据Parcel
- 【转】 Pro Android学习笔记(八十):服务(5):访问远程服务
- Android draw、onDraw、dispatchDraw、invalidate、computeScroll 一些简要说明
- 【转】 Pro Android学习笔记(七九):服务(4):远程服务的实现
- 【转】 Pro Android学习笔记(七八):服务(3):远程服务:AIDL文件
- 【转】 Pro Android学习笔记(七七):服务(2):Local Service
- 【转】 Pro Android学习笔记(七六):服务(1):local和remote
- Android M中 JNI的入门学习
- android数据缓存