Skip to content

Latest commit

 

History

History
120 lines (99 loc) · 11.4 KB

README.en.md

File metadata and controls

120 lines (99 loc) · 11.4 KB

release tag license +issue Welcome

[README 中文版]
provide a way to hot update Unity app on Android, support code & resources, not need lua js or IL runtime etc..., will not disturb your project development; just loading the new version apk file to achieve.
( dependent libraries ApkDiffPatch, xHook. )

The effect of this solution

When the android user running the app, update logic can complete the task of downloaded the new version app in the background; When the app is restarted, user run with new version app (not need reinstall through system, the "hot update" means this)!

Theory of hot update

When installing the Android app, the apk file is stored in path getApplicationContext().getPackageResourcePath(), and the native abi library files are decompressed and stored in path getApplicationContext().getApplicationInfo().nativeLibraryDir , including libmain.so, libunity.so, libil2cpp.so or libmono*.so, etc...
This solution will store the updated version apk file to the update path in getApplicationContext().getFilesDir().getPath(), and decompress the changed local abi library files from apk to the update path; When the app restarts, use the hook and java layer code to intercept the c file API in Unity's library files, map access path from the apk file and library files to the new apk file and new library files.
(The idea Hook Unity library's C file API to implement hot update, comes from UnityAndroidIl2cppPatchDemo, thanks.)

Feature

  • Implement simple and run fast
    The theory and implement are simple, and support hot update of code and resources; support il2cpp and mono two code backend, support libunity.so, libmono.so and other library files changes; support libmain.so little(RELEASE) version changes, not support Unity main(MAJOR & MINOR) version changes;
    Solution import into project is simple, easy to modify by yourself; (the description of import project is at back of this document.)
    After the hot update, the execution performance of the app is not affected, unlike the slow performance and Activity compatibility problems after using the plugin (or virtual) apk solution;
    This solution will not disturb your Unity project development, not need other program language or constraints, such as lua js or IL runtime etc...
  • The patch package by downloaded is small
    Not need to download the complete apk file, you just need to download the difference between the existing version and the latest version; then you can create the latest version apk file on device;
    You can select ApkDiffPatch for apk file diff&patch, it can generate very small patch package; for example, just some simple code changes, the patch package size about hundreds of KB.
  • Development environment and compatibility
    Test used Unity5.6, Unity2017, Unity2018, Unity2019;
    Test used mono and il2cpp backend;
    Test on armeabi-v7a arm64-v8a and x86 device;
    Test supported Andorid4.1+; but maybe only supported Andorid5+ when using Unity5.6 or Unity2017 + il2cpp backend, you need tests;
    It is generally compatible, if the main(MAJOR & MINOR) version of Unity unchanged (jni lib's API will not change) and no new .so library files been added;
    The project's other .so libraries can be added to the list of libraries that are allowed to be advance load, for compatible with the hot update (added in the HotUnity.java file, see the description of improt project);
    Tested to create a simple app with the same Unity main version, and successfully hot updated to an existing complex game app;
    Of course, those involving permissions or third-party services, etc., their compatibility need more testing.

How to import into your Unity project for testing

  • Export project: select Gradle and export project in your Unity project, and then modify it and do packaging by Android Studio;
  • Modify the exported project:
    add libhotunity.so(rebuild in path project_hook_unity_jni) to project jniLibs;
    add com/github/sisong/HotUnity.java to project; (You can add the .so in this file that need hot update, which will be loaded if exist new version;)
    edit file UnityPlayerActivity.java in project; add code: import com.github.sisong.HotUnity;, and add code: HotUnity.hotUnity(this); before mUnityPlayer = new UnityPlayer(this);
  • If you need to support upgrading little(RELEASE) version of Unity, you need to use the FixUnityJar program(code in path project_fix_unity_jar/fix_unity_jar) to modify the file unity-classes.jar in the export project, and add libnull.so(rebuild in path project_fix_unity_jar/null_lib ) to project jniLibs;
  • APK installation for compatibility with Android 10, need: Edit AndroidProject's AndroidManifest.xml file's application node, add:
<!-- if androidx: provider android:name="androidx.core.content.FileProvider" -->
<provider 
android:name="android.support.v4.content.FileProvider" android:authorities="{YourAppPackageName}.fileprovider" android:exported="false" android:grantUriPermissions="true">
      <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/update_files" />
</provider>

Create /xml/update_files.xml file in AndroidProject's res path, Enter the following content:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external" path="."/>
</paths>
  • Package the test project by Android Studio (you can automate the process of exporting modifying and packaging in Unity with the editor extension), the app should be able to running normally on the device; now you need test the app "hot" update to a new version;
  • Manual hot update test process: you have a new version app named update.apk, placed it in the HotUpdate subdirectory of the getApplicationContext().getFilesDir().getPath() (device path /data/data/<appid>/files/HotUpdate/); Decompress the *.so file in lib/<this test device abi>/ from update.apk, and place it in the directory HotUpdate/update.apk_lib/ (can also dispose only changed .so file); restart the app on the device, you should be able to see that the new version is already running!

How to automate hot update by app

(the following info provides an optional way to do this)

  • We have a new version apk, I can use the diff tool to generate patch between the old and new versions; put the version upgrade info and patch files on the server;
  • When the app running, it checks the upgrade info and download the patch file; the app call patch algorithm to generate a new version apk(ie HotUpdate/update.apk) using the latest apk and patch file; At the same time, choose to cache the changed .so files in this new apk to the HotUpdate/update.apk_lib/ path; If you need a "cold" re-install choose not to cache .so files;
  • Generate patch file between apks and merge it on device, used the ApkDiffPatch; if you want test diff program, you can download it at releases; The patch process needs to be executed on the user device. provided function virtualApkPatch() in java, and native function virtual_apk_patch() can be used in C#.
  • Note: ApkDiffPatch is specially optimized for zip file. Generally, it generates smaller patch file size than bsdiff or HDiffPatch; The ZipDiff tool has special requirements for the input apk file, if the apk has v2+ sign, then you need to normalized the apk files by the ApkNormalized tool; Then use AndroidSDK#apksigner to re-sign the apk; All the apks released for user need to done this process, this is to byte by byte restore the apk when patching; (apk after this processed, it is also compatible with the patch size optimization scheme archive-patcher of the Google Play Store)

Recommend a process for multi version update

v0 -> new install(v0)

v1 -> diff(v0,v1) -> pat01 ; v0: patch(v0,download(pat01)) -> v1 + cache_lib(v1)
      ( if error on patch then: download(v1) -> v1 + install(v1) )

v2 -> diff(v0,v2) -> pat02 ; v0: patch(v0,download(pat02)) -> v2 + cache_lib(v2)
      diff(v1,v2) -> pat12 ; v1: patch(v1,download(pat12)) -> v2 + cache_lib(v2)

v3 -> (if no patch for v0) ; v0: download(v3) -> v3 + install(v3)
      diff(v1,v3) -> pat13 ; v1: patch(v1,download(pat13)) -> v3 + cache_lib(v3)
      diff(v2,v3) -> pat23 ; v2: patch(v2,download(pat23)) -> v3 + cache_lib(v3)

if is_need_install(v3,v4):
v4 -> (if no patch for v0) ; v0: download(v4) -> v4 + install(v4)
      diff(v1,v4) -> pat14 ; v1: patch(v1,download(pat14)) -> v4 + install(v4)
      diff(v2,v4) -> pat24 ; v2: patch(v2,download(pat24)) -> v4 + install(v4)
      diff(v3,v4) -> pat34 ; v3: patch(v3,download(pat34)) -> v4 + install(v4)

v5 -> (if no patch for v0) ; v0: download(v5) -> v5 + install(v5)
      (if no patch for v1) ; v1: download(v5) -> v5 + install(v5)
      diff(v2,v5) -> pat25 ; v2: patch(v2,download(pat25)) -> v5 + install(v5)
      diff(v3,v5) -> pat35 ; v3: patch(v3,download(pat35)) -> v5 + install(v5)
      diff(v4,v5) -> pat45 ; v4: patch(v4,download(pat45)) -> v5 + cache_lib(v5)

This new version and patch release process needs to be automated: normalized apk, re-signing, creating patches, generate config, etc...

Defect

  • After changed API between Java and Unity, apk can't be hot update, the new apk needs to be installed; After changed the Unity main(MAJOR & MINOR) version, apk can't be hot update, and the same Unity main version can continue to hot update;
  • Can not switch between il2cpp and mono backend apks by hot update, the new apk needs to be installed;
  • The solution can only support Android and can't be applied on iOS; (The app developed by Unity run on PC, if needs difference update, you can using HDiffPatch to diff&patch between directory.)
  • The diff&patch algorithm selected ApkDiffPatch, which may not support this situation: apk must support v2+ sign, but released apk cannot be signed by self, then it is impossible to version control and diff;
  • The new apk file and cached .so files take up disk space; A recommended solution: fixed Unity version, initial version with a minimized apk (or obb split mode), subsequent updates are the full version; (Another possible improvement is to use the concept of virtual apk: Use hook to map the access to the original apk file when file API access unchanged entry files in virtual apk; the patch process also requires a new implementation; a similar implementation see UnityAndroidIl2cppPatchDemo.)

by housisong@gmail.com