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層中(源碼不可見)