在 “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方法被调用时,逻辑顺序如下
WebView就是通过这种方式,在Activity的Resource中加入了WebView的资源