Android增量更新

一、简介

增量更新就是通过某种算法找出新版本和旧版本不一样的地方,然后将不一样的地方抽取出来形成更新补丁(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 
        #include 
        #include
        
        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(oldApk), const_cast(outApk),
                              const_cast(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 {
        
                @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);      
                }
            }
        
        }
		
		配置清单文件
		
		
		
         中添加provider
		 

            
        
		
		在res下创建xml目录,新建一个file_paths.xml文件
		
		
        
            
                
            
        

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

		这里是windows所以需要使用bsdiff的windows版本,将bsdiff-win解压,使用cmd进入到
		
		解压目录中,将上面的项目打包成两个apk(版本号不同),放到解压目录中使用bsdiff命令将
		
		两个apk进行差分,得到patch文件。命令如下:
		
		bsdiff old.apk new.apk patch 
		
		将patch文件放到手机的根目录下,安装old.apk到手机中,点击按钮进行更新。

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

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

你可能感兴趣的:(Android)