Android更新优化 - 增量更新是如何节省用户时间和流量的

增量更新和全量更新

我想玩过大型手游的人都知道,手游的安装包非常大,因为资源图片众多。而你每次更新都把所有文件都更新下来,是非常耗时的,对吧。耗时是一个方面,有些人在户外开的是移动网络,动不动就几个G。这对于用户来说,是一笔不小的浪费。那么就引入了我们今天的话题,增量更新。

说起增量更新,那么就不得不提及一下与它相对的概念,全量更新。全量更新,是大多数,乃至几乎所有app的更新方式,即把整个安装包都下载下来。那既然大家都用全量更新,那必定有其存在的合理之处,存在即合理,对吧。全量更新流程非常简洁,而增量更新流程相对比较复杂,且一般应用的安装包不是很大,使用增量更新就像高射炮打苍蝇,这是一个方面,也是主要的方面。还有一个方面是因为,增量更新有一定的门槛,需要有一定的NDK和C语言的基础。

增量更新的流程(服务端)

增量更新无需下载最新的完整版本的apk文件,而只需下载本地版本升到最新版本的补丁文件,然后使用本地正在运行的app的merge补丁的功能合并成一个完整的最新版本的apk文件,最后手动将这个合并好的apk文件安装。

安装包1.1版本 = 安装包1.0版本 merge 1.0版本升1.1版本的补丁文件

1.0版本升1.1版本的补丁文件 = 安装包1.1版本 diff 安装包1.0版本

我们补丁拆分的过程是后端完成的,而补丁合并的过程是Android端完成的。后端需要使用云服务器下载一个类似于bsdiff-4.3.tar.gz的压缩包,然后解压并使用其命令进行补丁文件的拆分。

wget https://src.fedoraproject.org/lookaside/pkgs/bsdiff/bsdiff-4.3.tar.gz/e6d812394f0e0ecc8d5df255aa1db22a/bsdiff-4.3.tar.gz
tar zxvf bsdiff-4.3.tar.gz
cd bsdiff-4.3

然后编译,运行,结束

你以为事情会有如此顺利?

vim Makefile

截屏2023-07-18 18.41.51.png
注意看.ifndef WITHOUT_MAN这一行和.endif这一行,输入i进入insert模式,光标移动到这两行最前面,按下tab键缩进下格式,然后esc,退出insert模式到命令模式,输入冒号“:”,最后输入wq!回车保存退出。

截屏2023-07-18 18.46.37.png
最后make进行编译。

make

截屏2023-07-18 18.48.14.png
这样我们就得到了两个绿色的可执行文件。

bsdiff [oldfile] [newfile] [patchfile]

最后我们就可以正常执行文件拆分命令了。比如我们电脑本地的文件上传。

sftp root@dorachat.com

先使用sftp登录服务器,root@后面改成你服务器的公网ip,不要跟我的一样,你登不上来。

put old_apk_1.0.apk
put new_apk_1.1.apk

bsdiff命令的使用格式是这样。

bsdiff [oldfile] [newfile] [patchfile]

那我们就执行。

./bsdiff old_apk_1.0.apk new_apk_1.1.apk patch_1.0_1.1.patch

执行完成就会在当前目录生成patch_1.0_1.1.patch补丁文件了,这个路径要根据你apk实际存在服务器的路径,要不然也是没法读取的。最后将补丁文件的下载地址通过接口返回给客户端,服务端的事情就做完了。

增量更新的流程(Android端)

Android端要使用到NDK,所以,你得先确保安装好了NDK和CMake的包。

截屏2023-07-18 18.59.45.png

截屏2023-07-18 19.00.42.png

截屏2023-07-18 19.01.26.png

然后写一个PatchUtils工具类。

package com.dorachat.dorachat.util;public class PatchUtils {public native static int mergePatch(String oldApkPath, String newApkPath, String patchPath);static {System.loadLibrary("patchUpdate");}
}

截屏2023-07-18 19.03.38.png
然后将这些bzip相关的c文件扔进cpp文件夹,再修改app模块的build.gradle.kts,也可能是build.gradle,看你用的什么构建脚本了。

