Essential skills for advanced engineers: Full analysis of Android hot repair technology! (With video + e-book sharing)

Preface

Hot repair technology is a relatively advanced and popular knowledge point in the current Android development, and it is a skill that intermediate developers must master to advance to advanced development. At the same time, in the Android industry, hot repair technology is flourishing. Major manufacturers have launched their own hot repair solutions, and the technical solutions used are also different. Of course, each solution has its own limitations. I hope that through the combing and elaboration of this article, I can understand the comparison and implementation principles of these thermal repair schemes, master the essence of thermal repair technology, and also apply practice to actual projects to help you apply what you have learned (study notes are shared at the end of the article).

What is hot fix

Simply put, in order to fix the online problem, the patching process does not need to be reissued!
Comparison of normal version development and hot fix development process

Why learn hot fix

In the normal software development process, offline development -> online -> bug found -> emergency repair and online. However, this method is too expensive, and it will never avoid the following problems:

  1. Can the development of the online version guarantee that there are no bugs?
  2. Can the repaired version ensure that users are updated in time?
  3. How to minimize the impact of online bugs on the business?

In contrast, the hot fix development process is more flexible, no need to re-release, real-time and efficient hot fix, no need to download new applications, low cost, and the most important thing is to fix bugs in time. And with the development of hot repair technology, it is now possible to repair not only code, but also resource files and SO libraries.

How to choose a suitable thermal repair technical solution?

The article started by saying that now major factories have launched their own thermal repair solutions, so how do we choose a set of thermal repair technology that suits us to learn? Next, I will compare the current mainstream hot repair solutions to give you the answer.

Domestic mainstream thermal repair technology solutions

1. Ali Department

name Description
AndFix Open source, effective in real time
HotFix Ali Baichuan, not open source, free, effective in real time
Sophix Not open source, commercial charges, real-time effective/cold start repair

HotFix is ​​an optimized version of AndFix, and Sophix is ​​an optimized version of HotFix. Sophix is ​​currently the main push of Ali.

2. Tencent

name Description
Qzone Super Patch QQ space, not open source, cold start repair
QFix Hand Q team, open source, cold start repair
Tinker WeChat team, open source, cold start repair. Provide distribution management, basic version is free

3. Other

name Description
Robust Meituan, open source, real-time repair
Nuwa Dianping, open source, cold start repair
Friend Are you hungry, open source, cold start repair

Comparison of various thermal repair schemes

How to choose a suitable thermal repair plan
? This can only say that everything depends on demand. If the company's comprehensive strength is strong, it is fine to consider self-research, but it needs to consider cost and maintenance. Two suggestions are given below, as follows:

  1. Project requirements
  • Only need simple method level bug fix?
  • Need resources and so library repair?
  • What are the requirements for platform compatibility and success rate?
  • Is there a need to control distribution, perform statistics on monitoring data, and manage patch packages?
  • Do company resources support commercial payments?
  1. Learning and use cost
  • Integration difficulty
  • Code intrusiveness
  • Commissioning and maintenance
  1. Choose Dachang
  • Guaranteed technical performance
  • Have someone to maintain
  • High popularity, active open source community
  1. If you consider paying, it is recommended to choose Ali's Sophix. Sophix is ​​a product of comprehensive optimization, with complete functions, simple and transparent development, and distribution and monitoring management. If you don’t consider paying, you only need to support method-level bug fixes. Resources and so are not supported. Robust is recommended. If you consider the need to support both resources and so, Tinker is recommended. Finally, if the company's comprehensive strength is strong, self-research can be considered, which is the most flexible and controllable.

Principles of hot repair technical solutions

Technology Classification

image

NativeHook principle

Principle and realization

The principle of NativeHook is to exchange the structure information of the method directly at the native layer, so as to realize the perfect replacement of the old method and the old method, thereby realizing the hot repair function.
The following is an explanation with a piece of jni code of AndFix, as follows:

void replace_6_0(JNIEnv* env, jobject src, jobject dest) {

    // 通过Method对象得到底层Java函数对应ArtMethod的真实地址
    art::mirror::ArtMethod* smeth =
            (art::mirror::ArtMethod*) env->FromReflectedMethod(src);

    art::mirror::ArtMethod* dmeth =
            (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);

    reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->class_loader_ =
    reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->class_loader_; //for plugin classloader
    reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->clinit_thread_id_ =
    reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->clinit_thread_id_;
    reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->status_ = reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->status_-1;
    //for reflection invoke
    reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->super_class_ = 0;
    //把旧函数的所有成员变量都替换为新函数的
    smeth->declaring_class_ = dmeth->declaring_class_;
    smeth->dex_cache_resolved_methods_ = dmeth->dex_cache_resolved_methods_;
    smeth->dex_cache_resolved_types_ = dmeth->dex_cache_resolved_types_;
    smeth->access_flags_ = dmeth->access_flags_ | 0x0001;
    smeth->dex_code_item_offset_ = dmeth->dex_code_item_offset_;
    smeth->dex_method_index_ = dmeth->dex_method_index_;
    smeth->method_index_ = dmeth->method_index_;

    smeth->ptr_sized_fields_.entry_point_from_interpreter_ =
    dmeth->ptr_sized_fields_.entry_point_from_interpreter_;

    smeth->ptr_sized_fields_.entry_point_from_jni_ =
    dmeth->ptr_sized_fields_.entry_point_from_jni_;
    smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_ =
    dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_;

    LOGD("replace_6_0: %d , %d",
         smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_,
         dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_);
}

void setFieldFlag_6_0(JNIEnv* env, jobject field) {
    art::mirror::ArtField* artField =
            (art::mirror::ArtField*) env->FromReflectedField(field);
    artField->access_flags_ = artField->access_flags_ & (~0x0002) | 0x0001;
    LOGD("setFieldFlag_6_0: %d ", artField->access_flags_);
}

Each Java method corresponds to an ArtMethod in art. ArtMethod records all the information of this Java method, including access rights and code execution address. Get the real start address of the ArtMethod corresponding to the method through env->FromReflectedMethod, and then force it to the ArtMethod pointer to modify all its members.

In this way, when this method is called in the future, it will go directly to the realization of the new method to achieve the effect of hot repair.

advantage

  • Immediate effect
  • No performance overhead, no editor instrumentation or code rewriting

Disadvantage

  • There are stability and compatibility issues. The structure of ArtMethod basically refers to the open source code of Google. The ROMs of major manufacturers may be modified, which may cause inconsistencies in the structure and failure to repair.
  • Variables and classes cannot be added, only method-level bugs can be fixed, and new functions cannot be released

Principle of javaHook

Principle and realization

Taking Robust of Meituan as an example, the principle of Robust can be simply described as:

1. Insert stubs when building the basic package, insert a piece of logic of type ChangeQuickRedirect static variable before each method, the insertion process is completely transparent to business development

2. When loading the patch, read the class to be replaced and the specific replacement method implementation from the patch package, and create a new ClassLoader to load the patch dex. When changeQuickRedirect is not null, accessDispatch may be executed to replace the old logic before, achieving the purpose of fix

Let's analyze the Robust source code below.
First look at the code logic inserted in the basic package, as follows:

public static ChangeQuickRedirect u;
protected void onCreate(Bundle bundle) {
        //为每个方法自动插入修复逻辑代码,如果ChangeQuickRedirect为空则不执行
        if (u != null) {
            if (PatchProxy.isSupport(new Object[]{bundle}, this, u, false, 78)) {
                PatchProxy.accessDispatchVoid(new Object[]{bundle}, this, u, false, 78);
                return;
            }
        }
        super.onCreate(bundle);
        ...
    }

Robust's core repair source code is as follows:

