An overview,

I’ve been focusing on hot fixes lately, occasionally chatting about incremental updates, but of course two is not the same thing at all. I took this opportunity to find some information, collect and sort it out. Originally, I didn’t want to write a blog, because it was mainly about the implementation of tools. But last night when I was sorting out the information, I suddenly found that I almost forgot this thing and had to start from scratch again.

So, as a record, but also convenient to find their own.

The first thing to be clear about is what incremental updates are:

I believe we have seen in the application market to save traffic update software, a software of several hundred meters may only need to download a 20M incremental package to complete the update. So how does it work?

That’s the subject of this blog.

The process of incremental updates is as follows: users have an app installed on their phone, download the incremental package, merge the APK and the incremental package on their phone to form a new package, and then install it again (note that this process requires re-installation, of course, you may not be aware of the root permissions in some app markets).

Ok, so break the process down to a few key points:

  1. Extract the APK of the currently installed application from the user’s phone
  2. How to generate incremental files with old.apk and new.apk
  3. Add file to 1. Merge with old.apk and install

Solve the above three problems, it is OK.

Let’s start with the incremental file generation and merge. This process is the core of the whole process and also the technical difficulty. Fortunately, this technical difficulty has been implemented by tools for us.

Second, incremental file generation and merge

This is actually a tool to do a binary diff and patch.

Web site:

  • www.daemonology.net/bsdiff/

Download address:

  • www.daemonology.net/bsdiff/bsdi…

By the way, the environment of this paper is MAC. If other systems hinder you, you can search and solve it slowly.

Once you’ve downloaded it, unzip it, cut to the appropriate directory, and then make:

aaa:bsdiff4.3 zhy$ make
Makefile:13: *** missing separator.  Stop.Copy the code

En, you did not read wrong, reported wrong, this error is relatively easy to solve.

The decompressed file contains a file called Makefile, which is opened as text. Add an indent to install: if,endif.

The modification finished like this:

CFLAGS += -O3 -lbz2 PREFIX ? =/usr/local INSTALL_PROGRAM ? = ${INSTALL} -c -s -m555INSTALL_MAN ? = ${INSTALL} -c -m444

all:        bsdiff bspatch
bsdiff:        bsdiff.c
bspatch:    bspatch.c

install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
    .ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff1. bspatch1. ${PREFIX}/man/man1
    .endifCopy the code

Then, re-execute make:

aaa:bsdiff4.3 zhy$ make
cc -O3 -lbz2    bsdiff.c   -o bsdiff
cc -O3 -lbz2    bspatch.c   -o bspatch
bspatch.c:39:21: error: unknown type name 'u_char'; did you mean 'char'?
static off_t offtin(u_char *buf)
                    ^~~~~~
                    charCopy the code

This time it is better than last time, this time it generates a Bsdiff, but it generates a bspatch error. Fortunately, we only need to use bsdiff.

Because the incremental file generation must be done on the server, or on our local PC, using the bsdiff tool; Another bspatch, merging old.apk and delta files must have been done inside our app.

Of course, this problem can also be solved, search, a lot of solutions, we don’t want to waste space on this.

Here I provide a download address:

Github.com/hymanAndroi…

After downloading, make, bsdiff and Bspatch will be generated (on MAC).

============= The magic dividing line ==============

Ok, assuming that at this point, we have bsdiff and bspacth, no matter what method you use. Here’s how to use the tool:

First of all, we prepare two apKs, old.apk and new.apk. You can write a project by yourself, run it once and get the generated APK as old.apk. Then modify some code, or add some functionality, and run it again to generate new.apk;

  • Generating incremental files
./bsdiff old.apk new.apk old-to-new.patchCopy the code

This generates an incremental file old-to-new.patch

  • The incremental file and old.apk are merged into a new APK
./bspatch old.apk new2.apk old-to-new.patchCopy the code

This generates a new2.apk

How can we prove that new2.apk is exactly the same as new.apk?

If the MD5 values of two files are the same, it is almost certain that the two files are the same (don’t argue with me that collisions can produce the same MD5 values).

aaa:bsdiff4.3 zhy$ md5 new.apk 
MD5 (new.apk) = 0900d0d65f49a0cc3b472e14da11bde7
aaa:bsdiff4.3 zhy$ md5 new2.apk 
MD5 (new2.apk) = 0900d0d65f49a0cc3b472e14da11bde7Copy the code

You can see that the MD5 of the two files is exactly the same

Well, assuming you’re not a MAC, how do you get md5 for a file? (write your own code, download the tool, do not encounter such a problem, also pop window ME, I will be deducted wages…)

So now we know how to generate incremental files and merge patch and old files into new files. So let’s go through the whole process again:

  1. The server has done the delta file (this section is done)
  2. The client downloads the incremental file + extracts the apK of the application and merges it using bSPatch
  3. The new APK is generated and the installer is called

It’s pretty clear, so the main thing is the second point, the second point is two things, one is the APK of the extraction application; One is to use bSPatch merge, so this merge must need native method and SO file to do, that is to say, we need to type so by ourselves;