android {splits {abi {isEnable = truereset()include("x86", "x86_64", "armeabi-v7a", "arm64-v8a")// select ABIs to build APKs forisUniversalApk = true// generate an additional APK that contains all the ABIs}}externalNativeBuild {cmake {path = file("src/main/cpp/CMakeLists.txt")version = "3.18.1"}}
}

CMakeLists.txt的写法。


# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.18.1)# Declares and names the project.project("app")# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.find_library( # Sets the name of the path variable.log-liblog)# 不要加,跟增量更新没有关系
add_library(cryptoMsgSHAREDRSAUtils.hRSAUtils.cpp)
# 指定要编译成动态库的c文件
add_library(patchUpdateSHAREDbspatch.cbzip2/blocksort.cbzip2/bzip2.cbzip2/bzip2recover.cbzip2/bzlib.cbzip2/compress.cbzip2/crctable.cbzip2/decompress.cbzip2/dlltest.cbzip2/huffman.cbzip2/mk251.cbzip2/randtable.cbzip2/spewG.cbzip2/unzcrash.c
)#include_directories(src/main/cpp/bzip2)#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")#include_directories(src/main/cpp/)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library.#   cryptoMsgpatchUpdate${log-lib})

命令的入口代码bspatch.c。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <jni.h>
#include <err.h>
#include <string.h>
#include "bzip2/bzlib.h"static off_t offtin(u_char *buf) {off_t y;y = buf[7] & 0x7F;y = y * 256;y += buf[6];y = y * 256;y += buf[5];y = y * 256;y += buf[4];y = y * 256;y += buf[3];y = y * 256;y += buf[2];y = y * 256;y += buf[1];y = y * 256;y += buf[0];if (buf[7] & 0x80) y = -y;return y;
}int patchMethod(int argc, char *argv[]) {FILE *f, *cpf, *dpf, *epf;BZFILE *cpfbz2, *dpfbz2, *epfbz2;int cbz2err, dbz2err, ebz2err;int fd;ssize_t oldsize, newsize;ssize_t bzctrllen, bzdatalen;u_char header[32], buf[8];u_char *old, *new;off_t oldpos, newpos;off_t ctrl[3];off_t lenread;off_t i;if (argc != 4) errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]);/* Open patch file */if ((f = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);/*File format:0  8  "BSDIFF40"8  8  X16 8  Y24 8  sizeof(newfile)32 X  bzip2(control block)32+X   Y  bzip2(diff block)32+X+Y ???    bzip2(extra block)with control block a set of triples (x,y,z) meaning "add x bytesfrom oldfile to x bytes from the diff block; copy y bytes from theextra block; seek forwards in oldfile by z bytes".*//* Read header */if (fread(header, 1, 32, f) < 32) {if (feof(f))errx(1, "Corrupt patch\n");err(1, "fread(%s)", argv[3]);}/* Check for appropriate magic */if (memcmp(header, "BSDIFF40", 8) != 0)errx(1, "Corrupt patch\n");/* Read lengths from header */bzctrllen = offtin(header + 8);bzdatalen = offtin(header + 16);newsize = offtin(header + 24);if ((bzctrllen < 0) || (bzdatalen < 0) || (newsize < 0))errx(1, "Corrupt patch\n");/* Close patch file and re-open it via libbzip2 at the right places */if (fclose(f))err(1, "fclose(%s)", argv[3]);if ((cpf = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);if (fseeko(cpf, 32, SEEK_SET))err(1, "fseeko(%s, %lld)", argv[3],(long long) 32);if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);if ((dpf = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))err(1, "fseeko(%s, %lld)", argv[3],(long long) (32 + bzctrllen));if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);if ((epf = fopen(argv[3], "r")) == NULL)err(1, "fopen(%s)", argv[3]);if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))err(1, "fseeko(%s, %lld)", argv[3],(long long) (32 + bzctrllen + bzdatalen));if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);if (((fd = open(argv[1], O_RDONLY, 0)) < 0) ||((oldsize = lseek(fd, 0, SEEK_END)) == -1) ||((old = malloc(oldsize + 1)) == NULL) ||(lseek(fd, 0, SEEK_SET) != 0) ||(read(fd, old, oldsize) != oldsize) ||(close(fd) == -1))err(1, "%s", argv[1]);if ((new = malloc(newsize + 1)) == NULL) err(1, NULL);oldpos = 0;newpos = 0;while (newpos < newsize) {/* Read control data */for (i = 0; i <= 2; i++) {lenread = BZ2_bzRead(&cbz2err, cpfbz2, buf, 8);if ((lenread < 8) || ((cbz2err != BZ_OK) &&(cbz2err != BZ_STREAM_END)))errx(1, "Corrupt patch\n");ctrl[i] = offtin(buf);};/* Sanity-check */if (newpos + ctrl[0] > newsize)errx(1, "Corrupt patch\n");/* Read diff string */lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]);if ((lenread < ctrl[0]) ||((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))errx(1, "Corrupt patch\n");/* Add old data to diff string */for (i = 0; i < ctrl[0]; i++)if ((oldpos + i >= 0) && (oldpos + i < oldsize))new[newpos + i] += old[oldpos + i];/* Adjust pointers */newpos += ctrl[0];oldpos += ctrl[0];/* Sanity-check */if (newpos + ctrl[1] > newsize)errx(1, "Corrupt patch\n");/* Read extra string */lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]);if ((lenread < ctrl[1]) ||((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))errx(1, "Corrupt patch\n");/* Adjust pointers */newpos += ctrl[1];oldpos += ctrl[2];};/* Clean up the bzip2 reads */BZ2_bzReadClose(&cbz2err, cpfbz2);BZ2_bzReadClose(&dbz2err, dpfbz2);BZ2_bzReadClose(&ebz2err, epfbz2);if (fclose(cpf) || fclose(dpf) || fclose(epf))err(1, "fclose(%s)", argv[3]);/* Write the new file */if (((fd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0) ||(write(fd, new, newsize) != newsize) || (close(fd) == -1))err(1, "%s", argv[2]);free(new);free(old);return 0;
}JNIEXPORT jint JNICALL Java_com_dorachat_dorachat_util_PatchUtils_mergePatch(JNIEnv *env, jclass cls,jstring old_apk_path, jstring new_apk_path, jstring patch_path) {int argc = 4;char *argv[argc];argv[0] = "bspatch";argv[1] = (char *) ((*env)->GetStringUTFChars(env, old_apk_path, 0));argv[2] = (char *) ((*env)->GetStringUTFChars(env, new_apk_path, 0));argv[3] = (char *) ((*env)->GetStringUTFChars(env, patch_path, 0));int ret = patchMethod(argc, argv);(*env)->ReleaseStringUTFChars(env, old_apk_path, argv[1]);(*env)->ReleaseStringUTFChars(env, new_apk_path, argv[2]);(*env)->ReleaseStringUTFChars(env, patch_path, argv[3]);return ret;
}

