本文详细实现增量更新流程
涉及知识点
1.kotlin基本使用
2.NDK的使用
3.ubuntu使用
4.基本命令操作
准备工作
1.下载资源文件
bzip2源码下载地址 –> http://www.bzip.org/
Binary diff/patch utility下载地址 — > http://www.daemonology.net/bsdiff/
2.需要ubuntu环境进行差分 点击下面文章查看如何进行差分
http://blog.csdn.net/xie397361457/article/details/77895113
下面正式进入代码编写
1.demo需要的Contats配置
package com.manager.textbasec
import android.os.Environment
import java.io.File
/**
* 增量更新需要测试值
*/
class Contats {
companion object{
val PATCH_FILE = "apk.patch"
//差分文件下载地址,可根据实际项目接口返回
val URL_PATCH_DOWLOAD = "http://192.168.56.1:8080/" + PATCH_FILE
//项目包名
val PACKAGE_NAME = "com.manager.textbasec"
//外置卡路径(这里是方便测试使用)---->实际开发时应该将文件getCacheDir()目录下
val SD_CARD = Environment.getExternalStorageDirectory().absolutePath+File.separator
//给新APK一个存放路径
val NEW_APK_PATH = SD_CARD + "apk_new.apk"
}
}
2.layout代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorAccent"
android:textSize="16dp"
android:text="Hello_World_VERSION1"/>
</LinearLayout>
3.Native代码
package com.manager.framesource.incremental;
/**
* 本地方法存放类,目前推荐使用java类,不建议kotlin进行配置此类
* 一键生成c/c++头文件
* 1.打开Android studio 命令工具Terminal
* 2.cd app/src/main/java -->进入到java目录下面
* 3.javah 本类的package + 类名 eg: javah com.manager.framesource.incremental.BsPatchNative
* 注意:如果遇见-->错误: 编码GBK的不可映射字符此类错误,是由于jdk是国际版引起的
* 可以使用这个命令 javah -encoding UTF-8 com.manager.framesource.incremental.BsPatchNative
* 4.将生成的头文件移动到cpp文件夹下面,方便管理和使用
* kotlin 1.配置static 需要加上@JvmStatic注解
* 2.使用kotlin,不能使用javah 进行生成c/c++头文件
* 3.使用kotlin,不能一键链接到指定的c/c++头文件函数方法
*
*/
public class BsPatchNative {
public static native int bspatch(String oldPath,String newPath,String patchPath);
static {
System.loadLibrary("BsPatch");
}
}
4.kotlin扩展代码
package com.manager.textbasec
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
/**
* 使用kotlin给context添加扩展方法
* 参数格式说明
* 需要扩展的类.方法名(参数) : 返回值类型{ }
*/
//获取当前app版本号
public fun Context.getVersionCode():Int{
val packageManager = this.packageManager
val packageInfo = packageManager.getPackageInfo(this.packageName,0)
Log.d("versionCode = ",packageInfo.versionCode.toString())
return packageInfo.versionCode
}
//获取已安装APK文件目录
public fun Context.getSourceApkPath(packageName :String) :String{
val appInfo = this.packageManager.getApplicationInfo(packageName,0)
return appInfo.sourceDir
}
//安装APK
public fun Context.installApk(apkPath : String){
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(Uri.parse("file://"+apkPath),"application/vnd.android.package-archive")
this.startActivity(intent)
}
5.jni/c代码
这是导入C代码的截图
在bspatch.c加入方法
JNIEXPORT jint JNICALL Java_com_manager_textbasec_BsPatchNative_bspatch
(JNIEnv *env, jclass clazz, jstring oldPath, jstring newPath, jstring patchPath){
int result = -1;
int argc = 4;//上面main函数需要传的值
char *argv[4];
argv[0] = "BsPatch";
LOGD("JNI bspatch begin");
const char* oldPath_c = (*env)->GetStringUTFChars(env,oldPath,JNI_FALSE);
const char* newPath_c = (*env)->GetStringUTFChars(env,newPath,JNI_FALSE);
const char* patchPath_c = (*env)->GetStringUTFChars(env,patchPath,JNI_FALSE);
argv[1] = oldPath_c;
argv[2] = newPath_c;
argv[3] = patchPath_c;
//调用bapatch,开始合并
result = bspatchMain(argc,argv);
if (result < 0){
LOGD("JNI Bspatch fail");
}
//释放内存
(*env)->ReleaseStringUTFChars(env,oldPath,oldPath_c);
(*env)->ReleaseStringUTFChars(env,newPath,newPath_c);
(*env)->ReleaseStringUTFChars(env,patchPath,patchPath_c);
free(argv);
return result;
}
6.编辑CMakeList.txt
cmake_minimum_required(VERSION 3.4.1)
set(distribution_DIR ../../../../src/main/jniLibs/${ANDROID_ABI})
#增量更新 需要用到的c代码
#bzip2源码下载地址 --> http://www.bzip.org/
#(Binary diff/patch utility)bsPatch下载地址 --- > http://www.daemonology.net/bsdiff/
#(Binary diff/patch utility)bsPatch下载包里面还有个bsdiff此C文件是进行差分算法
#说明:上面两个包里面的C文件,凡是有main 函数,需要自己更改函数名字
add_library(
BsPatch
SHARED
src/main/cpp/incremental/bzip2/blocksort.c
src/main/cpp/incremental/bzip2/bzip2.c
src/main/cpp/incremental/bzip2/bzip2recover.c
src/main/cpp/incremental/bzip2/bzlib.c
src/main/cpp/incremental/bzip2/compress.c
src/main/cpp/incremental/bzip2/crctable.c
src/main/cpp/incremental/bzip2/decompress.c
src/main/cpp/incremental/bzip2/dlltest.c
src/main/cpp/incremental/bzip2/huffman.c
src/main/cpp/incremental/bzip2/mk251.c
src/main/cpp/incremental/bzip2/randtable.c
src/main/cpp/incremental/bzip2/spewG.c
src/main/cpp/incremental/bzip2/unzcrash.c
src/main/cpp/incremental/bspatch.c
)
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp )
find_library(
log-lib
log )
#include_dircetories : 包含头文件到类库里面 参数-->有头文件(.h文件)所涉及到的目录
include_directories(src/main/cpp)
include_directories(src/main/cpp/incremental)
include_directories(src/main/cpp/incremental/bzip2)
target_link_libraries(
BsPatch
native-lib
${log-lib} )
7.Activity代码
package com.manager.textbasec
import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.widget.Button
import com.manager.framesource.R
import com.manager.framesource.incremental.BsPatchNative
import org.jetbrains.anko.async
import org.jetbrains.anko.uiThread
import java.io.File
import java.net.URL
/**
* 增量更新
*/
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.incremental_activity_main)
val tv = findViewById(R.id.tv) as Button
tv.setOnClickListener {
//版本比对-->可根据实际开发,定义定制规则
if (this.getVersionCode() < 2){
downAndPatch()
}
}
}
fun downAndPatch(){
//kotlin 封装的异步下载
async {
//下载文件
val bytes = URL(Contats.URL_PATCH_DOWLOAD).readBytes()
//定义文件下载后存放路径
val patchFile = File(Environment.getExternalStorageDirectory(),Contats.PATCH_FILE)
Log.d("patchFile.ab = ",patchFile.absolutePath)
if (patchFile.exists()){
patchFile.delete()
}
//写入新文件
patchFile.writeBytes(bytes)
//进行合并 1.oldApk->放在系统的apk 目录= 当前安装的apk
var oldPatch = this@MainActivity.getSourceApkPath(Contats.PACKAGE_NAME)
Log.d("oldPatch.ab = ",oldPatch)
var newPath = Contats.NEW_APK_PATH
Log.d("newPath.ab = ",newPath)
var patchPath = patchFile.absolutePath
Log.d("patchPath.ab = ",patchPath)
//开始合并
BsPatchNative.bspatch(oldPatch,newPath,patchPath)
uiThread {
//安装APK
this@MainActivity.installApk(newPath)
}
}
}
}
测试
1.将build.gradle versionCode设置 1
layout 截图
运行–>生成–>old.APK
2.将build.gradle versionCode设置 2
运行–>生成new.apk
3.生成差分文件 .patch
将old.apk 与 new.apk 放到ubuntu里面进行差分
4.把.patch差分文件放到服务器进行下载使用
总结
实现增量更新原理就是使用当前版本old.apk和差分文件.patch进行合并重新生成新的new.apk
难点:
1.需要对c/c++,jni,kotlin代码掌握
2.需要对cmakelists.txt配置有一定的掌握
3.ubuntu进行差分使用,本人在这上面踩过很多坑,起初想自己更改makeFile
生成bspatch和bsdiff工具,但是网上资料太杂以及对shell的不熟悉,始终没能生成工具,所以才使用了ubuntu现成工具,就轻松搞定
4.获取app当前版本APK一定要使用
var oldPatch = this@MainActivity.getSourceApkPath(Contats.PACKAGE_NAME)
来获取