Android小知识-深入浅出Android热修复资源更新

Instant Run方案

Instant Run是Android Studio 2.0新增的一个运行机制,能够有效减少当前应用的构建和部署时间。

在没有使用Instant Run之前,代码修改及编译部署流程如下:

传统的代码修改及编译部署流程需要经历App的重新安装和重启App,非常的耗时。

使用Instant Run之后,代码修改及编译部署流程如下:

Instant Run的构建和部署都是基于更改的部分的,Instant Run部署有三种部署方式,分别是冷插拔、温插拔以及热插拔,这三种部署方式的共同点就不需要重新安装App,Instant Run会根据代码的情况来决定采用哪种部署方式。

下面对这三种部署方法进行简要介绍:

  1. 热插拔(Hot Swap):热插拔是效率最高的一种部署方式,代码的增量改变不需要重启App,也不需要重启当前的Activity,适用于多数的简单改变(包括一些方法实现的修改,或者变量值修改)。

  2. 温插拔(Warm Swap):App不需要重启,但是Activity需要重启。代码修改涉及到了资源文件、修改或者删除一个现有的资源文件时会采用温插拔。

  3. 冷插拔(Cold Swap):App需要重启,但是不需要重新安装,涉及结构性变化,比如添加、删除或修改一个字段和方法、添加一个类、修改方法签名等。

Instant Run资源修复的核心逻辑在MonkeyPatcher的monkeyPatchExistingResources方法,由于方法过长,这里分批给出。

monkeyPatchExistingResources方法(part1)如下所示:

public static void monkeyPatchExistingResources(Context context,
                                                String externalResourceFile, Collection activities) {
    ...
    try {
        AssetManager newAssetManager = (AssetManager) AssetManager.class
                .getConstructor(new Class[0]).newInstance(new Object[0]);
        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");
        }
        ...
    } catch (Throwable e) {
        throw new IllegalStateException(e);
    }
}

在monkeyPatchExistingResources方法中可以看到一开始就会创建一个新的AssetManager,并通过反射调用addAssetPath方法来加载外部(SD卡)的资源,也就是说新创建的AssetManager包含了外部资源。

monkeyPatchExistingResources方法(part2)如下所示:

public static void monkeyPatchExistingResources(Context context,
                                                String externalResourceFile, Collection activities) {
    ...
    try {
        AssetManager newAssetManager = (AssetManager) AssetManager.class
                .getConstructor(new Class[0]).newInstance(new Object[0]);
        ...
        Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod(
                "ensureStringBlocks", new Class[0]);
        mEnsureStringBlocks.setAccessible(true);
        mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);
        ...
    } catch (Throwable e) {
        throw new IllegalStateException(e);
    }
}

通过反射调用ensureStringBlocks方法,ensureStringBlocks方法是用于创建字符串资源池的,mStringBlocks指向的是一个StringBlock数组,Android应用程序在编译和打包过程中,每一个资源表都包含一个资源项值字符串资源池,AssetManager类的成员变量mStringBlocks就是用来保存所有的资源表中的资源项值字符串池的。

monkeyPatchExistingResources方法(part3)如下所示:

public static void monkeyPatchExistingResources(Context context,
                                                String externalResourceFile, Collection activities) {
    ...
    try {
        ...
        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) {
                    ...
                }
                ...
            }
        }
        ...
    } catch (Throwable e) {
        throw new IllegalStateException(e);
    }
}

接着通过遍历Activity列表,得到每个Activity的Resources,并通过反射获取Resouces的AssetManager类型的mAssets字段,最后将mAssets字段的引用替换成前面创建的AssetManager。

monkeyPatchExistingResources方法(part4)如下所示:

public static void monkeyPatchExistingResources(Context context,
                                                String externalResourceFile, Collection activities) {
    ...
    try {
        ...
        if (activities != null) {
            for (Activity activity : activities) {
                ...
                Resources.Theme theme = activity.getTheme();
                try {
                    try {
                        Field ma = Resources.Theme.class
                                .getDeclaredField("mAssets");
                        ma.setAccessible(true);
                        ma.set(theme, newAssetManager);
                    } catch (NoSuchFieldException ignore) {
                       ...
                    }
                    ...
                } catch (Throwable e) {
                    ...
                }
                ...
            }
        }
        ...
    } catch (Throwable e) {
        throw new IllegalStateException(e);
    }
}

遍历Activity列表,获取每个Activity的Resources.Theme,并通过反射获取Resources.Theme的AssetManager类型的mAssets字段,最后将mAssets字段的引用替换成前面创建的AssetManager。

monkeyPatchExistingResources方法(part5)如下所示:

public static void monkeyPatchExistingResources(Context context,
                                                    String externalResourceFile, Collection activities) {
        ...
        try {
            ...
            Collection<WeakReference<Resources>> 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) {
                    ...
                }
            } 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();
            }
            ...
        } catch (Throwable e) {
            throw new IllegalStateException(e);
        }
    }
}

根据SDK版本的不同,获取Resources的弱引用集合。

monkeyPatchExistingResources方法(part6)如下所示:

public static void monkeyPatchExistingResources(Context context,
                                                    String externalResourceFile, Collection activities) {
        ...
        try {
            ...
            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);
        }
    }
}

遍历之前获取的Resources的弱引用集合,将弱引用集合中的Resources的mAssets字段引用都替换成新创建的AssetManager。

在繁忙的工作与生活之中,用心感觉风的温度,看云在空中怎样飘过,倾听雨声,闻闻叶子的味道,这样的人,才是真正热爱生活的英雄。

来源:顾林海(wx:gulinhai531)

 作者:顾林海,资深技术人

 图片:网络

wx号:gulinhai531

顾林海公众号

不定期推出优质文

章,喜欢的朋友们

给我个好看。


©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页