Third, the behavior of the client

(1) Extract apK files of applications

In fact, it is very simple to extract the apK of the current application, as follows:

public class ApkExtract {
    public static String extract(Context context) {
        context = context.getApplicationContext();
        ApplicationInfo applicationInfo = context.getApplicationInfo();
        String apkPath = applicationInfo.sourceDir;
        Log.d("hongyang", apkPath);
        returnapkPath; }}Copy the code

(2) Make bspatch So

First declare a class and write a native method as follows:

public class BsPatch {

    static {
        System.loadLibrary("bsdiff");
    }

    public static native int bspatch(String oldApk, String newApk, String patch);

}Copy the code

The three parameters are already clear;

Also don’t forget to build. Gradle in module:

defaultConfig {
    ndk {
        moduleName = 'bsdiff'}}Copy the code

Note that this step requires you to configure the NDK environment (download NDK, set ndk.dir) ~

Ok, the next is to complete the writing of c code;

Create a folder jni in app/main and copy bspatch.c from bsdiff.

Then create a new method in it according to JNI rules:

JNIEXPORT jint JNICALL Java_com_zhy_utils_BsPatch_bspatch
        (JNIEnv *env, jclass cls,
         jstring old, jstring new, jstring patch){
    int argc = 4;
    char * argv[argc];
    argv[0] = "bspatch";
    argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0));
    argv[2] = (char*) ((*env)->GetStringUTFChars(env, new.0));
    argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));


    int ret = patchMethod(argc, argv);

    (*env)->ReleaseStringUTFChars(env, old, argv[1]);
    (*env)->ReleaseStringUTFChars(env, new, argv[2]);
    (*env)->ReleaseStringUTFChars(env, patch, argv[3]);
    return ret;
}Copy the code

Method names have rules, which need not be mentioned ~~

Note that there is no patchMethod method in Bsdiff. c, this method is actually main method, just modify it to patchMethod directly, it doesn’t matter if it is complicated, there is source code at the end of the article.

Ok, at this point you can try to run it and it will prompt you to rely on bzlib, as you can see from the include at the top of the file.

Since it depends on, let’s import it:

First download:

  • www.bzip.org/downloads.h…
  • www.bzip.org/1.0.6/bzip2…

Once the download is complete, unzip:

Extract the. H and. C files, and then select the folder to copy to our module app/main/jni, the result is as follows:

Remember to modify include in bsdiff:

#include "bzip2/bzlib.h"Copy the code

Run again;

Then you’ll find a bunch of errors like the following:

Error: (70) multiple definition of `main'Copy the code

The error message shows which classes contain the main method. You can delete the main method in these classes directly.

Delete after, ok ~~

So here, we have completed the preparation of JNI, of course, the file provided by BSDIff C source code.

Iv. Install after incremental update

Now that the above is done, the final step is simple: First prepare two APKs:

old.apk new.apkCopy the code

Then make a patch, patch.patch in the code below;

Install old.apk, then place new.apk and patch. PATCH to memory card;

Finally, the call is triggered in the Activity:

private void doBspatch(a) {
    final File destApk = new File(Environment.getExternalStorageDirectory(), "dest.apk");
    final File patch = new File(Environment.getExternalStorageDirectory(), "PATCH.patch");

    // Be sure to check that all files exist

    BsPatch.bspatch(ApkExtract.extract(this),
            destApk.getAbsolutePath(),
            patch.getAbsolutePath());

    if (destApk.exists())
        ApkExtract.install(this, destApk.getAbsolutePath());
    }Copy the code

Remember to enable read and write SDCard permission, remember to verify that the required files exist in the code.

Install is an Intent to install:

 public static void install(Context context, String apkPath) {
        Intent i = new Intent(Intent.ACTION_VIEW);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        i.setDataAndType(Uri.fromFile(new File(apkPath)),
                "application/vnd.android.package-archive");
        context.startActivity(i);

    }Copy the code

There may be a problem with 7.0, which exposes the path to another app and should be implemented by a FileProvider.

The rough renderings are as follows:

Five, the summary

If you simply want to use this functionality, you can simply copy the generated so file to loadLibrary.

Secondly, when doing incremental update, patch must send diFF file according to your current version number and the latest (or target) version APK by comparison. Meanwhile, it should also send MD5 of the target APK. After merging, don’t forget to check MD5.

The end of the blog, although very simple, mainly using tools to achieve, but it is still recommended to achieve once, want to run through or take some time, may also find some pits in the process, but also can improve their proficiency in JNI.

Source:

  • Github.com/hongyangAnd…

You can also choose to use so directly

  • Github.com/hongyangAnd…

Welcome to follow my wechat official account: hongyangAndroid

(Welcome to pay attention, do not miss every dry goods, support submission)

References and related links

  • www.daemonology.net/bsdiff/
  • www.bzip.org/downloads.h…
  • Blog.csdn.net/hmg25/artic…
  • www.cnblogs.com/lping/p/583…