bzip2的源文件,网上很多。比如这个
https://github.com/hongyangAndroid/BsDiff_And_Patch 。
提取本地apk文件可以使用这个方法。

    public static File extractApk(Context context) {ApplicationInfo applicationInfo = context.getApplicationContext().getApplicationInfo();String apkPath = applicationInfo.sourceDir;File apkFile = new File(apkPath);return apkFile;}

或者你直接使用
https://github.com/dora4/dora/blob/master/dora-mvvm/src/main/java/dora/util/ApkUtils.java 。

说到最后

最后聊一聊更新的具体逻辑。增量更新有固定数量法和固定业务法。你可以规定,相差版本小于等于3个,就采用增量更新,大于3个,直接走全量更新。此为固定数量法。固定业务法则是,版本分为大小版本号的更新,每个大版本必须走全量更新,而大版本中的小版本则采用增量更新。大版本采用强制更新,而小版本则可采用选择更新。每次更新到大版本,可能会再次拉取该版本的最终小版本的补丁文件发起二次更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/845064.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理·海明编码及其实验

前言&#xff1a;海明编码这一块在刚开始的时候没有弄懂&#xff0c;后面通过做实验、复习慢慢摸清了门道。在学习计算机组成原理的过程中&#xff0c;实验实践是很重要的&#xff0c;它会让你去搞清楚事情背后的原理&#xff0c;逼着你学会你没听懂的东西。这篇文章会从海明码…

