在 “Android N 替换context的mResources后引起WebView崩溃的问题”,我们提到直接替换Activity的Resources后如果打开一个

WebView,并长按网页弹出复制粘贴按钮时,会发生崩溃现象。

究其原因是因为WebView中的资源并未被正确加载,导致获取字符串时获取出错

在该文中,我采用了一种比较简单的方式来解决,即在替换之前,预先获取WebView的安装包地址,并且将apk资源预填充到用于

替换的Resources中,这种做法虽然有效的避免了崩溃,但是我心里还是比较焦虑的,主要是有以下几个疑问

1:从RePlugin中拿来的用来获取WebView安装地址的工具类是否靠谱?

2:如果有除了webView以外的第二种系统组件也采用这种资源分离的方式,那岂不是又搂不住了

3:webView资源分离加载的原理到底是什么样的

带着这三个疑问,我们从webView资源加载的原理来入手分析

webView在初始化阶段 会调用WebViewDelegate的addWebViewAssetPath方法

public void addWebViewAssetPath(Context context) {
        final String newAssetPath = WebViewFactory.getLoadedPackageInfo().applicationInfo.sourceDir;

        final ApplicationInfo appInfo = context.getApplicationInfo();
        final String[] libs = appInfo.sharedLibraryFiles;
        if (!ArrayUtils.contains(libs, newAssetPath)) {
            // Build the new library asset path list.
            final int newLibAssetsCount = 1 + (libs != null ? libs.length : 0);
            final String[] newLibAssets = new String[newLibAssetsCount];
            if (libs != null) {
                System.arraycopy(libs, 0, newLibAssets, 0, libs.length);
            }
            newLibAssets[newLibAssetsCount - 1] = newAssetPath;

            // Update the ApplicationInfo object with the new list.
            // We know this will persist and future Resources created via ResourcesManager
            // will include the shared library because this ApplicationInfo comes from the
            // underlying LoadedApk in ContextImpl, which does not change during the life of the
            // application.
            appInfo.sharedLibraryFiles = newLibAssets;

            // Update existing Resources with the WebView library.
            ResourcesManager.getInstance().appendLibAssetForMainAssetPath(
                    appInfo.getBaseResourcePath(), newAssetPath);
        }
    }

最终调用的方法是   ResourcesManager.getInstance().appendLibAssetForMainAssetPath(appInfo.getBaseResourcePath(), newAssetPath);

传入两个参数 第一个是当前应用的respath 第二个是webView的resPath 具体看如下源码注释

public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
        synchronized (this) {
            // Record which ResourcesImpl need updating
            // (and what ResourcesKey they should update to).
            final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
            final int implCount = mResourceImpls.size();
            //遍历所有的ResourcesImpl ResourcesImpl是组成Rescource的核心 他们之间的关系是Resource包含ResourcesImpl包含AssertManager
            for (int i = 0; i < implCount; i++) {
                final ResourcesKey key = mResourceImpls.keyAt(i);
                final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
                final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
                //这里首先进行判断的ResourcesImpl是否包含assetPath 也就是说如果一个ResourcesImpl的mResDir不是当前应用的 则不会进行处理
                if (impl != null && Objects.equals(key.mResDir, assetPath)) {
                    //还要判断新的资源路径是不是已经存在了 如果存在了就不做处理
                    if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
                        final int newLibAssetCount = 1 + (key.mLibDirs != null ? key.mLibDirs.length : 0);
                        final String[] newLibAssets = new String[newLibAssetCount];
                        if (key.mLibDirs != null) {
                            //这里就将新的路径添加到需要添加的ResourcesImpl所对应的ResourcesKey的libDir上面了
                            System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length);
                        }
                        newLibAssets[newLibAssetCount - 1] = libAsset;
                        updatedResourceKeys.put(impl, new ResourcesKey(key.mResDir, key.mSplitResDirs, key.mOverlayDirs, newLibAssets, key.mDisplayId, key.mOverrideConfiguration, key.mCompatInfo));
                    }
                }
            }
            redirectResourcesToNewImplLocked(updatedResourceKeys);
        }
    }
//这个方法是更新当前持有ResourcesImpl的Resource
    private void redirectResourcesToNewImplLocked(@NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
        // Bail early if there is no work to do.
        if (updatedResourceKeys.isEmpty()) {
            return;
        }

        // Update any references to ResourcesImpl that require reloading.
        final int resourcesCount = mResourceReferences.size();
        for (int i = 0; i < resourcesCount; i++) {
            final WeakReference<Resources> ref = mResourceReferences.get(i);
            final Resources r = ref != null ? ref.get() : null;
            if (r != null) {
                //首先是根据老的ResourcesImpl找到新的ResourcesKey
                final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
                if (key != null) {
                    //然后根据新的ResourcesKey生成新的ResourcesImpl
                    final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
                    if (impl == null) {
                        throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
                    }
                    //最后在替换掉Resources中的ResourcesImpl
                    r.setImpl(impl);
                }
            }
        }

        // Update any references to ResourcesImpl that require reloading for each Activity.
        //这边跟上面是一样的道理 只不过这里处理的是所有记录的Activity的Resource
        for (ActivityResources activityResources : mActivityResourceReferences.values()) {
            final int resCount = activityResources.activityResources.size();
            for (int i = 0; i < resCount; i++) {
                final WeakReference<Resources> ref = activityResources.activityResources.get(i);
                final Resources r = ref != null ? ref.get() : null;
                if (r != null) {
                    final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
                    if (key != null) {
                        final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
                        if (impl == null) {
                            throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
                        }
                        r.setImpl(impl);
                    }
                }
            }
        }
    }

好吧,不喜欢看源码,还是来个画个流程图吧

当appendLibAssetForMainAssetPath方法被调用时,逻辑顺序如下

viewResolver为什么不起作用 view resource_System

WebView就是通过这种方式,在Activity的Resource中加入了WebView的资源