增量跟新,这个词相信大家都听说过,有些人也会增量跟新理解为是热跟新,其实不是,增量跟新比热跟新还要重量级一点,需要用户安装,只是下载不是高版本app的资源包,而是下载高版本app跟现版本的app的差分包,一般大小在几百K到几M之间。
增量跟新与全量跟新具有哪些优势?
主要优势:减少用户的跟新下载流量,达到轻量级跟新效果,想一想如果王者荣耀没跟新一点点就下载一个这个大的包,得多浪费流量啊,用户体验也不好。
先讲一下这个大概的流程:
客户端—–>向服务器索取升级信息——>服务器生成差分包——>客户端获取差分包—>合成新的app
服务器:
这里可以调用bspatch里面的main函数就可以生成一个.patch文件,这个就是差分包
在源代码里面bspatch的main函数有2个参数,一个int的argc,一个char*指针数组,如果你直接双击就会退出
所以我们要用cmd执行
输入以上就可以生成差分包,如果是服务器的话,需要下载bspatch的so库,然后用jni调用生成,这里暂时只讲安卓端的,服务器的先略过了哈
现在我们假如apk.patch的路径为http://192.168.1.100/apk.patch
客户端:
Android端:我们拿到bspatch的源码在visuaStudio小小修改一下,把重要的几个C文件copy到app目录当中
这里就不讲这个过程了
MainActivity:
private static String TAG = "Activity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ApkUtils.getVersionCode(this ,getPackageName()) < 2.0){
Log.i(TAG , "不是最新的版本号! 开始更新!");
new ApkUpdateTask().execute();
} else {
Log.i(TAG , "最新版本号 无需更新!");
}
}
class ApkUpdateTask extends AsyncTask<Void ,Void ,Boolean>{
@Override
protected Boolean doInBackground(Void... voids) {
Log.i(TAG , "开始下载...");
File patchFile = DownLoadUtils.download(Constants.URL_PATCH_DOWNLOAD);
Log.i(TAG , "下载完成......");
String oldFile = ApkUtils.getSourceApkPath(MainActivity.this , getPackageName());
String newFile = Constants.NEW_APK_PATH;
String patchFileString = patchFile.getAbsolutePath();
Log.i(TAG , "开始合并");
int ret = BsPatch.patch(oldFile , newFile , patchFileString);
Log.i(TAG , "合并完成......");
if(ret == 0) {
return true;
} else {
return false;
}
}
@Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
if (aBoolean){
Log.i(TAG , "合并成功 开始安装新APK");
ApkUtils.installApk(MainActivity.this , Constants.NEW_APK_PATH);
}
}
}
BsPatch:
/**
* 合并
* @param oldFlie
* @param newFile
* @param patchFlie
* @return
*/
public native static int patch(String oldFlie , String newFile ,String patchFlie);
static {
System.loadLibrary("JasonBsPatch");
}
ApkUtils:
//获取APK版本号 在公司实际开发中 是根据 key uuid判断(渠道 版本)
public static int getVersionCode (Context context, String packageName) {
PackageManager pm = context.getPackageManager();
try {
PackageInfo info = pm.getPackageInfo(packageName, 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取已安装Apk文件的源Apk文件
* 如:/data/app/my.apk
*
* @param context
* @param packageName
* @return
*/
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 (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 安装Apk
*
* @param context
* @param apkPath
*/
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);
}
Constants:
public class Constants {
public static final String PATCH_FILE = "apk.patch";
public static final String URL_PATCH_DOWNLOAD = "http://000.000.0.000:8080/" +PATCH_FILE; //这里换成自己的服务器路径
public static final String SD_CARD = Environment.getExternalStorageDirectory() + "/";
//新版本apk的目录
public static final String NEW_APK_PATH = SD_CARD + "dn_apk_new.apk";
public static final String PATCH_FILE_PATH = SD_CARD + PATCH_FILE;
}
DownLoadUtils:
public class DownLoadUtils {
/**
* 下载差分包
* @param url
* @return
* @throws Exception
*/
public static File download(String url){
File file = null;
InputStream is = null;
FileOutputStream os = 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);
is = conn.getInputStream();
os = new FileOutputStream(file);
byte[] buffer = new byte[1*1024];
int len = 0;
while((len = is.read(buffer)) != -1){
Log.d("Tim", String.valueOf(len));
os.write(buffer, 0, len);
}
} catch(Exception e){
e.printStackTrace();
}finally{
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
}
我们先copy这几个类。
copy C文件到cpp目录
把bspatch.c的main方法修改一下名字,因为一个应用程序只能有一个main方法,
在bspatch.c的最下面添加一个自己的jni方法,给自己的应用程序调用,把方法路径修改成自己的或者自己可以在BsPatch.java的native里自己生成
/*
* Class: 路径修改成自己的
* Method: patch
* Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_112_BsPatch_patch
(JNIEnv *env, jclass jazz, jstring oldPath_jstr, jstring newPath_jstr, jstring patchPath_jstr){
LOGI("jni patch begin...");
int ret = -1;
char *oldPath = (*env) ->GetStringUTFChars(env , oldPath_jstr ,JNI_FALSE);
char *newPath = (*env) ->GetStringUTFChars(env , newPath_jstr ,JNI_FALSE);
char *patchPath = (*env) ->GetStringUTFChars(env , patchPath_jstr ,JNI_FALSE);
int argc = 4;
char *argv[4];
argv[0] = "JasonDsPatch";
argv[1] = oldPath;
argv[2] = newPath;
argv[3] = patchPath;
//如果成功的话,ret等于0
ret = bspatch_main(argc ,argv);
(*env) ->ReleaseStringUTFChars(env , oldPath_jstr , oldPath);
(*env) ->ReleaseStringUTFChars(env , newPath_jstr , newPath);
(*env) ->ReleaseStringUTFChars(env , patchPath_jstr , patchPath);
return ret;
}
在CmakeList里面添加所有的C文件
把自己的c++文件删掉,不删也行,不过CmakeList要自己添加一下
然后自己动手可以试一下,本人亲测可用!