虽然现在有插件化开发和热修复,但为何还需要增量更新?插件化开发和热修复依赖于宿主程序,增量更新适合更新宿主程序。

差分包生成的前提

差分包的生成依赖于BsDiff开源项目,而BsDiff又依赖于Bzip2

BsDiff源代码下载地址:BsDiff

Bzip2源代码下载地址:Bzip2

Window服务器端配置

新建Java Web项目

  • new -> Web -> Dynamic Web Project

    由于我本地装的是tomcat 7,这里就选择Apache Tomcat v7.0

  • 在src目录下生成三个类,用于生成差分包

    路径类(Constants.java
public class Constants {
public static final String OLD_APK_PATH = "E:\\workspace\\android\\appupdatetest\\AppUpdate_old.apk";
public static final String NEW_APK_PATH = "E:\\workspace\\android\\appupdatetest\\AppUpdate_new.apk";
public static final String PATCH_PATH = "E:\\workspace\\android\\appupdatetest\\apk.patch";
}

native方法类(BsDiff.java

public class BsDiff {
public native static void diff(String oldfile, String newfile, String patchfile);
static {
System.loadLibrary("bsdiff");
}
}

主方法类(BsDiffTest.java

public class BsDiffTest {
public static void main(String[] args) {
BsDiff.diff(Constants.OLD_APK_PATH, Constants.NEW_APK_PATH, Constants.PATCH_PATH);
}
}

生成Windows平台下的dll动态库(VS)

  • 新建空项目 -> 将原代码添加到项目(包含c,cpp,h) -> 移除bspatch.cpp(Server端不需要合并)
  • 去除警告(项目右键 -> 属性 -> 配置属性 -> C/C++ -> 命令行 -> 添加指令)

-D _CRT_SECURE_NO_WARNINGS -D _CRT_NONSTDC_NO_DEPRECATE

  • 去除严格语法检查(配置属性 -> C/C++ -> 常规 -> SDL检查(否))
  • 生成dll动态库(配置属性 -> 常规 -> 配置类型 -> dll动态库)
  • 生成x64平台dll(Debug -> 配置管理器 -> win32 -> x64)
  • 将bsdiff.cpp中的main改为bsdiff_main,方便JNI调用
  • 将编写好的native方法类生成头文件,并在项目中添加进来
  • VS中引入头文件jni.h和jni_md.h,并将头文件包含#includ <jni.h>改为#include "jni.h"
  • 在bsdiff.cpp文件中实现native方法(注意在这里要统一所有源文件的编码格式,否可能找不都头文件)
//JNI调用
JNIEXPORT void JNICALL Java_com_cj5785_appuodateserver_bsdiff_BsDiff_diff
(JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr)
{
int argc = 4;
char *oldfile = (char *)env->GetStringUTFChars(oldfile_jstr, NULL);
char *newfile = (char *)env->GetStringUTFChars(newfile_jstr, NULL);
char *patchfile = (char *)env->GetStringUTFChars(patchfile_jstr, NULL);
//参数,第一个参数无效,第二个参数为源文件路径,第三个参数为新文件路径,第四个参数为差分包路径
char *argv[4] = { "bsdiff" , oldfile, newfile, patchfile};
bsdiff_main(argc, argv);
env->ReleaseStringUTFChars(oldfile_jstr, oldfile);
env->ReleaseStringUTFChars(newfile_jstr, newfile);
env->ReleaseStringUTFChars(patchfile_jstr, patchfile);
}
  • 此时如果生成,会报错(“DWORD FormatMessageW(DWORD,LPCVOID,DWORD,DWORD,LPWSTR,DWORD,va_list *)”: 无法将参数 5 从“char [1024]”转换为“LPWSTR”),此处将lastErrorTxt强转为LPWSTR即可((LPWSTR)lastErrorTxt)
  • 去除错误,编译即可生成dll动态库

生成差分包

  • 将生成的dll放入web项目根中
  • 运行web程序,生成差分包
  • 将生成的差分包放在服务器Webcontent(网页根目录)下

Android端配置

在Android端,最主要的就是bspatch.c文件,这个文件用于差分包的合成

在这里通过演示一个前台的活动去更新软件,实际开发中一般放在后台,通过每次启动区服务端检查有无更新,从而决定时候下载差分包

调用差分合成的native类(BsPatch.java)

public class BsPatch {
public static native void patch(String oldfile, String newfile, String patchfile);
static {
System.loadLibrary("bspatch");
}
}

根据BsPatch.java,使用javah生成头文件

新建jni文件夹,将头文件拷贝至jni文件夹,添加本地支持(具体操作步骤参考之前的NDK开发流程一文)

在bspatch.c中实现头文件声明的函数,同时还需要导入依赖的Bzip2中用到的C文件

同时将main改为bspatch_main,方便jni调用

其实现类似于服务端,在此不再赘述

JNIEXPORT void JNICALL Java_com_cj5785_appupdate_BsPatch_patch
(JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr)
{
int argc = 4;
char *oldfile = (char *)(*env)->GetStringUTFChars(env, oldfile_jstr, NULL);
char *newfile = (char *)(*env)->GetStringUTFChars(env, newfile_jstr, NULL);
char *patchfile = (char *)(*env)->GetStringUTFChars(env, patchfile_jstr, NULL);
//参数,第一个参数无效,第二个参数为源文件路径,第三个参数为新文件路径,第四个参数为差分包路径
char *argv[4] = { "bspatch" , oldfile, newfile, patchfile};
bspatch_main(argc, argv);
(*env)->ReleaseStringUTFChars(env, oldfile_jstr, oldfile);
(*env)->ReleaseStringUTFChars(env, newfile_jstr, newfile);
(*env)->ReleaseStringUTFChars(env, patchfile_jstr, patchfile);
}

常量类(Constants.java)

此处使用本地tomcat服务器测试,实际中使用实际主机的IP

import java.io.File;

import android.os.Environment;

public class Constants {
public static final String PATCH_FILE = "apk.patch";
public static final String URL_PATCH_DOWNLOAD = "http://192.168.1.3:8080/" + PATCH_FILE;
public static final String PACKAGE_NAME = "com.cj5785.appupdate";
public static final String SD_CARD = Environment.getExternalStorageDirectory().toString() + File.separatorChar;
public static final String NEW_APK_PATH = SD_CARD + "apk_new_test.apk";
public static final String PATCH_FILE_PATH = SD_CARD + PATCH_FILE;
}

下载工具类(DownloadUtils.java)

主要用于下载差分包

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL; import android.os.Environment; public class DownloadUtils {
public static File download(String url) {
File file = null;
InputStream iStream = null;
FileOutputStream oStream = null;
try {
file = new File(Environment.getExternalStorageDirectory(), Constants.PATCH_FILE);
if(file.exists()) {
file.delete();
}
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setDoInput(true);
iStream = conn.getInputStream();
oStream = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len = iStream.read(buf)) != -1) {
oStream.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
iStream.close();
} catch (Exception e2) {
e2.printStackTrace();
}
try {
oStream.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
return file;
}
}

apk工具类(ApkUtils.java)

此工具类主要用于apk的安装

import java.io.File;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.text.TextUtils; public class ApkUtils { public static String getSourceApkPath(Context context, String packageName) {
if(TextUtils.isEmpty(packageName)) {
return null;
}
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
return appInfo.sourceDir;
} catch (Exception e) {
e.printStackTrace();
}
return null;
} public static void installApk(Context context, String apkPath) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + apkPath), "application/vnd.android.package-archive");
context.startActivity(intent);
}
}