public class PatchExecutor extends Thread {
    @Override
    public void run() {
        ...
        applyPatchList(patches);
        ...
    }
    /**
     * 应用补丁列表
     */
    protected void applyPatchList(List<Patch> patches) {
        ...
        for (Patch p : patches) {
            ...
            currentPatchResult = patch(context, p);
            ...
            }
    }
     /**
     * 核心修复源码
     */
    protected boolean patch(Context context, Patch patch) {
        ...
        //新建ClassLoader
        DexClassLoader classLoader = new DexClassLoader(patch.getTempPath(), context.getCacheDir().getAbsolutePath(),
                null, PatchExecutor.class.getClassLoader());
        patch.delete(patch.getTempPath());
        ...
        try {
            patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
            patchesInfo = (PatchesInfo) patchsInfoClass.newInstance();
            } catch (Throwable t) {
             ...
        }
        ...
        //通过遍历其中的类信息进而反射修改其中 ChangeQuickRedirect 对象的值
        for (PatchedClassInfo patchedClassInfo : patchedClasses) {
            ...
            try {
                oldClass = classLoader.loadClass(patchedClassName.trim());
                Field[] fields = oldClass.getDeclaredFields();
                for (Field field : fields) {
                    if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) {
                        changeQuickRedirectField = field;
                        break;
                    }
                }
                ...
                try {
                    patchClass = classLoader.loadClass(patchClassName);
                    Object patchObject = patchClass.newInstance();
                    changeQuickRedirectField.setAccessible(true);
                    changeQuickRedirectField.set(null, patchObject);
                    } catch (Throwable t) {
                    ...
                }
            } catch (Throwable t) {
                 ...
            }
        }
        return true;
    }
}

advantage

  • High compatibility (Robust is only using DexClassLoader normally), high stability, and the repair success rate is as high as 99.9%
  • The patch takes effect in real time, no need to restart
  • Support method-level repairs, including static methods
  • Support adding methods and classes
  • Support ProGuard's obfuscation, inlining, optimization and other operations

Disadvantage

  • The code is intrusive, and relevant code will be added to the original class
  • So and resource replacement are temporarily not supported
  • Will increase the size of the apk. On average, a function will increase by 17.47 bytes, and 100,000 functions will increase by 1.67M.

Principle of java mulitdex

Principle and realization

Android uses three class loaders, BaseDexClassLoader, PathClassLoader, and DexClassLoader to read class data from DEX files. PathClassLoader and DexClassLoader are all inherited from BaseDexClassLoader. The dex file is converted into a dexFile object, stored in the Element[] array, and findclass sequentially traverses the Element array to obtain the DexFile, and then executes the findclass of DexFile. The source code is as follows:

// 加载名字为name的class对象
public Class findClass(String name, List<Throwable> suppressed) {
    // 遍历从dexPath查询到的dex和资源Element
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        // 如果当前的Element是dex文件元素
        if (dex != null) {
            // 使用DexFile.loadClassBinaryName加载类
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

So the principle of this solution is to hook ClassLoader.pathList.dexElements[], and insert the patched dex into the front end of the array. Because the findClass of ClassLoader finds the class by traversing the dex in dexElements[]. Therefore, the repaired class will be found first. So as to achieve the effect of repair.

The following uses Nuwa's key implementation source code to explain as follows:

public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        //新建一个ClassLoader加载补丁Dex
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader());
        //反射获取旧DexElements数组
        Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
        //反射获取补丁DexElements数组
        Object newDexElements = getDexElements(getPathList(dexClassLoader));
        //合并,将新数组的Element插入到最前面
        Object allDexElements = combineArray(newDexElements, baseDexElements);
        Object pathList = getPathList(getPathClassLoader());
        //更新旧ClassLoader中的Element数组
        ReflectionUtils.setField(pathList, pathList.getClass(), "dexElements", allDexElements);
    }

    private static PathClassLoader getPathClassLoader() {
        PathClassLoader pathClassLoader = (PathClassLoader) DexUtils.class.getClassLoader();
        return pathClassLoader;
    }

    private static Object getDexElements(Object paramObject)
            throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
        return ReflectionUtils.getField(paramObject, paramObject.getClass(), "dexElements");
    }

    private static Object getPathList(Object baseDexClassLoader)
            throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        return ReflectionUtils.getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    private static Object combineArray(Object firstArray, Object secondArray) {
        Class<?> localClass = firstArray.getClass().getComponentType();
        int firstArrayLength = Array.getLength(firstArray);
        int allLength = firstArrayLength + Array.getLength(secondArray);
        Object result = Array.newInstance(localClass, allLength);
        for (int k = 0; k < allLength; ++k) {
            if (k < firstArrayLength) {
                Array.set(result, k, Array.get(firstArray, k));
            } else {
                Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
            }
        }
        return result;
    }

