zoukankan      html  css  js  c++  java
  • NDK学习笔记-增量更新

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

    差分包生成的前提

    差分包的生成依赖于BsDiff开源项目,而BsDiff又依赖于Bzip2
    BsDiff源代码下载地址:BsDiff
    Bzip2源代码下载地址:Bzip2

    Window服务器端配置

    新建Java Web项目

    • new -> Web -> Dynamic Web Project
      由于我本地装的是tomcat 7,这里就选择Apache Tomcat v7.0
      创建Windows服务器差分
    • 在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++代码都是可以的

  • 相关阅读:
    MongoDB Query 常用方法
    plsql中文乱码问题(显示问号)
    xtype的使用
    LinQ:list基础操作
    VMware Fusion自动切换分辨率
    C#截取字符串的方法小结
    HTML 编码
    AMQP(Advanced Message Queuing Protocol)
    rabibtMQ安装及集群配置linux
    今天是个开始
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664669.html
Copyright © 2011-2022 走看看