0x00 背景
在高版本的安卓版本中,webview的实现方式与以往的都有那么点不一样,以前的webview是作用一个内置的组件去使用,但在7.0后,android将webview的实现开放开来,在开发者模式下有一个”WebView implementation”的选择,可以让我们选择使用内置的webview实现,还是第三方的webview实现(如Chrome)。并且,在高版本中,内置的webview也不再是属于framework-lib的一部分了,而是作为一个独立的应用apk存在,可方便webview的更新,且同时遵循webview的统一规范,以方便在上述的开发者模式中切换webview实现。
因为webview的实现变成了一个独立的apk,它的资源与代码都不是封装在系统级别了,由此会产生一个问题,我们在应用使用的时候并没有显式地去加载这个独立的apk,那正常来说应用应该是不能够访问到webview的实现与资源等,但事实上,我们的确是可以访问到的,那是因为系统帮我们自动加载了吗?
假如是系统帮应用加载了webview,那对于使用插件/沙箱环境的应用来说,是否就不能正常使用webview的资源代码呢?
0x10 webview加载行为
先是第一个问题,应用是如何使用到webview的资源代码的呢?这个需要从android.webkit.WebView开始分析。
因为webview的具体实现只会遵循接口协议而已,webview类作为入口类,在初始化时,需要根据当前系统的设置去创建真实的webview实现实例,这个过程都是通过provider实现的,创建provider代码如下
1 | private void ensureProviderCreated() { |
而具体的webview实现需要深入到provider的创建过程,如下图
涉及到几个角色
- Webview: 入口类,作用android sdk的一部分
- WebViewFactory: 用于加载与桥接真实webview实现
- WebViewChromiumFactoryProvider: 用于生成WebViewChromium
- InitialApplication: 应用的Application
0x11 获取ProviderClass
在webview创建开始时,需要先获取一个provider,而这个provider是通过反射来获取的,核心流程主要集中的WebViewFactory#getProviderClass这个方法里。
首先是通过getWebViewContextAndSetProvider方法,连接系统的 webviewupdate 服务,获取到系统里webview实现的对应包信息(系统的实现也是同样的处理),并为这个apk创建一个context。
完成创建上下文后,需要将webview的apk信息加入到系统上下文的assetPath中,对应使用了addAssetPathAsSharedLibrary(7.0+后提供)
1 | initialApplication.getAssets().addAssetPathAsSharedLibrary( |
addAssetPathAsSharedLibrary与普通的addAssetPath的相关实现如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
public final int addAssetPathAsSharedLibrary(String path) {
return addAssetPathInternal(path, true);
}
private final int addAssetPathInternal(String path, boolean appAsLib) {
synchronized (this) {
int res = addAssetPathNative(path, appAsLib);
makeStringBlocks(mStringBlocks);
return res;
}
}
private native final int addAssetPathNative(String path, boolean appAsLib);
两者最终都会调用到addAssetPathNative,区别仅为第二个参数是true还是false,而第二个参数看命名可以理解为”当成library添加”(?),最终会调用到bool AssetManager::appendPathToResTable中,将路径添加到Resource对象中。
(暂时未清楚appAsLib为true还是false的影响,待查)
apk添加完成后,需要找到对应FactoryProvider的类,这里是在WebViewFactory中写死的
1 | private static final String CHROMIUM_WEBVIEW_FACTORY ="com.android.webview.chromium.WebViewChromiumFactoryProvider"; |
WebViewChromiumFactoryProvider
至此,getProviderClass方法执行过程就结束了,最后返回一个FactoryProvider的Class对象。
0x12 创建Provider
回到WebViewFactory#getProvider方法中,在获取到provider的class对象后,就可以利用反射实例化provider对象。
1 | sProviderInstance = providerClass.getConstructor(WebViewDelegate.class).newInstance(new WebViewDelegate()); |
传入一个WebViewDelegate的对象作为provider的参数,以此来进行一些监控托管。
需要注意的是,WebViewDelegate中同样也有提addWebViewAssetPath的方法,通过直接更新Context的ApplicationInfo中的sharedLibraryFiles的链接,达到应用可以访问webview资源代码的作用,但在这几个类里并没有找到相关的调用。
1 | /** |
0x13 创建WebViewProvider
以上目的是创建了WebViewFactoryProvider,这一个Provider目的是为了创建WebView的Provider(FactoryProvider create WebViewProvider),WebViewFactoryProvider定义了一个接口
1 | /** |
调用createWebView后最终可以获取WebViewProvider,framework-lib中的WebView就可以将请求转移到WebViewProvider对象中处理了
至此,可以较为完整地看到7.0以上webview在app中是如何初始化使用的。
0x20 webview在插件环境中的初始化
在普通的app里,webview在初始化加载时系统会自动帮我们完成上面的流程,但在插件环境(自建沙箱)里,系统就没办法帮我们处理了,因为在插件环境里,一般都是自建上下文,而非使用系统帮我们创建的上下文,所以系统不知道怎么插入。
要解决这个问题,就得我们自己模拟系统的行为,给我们的context添加上webview的apk信息就行了。另外方案需要满足
- 系统里使用哪个webview实现我们是不知道的,必须问题系统拿
- webview的资源id与应用的资源id可能冲突
0x21 前提判断
因为允许更换webview实现是在7.0之后才支持的,那方案只需要支持7.0(API-Level 24)以后的就可以了。
1 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {...} |
0x22 获取webview implemetation 信息
1 | Class webviewFactory = Class.forName("android.webkit.WebViewFactory"); |
因为webview也是直接调用 WebViewFactory 的,所以我们直接反射WebViewFactory就好了,目的也是为了尽量避免生成太多中间对象,或者对当前环境造成影响。
0x23 添加到asset路径中
获取到webview的路径后,我们就可以将其添加到assets中去了,在插件环境里,我们还需要添加插件apk到Asset路径中,完整创建Resource的代码如下
1 | if (null == this.mResources) { |
总结
在插件环境中使用webview的东西,有可能出问题的一个场景是webview中进行长按复制等操作,因为这时候会系统会弹出来一个菜单,而这个菜单有时并非是系统或者就当前应用的资源,而是webview implemetation的资源(in apk),如果插件上下文不能正常找到webview apk的资源,就会触发到崩溃。
这套方案因为基本上参考源码实现的,总体来说应该算是比较稳定的。目前来看没发现兼容问题。
参考
ResourceTypes
AssetManager#appendPathToResTable
AssetManager.cpp
WebView#getFactory
WebViewFactory#getProvider
WebViewFactory#getWebViewContextAndSetProvider
WebViewDelegate#addWebViewAssetPath
Android7.0中的ResourceNotFoundException