advantage

  • No need to consider adapting the dalvik virtual machine and art virtual machine
  • The code is non-intrusive and has little effect on the size of the apk

Disadvantage

  • Need to be repaired next time
  • The performance loss is large, in order to avoid the class being added CLASS_ISPREVERIFIED, use instrumentation and put a separate helper class in a separate dex for other classes to call.

dex replacement

Principle and realization

In order to avoid performance loss caused by dex instrumentation, dex replacement takes another approach. The principle is to provide a dex difference package and replace dex as a whole. Give patch.dex by difference, then merge patch.dex and application classes.dex into a complete dex, load the complete dex and get the dexFile object as a parameter to construct an Element object and then replace the old dex-Elements array as a whole .

This is also the scheme adopted by WeChat Tinker, and Tinker self-developed DexDiff/DexMerge algorithm. Tinker also supports the update of resources and So packages. The So patch package is generated by BsDiff, and the resource patch package is directly generated by file md5 comparison. For relatively large resources (the default is larger than 100KB is a large file), BsDiff will be used to generate a difference in the file Quantity patch.

Let's take a look at the implementation source code of Tinker. Of course, the specific implementation algorithm is very complicated. We only look at the key implementation. The final fix is ​​the tryPatch method in UpgradePatch, as follows:

  @Override
    public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
        //省略一堆校验
        ... ....

        //下面是关键的diff算法及合并实现,实现相对复杂,感兴趣可以再仔细阅读源码
        //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
        if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
            return false;
        }

        if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
            return false;
        }

        if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
            return false;
        }

        // check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted
        if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed");
            return false;
        }

        if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) {
            TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");
            manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);
            return false;
        }

        TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok");
        return true;
    }

advantage

  • High compatibility
  • Small patch
  • Transparent development, non-intrusive code

Disadvantage

  • Cold start repair, next start repair
  • Dex merge memory is consumed on vm head, easy to OOM, and finally cause merge failure

Principles of Resource Recovery

Instant Run

1. Construct a new AssetManager and call addAssertPath through reflection to add this complete new resource package to the AssetManager. This will get an AssetManager with all new resources