Check Point 安全网关任意文件读取漏洞复现(CVE-2024-24919)

Check Point 安全网关任意文件读取漏洞复现(CVE-2024-24919) 1.漏洞描述 Check Point Security Gateways 是 Check Point Sofware 提供的一系列 网络安全Q解决方案。这些解决方案包括下一代防火墙(NGFW)、数据中心安全网关和 A1驱动的量子网关&#xff0c;旨在为企业提供针对…

@Value 读取环境变量配置

在项目开发过程中&#xff0c;有必要使用一些灰色规则&#xff08;即仅用于开发使用过程中的逻辑控制变量&#xff09;。 比如&#xff0c;本地开发中&#xff0c;一些业务逻辑需要调用第三方代码&#xff0c;但又在本地调不通&#xff0c;怎么办。只能通过 if(本地开发) {mock…

【开源】渔具租赁系统 JAVA+Vue.js+SpringBoot+MySQL

目录 一、项目介绍 1.1渔具档案模块 1.2渔具租赁模块 1.3渔具归还模块 1.4在线留言模块 二、项目截图 三、核心代码 一、项目介绍 Vue.jsSpringBoot前后端分离新手入门项目《渔具租赁系统》&#xff0c;包括渔具档案模块、渔具租赁模块、渔具归还模块、在线留言模块和部…

当新媒体运营开始说真话,这些道理你真的懂么?沈阳新媒体运营培训

运营新人&#xff0c;尤其是刚毕业、啥都不会的大学生&#xff0c;一定要认清的现实就是&#xff1a;虽然新媒体运营这个岗位门槛比较低&#xff0c;薪资也比较香&#xff0c;但绝不是养老型的工作。 平时大家还是很忙的&#xff0c;所以一定要摒弃学生思维&#xff0c;千万别…

02--nginx代理缓存

前言&#xff1a;比较常用的用法反向代理&#xff0c;和缓存的一些操作&#xff0c;用虚拟环境复刻出来&#xff0c;里面参数不用详细记录&#xff0c;用作复习&#xff0c;使用时直接查找即可。环境搭建过程参考前一篇文章nginx基础。 1、基础环境 IP角色作用192.168.189.143…

freertos初体验 - 在stm32上移植

1. 说明 freertos内核 非常精简&#xff0c;代码量也很少&#xff0c;官方也针对主流的编译器和内核准备好了移植文件&#xff0c;所以 freertos 的移植是非常简单的&#xff0c;很多工具&#xff08;例如CubeMX&#xff09;点点鼠标就可以生成一个 freertos 的工程&#xff0…

AquaCrop农业水资源管理,模拟作物生长过程中水分的需求与消耗

AquaCrop是由世界粮食及农业组织&#xff08;FAO&#xff09;开发的一个先进模型&#xff0c;旨在研究和优化农作物的水分生产效率。这个模型在全球范围内被广泛应用于农业水管理&#xff0c;特别是在制定农作物灌溉计划和应对水资源限制方面显示出其强大的实用性。AquaCrop 不…

VR导航的实现原理、技术优势和应用场景

VR导航通过虚拟现实技术提供沉浸式环境&#xff0c;结合室内定位技术实现精准导航。目前&#xff0c;VR导航已在多个领域展现出其独特的价值和潜力&#xff0c;预示着智能导航系统的未来发展。 一、实现原理 VR导航技术依托于虚拟现实(VR)和室内定位系统。VR技术利用计算机模…

Python考试复习---day5

1.打印商品名 ainput().split() print("商品列表&#xff1a;") for i,name in enumerate(a):print("{}\t{}".format(i,name))enumerate----枚举--利用它可以同时获得索引和值 enumerate多用于在for循环中得到计数 例如&#xff1a; list1 ["这&qu…

Netty SSL双向验证

