Android incremental update fully resolved is incremental not hotfix

This article was first published on my WeChat public account: Hongyang (hongyangAndroid).

Please indicate the source for reprinting:
http://blog.csdn.net/lmj623565791/article/details/52761658 ;
This article comes from: [Zhang Hongyang's blog]

I. Overview

Recently, I have been focusing on hot fixes, and occasionally chatting about incremental updates, of course, the two are not the same thing at all. I found some information through this, collected and sorted it out, I didn’t want to write a blog, because it was mainly the realization of tools, but when I was sorting out the information last night, I suddenly found that I was about to forget this stuff, and I had to find it all over again. A circle of tools.

So, when the right is a record, it is also convenient to find it by yourself in the future.

The first thing to be clear about is what an incremental update is:

I believe that everyone has seen the software update in the application market to save traffic. A software of several hundred M may only need to download a 20M incremental package to complete the update. So how does it do it?

That's the topic of this blog.

The process of incremental update is: an application is installed on the user's mobile phone, the incremental package is downloaded, the apk and the incremental package on the mobile phone are combined to form a new package, and then installed again (note that this process requires reinstallation, of course Some app markets have root permissions, you may not be aware of them).

ok, then refine the whole process into several key points:

  1. Extract the apk of the currently installed application on the user's mobile phone
  2. How to use old.apk and new.apk to generate incremental files
  3. The increase file is merged with the old.apk in 1., and then installed

After solving the above 3 problems, it is ok.

Let’s start to solve it. First, let’s look at the generation and merging of incremental files. This link can be said to be the core of the entire process and also a technical difficulty. Fortunately, this technical difficulty has been implemented for us by tools.

2. Generation and merging of incremental files

This is actually the use of tools to do a binary diff and patch.

URL:

download link:

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

After downloading, unzip it, switch to the corresponding directory, and then execute make:

aaa:bsdiff-4.3 zhy$ make
Makefile:13: *** missing separator.  Stop.

Well, you read that right, the error is reported, and this error is relatively easy to solve.

There is a file in the decompressed file: Makefile, open it in the form of text, and add an indent to the if and endif below install:.

The modification is completed like this:

CFLAGS      +=  -O3 -lbz2

PREFIX      ?=  /usr/local
INSTALL_PROGRAM ?=  ${INSTALL} -c -s -m 555
INSTALL_MAN ?=  ${INSTALL} -c -m 444

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

install:
    ${INSTALL_PROGRAM} bsdiff bspatch ${PREFIX}/bin
    .ifndef WITHOUT_MAN
    ${INSTALL_MAN} bsdiff.1 bspatch.1 ${PREFIX}/man/man1
    .endif

Then, re-execute make:

aaa:bsdiff-4.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)
                    ^~~~~~
                    char

This time is better than last time. This time, a bsdiff was generated, but an error was reported when bspatch was generated. Fortunately, we only need to use bsdiff. Why do you say that?

Because the generation of incremental files must be done on the server, or on our local PC, using the bsdiff tool;

another bspatch, merging old.apk and incremental files must be done inside our application.

Of course, this problem can also be solved. Search, many solutions, we will not continue to waste space on this.

I provide a download address here:

https://github.com/hymanAndroid/tools/tree/master/bsdiff-4.3

After the download is complete, direct make, bsdiff and bspatch will be generated (in the mac environment).

============Magic dividing line==============

ok, assuming here, no matter what method you use, we already have bsdiff and bspacth. Let's demonstrate the use of this tool:

First, we prepare two apks, old.apk and new.apk. You can write a project by yourself, run it once to get the generated apk as old.apk; then modify some code, or add some functions, and run it again to generate new.apk;

  • Generate incremental files
./bsdiff old.apk new.apk old-to-new.patch

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

  • Incremental files and old.apk merged into new apk
./bspatch old.apk new2.apk old-to-new.patch

This will generate a new2.apk

So how do we prove that the generated new2.apk is exactly the same as our new.apk?

We can check the md5 value. If the md5 value of the two files is the same, then it is almost certain that the two files are exactly the same (don't tell me that collision can produce the same md5 value~~).

aaa:bsdiff-4.3 zhy$ md5 new.apk 
MD5 (new.apk) = 0900d0d65f49a0cc3b472e14da11bde7
aaa:bsdiff-4.3 zhy$ md5 new2.apk 
MD5 (new2.apk) = 0900d0d65f49a0cc3b472e14da11bde7

You can see that the md5 of the two files is the same~~

Well, assuming you are not a mac, how do you get the md5 of a file? (Write the code yourself, download the tools, don't encounter such problems, and pop up the window for me, I will be deducted from my salary...)

So here we already know how to generate incremental files and merge patches with old files into new files. So let's sort out the whole process again:

  1. The server has prepared incremental files (completed in this section)
  2. The client downloads incremental files + extracts the app's apk, merges using bspatch
  3. Generate a new apk, call the installer

It's still quite clear, so it's mainly the second point. The second point has two things, one is to extract the apk of the application; the other is to use bspatch to merge, then this merge must be done by native methods and so files, that is Said we have to make a so ourselves;

The behavior of the client

(1) Extract the apk file of the application

In fact, it is very simple to extract the apk of the current application, the following code:

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

(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);

}

The three parameters are already clear;

At the same time, don't forget to under the module's build.gradle:

defaultConfig {
    ndk {
        moduleName = 'bsdiff'
    }
}

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

ok, the next step is to complete the writing of the c code;

First, create a new folder jni in the app/main directory, and copy the bspatch.c in the previously downloaded bsdiff into it;

Then according to the rules of jni, create a new method in it:

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;
}

There are rules for method names, so don’t mention this rule~~

Note that there is no patchMethod method in bsdiff.c. This method is actually the main method. You can directly modify it to patchMethod. It doesn't matter if it is complicated. There is a source code at the end of the article.

ok, you can try to run at this time, it will prompt to depend on bzlib, in fact, it can be seen from the include at the top of the file.

Since it depends, let's import it:

First download:

After the download is complete, unzip it:

Extract the .h and .c files, and then you can choose to copy the folder to app/main/jni of our module. The result is as follows:

Remember to modify the include in bsdiff:

#include "bzip2/bzlib.h"

run again;

Then you will find a bunch of errors like the following:

Error:(70) multiple definition of `main'

It prompts that the main method is repeatedly defined. The error message will show which classes contain the main method. You can choose to delete the main method in these classes directly.

After deleting it, it's ok~~

So here, we have completed the preparation of JNI, of course, the file is the c source code provided by bsdiff.

Fourth, install after incremental update

After the above operation is completed, the last step is simple, first prepare two apks:

old.apk new.apk

Then make a patch, PATCH.patch in the following code;

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

Finally trigger the call in Activity:

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

    //一定要检查文件都存在

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

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

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

install is actually installed through Intent:

 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);
    }

There may be a problem with 7.0 here. The path is exposed to other apps, and FileProvider should be needed to implement it (unexperimented, guessing may be possible).

The general effect diagram is as follows:

V. Summary

If you just want to use this function, you can directly copy the generated so file and use it directly with loadLibrary.

Secondly, when doing incremental updates, the patch must be based on your current version number and the latest (or target) version apk, compare and deliver the diff file, at the same time, you should also deliver the md5 of the target apk, and then do After merging, don't forget to check md5;

At the end of the blog, although it is very simple, it is mainly implemented with tools, but it is recommended to implement it once. It will take some time to run through it at one time, and some pits may be found in the process, which can also improve your proficiency in JNI.

Source code:

You can also choose to use so directly


Welcome to follow my Weibo:
http://weibo.com/u/3165018720


Group number: 497438697 , welcome to join the group

WeChat public account: hongyangAndroid
(Welcome to pay attention, don't miss every piece of dry goods, support submission)

References and related links

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325765723&siteId=291194637