Android实现增量更新

常用的App更新手段一般是热更新、增量更新和全量更新。今天我们要实现的是增量更新,增量更新简单来说,就是在服务器端通过对比旧版本和新版本的apk文件来生成一个差分包,再由客户端下载该差分包与旧版本的apk文件进行合并,从而形成新版本的apk包。增量更新的最大优点就是可以省流量,减少用户的等待时间,当然,前提是差分包的大小要比全量更新的apk大小要小得多才有意义。

bsdiff

要得到新旧版本的差分包,需要借助第三方工具 —— bsdiff,该工具可以制作差分包并将差分包与旧版本文件合并成新版本文件。

Android Studio环境搭建

新建NDK项目,将下载好的bsdiff文件中的bspatch.c复制到 src/cpp/ 目录下,该文件用于在客户端合并差分包与旧版本apk文件

CMakeLists.txt文件中,将bspatch.c添加到 add_library 参数中,打开bspatch.c我们会发现第一个include文件(bzlib.h)报错,提示找不到库。这是由于我们在Windows系统中并没有该系统库,所以需要自行下载并添加到项目中。

下载bzip2源码(该库是用来做文件压缩的,bsdiff中使用了该库)。

src/cpp/ 下新建目录 bzip,将下载好的bzip2源码中的以下文件复制到该目录下:

该目录下的文件对应着源码中 MakeFile 中引用的文件:

修改 CMakeLists.txt,添加库引用。

