- 背景
- 原理
- 实现
- package_ng在as中配置
- 使用特别注意事项
背景
我们在日常使用应用可能会遇到以下场景。
场景1:多渠道打包,算下公司渠道有100多个,每次发版打包就要花掉1个多小时。发版中出现bug需要及时修复,就需要重复打包。严重影响上线效率。那么有没有一种技术能代码gradle脚本,实现快速打包呢?答案是肯定的
Package_ng链接地址:
https://github.com/mcxiaoke/packer-ng-plugin
原理
android使用的apk包的压缩方式是zip,与zip有相同的文件结构,在zip的Central directory file header中包含一个File comment区域,可以存放一些数据。File comment是zip文件如果可以正确的修改这个部分,就可以在不破坏压缩包、不用重新打包的的前提下快速的给apk文件写入自己想要的数据。
comment是在Central directory file header末尾储存的,可以将数据直接写在这里,下表是header末尾的
由于数据是不确定的,我们无法知道comment的长度,从表中可以看到zip定义comment的长度的位置在comment之前,所以无法从zip中直接获取comment的长度。这里我们需要自定义comment的长度,在自定义comment内容的后面添加一个区域储存comment的长度,结构如下图。
这里可以将一个固定的结构写在comment中,然后根据自定义的长度分区获取每个部分的内容,还可以添加其它数据,如校验码、版本等。
具体源码介绍:
查看master中的Pack_ng 类,核心逻辑都在这个类中。
下面给出PACKER_Ng中的类结构
上面圈红的类就是具体的读写marker内容的类
实现
1.将数据写入comment
这一部分可以在本地进行,需要定义一个长度为2的byte[]来储存comment的长度,直接使用Java的api就可以把comment和comment的长度写到apk的末尾,代码如下。
public static void writeApk(File file, String comment) {
ZipFile zipFile = null;
ByteArrayOutputStream outputStream = null;
RandomAccessFile accessFile = null;
try {
zipFile = new ZipFile(file);
String zipComment = zipFile.getComment();
if (zipComment != null) {
return;
}
byte[] byteComment = comment.getBytes();
outputStream = new ByteArrayOutputStream();
outputStream.write(byteComment);
outputStream.write(short2Stream((short) byteComment.length));
byte[] data = outputStream.toByteArray();
accessFile = new RandomAccessFile(file, "rw");
accessFile.seek(file.length() - 2);
accessFile.write(short2Stream((short) data.length));
accessFile.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (zipFile != null) {
zipFile.close();
}
if (outputStream != null) {
outputStream.close();
}
if (accessFile != null) {
accessFile.close();
}
} catch (Exception e) {
}
}
}
2.读取apk包中的comment数据
首先获取apk的路径,通过context中的getPackageCodePath()方法就可以获取,代码如下。
public static String getPackagePath(Context context) {
if (context != null) {
return context.getPackageCodePath();
}
return null;
}
获取路径之后就可以读取comment的内容了,这里不能直接使用ZipFile中的getComment()方法直接获取comment,因为这个方法是Java7中的方法,在android4.4之前是不支持Java7的,所以我们需要自己去读取apk文件中的comment。首先根据之前自定义的结构,先读取写在最后的comment的长度,根据这个长度,才可以获取真正comment的内容,代码如下。
public static String readApk(File file) {
byte[] bytes = null;
try {
RandomAccessFile accessFile = new RandomAccessFile(file, "r");
long index = accessFile.length();
bytes = new byte[2];
index = index - bytes.length;
accessFile.seek(index);
accessFile.readFully(bytes);
int contentLength = stream2Short(bytes, 0);
bytes = new byte[contentLength];
index = index - bytes.length;
accessFile.seek(index);
accessFile.readFully(bytes);
return new String(bytes, "utf-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
这里的stream2Short()和short2Stream()参考了MultiChannelPackageTool中的方法。
然后就是断点查看了。看结果28s 时间打了 100 +个渠道包。(mama 再也不担心我的多渠道打包!!!)
3.package_ng在as中配置
Eg:这里以umeng 服务为样本
一般卸载app的入口处,Application中
/设置渠道/
market = PackerNg.getMarket(context); //读取apk中的渠道
AnalyticsConfig.setChannel(market); //调用umeng的api 进行渠道设置
Build.gradle中的配置 主要配置Pg依赖
项目 root 下的Build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.mcxiaoke.gradle:packer-ng:1.0.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
app root 下的build.gradle文件
apply plugin: 'packer'
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile files('libs/android-support-v4.jar')
compile files('libs/gson-2.2.2.jar')
compile files('libs/nineoldandroids.jar')
compile files('libs/systembartint-1.0.4.jar')
compile files('libs/umeng-analytics-v5.5.3.jar')
compile files('libs/umeng-update-v2.6.0.1.jar')
compile files('libs/universal-image-loader-1.9.3.jar')
compile 'com.mcxiaoke.gradle:packer-helper:1.0.8'
compile 'de.greenrobot:greendao:1.3.7'
compile project(':PushSDK')
}
使用特别注意事项
近期使用packet_ng 打渠道包,出现360应用市场统计不到渠道信息问题,具体原因是由于 360加固导致。从新查看的原作者文章看到有具体说明,一定要看完整文档!!!!!
本人使用packet_ng jar 进行命令行打包,发现打出来的包渠道信息乱码,最后选择使用Helper 类中的api (writer reader)
@Test
public void readZipComment() {
try {
String name = "app-release1_252_jiagu_sign.apk";
File file = new File("E:\\2.5.5\\app\\build\\outputs\\apk\\" + name + "");
String mart = PackerNg.Helper.readMarket(file);
Log.d("readZipComment", mart);
} catch (IOException e) {
Log.d("", "readZipComment: " + e.getMessage());
}
}
@Test
public void WriterZipComment() {
try {
String name = "app-release1_252_jiagu_sign.apk";
String channelName = "360dev";
File file = new File("E:\\2.5.5\\app\\build\\outputs\\apk\\" + name + "");
PackerNg.Helper.writeMarket(file, "360dev");
Log.d("readZipComment", channelName);
} catch (IOException e) {
Log.d("", "readZipComment: " + e.getMessage());
}
}
进行读写,不会出现乱码问题的,该问题已经联系作者,日后更新,特此更正
具体详细细节可以参照package_ng 作者介绍。
快速打包原理:
http://pingguohe.net/2016/03/21/Dynimac-write-infomation-into-apk.html?utm_source=tuicool&utm_medium=referral