private WebViewProvider mProvider
mProvider作为WebView中一个重要的成员变量,几乎大部分WebView的方法实际实现是在这个对象里的。那么这个对象究竟是如何创建的呢?
private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
// As this can get called during the base class constructor chain, pass the minimum
// number of dependencies here; the rest are deferred to init().
mProvider = getFactory().createWebView(this, new PrivateAccess());
}
}
这里的PrivateAccess是WebView的一个内部类 持有对WebView外部类的引用。它的功能是开放给mProvider对象访问WebView.super(也就是AbsoluteLayout)的部分功能的一个代理
private static synchronized WebViewFactoryProvider getFactory() {
return WebViewFactory.getProvider();
}
WebViewFactory静态工厂方法 同步锁获取抽象工厂提供者 该方法的目的是最小化(通过代理)访问WebView内部
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
// For now the main purpose of this function (and the factory abstraction) is to keep
// us honest and minimize usage of WebView internals when binding the proxy.
if (sProviderInstance != null) return sProviderInstance;
final int uid = android.os.Process.myUid();
if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
//WebView 不可以在特权进程中 Root或者System
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
//通过这个方法获取到工厂提供者的类 用于下面的反射构造
Class<WebViewFactoryProvider> providerClass = getProviderClass();
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
try {
//反射
sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
.newInstance(new WebViewDelegate());
if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
return sProviderInstance;
} catch (Exception e) {
Log.e(LOGTAG, "error instantiating provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
StrictMode.setThreadPolicy(oldPolicy);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
}
}
getProvidedClass方法中先加载库 再创建工厂
方法中先调用了loadNativeLibrary(){
//做了三件事
//第一件:创建Relro只读
//Called in an unprivileged child process to create the relro file.在一个无特权的进程中创建relro文件
getUpdateService().waitForRelroCreationCompleted(VMRuntime.getRuntime().is64Bit());
//第二件:获取WebView的原生库所在路径 32位和64位的(依据cpu架构 )
//getLoadFromApkPath的方法从Build.SUPPORTED_64(或32)_BIT_ABIS的abi列表拼接出可用apk路径,找到可以用来dlopen()挂载的那个zipEntry路径
//这里我们可以学到获取系统的WebView的PackageInfo信息是怎么获取的 getWebViewPackageName()->fetchPackageInfo()进一步可以通过getWebViewApplicationInfo()获取成员变量ApplicationInfo, WebViewLibrary的Path就是存在ApplicationInfo中的ai.metaData.getString("com.android.webview.WebViewLibrary")
String[] args = getWebViewNativeLibraryPaths();
//第三件: 加载 这是一个jni的方法实现在C
int result = nativeLoadWithRelroFile(args[0] /* path32 */,
args[1] /* path64 */,
CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
}
tips:
RELRO
在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域. 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.
GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation.大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读.
RELRO设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。
有关RELRO的技术细节 https://hardenedlinux.github.io/2016/11/25/RelRO.html。
有关GOT攻击的技术原理参考 http://blog.csdn.net/smalosnail/article/details/53247502。
// throws MissingWebViewPackageException
private static Class<WebViewFactoryProvider> getChromiumProviderClass()
throws ClassNotFoundException {
Application initialApplication = AppGlobals.getInitialApplication();
//就是ActivityThread.currentApplication();
try {
// Construct a package context to load the Java code into the current app.
//拿到webView的Package上下文
Context webViewContext = initialApplication.createPackageContext(
sPackageInfo.packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
//getAssets()是隐藏方法 你只能用反射哦 addAssetPath是AssetManager的native方法
initialApplication.getAssets().addAssetPath(
webViewContext.getApplicationInfo().sourceDir);
//用webView的Package上下文拿到classLoader 保证能加载到
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
try {
//重点来了 最终的实现类 包名路径
return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
clazzLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (PackageManager.NameNotFoundException e) {
throw new MissingWebViewPackageException(e);
}
}
那么就来看看面纱下的CHROMIUM_WEBVIEW_FACTORY到底是啥 "com.android.webview.chromium.WebViewChromiumFactoryProvider"
这个类在sdk 25 26中都找不到 最终在22下找到了对应的文件
sources\android-22\com\android\webview\chromium\WebViewChromiumFactoryProvider.java
记得前面反射的时候嘛 我们用的是有参的构造方法
/**
* Constructor called by the API 22 version of {@link WebViewFactory} and later.
*/
public WebViewChromiumFactoryProvider(android.webkit.WebViewDelegate delegate) {
initialize(WebViewDelegateFactory.createProxyDelegate(delegate));
}
private void initialize(WebViewDelegate webViewDelegate) {
mWebViewDelegate = webViewDelegate;
if (isBuildDebuggable()) {
// Suppress the StrictMode violation as this codepath is only hit on debugglable builds.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
CommandLine.initFromFile(COMMAND_LINE_FILE);
StrictMode.setThreadPolicy(oldPolicy);
} else {
CommandLine.init(null);
}
CommandLine cl = CommandLine.getInstance();
// TODO: currently in a relase build the DCHECKs only log. We either need to insall
// a report handler with SetLogReportHandler to make them assert, or else compile
// them out of the build altogether (b/8284203). Either way, so long they're
// compiled in, we may as unconditionally enable them here.
cl.appendSwitch("enable-dcheck");
ThreadUtils.setWillOverrideUiThread();
// Load chromium library.
AwBrowserProcess.loadLibrary();
// Load glue-layer support library.
System.loadLibrary("webviewchromium_plat_support");
// Use shared preference to check for package downgrade.
mWebViewPrefs = mWebViewDelegate.getApplication().getSharedPreferences(
CHROMIUM_PREFS_NAME, Context.MODE_PRIVATE);
int lastVersion = mWebViewPrefs.getInt(VERSION_CODE_PREF, 0);
int currentVersion = WebViewFactory.getLoadedPackageInfo().versionCode;
if (lastVersion > currentVersion) {
// The WebView package has been downgraded since we last ran in this application.
// Delete the WebView data directory's contents.
String dataDir = PathUtils.getDataDirectory(mWebViewDelegate.getApplication());
Log.i(TAG, "WebView package downgraded from " + lastVersion + " to " + currentVersion +
"; deleting contents of " + dataDir);
deleteContents(new File(dataDir));
}
if (lastVersion != currentVersion) {
mWebViewPrefs.edit().putInt(VERSION_CODE_PREF, currentVersion).apply();
}
// Now safe to use WebView data directory.
}
用命令行加载库
这里我们要看的最重要方法就是createWebView
@Override
public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);
synchronized (mLock) {
if (mWebViewsToStart != null) {
mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc));
}
}
return wvc;
}
同样我们在22的源码中才能找到WebViewChromium.java
// This does not touch any global / non-threadsafe state, but note that
// init is ofter called right after and is NOT threadsafe.
public WebViewChromium(WebViewChromiumFactoryProvider factory, WebView webView,
WebView.PrivateAccess webViewPrivate) {
mWebView = webView;
mWebViewPrivate = webViewPrivate;
mHitTestResult = new WebView.HitTestResult();
mAppTargetSdkVersion = mWebView.getContext().getApplicationInfo().targetSdkVersion;
mFactory = factory;
mRunQueue = new WebViewChromiumRunQueue();
factory.getWebViewDelegate().addWebViewAssetPath(mWebView.getContext());
}
至此大功告成
接下来多问一句这里就是浏览器的实现嘛?我们都知道4.4以前的WebKit for Android已经被移除(external/WebKit目录),取代为chromium(chromium_org)。然而WebViewProvider的目的就是用接口实现实现隔离达到兼容的效果。以上我们看到的都是AOSP层的android源码,WebViewChromium实现类中的实际功能还是由 AwContents 或 ContentViewCore 实现的而这部分的代码是在Chromium Project层中(源码不可见)