2. Find all valuable references to the original AssetManager, and replace the references with AssetManager through reflection

 public static void monkeyPatchExistingResources(Context context,
                                                    String externalResourceFile, Collection activities) {
        if (externalResourceFile == null) {
            return;
        }
        try {
            //反射一个新的   AssetManager
            AssetManager newAssetManager = (AssetManager) AssetManager.class
                    .getConstructor(new Class[0]).newInstance(new Object[0]);
           //反射 addAssetPath 添加新的资源包
            Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", new Class[]{String.class});
            mAddAssetPath.setAccessible(true);
            if (((Integer) mAddAssetPath.invoke(newAssetManager,
                    new Object[]{externalResourceFile})).intValue() == 0) {
                throw new IllegalStateException(
                        "Could not create new AssetManager");
            }
            Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks", new Class[0]);
            mEnsureStringBlocks.setAccessible(true);
            mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);
            //反射得到Activity中AssetManager的引用处,全部换成刚新构建的AssetManager对象
            if (activities != null) {
                for (Activity activity : activities) {
                    Resources resources = activity.getResources();
                    try {
                        Field mAssets = Resources.class.getDeclaredField("mAssets");
                        mAssets.setAccessible(true);
                        mAssets.set(resources, newAssetManager);
                    } catch (Throwable ignore) {
                        Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
                        mResourcesImpl.setAccessible(true);
                        Object resourceImpl = mResourcesImpl.get(resources);
                        Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
                        implAssets.setAccessible(true);
                        implAssets.set(resourceImpl, newAssetManager);
                    }
                    Resources.Theme theme = activity.getTheme();
                    try {
                        try {
                            Field ma = Resources.Theme.class.getDeclaredField("mAssets");
                            ma.setAccessible(true);
                            ma.set(theme, newAssetManager);
                        } catch (NoSuchFieldException ignore) {
                            Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");
                            themeField.setAccessible(true);
                            Object impl = themeField.get(theme);
                            Field ma = impl.getClass().getDeclaredField("mAssets");
                            ma.setAccessible(true);
                            ma.set(impl, newAssetManager);
                        }
                        Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme");
                        mt.setAccessible(true);
                        mt.set(activity, null);
                        Method mtm = ContextThemeWrapper.class.getDeclaredMethod("initializeTheme", new Class[0]);
                        mtm.setAccessible(true);
                        mtm.invoke(activity, new Object[0]);
                        Method mCreateTheme = AssetManager.class.getDeclaredMethod("createTheme", new Class[0]);
                        mCreateTheme.setAccessible(true);
                        Object internalTheme = mCreateTheme.invoke(newAssetManager, new Object[0]);
                        Field mTheme = Resources.Theme.class.getDeclaredField("mTheme");
                        mTheme.setAccessible(true);
                        mTheme.set(theme, internalTheme);
                    } catch (Throwable e) {
                        Log.e("InstantRun",
                                "Failed to update existing theme for activity "
                                        + activity, e);
                    }
                    pruneResourceCaches(resources);
                }
            }
            Collection references;
            if (Build.VERSION.SDK_INT >= 19) {
                Class resourcesManagerClass = Class.forName("android.app.ResourcesManager");
                Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance", new Class[0]);
                mGetInstance.setAccessible(true);
                Object resourcesManager = mGetInstance.invoke(null, new Object[0]);
                try {
                    Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
                    fMActiveResources.setAccessible(true);
                    ArrayMap  arrayMap = (ArrayMap) fMActiveResources.get(resourcesManager);
                    references = arrayMap.values();
                } catch (NoSuchFieldException ignore) {
                    Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
                    mResourceReferences.setAccessible(true);
                    references = (Collection) mResourceReferences.get(resourcesManager);
                }
            } else {
                Class activityThread = Class.forName("android.app.ActivityThread");
                Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");
                fMActiveResources.setAccessible(true);
                Object thread = getActivityThread(context, activityThread);
                HashMap  map = (HashMap) fMActiveResources.get(thread);
                references = map.values();
            }
            for (WeakReference wr : references) {
                Resources resources = (Resources) wr.get();
                if (resources != null) {
                    try {
                        Field mAssets = Resources.class.getDeclaredField("mAssets");
                        mAssets.setAccessible(true);
                        mAssets.set(resources, newAssetManager);
                    } catch (Throwable ignore) {
                        Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
                        mResourcesImpl.setAccessible(true);
                        Object resourceImpl = mResourcesImpl.get(resources);
                        Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
                        implAssets.setAccessible(true);
                        implAssets.set(resourceImpl, newAssetManager);
                    }
                    resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
                }
            }
        } catch (Throwable e) {
            throw new IllegalStateException(e);
        }
    }

so repair principle

Interface call replacement

SDK provides an interface to replace the interface of System default loading so library

