android APK动态添加数据

来源:互联网 发布:安心360定位软件 编辑:程序博客网 时间:2024/05/23 13:10

前言:

前段时间遇到个需求:
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 stubbyte[] 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的长度来获取commentString 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 blocke.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 blocke.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 stubFile 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渠道信息。

原创粉丝点击