文档章节

一种动态写入apk数据的方法(用于用户关系绑定、添加渠道号等)

o
 osc_gu9d45li
发布于 2019/04/04 19:17
字数 1310
阅读 5
收藏 0

精选30+云产品,助力企业轻松上云!>>>

背景:

正在开发的APP需要记录业务员与客户的绑定关系。具体应用场景如下:

由流程图可知,并没有用户填写业务人员信息这一步,因此在用户下载的APP中就已经携带了业务人员的信息。

由于业务人员众多,不可能针对于每一个业务人员单独生成一个安装包,于是就有了动态修改APP安装包的想法。

原理:

Android使用的apk包的压缩方式是zip,与zip有相同的文件结构(zip文件结构见zip文件格式说明),在zip的EOCD区域中包含一个Comment区域。

如果我们能够正确修改该区域,就可以在不破坏压缩包、不重新打包的前提下快速给apk文件写入自己想要的数据。

apk默认情况下没有Comment,所以Comment length的short两个字节为0,我们需要把这个值修改为我们的Comment长度,并把Comment追加到后面即可。

整体过程:

服务端实现:

实现下载接口:

 1 @RequestMapping(value = "/download", method = RequestMethod.GET)
 2 public void download(@RequestParam String token, HttpServletResponse response) throws Exception {
 3 
 4     // 获取干净的apk文件
 5     Resource resource = new ClassPathResource("app-release.apk");
 6     File file = resource.getFile();
 7 
 8     // 拷贝一份新文件(在新文件基础上进行修改)
 9     File realFile = copy(file.getPath(), file.getParent() + "/" + new Random().nextLong() + ".apk");
10 
11     // 写入注释信息
12     writeApk(realFile, token);
13 
14     // 如果文件名存在,则进行下载
15     if (realFile != null && realFile.exists()) {
16         // 配置文件下载
17         response.setHeader("content-type", "application/octet-stream");
18         response.setContentType("application/octet-stream");
19         // 下载文件能正常显示中文
20         response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realFile.getName(), "UTF-8"));
21 
22         // 实现文件下载
23         byte[] buffer = new byte[1024];
24         FileInputStream fis = null;
25         BufferedInputStream bis = null;
26         try {
27             fis = new FileInputStream(realFile);
28             bis = new BufferedInputStream(fis);
29             OutputStream os = response.getOutputStream();
30             int i = bis.read(buffer);
31             while (i != -1) {
32                 os.write(buffer, 0, i);
33                 i = bis.read(buffer);
34             }
35             System.out.println("Download successfully!");
36         } catch (Exception e) {
37             System.out.println("Download failed!");
38         } finally {
39             if (bis != null) {
40                 try {
41                     bis.close();
42                 } catch (IOException e) {
43                     e.printStackTrace();
44                 }
45             }
46             if (fis != null) {
47                 try {
48                     fis.close();
49                 } catch (IOException e) {
50                     e.printStackTrace();
51                 }
52             }
53         }
54     }
55 }

拷贝文件:

 1 private File copy(String source, String target) {
 2     Path sourcePath = Paths.get(source);
 3     Path targetPath = Paths.get(target);
 4 
 5     try {
 6         return Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING).toFile();
 7     } catch (IOException e) {
 8         e.printStackTrace();
 9     }
10     return null;
11 }

往apk中写入信息:

 1 public static void writeApk(File file, String comment) {
 2     ZipFile zipFile = null;
 3     ByteArrayOutputStream outputStream = null;
 4     RandomAccessFile accessFile = null;
 5     try {
 6         zipFile = new ZipFile(file);
 7 
 8         // 如果已有comment,则不进行写入操作(其实可以先擦除再写入)
 9         String zipComment = zipFile.getComment();
10         if (zipComment != null) {
11             return;
12         }
13 
14         byte[] byteComment = comment.getBytes();
15         outputStream = new ByteArrayOutputStream();
16 
17         // comment内容
18         outputStream.write(byteComment);
19         // comment长度(方便读取)
20         outputStream.write(short2Stream((short) byteComment.length));
21 
22         byte[] data = outputStream.toByteArray();
23 
24         accessFile = new RandomAccessFile(file, "rw");
25         accessFile.seek(file.length() - 2);
26 
27         // 重写comment实际长度
28         accessFile.write(short2Stream((short) data.length));
29         // 写入comment内容
30         accessFile.write(data);
31     } catch (IOException e) {
32         e.printStackTrace();
33     } finally {
34         try {
35             if (zipFile != null) {
36                 zipFile.close();
37             }
38             if (outputStream != null) {
39                 outputStream.close();
40             }
41             if (accessFile != null) {
42                 accessFile.close();
43             }
44         } catch (Exception e) {
45             e.printStackTrace();
46         }
47     }
48 }

其中:

1 private static byte[] short2Stream(short data) {
2     ByteBuffer buffer = ByteBuffer.allocate(2);
3     buffer.order(ByteOrder.LITTLE_ENDIAN);
4     buffer.putShort(data);
5     buffer.flip();
6     return buffer.array();
7 }

客户端实现:

获取comment信息并写入TextView:

 1 @Override
 2 protected void onCreate(Bundle savedInstanceState) {
 3     super.onCreate(savedInstanceState);
 4     setContentView(R.layout.activity_main);
 5 
 6     TextView textView = findViewById(R.id.tv_world);
 7 
 8     // 获取包路径(安装包所在路径)
 9     String path = getPackageCodePath();
10     // 获取业务员信息
11     String content = readApk(path);
12 
13     textView.setText(content);
14 }

