Android增量更新
阅读原文时间:2021年04月20日阅读:1

一、简介

增量更新就是通过某种算法找出新版本和旧版本不一样的地方,然后将不一样的地方抽取出来形成更新补丁(patch),也称之为差分包。客户端在检测到更新的时候,只需要下载差分包到本地,然后将差分包合并至本地的安装包安装。

由此就可以看出增量更新主要分为两步:

1、服务端拿新版本Apk和旧版本Apk做差分,生成差分包(patch)

2、客户端检测到可增量更新的差分包,下载差分包(patch)之后,和本地旧版本做合成,生成新版本安装。

二、实现

1、工具

使用开源库bsdiff来实现,由于bsdiff进行差分合并时依赖bzip2,所以还需要bzip2源码

<1> 将bsdiff解压,可以看到bsdiff.c(差分) 和 bspatch.c (合并)

<2> 将bzip2解压,可以看到好多.c文件、.h头文件以及一些其他文件(包括Makefile文件)        
    打开Makefile文件,可以看到:
        OBJS= blocksort.o  \
        huffman.o    \
        crctable.o   \
        randtable.o  \
        compress.o   \
        decompress.o \
        bzlib.o
    这些就是需要的文件,将对应的.c文件和.h头文件拿出来

2、实现

<1> 新建一个Library项目,勾选支持C语言

<2> 将解压出来的bspatch.c (合并)放到src/main/cpp目录下,然后在src/main/cpp新建一个bzip目录,将
bzip2解压出来对应的.c文件和.h头文件放进去

<3> 打开CMakeLists.txt文件

      cmake_minimum_required(VERSION 3.4.1)
    file(GLOB bzip_source src/main/cpp/bzip/*.c)
    add_library( # Sets the name of the library.
                     update-lib

                     # Sets the library as a shared library.
                     SHARED

                     # Provides a relative path to your source file(s).
                     src/main/cpp/update-lib.cpp
                     src/main/cpp/bspatch.c
                     ${bzip_source})
     include_directories(src/main/cpp/bzip)

<4> 新建一个类TestUpdate

        public class TestUpdate {

            static {
                //加载动态库
                System.loadLibrary("update-lib");

            }
            //oldApk是当前APP的路径 outApk将要输出的apk路径  patch差分包路径
            public static native void testPatch(String oldApk,String outApk,String patch);

        }

<5> 打开bspatch.c文件,找到main函数,将mian修改为bsPatch_main(随意),防止和其他c文件中的main函数重名

<6> 打开src/main/cpp目录下的update-lib.cpp文件

        #include <jni.h>
        #include <string>
        #include<android/log.h>

        extern "C" {
        //bspatch.c中在执行合成方法就是其中bsPatch_main(int argc,char * argv[])
        //所以在这里需要引入该方法
        extern int bsPatch_main(int argc,char * argv[]);
        }

        extern "C"
        JNIEXPORT void JNICALL
        Java_com_test_updatelibrary_TestUpdate_testPatch(JNIEnv *env, jclass type, jstring oldApk_,
                                                         jstring outApk_, jstring patch_) {
            const char *oldApk = env->GetStringUTFChars(oldApk_, 0);
            const char *outApk = env->GetStringUTFChars(outApk_, 0);
            const char *patch = env->GetStringUTFChars(patch_, 0);
            //打印路径
            __android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%s",oldApk);
            __android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%s",outApk);
            __android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%s",patch);


            char * arvg[4] = {"", const_cast<char *>(oldApk), const_cast<char *>(outApk),
                              const_cast<char *>(patch)};
            //调用bspatch.c中bsPatch_main函数
            int i = bsPatch_main(4, arvg);
            //打印返回值,返回0表示执行完成
            __android_log_print(ANDROID_LOG_ERROR,"HelloJni","oldfile*%i",i);

            env->ReleaseStringUTFChars(oldApk_, oldApk);
            env->ReleaseStringUTFChars(outApk_, outApk);
            env->ReleaseStringUTFChars(patch_, patch);
        }

<7> 测试 7.0的手机安装需要用到FileProvider

        新建一个主项目,引入上面的Library模块,

        public class TestUpdateActivity extends AppCompatActivity {

            TextView textView;

            @Override
            protected void onCreate(@Nullable Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_test_update);
                textView = (TextView) findViewById(R.id.test_update_version_tv);
                textView.setText(getVersionName());
            }
            ///通过PackageInfo得到应用的版本号
            private String getVersionName() {
                PackageInfo pInfo = null;

                try {
                    //通过PackageManager可以得到PackageInfo
                    PackageManager pManager = getPackageManager();
                    pInfo = pManager.getPackageInfo(getPackageName(),
                            PackageManager.GET_CONFIGURATIONS);

                    return pInfo.versionName;
                } catch (Exception e) {
                    e.printStackTrace();
                }          
                return pInfo.versionName;
            }

            //点击按钮进行合并
            public void testUpdate(View view) {
                //模拟网络下载patch包
                new MyAsyncTask().execute();

            }

            class MyAsyncTask extends AsyncTask<Void,Void,File> {

                @Override
                protected File doInBackground(Void... voids) {
                    //获取当前APP的路径
                    String oldPath =  getApplication().getApplicationInfo().sourceDir;
                    //合成后输出的apk路径
                    String outPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "out.apk";
                    //差分包的路径(一般是从服务端下载得到,这里演示就用本地路径)
                    String patch = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "patch";
                    File file = new File(outPath);
                    if(!file.exists()){
                        try {
                            file.createNewFile();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    //调用testPatch进行合成
                    TestUpdate.testPatch(oldPath,outPath,patch);
                    return file;
                }

                @Override
                protected void onPostExecute(File file) {
                    super.onPostExecute(file);
                    if(file == null){
                        return;
                    }
                    //调用系统进行安装
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    //判断是否大于android7.0,7.0的手机安装需要FileProvider
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                        //不是就执行安装
                        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
                    } else {
                        //是的话 需要声明临时权限
                        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                        //获取包名
                        String packageName = getApplication().getPackageName();
                        // 第二个参数,即清单文件中配置的authorities,第三个参数就是要执行的文件
                        Uri contentUri = FileProvider.getUriForFile(TestUpdateActivity.this, packageName + ".fileprovider", file);
                        intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
                    }
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);      
                }
            }

        }

        配置清单文件
        <!--读写权限-->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
        <!--位未知来源权限,适配8.0-->
        <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

        在<application> </application>中添加provider
         <provider
            android:name="android.support.v4.content.FileProvider"
            //与Intent中的FileProvider.getUriForFile方法中的第二个参数一致
            android:authorities="com.test.demo.test.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">

            <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"?>
        <resources>
            <paths>
                <external-path path="" name="installApk"/>
            </paths>
        </resources>

这样就完成了,可以运行测试了。由于是测试,所以需要在本地生成patch差分包。

        这里是windows所以需要使用bsdiff的windows版本,将bsdiff-win解压,使用cmd进入到

        解压目录中,将上面的项目打包成两个apk(版本号不同),放到解压目录中使用bsdiff命令将

        两个apk进行差分,得到patch文件。命令如下:

        bsdiff old.apk new.apk patch 

        将patch文件放到手机的根目录下,安装old.apk到手机中,点击按钮进行更新。

在实际的项目中差分是由服务端完成的,客户端只是将旧版本与差分包进行合并成新的版本。

开源库bsdiff中的bsdiff.c就是用于差分用的,用法和合并是一样的,里面也是一个main方法

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器