主活动(MainActivity.java)

import java.io.File;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new ApkUpdateTask().execute();
} class ApkUpdateTask extends AsyncTask<Void, Void, Boolean>{ @Override
protected Boolean doInBackground(Void... params) {
try {
//下载差分包
File patchFile = DownloadUtils.download(Constants.URL_PATCH_DOWNLOAD);
//获取当前应用的apk文件
String oldfile = ApkUtils.getSourceApkPath(MainActivity.this, getPackageName());
//和并得到最新版的APK文件
String newfile = Constants.NEW_APK_PATH;
String patchfile = patchFile.getAbsolutePath();
BsPatch.patch(oldfile, newfile, patchfile);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
} @Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
//安装apk
if(result) {
ApkUtils.installApk(MainActivity.this, Constants.NEW_APK_PATH);
}
}
}
}

其他

布局文件并没有与项目有关的地方,这里就不用贴出来了

清单文件与项目有关的地方有两个,一个是versionCode和versionName,这个地方主要是用来做安装校验的,现在的代码在安装的时候并没有做校验,所以还存在一些问题,即安装校验和文件删除

还有一个就是用户权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

至此,Android的核心代码就已经贴完了

Linux服务器配置

Windows服务端搞定了,那么Linux服务端也顺便搞一搞

准备源代码

