您的位置:首页 > 移动开发 > Android开发

android APK动态添加数据

2017-07-14 01:47 323 查看

前言:

前段时间遇到个需求:
1.需要在不安装apk的前提下,获取当前apk的渠道信息。
2.用户在特定的页面下载的apk,需要跳到与app中对应的页面,从而让app的用户体验更好。
第一个需求好处理,只需要解析xml文件就可以获取到渠道信息了,如果不清楚的可以看我这篇博客android 解析未安装apk中的AndroidManifest.xml以及系统源码分析
第二个需求就不好处理,虽然也可以通过写入androidManifest.xml,但是如果这样的页面过多的话,就需要打很多个包,可以说是一种极端的处理方法。后来又想到过插件化的方式来处理,但是一想,这样也不太好,会导致内部逻辑复杂,同样的服务端要准备多个包来供客户端实现插件化。后来通过公司的一个技术文档了解到,原来zip文件能动态写入信息,我们的apk也就是一个zip文件,它有个Comment属性能动态往里面写入信息,这样就能在不破坏apk的结构的基础上,动态添加需要跳转的页面的信息。那我们只需要把我们的渠道包给到后台,然后后台往里面添加数据就可以了。
在这之前需要先了解下zip的文件结构:
这里有个链接--- ZIP文件结构
它的整体结构是这样的:
Overall .ZIP file format:

[local file header 1]
[file data 1]
[data descriptor 1]
.
.
.
[local file header n]
[file data n]
[data descriptor n]
[archive decryption header] (EFS)
[archive extra data record] (EFS)
[central directory]
[zip64 end of central directory record]
[zip64 end of central directory locator]
[end of central directory record]


基本上是由  (文件头+数据+目录结构)....+整体目录结构+末尾信息  组成的。这里就不详细说明了,网上有许多关于Zip文件结构的分析。而我们的Coment就.end of central directory record 中。

end of central directory record:

End of central directory record:

end of central dir signature    4 bytes  (0x06054b50)
number of this disk             2 bytes
number of the disk with the
start of the central directory  2 bytes
total number of entries in the
central directory on this disk  2 bytes
total number of entries in
the central directory           2 bytes
size of the central directory   4 bytes
offset of start of central
directory with respect to
the starting disk number        4 bytes
.ZIP file comment length        2 bytes
.ZIP file comment       (variable size)


这里只需要看最后两个字段,zip文件comment的长度,占2个字节,而我们的short类型也是占2个字节,所以comment的长度不能超过short的最大长度。并且每个zip文件这个comment信息在没有添加之前是为null的,所以不需要担心我们打包的apk中会有comment信息,并不会影响我们添加数据进去的完整性。

整个demo就两个类,一个是AddMessage,一个是GetMessage
先看下我们的AddMessage

public class AddMessage {

public static void main(String[] args) {
// TODO Auto-generated method stub
byte[] bytes = new byte[2];
ZipFile zipFile = null;
ByteArrayOutputStream baos = null;
RandomAccessFile ranFile = null;
File file = null;
try {
//获取apk文件
file = new File("XXX.apk");
zipFile = new ZipFile(file);
//直接拿到comment,这个方法仅仅在JAVA7中存在
//而android4.4之前是不支持的,所以我们只能根据comment的长度来获取comment
String zipComment = zipFile.getComment();
System.out.println("zipComment : " + zipComment);
if(zipComment != null){
return;
}

String comment = "123456789";
byte[] byteComment = comment.getBytes();
baos = new ByteArrayOutputStream();
//这里值得注意的是,我们在客户端获取comment的时候并不知道comment有多长
//所以在comment的末尾也把我们写的comment的长度追加进去
//这样在客户端获取comment信息的时候,我们把最后的两字节信息获取到就是我们的comment长度信息了
baos.write(byteComment);
baos.write(shortToByte((short)byteComment.length));

byte[] data = baos.toByteArray();
ranFile = new RandomAccessFile(file, "rw");
ranFile.seek(file.length() - 2);
ranFile.write(shortToByte((short) data.length));
ranFile.write(data);

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
if(zipFile!=null){
zipFile.close();
}

if(baos != null){
baos.close();
}

if(ranFile!=null){
ranFile.close();
}

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

//short转成byte[]
public static byte[] shortToByte(short number) {
int temp = number;
byte[] b = new byte[2];
for (int i = 0; i < b.length; i++) {
b[i] = new Integer(temp & 0xff).byteValue();// 将最低位保存在最低位
temp = temp >> 8; // 向右移8位
}
return b;
}

}


然后就是我们GetMessage的代码:

public class GetMessage {

public static void main(String[] args) {
// TODO Auto-generated method stub
File file = null;
file = new File("XXX.apk");
getComment(file);
}

public static String getComment(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);
//拿到我们追加到comment中的comment长度
int contentLength = byteToShort(bytes);

bytes = new byte[contentLength];
index = index - bytes.length;
accessFile.seek(index);
accessFile.readFully(bytes);
System.out.println("comment-String : " + new String(bytes, "utf-8"));
return new String(bytes, "utf-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

public static short byteToShort(byte[] b) {
short s = 0;
short s0 = (short) (b[0] & 0xff);// 最低位
short s1 = (short) (b[1] & 0xff);
s1 <<= 8;
s = (short) (s0 | s1);
return s;
}

}


最后得到的comment也就是我们写入的123456789,具体该注意的地方已经在代码中写出来了,所以这里就不做详细说明了。

zip的动态写入数据就说到这了,这里值得一说的是,真正在应用中使用的时候,我们的apk的路径可以通过context.getPackageCodePath()获取到,这是在安装的时候原apk复制到我们/data/app目录下的复制文件路径,所以并不要担心用户在安装之后把我们的apk删除了这种方法就没效果了。当然它也能完成我们的需求1,这样在不需要解析androidManifest.xml文件也能获取到apk渠道信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: