Android7.0中的ResourceNotFoundException

背景

随着Android N的出现,适配7.0的问题也成为了各大产品头疼的问题。而最近在我们的平台上面收到了7.0的Crash。具体的栈如下:

Crash栈

而我们发现0x7303003这个ID是插件中的资源ID,但是在已经安装加载的插件列表中发现,这个插件是已经加载过了的。并且只有7.0的系统会出现该Crash。

发现问题

当出现该问题的时候,我们怀疑是因为指向资源的所有路径中没有指向该插件而导致的。随后,我们发现在进入WebView,再退出来后,从Resource中获取到的AssetManager对象被替换了。

插件资源原理

所有的插件资源都是通过AssetManager获取的,而AssetManager会通过addAssetPath函数将所有插件的路径添加到AssetManager中,从而使得在运行时,可以通过ID去所有的资源路径中进行查找,找到后就加载,否则报出ResourceNotFoundException。

跟踪源码

WebView的创建非常复杂,而在ensureProviderCreated()函数中,会创建WebViewProvider对象,而创建的过程可以缩减成: mProvider=WebViewChromiumFactoryProvider.createWebView()

WebView构造函数

而在创建WebView的时候,实际上是将WebView委托到了WebViewChromium中。

创建WebView

在WebViewChromium初始化的时候,会调用addWebViewAssetPath将路径添加到AssetManager中。

WebViewChromium构造函数

在WebViewDelegate.java类中,添加WebView的资源路径。

其中: newAssetPath:/system/app/WebViewGoogle/WebViewGoogle.apk,查看WebViewFactory.getLoadedPackageInfo()的源码可以看到,其实这个sourceDir获取到的应该是用户设定的浏览器的APK的路径,由于我们默认是Chrome所以指向的路径是Google WebView的APK。 Context.getApplicationInfo().getBaseResourcePath:获取到的是本应用的APK路径。 也就是说,WebView中会判断WebView的路径是否在SharedLibraryFiles中,如果存在的话,那么就直接返回了,如果不存在的话,那么就需要将它的路径添加到主包的资源路径中,以达到可以访问WebView中资源的效果。 而在Activity使用资源的时候,使用到的都是ContextImpl.java中的Resource。在其构造函数中,会初始化已经类似于静态变量Resource,全局只有一个。

ContextImpl中初始化资源的地方

其中mainThread是ActivityThread,每个进程都只有一个。 最终会判断LoadedApk.getResources中获取到的Resource是否为空,所以当mResources不为空的时候,就直接返回了:

LoadedApk.getResources

在ActivityThread.getTopLevelResources中,最终通过: mResourcesManager.getResources来获取到的Resources对象

Paste_Image.png

而ResourceManager中,有一个ArrayMap,用来存储ResourcesKey和ResourcesImpl的映射表: private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = new ArrayMap<>(); 而ResourcesImpl中只是对AssetManager、Drawable获取等的一层封装,以后可能会是一个APP会有多种Resources对象。 所以第一次创建的时候,就会创建一个新的ResourcesKey来对应一个ResourcesImpl,并且传入各种路径参数。

创建ResourcesKey

而在getOrCreateResources函数中,会通过Key去查找ResourcesImpl是否存在,如果存在的话,那么就直接返回了,而在查找的时候,会根据ResourcesKey.equals方法去比较Map中的Key。而ResourcesKey中的equals函数已经被重写了。

ResourcesKey.equals

如果为空的话,就会创建一个ResourcesImpl对象,添加到上面的Map中去。 所以,在appendLibAssetForMainAssetPath函数中,首先会遍历所有的ResourceImpl,判断ResourceKey中的mResDir是否为主包的路径,如果主包路径中的mLibDirs中没有WebView.apk的路径,则会将/system/app/WebViewGoogle/WebViewGoogle.apk添加到原有路径后,并且创建新的ResourcesKey中。导致在后面逻辑中判断ResourceKey对象已经变了,导致重新创建了ResourceImpl对象,导致重新创建了AssetManager。

查询是否需要更新ResourceKey

如果ResourceKey变了的话,那么就会创建新的ResourceImpl,并且更新到Map中。

更新ResourceKey对应的ResourceImpl

于是,上面的问题就得到了答案,如果WebView.apk的路径不在Application.sharedLibarary中的话,那么则会创建一个新的ResourceKey,但是由于libAssets中的路径已经发生改变,导致ResourcesKey匹配不到原来的ResourceImpl,被认为是新的,所以导致了创建新的ResourcesImpl,从而创建了新的AssetManager,而之前的插件路径也都不复存在了。

解决方案

在添加插件路径的时候,需要把WebView.apk的路径添加到sharedLibraryFiles字段中, 这样的话,就不会有需要更新的ResourceImpl了,可以避免过去,不过可以看看还有没有更好的办法。比如说是否有AndroidManifest.xml文件中配置一下,就可以将WebView.apk的路径添加进来。也没有再深追了。

解决方案

但是在部分机型上面发现还会出现该问题,最终我们选择的解决方案是在Application的时候创建一个WebView对象,让静态方法先执行,虽然会影响一些启动速度,但是可以解决该问题。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏cmazxiaoma的架构师之路

Android实战 粗略实现一个简单的C/S结构聊天室的功能

23850
来自专栏Phoenix的Android之旅

观察者模式--DataBinding的原理和坑

上一次我们介绍了DataBinding的应用,不过只在应用层面描述了下,没有做深入分析。 关于DataBinding的实现原理,它的根本思想是观察者模式。 这篇...

34220
来自专栏向治洪

android dataBinding详解

官方介绍地址:http://developer.android.com/intl/zh-cn/tools/data-binding/guide.html 201...

251100
来自专栏用户2442861的专栏

Android从启动到程序运行发生的事情

转载请注明出处  博客地址:http://blog.csdn.net/JonsTank2013/article/details/51118563 作者:...

12310
来自专栏白驹过隙

ZeroMQ - 三种模型的python实现

620140
来自专栏开发之途

Android 获取应用Crash信息的方法

18870
来自专栏非著名程序员

Android 开发如何做好内存优化

Android的一个应用程序的内存泄露对别的应用程序影响不大。为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都会使用一个专有...

22870
来自专栏一个会写诗的程序员的博客

浅谈android hook技术浅谈android hook技术-- coding:utf-8 --print jscode author = 'gaohe'-- coding:utf-8 --pri

您当前的位置: 安全博客 > 技术研究 > 浅谈android hook技术 浅谈android hook技术 2017年03月17日 10:06 1...

92220
来自专栏向治洪

Activity之间传递大数据问题

Android开发人员都知道,Intent适用于在不同的Activity之间传递数据,包括参数、字符串、以及序列化的对象等。但是笔者所做的项目用到了使用Inte...

68490
来自专栏Android常用基础

Dagger2-从入门到精通(上)

最近在做项目中,用到了Dagger2,所以找了一些博客和其他的一些学习资源,算是知道如何使用了,但是对其理解还相差很远。所以这篇文章重点针对与使用,和使用中常见...

14510

扫码关注云+社区

领取腾讯云代金券