将所需的bsdiff.c源文件和bzip2相关源文件以及Linux下的jni.hjni_md整理出来,这里我直接提取了Linux端的java目录下的jni.hjni_md.h

修改bsdiff.c源文件,添加JNI头文件,使其能被JNI调用

同时引入bsdiff.c所需文件

bsdiff.c中的main改为bsdiff_main

bsdiff.c中调用bsdiff_main函数(即实现JNI头函数)

此处和windows类似,可以参考Windows下的dll编译

//JNI调用
JNIEXPORT void JNICALL Java_com_cj5785_appuodateserver_bsdiff_BsDiff_diff
(JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr)
{
int argc = 4;
char *oldfile = (char *)env->GetStringUTFChars(oldfile_jstr, NULL);
char *newfile = (char *)env->GetStringUTFChars(newfile_jstr, NULL);
char *patchfile = (char *)env->GetStringUTFChars(patchfile_jstr, NULL);
//参数,第一个参数无效,第二个参数为源文件路径,第三个参数为新文件路径,第四个参数为差分包路径
char *argv[4] = { "bsdiff" , oldfile, newfile, patchfile};
bsdiff_main(argc, argv);
env->ReleaseStringUTFChars(oldfile_jstr, oldfile);
env->ReleaseStringUTFChars(newfile_jstr, newfile);
env->ReleaseStringUTFChars(patchfile_jstr, patchfile);
}

编译生成动态库

gcc -fPIC -shared blocksort.c decompress.c bsdiff.c randtable.c bzip2.c huffman.c compress.c bzlib.c crctable.c -o bsdiff.so

Linux下的jar包生成

将生成的.so动态库放入根目录,其代码与Windows服务端代码类似

修改Contants.java下的路径,使其为Linux目录

修改BsDiff.java文件,指定动态库路径(这里有两种做法,不修改其路径,将动态库放入系统动态库目录,不建议这么做,建议放在自定义目录,使用System.load加载)

static {
System.load("/home/ubuntu/bsdiff.so");
}

导出jar包

根据Contants.java路径放入旧文件和新文件

运行jar包,生成差分包

java -jar jarname.jar

差分算法简单分析

  • 不同部分用Bzip压缩
  • 型旧版本重复越多,差分包越小
  • 新旧版本重复越少,差分包越大

差分运用

无论是Windows还是Linux,在使用时候都是类似的

由原代码的情况下,可以编译出很多可用的版本

命令行的C语言代码

可视化的C++代码都是可以的