读取comment信息:

 1 public String readApk(String path) {
 2     byte[] bytes = null;
 3     try {
 4         File file = new File(path);
 5         RandomAccessFile accessFile = new RandomAccessFile(file, "r");
 6         long index = accessFile.length();
 7 
 8         // 文件最后两个字节代表了comment的长度
 9         bytes = new byte[2];
10         index = index - bytes.length;
11         accessFile.seek(index);
12         accessFile.readFully(bytes);
13 
14         int contentLength = bytes2Short(bytes, 0);
15 
16         // 获取comment信息
17         bytes = new byte[contentLength];
18         index = index - bytes.length;
19         accessFile.seek(index);
20         accessFile.readFully(bytes);
21 
22         return new String(bytes, "utf-8");
23     } catch (FileNotFoundException e) {
24         e.printStackTrace();
25     } catch (IOException e) {
26         e.printStackTrace();
27     }
28     return null;
29 }

其中:

1 private static short bytes2Short(byte[] bytes, int offset) {
2     ByteBuffer buffer = ByteBuffer.allocate(2);
3     buffer.order(ByteOrder.LITTLE_ENDIAN);
4     buffer.put(bytes[offset]);
5     buffer.put(bytes[offset + 1]);
6     return buffer.getShort(0);
7 }

遇到的问题:

修改完comment之后无法安装成功:

最开始遇到的就是无法安装的问题,一开始以为是下载接口写的有问题,经过多次调试之后发现是修改完comment之后apk就无法安装了。

查询谷歌官方文档可知

因此,只需要打包的时候签名方式只选择V1不选择V2就行。

多人同时下载抢占文件导致的线程安全问题:

这个问题暂时的考虑方案是每当有下载请求就会先复制一份,将复制的文件进行修改,客户端下载成功再删除。

但是未做测试,不知是否会产生问题。

思考:

  • 服务端和客户端不一样,服务端的任何请求都需要考虑线程同步问题;
  • 既然客户端可以获取到安装包,则其实也可以通过修改包名来进行业务人员信息的传递;
  • 利用该方法可以传递其他数据用来实现其他一些功能,不局限于业务人员的信息。

 

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Android的多渠道打包|SquirrelNote

系列文章: Android的反编译和代码混淆 Android的打包签名 [Android的多渠道打包 前言 本篇包括以下内容: 多渠道打包概述 友盟的多渠道打包 美团的多渠道打包 360的多渠道打包 多渠道打包概...

跳动的松鼠
2017/11/21
0
0
Android渠道打包工具--packer-ng-plugin

packer-ng-plugin 是下一代Android渠道打包工具Gradle插件,支持极速打包,1000个渠道包只需要5秒钟,速度是 gradle-packer-plugin 的1000倍以上,可方便的用于CI系统集成,支持自定义输出目...

sikkx
2015/12/31
4K
3
你知道吗?多渠道不打包也能统计!

什么是多渠道打包? BD为了统计营销推广的效果,需要在APK里写入推广渠道,去弄清用户、广告销售是来源于哪个渠道,如是来源于应用宝、百度手机助手这样的应用商店,还是广点通、百度联盟这样...

仙仙那个aby
2018/07/19
35
0
Android安卓批量打包运营 安卓渠道追踪思路是什么

当我们在开发完成一个APP或者是一款游戏时,运营人员有不同的推广渠道去进行推广。并且为了更好的优化运营方式和方案,会对APP进行一个渠道的分析和统计。但是由于安卓渠道广泛,各大应用商店...

TOMdaBABY
2018/07/23
3
0
Android Studio 多渠道打包

友盟的多渠道打包 在AndroidManifest中添加<meta-data 然后修改app级别的gradle 然后选择自己的证书, 打包就可以了 , app文件夹下就会有对应渠道的Apk包, 如果要增加渠道只需在上方的代码中添...

lanyu96
2018/12/29
19
0

没有更多内容

加载失败,请刷新页面

加载更多

图解ARP协议(二)ARP***原理与实践

一、ARP***概述 在上篇文章里,我给大家普及了ARP协议的基本原理,包括ARP请求应答、数据包结构以及协议分层标准,今天我们继续讨论大家最感兴趣的话题:ARP***原理是什么?通过ARP***可以做...

osc_91g5cdgs
21分钟前
0
0
shell进度条实现

#!/bin/bashb=''i=0while [ $i -le  100 ]do    printf "progress:[%-50s]%d%%\r" $b $i    sleep 0.1    i=`expr 2 + $i`            b=#$b......

osc_npw5uz1o
22分钟前
13
0
通过ssh实现登录服务器脚本

版本v1 #!/bin/bash########################author: Bovin########################show all host infos of serverList.txtif [[ -f $HOME/.serverList.txt ]]then  hos......

osc_lt2jwwhb
24分钟前
8
0
VMware Fusion下Centos联网

1.VMware Fusion设置选择“网络适配器” 2.“连接我的网络适配器”选择“与我的mac共享” 3.编辑centos的ip配置文件 [root@Centos ~]# more /etc/sysconfig/network-scripts/ifcfg-eth0D...

osc_pg5rp78i
25分钟前
6
0
Kickstart配置文件参数详解

kickstart是什么? KickStart是一种无人值守的安装方法。它的工作原理时在安装过程中记录典型的需要人工干预填写的各种参数,并生成一个名为ks.cfg的文件。如果在安装过程中(不只局限于生成K...

osc_r9yyhhqz
26分钟前
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部