NDK学习笔记-增量更新
虽然现在有插件化开发和热修复,但为何还需要增量更新?插件化开发和热修复依赖于宿主程序,增量更新适合更新宿主程序。
差分包生成的前提
差分包的生成依赖于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.h
和jni_md
整理出来,这里我直接提取了Linux端的java目录下的jni.h
和jni_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学习笔记-增量更新的更多相关文章
- Lucene学习笔记(更新)
1.Lucene学习笔记 http://www.cnblogs.com/hanganglin/articles/3453415.html
- 数据分析之Pandas和Numpy学习笔记(持续更新)<1>
pandas and numpy notebook 最近工作交接,整理电脑资料时看到了之前的基于Jupyter学习数据分析相关模块学习笔记.想着拿出来分享一下,可是Jupyter导出来h ...
- Python 学习笔记 - 不断更新!
Python 学习笔记 太久不写python,已经忘记以前学习的时候遇到了那些坑坑洼洼的地方了,开个帖子来记录一下,以供日后查阅. 摘要:一些报错:为啥Python没有自增 ++ 和自减 --: 0x ...
- python3.x学习笔记2018-02-05更新
前言:python3.x部分学习笔记,有意交流学习者可加wechat:YWNlODAyMzU5MTEzMTQ=.如果笔记内容有错,请指出来. 对数据类型的操作 可变数据类型:列表,集合,字典 列表: ...
- NDK学习笔记(三):DynamicKnobs的机制
最近的NDK开发涉及到了动态input及动态knobs的问题. 开发需求如下:建立一个节点,该节点能获取每一个input上游的inputframerange信息. 具体下来就是:需要Node的inpu ...
- 保姆级尚硅谷SpringCloud学习笔记(更新中)
目录 前言 正文内容 001_课程说明 002_零基础微服务架构理论入门 微服务优缺点[^1] SpringCloud与微服务的关系 SpringCloud技术栈 003_第二季Boot和Cloud版 ...
- java 数据库编程 学习笔记 不断更新
最近开始学习java,感觉java的数据库编程需要发个随笔记录一下,话不多说 切入正题. 一.数据库访问技术的简介 应用程序 → 执行SQL语句 →数据库 → 检索数据结果 → 应用程序 ( ...
- NDK学习笔记(四):OutputContext机制
首先NDK文档中的Op.h头文件中已经有了相关概念的解释,摘录翻译如下: /*! \fn const OutputContext& Op::outputContext() const; The ...
- STL学习笔记(不定期更新)
algorithm *1.sort() 用法:sort(数组名,名+长度(,cmp)); int cmp(T a,T b)//T是要排序的a,b的类型,也可以是结构体中任意成员变量 { return ...
随机推荐
- 浅析pagehelper分页原理(转)
之前项目一直使用的是普元框架,最近公司项目搭建了新框架,主要是由公司的大佬搭建的,以springboot为基础.为了多学习点东西,我也模仿他搭了一套自己的框架,但是在完成分页功能的时候,确遇到了问题. ...
- PRIMARY KEY,key,unique key
主键索引(必须指定为“PRIMARY KEY”,没有PRIMARY Index). 唯一索引(unique index,一般写成unique key). 普通索引(index,只有这一种才是纯粹的in ...
- learning express step(二)
install express-generator C:\Users\admin\WebstormProjects\learning-express-step2>npm install expr ...
- leetcode解题报告(12):Maximum Subarray
描述 Find the contiguous subarray within an array (containing at least one number) which has the large ...
- ECMAScript 提案阶段
stage0 strawman任何讨论.想法.改变或者还没加到提案的特性都在这个阶段.只有TC39成员可以提交. stage1 proposal (1)产出一个正式的提案. (2)发现潜在的问题,例如 ...
- js和jQuery实现的Ajax
1. JS实现Ajax <!doctype html> <html lang="en"> <head> <meta charset=&qu ...
- c 判断字符是否为字母 (iswalpha example)
#include <stdio.h> #include <wctype.h> int main () { ; wchar_t str[] = L"C++"; ...
- 二十五、grub (Boot Loader) 以及修复grub
双系统安装(先Windows后Linux,以免windows NTloader会覆盖Linux loader) GRUB Grand Uniform Bootloader CentOS5,6 grub ...
- Harmonious Graph
D. Harmonious Graph 好后悔在写这个题之前浪费了几分钟时间,不然我就写出来了.... 因为他就是连通块之间的合并问题,所以就用并查集就好了 复杂度好像也只是线性的吧... 然后就A了 ...
- python3安装web.py
今天准备测试代理池IPProxyPool获取到ip的质量,在安装web.py的时候遇到了些问题,在此记录一下. 1.安装资料 web.py官网:http://webpy.org/ web.py的git ...