Netty SSL双向验证 1. 环境说明2. 生成证书2.1. 创建根证书 密钥证书2.2. 生成请求证书密钥2.3. 生成csr请求证书2.4. ca证书对server.csr、client.csr签发生成x509证书2.5. 请求证书PKCS#8编码2.6. 输出文件 3. Java代码3.1. Server端3.2. Client端3.3. 证书存放 4. 运行效果4…

消费者组到底是什么?no.15

Kafka的消费者组。 消费者组&#xff0c;即Consumer Group&#xff0c;应该算是Kafka比较有亮点的设计了。那么何谓Consumer Group呢&#xff1f;用一句话概括就是&#xff1a;Consumer Group是Kafka提供的可扩展且具有容错性的消费者机制。既然是一个组&#xff0c;那么组内必…

JavaScript 贪心算法(Greedy Algo)

贪婪是一种算法范式&#xff0c;它逐步构建解决方案&#xff0c;始终选择提供最明显和直接收益的下一个部分。贪婪算法用于解决优化问题。 如果问题具有以下属性&#xff0c;则可以使用贪心法解决优化问题&#xff1a; 每一步&#xff0c;我们都可以做出当前看来最好的选择&…

路由器的工作原理

5.1路由器的工作原理 如图5-1所示配置IP地址&#xff08;此处省略&#xff0c;请读者自行配置&#xff09;&#xff0c;配置完成后&#xff0c;我们在R1上分别ping 12.1.1.2 、23.1.1.2、23.1.1.3&#xff0c;我们可以发现&#xff0c;在R1上ping 12.1.1.2可以通&#xff0c;但…

光电耦合器:航天航空领域的先进连接技术

光电耦合器作为一种关键的电子连接器&#xff0c;在航天航空领域扮演着重要角色。本文将深入探讨光电耦合器在航天航空领域的应用及其技术特点。 光电耦合器在航天航空领域的应用 光电耦合器作为一种高可靠性、高速传输、抗干扰能力强的连接器&#xff0c;在航天航空领域有着广…

释放视频潜力:Topaz Video AI for mac/win 一款全新的视频增强与修复利器

在数字时代&#xff0c;视频已经成为我们记录生活、分享经历的重要方式。然而&#xff0c;有时候我们所拍摄的视频可能并不完美&#xff0c;可能存在模糊、噪点、抖动等问题。这时候&#xff0c;就需要一款强大的视频增强和修复工具来帮助我们提升视频质量&#xff0c;让它们更…

MT8781安卓核心板_MTK联发科Helio G99核心板规格参数

MT8781安卓核心板采用先进的台积电6纳米级芯片生产工艺&#xff0c;配备高性能Arm Cortex-A76处理器和Arm Mali G57 GPU&#xff0c;加上LPDDR4X内存和UFS 2.2存储&#xff0c;在处理速度和数据访问速度上都有着出色的表现。 MT8781还支持120Hz显示器&#xff0c;无需额外的DSC…

资深开发推荐的IDEA 插件

开发如虎添翼 工欲善其事&#xff0c;必先利其器。想要提升编程开发效率&#xff0c;必须选择一款顺手的开发工具&#xff0c;插件不在多&#xff0c;而在精&#xff0c;作为从业10年的程序员&#xff0c;我目前用到这十几个插件&#xff0c;在平时开发&#xff0c;代码review…

C#WPF数字大屏项目实战01--开发环境与项目创建

1、学习目标 -界面布局 &#xff0c;- 模板调整&#xff0c;- 控件封装&#xff0c;- 图表&#xff0c;- 通信对接&#xff0c;- 动态更新 2、开发环境 开发工具&#xff1a;Visual Studio-2022-17.8.6-Community 运行时框架&#xff1a;.Net 6或Framework 4.5以上 UI框…

SpringCloud-OpenFeign

一 OpenFeign是什么?有什么用? 以往我们是通过 RestTemplate 发起远程调用&#xff0c;如下: 存在问题如下&#xff1a; 代码可读性差&#xff0c;编程体验不统一参数复杂URL难以维护 Feign 是一个声明式的 http 客户端&#xff0c;其作用就是用来把我们解决上述问题的~ 二…