NDK学习笔记-增量更新的更多相关文章

  1. Lucene学习笔记(更新)

    1.Lucene学习笔记 http://www.cnblogs.com/hanganglin/articles/3453415.html    

  2. 数据分析之Pandas和Numpy学习笔记(持续更新)<1>

    pandas and numpy notebook        最近工作交接,整理电脑资料时看到了之前的基于Jupyter学习数据分析相关模块学习笔记.想着拿出来分享一下,可是Jupyter导出来h ...

  3. Python 学习笔记 - 不断更新!

    Python 学习笔记 太久不写python,已经忘记以前学习的时候遇到了那些坑坑洼洼的地方了,开个帖子来记录一下,以供日后查阅. 摘要:一些报错:为啥Python没有自增 ++ 和自减 --: 0x ...

  4. python3.x学习笔记2018-02-05更新

    前言:python3.x部分学习笔记,有意交流学习者可加wechat:YWNlODAyMzU5MTEzMTQ=.如果笔记内容有错,请指出来. 对数据类型的操作 可变数据类型:列表,集合,字典 列表: ...

  5. NDK学习笔记(三):DynamicKnobs的机制

    最近的NDK开发涉及到了动态input及动态knobs的问题. 开发需求如下:建立一个节点,该节点能获取每一个input上游的inputframerange信息. 具体下来就是:需要Node的inpu ...

  6. 保姆级尚硅谷SpringCloud学习笔记(更新中)

    目录 前言 正文内容 001_课程说明 002_零基础微服务架构理论入门 微服务优缺点[^1] SpringCloud与微服务的关系 SpringCloud技术栈 003_第二季Boot和Cloud版 ...

  7. java 数据库编程 学习笔记 不断更新

    最近开始学习java,感觉java的数据库编程需要发个随笔记录一下,话不多说 切入正题. 一.数据库访问技术的简介 应用程序  →  执行SQL语句 →数据库 → 检索数据结果 → 应用程序   ( ...

  8. NDK学习笔记(四):OutputContext机制

    首先NDK文档中的Op.h头文件中已经有了相关概念的解释,摘录翻译如下: /*! \fn const OutputContext& Op::outputContext() const; The ...

  9. STL学习笔记(不定期更新)

    algorithm *1.sort() 用法:sort(数组名,名+长度(,cmp)); int cmp(T a,T b)//T是要排序的a,b的类型,也可以是结构体中任意成员变量 { return ...

随机推荐

  1. 浅析pagehelper分页原理(转)

    之前项目一直使用的是普元框架,最近公司项目搭建了新框架,主要是由公司的大佬搭建的,以springboot为基础.为了多学习点东西,我也模仿他搭了一套自己的框架,但是在完成分页功能的时候,确遇到了问题. ...

  2. PRIMARY KEY,key,unique key

    主键索引(必须指定为“PRIMARY KEY”,没有PRIMARY Index). 唯一索引(unique index,一般写成unique key). 普通索引(index,只有这一种才是纯粹的in ...

  3. learning express step(二)

    install express-generator C:\Users\admin\WebstormProjects\learning-express-step2>npm install expr ...

  4. leetcode解题报告(12):Maximum Subarray

    描述 Find the contiguous subarray within an array (containing at least one number) which has the large ...

  5. ECMAScript 提案阶段

    stage0 strawman任何讨论.想法.改变或者还没加到提案的特性都在这个阶段.只有TC39成员可以提交. stage1 proposal (1)产出一个正式的提案. (2)发现潜在的问题,例如 ...

  6. js和jQuery实现的Ajax

    1. JS实现Ajax <!doctype html> <html lang="en"> <head> <meta charset=&qu ...

  7. c 判断字符是否为字母 (iswalpha example)

    #include <stdio.h> #include <wctype.h> int main () { ; wchar_t str[] = L"C++"; ...

  8. 二十五、grub (Boot Loader) 以及修复grub

    双系统安装(先Windows后Linux,以免windows NTloader会覆盖Linux loader) GRUB Grand Uniform Bootloader CentOS5,6 grub ...

  9. Harmonious Graph

    D. Harmonious Graph 好后悔在写这个题之前浪费了几分钟时间,不然我就写出来了.... 因为他就是连通块之间的合并问题,所以就用并查集就好了 复杂度好像也只是线性的吧... 然后就A了 ...

  10. python3安装web.py

    今天准备测试代理池IPProxyPool获取到ip的质量,在安装web.py的时候遇到了些问题,在此记录一下. 1.安装资料 web.py官网:http://webpy.org/ web.py的git ...