SOPatchManger.loadLibrary(String libName)
替换
System.loadLibrary(String libName)

When the SOPatchManger.loadLibrary interface loads the so library, it first tries to load the so in the specified directory of the SDK. If it does not exist, then load the so library in the installation apk directory

Advantages: There is no need to be compatible with different SDK versions, so the SDK versions are all System.loadLibrary interface

Disadvantages: need to invade the business code, replace the system default loading so library interface

Reflection injection

A similar type of repair reflection injection method is adopted. As long as the path of the patch so library is inserted into the front of the nativeLibraryDirectories array, it can be achieved that the so library is loaded instead of the original so library directory, so as to achieve the repair.

public String findLibrary(String libraryName) {
        String fileName = System.mapLibraryName(libraryName);

        for (NativeLibraryElement element : nativeLibraryPathElements) {
            String path = element.findNativeLibrary(fileName);

            if (path != null) {
                return path;
            }
        }

        return null;
    }

Advantages: no need to invade user interface calls

Disadvantages: need to do version compatibility control, poor compatibility

What are the issues that need attention when using hot repair technology?

Version management

After the hot fix technology is used, due to the changes in the release process, the corresponding branch management must be adopted for control.

Usually the branch management of mobile development uses feature branches, as follows:

Branch description
master Main branch (only merge, not commit, set permissions), used to manage online version, set corresponding Tag in time
dev Development branch, the research and development of each new version is created based on the master branch according to the version number. After the test is verified, it will go online and merge into the master branch.
function X Function branch, set according to demand. Create based on the development branch, merge into the dev development branch after the function development is completed

After the hot fix is ​​connected, the following branch strategies are recommended:

Branch description
master Main branch (only merge, not commit, set permissions), used to manage the online version, and set the corresponding Tag in time (usually a 3-digit version number)
hot_fix Hot fix branch. Created based on the master branch, after fixing urgent issues, after testing the push, merge hot_fix into the master branch. Tag the master branch again. (Generally 4-digit version number)
dev Development branch, the research and development of each new version is created based on the master branch according to the version number. After the test is verified, it will go online and merge into the master branch.
function X Function branch, set according to demand. Create based on the development branch, merge into the dev development branch after the function development is completed

Note that the test and release process of the hot fix branch is consistent with the normal version process to ensure quality.

Distribution monitoring

The current mainstream hot repair solutions, such as Tinker and Sophix, will provide patch distribution and monitoring. This is also one of the key factors that we need to consider when choosing a thermal repair technology solution. After all, in order to ensure the quality of the online version, distribution control and real-time monitoring are essential.

At last

To understand the hot fix in depth, you need to understand the class loading mechanism, Instant Run, multidex and java low-level implementation details, JNI, AAPT and virtual machine knowledge, you need a huge stock of knowledge to understand, of course, the implementation details of Android Framwork are very important. Familiar with the principle of hot repair helps us to provide our own programming level and improve our ability to solve problems. In the end, hot repair is not a simple client SDK, it also includes security mechanisms and server-side control logic, and the entire link is not It can be done quickly in a short time.

So in order to facilitate my friends to learn and master Android hot repair technology more intuitively and quickly, I am here to collect and organize a set of learning materials for the hot repair series of videos + e-books. The video tutorial was given by Mr. Lance, a senior engineer from iQIYI, and took the Qzone hot repair actual project as an example to explain the hot repair technology in a comprehensive way. The e-book is derived from Ali’s "In-depth Exploration of the Principles of Android Hot Repair Technology" which has a deep interpretation of the hot repair technology.

Due to space reasons, only some screenshots are shown here. Friends who need complete information can get them for free by clicking here after likes + comments !

Hot fix learning video
Hot fix video content

In-depth exploration of the principle of Android hot repair technology e-book
E-book content on the principle of thermal repair technology

Friends who need complete information can click here to get it for free after like+comment !

Guess you like

Origin blog.csdn.net/star_nwe/article/details/110875392