cmake_minimum_required(VERSION 3.4.1)file(GLOB bzip ${CMAKE_SOURCE_DIR}/bzip/*.c)add_library(        native-lib        SHARED        native-lib.cpp        bspatch.c        ${bzip}        )include_directories(${CMAKE_SOURCE_DIR}/bzip)find_library(        log-lib        log)target_link_libraries(        native-lib        ${log-lib})

编写差分包合并方法

新建BsPatcher.java

public class BsPatcher {    static {        System.loadLibrary("native-lib");    }    public static native void bsPatch(String oldAdk, String patch, String output);}

native-lib.cpp 中添加方法

#include #include extern "C" {    int main(int argc, const char * argv[]);}extern "C"JNIEXPORT void JNICALLJava_com_mkl_bsdiff_BsPatcher_bsPatch(JNIEnv *env, jclass type, jstring oldApk_, jstring patch_,jstring output_) {    const char *oldApk = env->GetStringUTFChars(oldApk_, 0);    const char *patch = env->GetStringUTFChars(patch_, 0);    const char *output = env->GetStringUTFChars(output_, 0);    const char *argv[] = {"", oldApk, output, patch};    main(4, argv);    env->ReleaseStringUTFChars(oldApk_, oldApk);    env->ReleaseStringUTFChars(patch_, patch);    env->ReleaseStringUTFChars(output_, output);}

tips:请将com_mkl_bsdiff替换成实际的包名。

差分包合并非常简单,只需要调用 bspatch.c 中的main方法即可(该main方法并非我们常说的程序入口方法,该方法是一个成员方法,为了避免混淆,可以自行修改方法名称)。

生成新旧版本apk文件

旧版本

修改 MainActivity.java

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        TextView tv = findViewById(R.id.sample_text);        tv.setText(BuildConfig.VERSION_NAME);        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {            String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE};            if (checkSelfPermission(perms[0]) == PackageManager.PERMISSION_DENIED) {                requestPermissions(perms, 1000);            }        }    }    public void update(View view) {        new AsyncTask<Void, Void, File>() {            @Override            protected File doInBackground(Void... voids) {            // sourceDir即为当前应用的apk备份包路径                String oldApk = getApplicationInfo().sourceDir;                String patch = new File(Environment.getExternalStorageDirectory(), "patch").getAbsolutePath();                String output = createNewApk().getAbsolutePath();                BsPatcher.bsPatch(oldApk, patch, output);                return new File(output);            }            @Override            protected void onPostExecute(File file) {                if (file != null) {                    if (!file.exists()) {                        return;                    }                    Intent intent = new Intent(Intent.ACTION_VIEW);                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                        Uri fileUri = FileProvider.getUriForFile(MainActivity.this, getApplicationInfo().packageName + ".fileprovider", file);                        intent.setDataAndType(fileUri, "application/vnd.android.package-archive");                    } else {                        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");                    }                    startActivity(intent);                } else {                    Toast.makeText(MainActivity.this, "差分包不存在", Toast.LENGTH_SHORT).show();                }                super.onPostExecute(file);            }        }.execute();    }    private File createNewApk() {        File newApk = new File(Environment.getExternalStorageDirectory(), "bsdiff.apk");        if (!newApk.exists()) {            try {                newApk.createNewFile();            } catch (IOException e) {                e.printStackTrace();            }        }        return newApk;    }}

MainActivity做了三件事,权限申请、异步合并差分包、安装新apk。

布局文件如下:
activity_main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity"    android:orientation="vertical">    <TextView        android:id="@+id/sample_text"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Hello World!"/>    <Button        android:onClick="update"        android:text="Update"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></LinearLayout>

AndroidManifest.xml 中添加:

权限

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

provider

<provider android:name="android.support.v4.content.FileProvider" android:grantUriPermissions="true"    android:exported="false" android:authorities="${applicationId}.fileprovider">    <meta-data android:name="android.support.FILE_PROVIDER_PATHS"        android:resource="@xml/file_paths"/></provider>

res目录下新建xml文件夹,添加file_paths.xml文件如下:

<?xml version="1.0" encoding="utf-8"?><paths>    <root-path path="" name="root"/>    <files-path path="." name="files"/>    <cache-path path="." name="cache"/>    <external-path path="." name="external"/>    <external-files-path path="." name="external_file_path"/>    <external-cache-path path="." name="external_cache_path"/></paths>

构建项目,生成apk包,将outputs/apk/debug下的app-debug.apk保存,并修改名称为old.apk

新版本

activity_main.xml中添加一张图片:

    <ImageView        android:src="@drawable/image"        android:layout_width="match_parent"        android:layout_height="wrap_content" />

res/drawable 下添加该图片文件。

修改 build.gradle 文件中,将 versionName 改为 2.0

重新生成apk包,保存并修改其名称为new.apk

构建差分包(Linux中操作)

使用wget命令下载bsdiff源码到Linux中,使用 tar -xvf bsdiff-4.3.tar.gz 解压文件,得到如下目录。

在该目录下使用make命令,自动生成bsdiffbspatch可执行文件。

在使用该命令时,可能会遇到以下两个错误:

1、Makefile:13: *** missing separator. Stop.

该错误是由于Makefile文件格式错误,使用vim Makefile修改,在第13行和15行前输入tab键。

2、fatal error: bzlib.h: No such file or directory

该错误和我们在Android Studio中遇到的bzlib库错误一样,只需要在Linux中安装该库即可。

使用 yum install bzip2-devel.x86_64 -y 安装即可解决。

将new.apk和old.apk拷贝到当前目录下,执行以下命令来构建差分包:

./bsdiff old.apk new.apk patch

测试

将得到的 patch 文件拷贝到手机SD卡根目录下,在手机上安装运行old.apk,点击更新按钮,即可执行合并并启动安装程序,安装完成后,我们会看到app已更新到2.0版本。

更多相关文章

  1. android 逆向工程
  2. Android中怎么破解游戏之修改金币数
  3. Android如何防止apk程序被反编译(尊重劳动成果)
  4. Android9.0万年历毕业设计H5小应用webview应用源码分析已运行通
  5. TTF字体库系列文章1 —— Android使用ttf字体库替代替图片(iconf
  6. android语音即时通讯之录音、播放功能实现代码
  7. Android(安卓)数据库更新 onupgrade
  8. APP批量自动生成各种不同分辨率尺寸图标和启动页(Android和iOS都
  9. Android(安卓)MP3文件录制 + 声音分贝大小自定义View实现

随机推荐

  1. Android(安卓)view中invalidate方法学习
  2. Android(安卓)SAX解析xml为java
  3. Apple iPad 的替代产品大比拼
  4. 获取当前locale
  5. 懒加载fragment基类
  6. life cycle of an Android(安卓)activity
  7. ShareSDK Android常见问题汇总
  8. Android(安卓)studio button 按钮 四种绑
  9. android音视频采集参考
  10. Android与HTML5交互模版