<Excerpt in index | 首页摘要>
Android差分升级-中-apk利用差分包合并
<The rest of contents | 余下全文>
概述
本来这篇打算将合并和linuxso的生成,后发现内容太多,先将合并吧。
首先阅读本篇之前,需要了解差分的生成:Android差分升级-上-apk差分
本篇的源码(web、Android,v1.apk,v2.apk,v1-v2.patch 文章结尾提供下载)
流程分析
上篇中,我们知道了如何进行v2.0和v1.0的apk的差分包的生成。
这篇利用bsdiff来利用生成的差分包进行合并成为新版本的apk包并提示安装。
工具
1>AS //AndroidStudio
2>bzip2
3>bpatch.c
流程
废话不多说。
首先,创建一个包含c++的项目(as这里用的是cmake).
然后打开CMakeLists.txt文件
编辑内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library. bspatch
# Sets the library as a shared library. SHARED
# Provides a relative path to your source file(s). src/main/cpp/bspatch.c )
find_library( # Sets the name of the path variable. log-lib
# Specifies the name of the NDK library that # you want CMake to locate. log )
target_link_libraries( # Specifies the target library. bspatch
# Links the target library to the log library # included in the NDK. ${log-lib} )
|
我删除了注释内容,主要就是这些了.
修改完成之后点击同步按钮
我这里生成了jni目录不知道你们那里生成了没有。如果有删了即可。
删除cpp目录下所有内容。
讲MainActivity里面和jni有关的内容删了。
复制bzip2到cpp目录下。
复制bsdiff里面的bspatch.c到cpp目录下。
项目结构如截图:
然后打开bspatch.c
删掉顶部注释,然后修改顶部的include命令
从
1 2 3 4 5 6 7 8 9 10 11
| #if 0 __FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:06 cperciva Exp $"); #endif
#include <bzlib.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <err.h> #include <unistd.h> #include <fcntl.h>
|
改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <jni.h> #include "bzip2/bzlib.c" #include "bzip2/crctable.c" #include "bzip2/compress.c" #include "bzip2/decompress.c" #include "bzip2/randtable.c" #include "bzip2/blocksort.c" #include "bzip2/huffman.c"
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <err.h> #include <unistd.h> #include <fcntl.h>
|
创建JniUtils.java
内容如下:
1 2 3 4 5 6 7 8 9 10
| package com.gloomyer.diff_update;
public class JniUtils { public static native void merge(String oldPath, String patchPath, String newPath);
static { System.loadLibrary("bspatch"); } }
|
然后打开bspatch.c文件
修改main函数函数名为bspatch_merge
在文件结尾加入如下实现:
这里说一下bspatch_merge的两个参数
第一个参数恒定为4
第二个参数是个字符串数组,0:无意义随便填,1:老的apk路径(已经安装的应用的apk如何获取安装包 下面的那个ApkUtils里面有方法) 2:生成的新的apk的文件路径 3:差分包路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| JNIEXPORT void JNICALL Java_com_gloomyer_diff_1update_JniUtils_merge(JNIEnv *env, jclass jcls, jstring oldPath_jstr, jstring patchPath_jstr, jstring newPath_jstr) {
char *argv[4] = {"bsdiff"}; argv[1] = (char *) (*env)->GetStringUTFChars(env, oldPath_jstr, 0); argv[2] = (char *) (*env)->GetStringUTFChars(env, newPath_jstr, 0); argv[3] = (char *) (*env)->GetStringUTFChars(env, patchPath_jstr, 0);
bspatch_merge(4, argv);
(*env)->ReleaseStringUTFChars(env, oldPath_jstr, argv[1]); (*env)->ReleaseStringUTFChars(env, newPath_jstr, argv[2]); (*env)->ReleaseStringUTFChars(env, patchPath_jstr, argv[3]); }
|
还有一个工具类:
ApkUtils:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.gloomyer.diff_update;
import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.net.Uri; import android.text.TextUtils;
class ApkUtils {
public static String getOldPath(Context mContext, String packageName) { if (TextUtils.isEmpty(packageName)) return null;
try { ApplicationInfo appInfo = mContext.getPackageManager() .getApplicationInfo(packageName, 0); return appInfo.sourceDir; } catch (Exception e) { e.printStackTrace(); }
return null; }
public static void installApk(Context mContext, String apkPath) {
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse("file://" + apkPath), "application/vnd.android.package-archive"); mContext.startActivity(intent); } }
|
实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.gloomyer.diff_update;
public class UpdateBean { private boolean hasUpdate; private String url;
public boolean isHasUpdate() { return hasUpdate; }
public void setHasUpdate(boolean hasUpdate) { this.hasUpdate = hasUpdate; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
@Override public String toString() { return "UpdateBean{" + "hasUpdate=" + hasUpdate + ", url='" + url + '\'' + '}'; } }
|
MainActivity代码
主要内容就是根据后台检测更新,然后根据返回结果下载差分包然后调用jni合并成新的包,然后提示用户安装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| package com.gloomyer.diff_update;
import android.os.Environment; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Toast;
import com.google.gson.Gson;
import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity"; private static final String BASEURL = "http://192.168.3.213:8090/"; private static final String requestUrl = BASEURL + "DiffServlet"; private Handler mHander;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHander = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0) { downloadPatch((UpdateBean) msg.obj); } else if (msg.what == 1) { ApkUtils.installApk(MainActivity.this, (String) msg.obj); } }
}; }
private void downloadPatch(final UpdateBean info) { Log.e(TAG, info.toString()); new Thread(new Runnable() { @Override public void run() { try { URL url = new URL(info.getUrl()); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setUseCaches(false); connection.setConnectTimeout(60000); connection.setReadTimeout(60000);
File patchFile = Environment.getExternalStorageDirectory(); patchFile = new File(patchFile, "patch_" + System.currentTimeMillis() + ".patch");
connection.connect(); if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream is = connection.getInputStream(); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(patchFile));
int len; byte buffer[] = new byte[512]; while ((len = is.read(buffer)) > 0) { bos.write(buffer, 0, len); }
bos.flush(); bos.close(); is.close(); }
File newApkFile = Environment.getExternalStorageDirectory(); newApkFile = new File(newApkFile, "apkfile_" + System.currentTimeMillis() + ".apk"); JniUtils.merge(ApkUtils.getOldPath(MainActivity.this, getPackageName()), patchFile.getAbsolutePath(), newApkFile.getAbsolutePath());
mHander.obtainMessage(1, newApkFile.getAbsolutePath()).sendToTarget(); } catch (Exception e) { e.printStackTrace(); }
} }).start(); }
public void update(View v) {
new Thread(new Runnable() { @Override public void run() { try { URL url = new URL(requestUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setRequestProperty("Charset", "UTF-8"); connection.setRequestProperty("Content-Type", "text/xml; charset=UTF-8"); connection.connect(); if (connection.getResponseCode() == 200) { InputStream is = connection.getInputStream(); int len; byte buffer[] = new byte[512]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); if ((len = is.read(buffer)) > 0) { baos.write(buffer, 0, len); } baos.flush();
String json = new String(baos.toByteArray()); UpdateBean info = new Gson().fromJson(json, UpdateBean.class); baos.close(); is.close();
mHander.obtainMessage(0, info).sendToTarget(); } } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
|
打包下载
这边相对来说比后端要完成的东西要简单一些。
源码我这里打包一下。
打包下载