携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
WebView 的构造方法
WebView 的初始化当然是要从构造方法开始啦,因为我们可以直接使用 WebView(Context) 来直接创建一个 WebView 对象,并将其添加到布局中使用。
下面就是它对外暴露的构造方法:
@Widget
public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener,
ViewGroup.OnHierarchyChangeListener,
ViewDebug.HierarchyHandler {
// WebView 应该使用 Activity Context 对象,如果使用 Application Context 对象,WebView 可能会无法提供一系列功能,例如
public WebView(@NonNull Context context) {
this(context, null);
}
public WebView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
public WebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public WebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
this(context, attrs, defStyleAttr, defStyleRes, null, false);
}
// ...
}
对外暴露的构造方法并没有真实的初始化逻辑,而是在对内的构造方法中:
@UnsupportedAppUsage
protected WebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
@Nullable Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
}
@SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
protected WebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes, @Nullable Map<String, Object> javaScriptInterfaces,
boolean privateBrowsing) {
super(context, attrs, defStyleAttr, defStyleRes);
// WebView is important by default, unless app developer overrode attribute.
if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
}
if (getImportantForContentCapture() == IMPORTANT_FOR_CONTENT_CAPTURE_AUTO) {
setImportantForContentCapture(IMPORTANT_FOR_CONTENT_CAPTURE_YES);
}
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
if (mWebViewThread == null) {
throw new RuntimeException(
"WebView cannot be initialized on a thread that has no Looper.");
}
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
ensureProviderCreated();
mProvider.init(javaScriptInterfaces, privateBrowsing);
// Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
CookieSyncManager.setGetInstanceIsAllowed();
}
第二个 protected 的构造方法中是核心的初始化逻辑,在这个方法中的逻辑如下:
- 处理一些重要的属性
- 检查 context 是否存在
- 不存在抛出 IllegalArgumentException
- 检查 WebView 的线程是否存在 Looper
- 不存在抛出异常 RuntimeException
- 读取当前 targetVersion 版本是否高于 Android 4.3,供后续检查线程时使用。
- 检查线程是否合法
- 确保创建 WebViewProvider
- WebViewProvider 调用 init 进行初始化
CookieSyncManager.setGetInstanceIsAllowed()
环境检查
在上面的逻辑中,2、3、4、5 是用于构造 WebView 之前的一些环境因素保障的代码。确保不会因为一些对象不存在而出现异常情况。
检查 Looper
mWebViewThread 的初始化:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private final Looper mWebViewThread = Looper.myLooper();
所以第 3 步检查 mWebViewThread 对象为 null ,说明线程没有创建 Looper 对象,直接报错:
if (mWebViewThread == null) {
throw new RuntimeException(
"WebView cannot be initialized on a thread that has no Looper.");
}
第 4 步则是对 targetSdkVersion 做了一个判断:
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN_MR2;
targetSdkVersion 是否大于等于 Android 4.3 ,根据这个条件会在接下来的第 5 步中决定是否直接抛出异常。
第 5 步检查线程:
private void checkThread() {
// 忽略 mWebViewThread == null 因为这可以在超类构造函数中调用,甚至在这个类自己的构造函数开始之前。
if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
Throwable throwable = new Throwable(
"A WebView method was called on thread '" +
Thread.currentThread().getName() + "'. " +
"All WebView methods must be called on the same thread. " +
"(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
", FYI main Looper is " + Looper.getMainLooper() + ")");
StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
// targetVersion 版本高于 Android 4.3 直接抛出异常
if (sEnforceThreadChecking) {
throw new RuntimeException(throwable);
}
}
}
WebViewProvider 的创建流程
第 6 步创建 WebViewProvider ,首先是 mProvider 的声明
@UnsupportedAppUsage
private WebViewProvider mProvider;
然后是构造方法中执行 ensureProviderCreated() 方法来进行初始化:
// 创建 mProvider 对象
private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
mProvider = getFactory().createWebView(this, new PrivateAccess());
}
}
getFactory
mProvider 的初次赋值,通过 getFactory() 进行:
@UnsupportedAppUsage
private static WebViewFactoryProvider getFactory() {
return WebViewFactory.getProvider();
}
这里直接调用 WebViewFactory 的静态方法:
@UnsupportedAppUsage
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
if (sProviderInstance != null) return sProviderInstance;
sTimestamps.mWebViewLoadStart = SystemClock.uptimeMillis();
// 进程 uid
final int uid = android.os.Process.myUid();
if (uid == android.os.Process.ROOT_UID
|| uid == android.os.Process.SYSTEM_UID
|| uid == android.os.Process.PHONE_UID
|| uid == android.os.Process.NFC_UID
|| uid == android.os.Process.BLUETOOTH_UID) {
throw new UnsupportedOperationException("安全原因, WebView 没有该进程权限");
}
if (!isWebViewSupported()) {
// 设备不支持 WebView
throw new UnsupportedOperationException();
}
if (sWebViewDisabled) {
throw new IllegalStateException("WebView.disableWebView() was called: WebView is disabled");
}
try {
Class<WebViewFactoryProvider> providerClass = getProviderClass();
Method staticFactory = providerClass.getMethod(CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
try {
sProviderInstance = (WebViewFactoryProvider)staticFactory.invoke(null, new WebViewDelegate());
return sProviderInstance;
}
} catch (Exception e) {
throw new AndroidRuntimeException(e);
}
}
}
getProvider 方法中的逻辑是:
- 单例对象不为空,无需重新初始化直接返回对象
- 检查进程是否允许 WebView 正常运行
- 检查 WebView 的状态
- 尝试通过 getProviderClass 方法,通过反射的方式获取 Provider 的类实例 (
Class<WebViewFactoryProvider>) - 尝试通过反射框架调用 Class 的 create ,创建一个 WebViewFactoryProvider 对象。
在深入这部分逻辑之前,我们要先知道 WebViewFactoryProvider 是个什么东西:
@SystemApi
public interface WebViewFactoryProvider {
interface Statics {
String findAddress(String addr);
String getDefaultUserAgent(Context context);
void freeMemoryForTests();
void setWebContentsDebuggingEnabled(boolean enable);
void clearClientCertPreferences(Runnable onCleared);
void enableSlowWholeDocumentDraw();
Uri[] parseFileChooserResult(int resultCode, Intent intent);
void initSafeBrowsing(Context context, ValueCallback<Boolean> callback);
void setSafeBrowsingWhitelist(List<String> hosts, ValueCallback<Boolean> callback);
@NonNull
Uri getSafeBrowsingPrivacyPolicyUrl();
}
Statics getStatics();
WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess);
GeolocationPermissions getGeolocationPermissions();
CookieManager getCookieManager();
TokenBindingService getTokenBindingService();
TracingController getTracingController();
ServiceWorkerController getServiceWorkerController();
WebIconDatabase getWebIconDatabase();
WebStorage getWebStorage();
WebViewDatabase getWebViewDatabase(Context context);
@NonNull
default PacProcessor getPacProcessor() {
throw new UnsupportedOperationException("Not implemented");
}
@NonNull
default PacProcessor createPacProcessor() {
throw new UnsupportedOperationException("Not implemented");
}
ClassLoader getWebViewClassLoader();
}
WebViewFactoryProvider 是一个接口,这里一定要看清楚是 WebViewFactoryProvider 而不是 WebViewProvider 。
在这个方法中,第 4 步通过 getProviderClass 方法,返回了 Class<WebViewFactoryProvider> :
private static Class<WebViewFactoryProvider> getProviderClass() {
Context webViewContext = null;
Application initialApplication = AppGlobals.getInitialApplication();
try {
// 【1】 获取 context
webViewContext = getWebViewContextAndSetProvider();
try {
// ...
// 【2】 通过 webViewContext 获取一个 ClassLoader
for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
}
ClassLoader clazzLoader = webViewContext.getClassLoader();
// 【3】 WebViewFactory.loadNativeLibrary()
WebViewLibraryLoader.loadNativeLibrary(clazzLoader,getWebViewLibrary(sPackageInfo.applicationInfo));
// 【4】 Class.forName()
return getWebViewProviderClass(clazzLoader);
} catch (ClassNotFoundException e) {
Log.e(LOGTAG, "error loading provider", e);
throw new AndroidRuntimeException(e);
}
} catch (MissingWebViewPackageException e) {
Log.e(LOGTAG, "Chromium WebView package does not exist", e);
throw new AndroidRuntimeException(e);
}
}
在这个方法中执行了四个重要的步骤:
- 获取 Context 并设置 Provider 需要的一些信息和状态。
- 这个步骤主要是获取 ClassLoader 。
- WebViewFactory.loadNativeLibrary(),加在 Native 的一些 Library 。
- 通过 getWebViewProviderClass 方法创建了 Class<WebViewFactoryProvider> 对象。
WebViewFactory.getWebViewContextAndSetProvider 方法:
private static Context getWebViewContextAndSetProvider() throws MissingWebViewPackageException {
Application initialApplication = AppGlobals.getInitialApplication();
try {
// 加载一个 Provider
WebViewProviderResponse response = getUpdateService().waitForAndGetProvider();
if (response.status != LIBLOAD_SUCCESS && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) {
throw new MissingWebViewPackageException("Failed to load WebView provider: " + getWebViewPreparationErrorReason(response.status));
}
ActivityManager.getService().addPackageDependency(response.packageInfo.packageName);
// 验证 PackageInfo 和 Context 相关内容 ...
} catch (RemoteException | PackageManager.NameNotFoundException e) {
throw new MissingWebViewPackageException("Failed to load WebView provider: " + e);
}
}
这里的 getUpdateService() :
public static IWebViewUpdateService getUpdateService() {
if (isWebViewSupported()) {
return getUpdateServiceUnchecked();
} else {
return null;
}
}
static IWebViewUpdateService getUpdateServiceUnchecked() {
return IWebViewUpdateService.Stub.asInterface(
ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
}
Service 是 WebViewUpdateService :
@Override // Binder call
public WebViewProviderResponse waitForAndGetProvider() {
if (Binder.getCallingPid() == Process.myPid()) {
throw new IllegalStateException("Cannot create a WebView from the SystemServer");
}
final WebViewProviderResponse webViewProviderResponse =
WebViewUpdateService.this.mImpl.waitForAndGetProvider();
if (webViewProviderResponse.packageInfo != null) {
grantVisibilityToCaller(
webViewProviderResponse.packageInfo.packageName, Binder.getCallingUid());
}
return webViewProviderResponse;
}
代理了 mImpl 的方法:
WebViewProviderResponse waitForAndGetProvider() {
PackageInfo webViewPackage = null;
// ... 这里处理了 webViewStatus ,细节不重要忽略此部分代码
return new WebViewProviderResponse(webViewPackage, webViewStatus);
}
WebViewProviderResponse 是一个实现了 Parcelable 序列化的数据类,用来传递两个参数:
- PackageInfo
- status
这两个参数用来处理 getWebViewContextAndSetProvider 方法的后续逻辑。
最后需要关注的是 getWebViewProviderClass :
private static final String CHROMIUM_WEBVIEW_FACTORY ="com.android.webview.chromium.WebViewChromiumFactoryProviderForS";
public static Class<WebViewFactoryProvider> getWebViewProviderClass(ClassLoader clazzLoader)
throws ClassNotFoundException {
return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY,
true, clazzLoader);
}
通过反射,最终的 WebViewFactoryProvider 是一个 WebViewChromiumFactoryProviderForS :
package com.android.webview.chromium;
class WebViewChromiumFactoryProviderForS extends WebViewChromiumFactoryProvider {
public static WebViewChromiumFactoryProvider create(android.webkit.WebViewDelegate delegate) {
return new WebViewChromiumFactoryProviderForS(delegate);
}
protected WebViewChromiumFactoryProviderForS(android.webkit.WebViewDelegate delegate) {
super(delegate);
}
}
WebViewChromiumFactoryProviderForS 是专门给 Android S 使用的类,其他还有 ForO、 ForP 代表不同的 Android 版本 。它的父类型 WebViewChromiumFactoryProvider 实现了 WebViewFactoryProvider:
public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider
至此 getFactory 方法返回的对象算是明确了。
createWebView
getFactory() 方法返回 WebViewFactoryProvider 后,调用 createWebView 方法创建 WebViewProvider:
mProvider = getFactory().createWebView(this, new PrivateAccess());
这个方法在 WebViewChromiumFactoryProvider 中的实现是:
@Override
public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
return new WebViewChromium(this, webView, privateAccess, mShouldDisableThreadChecking);
}
返回了一个 WebViewChromium 对象,而 WebViewChromium 实现了 WebViewProvider 接口:
class WebViewChromium implements WebViewProvider, WebViewProvider.ScrollDelegate,
WebViewProvider.ViewDelegate, SmartClipProvider
至此,WebView 的 mProvider 对象的创建完成了。
WebViewProvider 的初始化
回顾 WebView 的构造方法中的初始化逻辑:
- 处理一些重要的属性
- 检查 context 是否存在
- 不存在抛出 IllegalArgumentException
- 检查 WebView 的线程是否存在 Looper
- 不存在抛出异常 RuntimeException
- 读取当前 targetVersion 版本是否高于 Android 4.3,供后续检查线程时使用。
- 检查线程是否合法
- 确保创建 WebViewProvider
- WebViewProvider 调用 init 进行初始化
CookieSyncManager.setGetInstanceIsAllowed()
分析完 WebViewProvider 的创建后,下一个步骤是调用 init 方法,这个方法在 WebView 完全构造后调用:
@Override
// BUG=6790250 |javaScriptInterfaces| was only ever used by the obsolete DumpRenderTree
// so is ignored. TODO: remove it from WebViewProvider.
public void init(final Map<String, Object> javaScriptInterfaces, final boolean privateBrowsing) {
long startTime = SystemClock.uptimeMillis();
boolean isFirstWebViewInit = !mFactory.hasStarted();
try (ScopedSysTraceEvent e1 = ScopedSysTraceEvent.scoped("WebViewChromium.init")) {
if (privateBrowsing) {
mFactory.startYourEngines(true);
final String msg = "Private browsing is not supported in WebView.";
if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) {
throw new IllegalArgumentException(msg);
} else {
Log.w(TAG, msg);
TextView warningLabel = new TextView(mContext);
warningLabel.setText(mContext.getString(org.chromium.android_webview.R.string.private_browsing_warning));
mWebView.addView(warningLabel);
}
}
if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// 如果 App 的 TargetSdkVersion >= JB MR2 (4.3),那么我们要求 WebView 仅在单个线程中使用。 所以,我们:
// 1) 使用当前线程作为 UI 线程启动 Chromium(如果它已经启动,这是一个无操作)。
mFactory.startYourEngines(false);
// 2) 检查当前线程是否是 UI 线程,如果它已经使用不同的线程作为 UI 线程启动,则会抛出。
checkThread();
} else {
// 对于较旧的应用程序,只有与视图层次结构相关的视图方法必须来自单个线程。 其他调用,包括构造函数本身,可以来自任何线程,并且会在必要时发布到 UI 线程。
// 我们曾经尽可能长时间地推迟决定哪个线程是 UI 线程,以允许针对 < JB MR2 的应用程序使用不同线程的情况,但这种初始化非常复杂,几乎从未在野外遇到过。 我们不能像正常情况那样只使用当前线程作为 UI 线程,因为旧应用程序在后台线程上构建 WebView 然后将其附加到主循环器上的视图层次结构中是很常见的。
// 因此,我们只是使用主循环器作为 UI 线程启动 Chromium,它几乎适用于所有旧应用程序,并接受其中极少数会损坏的事实。
mFactory.startYourEngines(true);
}
final boolean isAccessFromFileURLsGrantedByDefault = mAppTargetSdkVersion < Build.VERSION_CODES.JELLY_BEAN;
final boolean areLegacyQuirksEnabled = mAppTargetSdkVersion < Build.VERSION_CODES.KITKAT;
final boolean allowEmptyDocumentPersistence = mAppTargetSdkVersion <= Build.VERSION_CODES.M;
final boolean allowGeolocationOnInsecureOrigins = mAppTargetSdkVersion <= Build.VERSION_CODES.M;
// https://crbug.com/698752
final boolean doNotUpdateSelectionOnMutatingSelectionRange = mAppTargetSdkVersion <= Build.VERSION_CODES.M;
mContentsClientAdapter = mFactory.createWebViewContentsClientAdapter(mWebView, mContext);
try (ScopedSysTraceEvent e2 = ScopedSysTraceEvent.scoped("WebViewChromium.ContentSettingsAdapter")) {
mWebSettings = mFactory.createContentSettingsAdapter(new AwSettings(mContext,
isAccessFromFileURLsGrantedByDefault, areLegacyQuirksEnabled,
allowEmptyDocumentPersistence, allowGeolocationOnInsecureOrigins,
doNotUpdateSelectionOnMutatingSelectionRange));
}
if (mAppTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
// 在 Lollipop 之前,我们始终允许第三方 cookie 和混合内容。
mWebSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
mWebSettings.setAcceptThirdPartyCookies(true);
mWebSettings.getAwSettings().setZeroLayoutHeightDisablesViewportQuirk(true);
}
if (mAppTargetSdkVersion >= Build.VERSION_CODES.P) {
mWebSettings.getAwSettings().setCSSHexAlphaColorEnabled(true);
mWebSettings.getAwSettings().setScrollTopLeftInteropEnabled(true);
}
if (mShouldDisableThreadChecking) disableThreadChecking();
mSharedWebViewChromium.init(mContentsClientAdapter);
mFactory.addTask(new Runnable() {
@Override
public void run() {
initForReal();
if (privateBrowsing) {
// 故意不可逆地禁用 webview 实例,以使私人用户数据不会因滥用非私人浏览的 WebView 实例而泄漏。 不能只将 mAwContents 归零,因为我们在使用前从不对其进行归零检查。
destroy();
}
}
});
}
// 如果没有延迟初始化,则记录启动时间直方图条目。
if (mFactory.hasStarted()) {
if (isFirstWebViewInit) {
RecordHistogram.recordTimesHistogram(
"Android.WebView.Startup.CreationTime.Stage2.ProviderInit.Cold",
SystemClock.uptimeMillis() - startTime);
} else {
RecordHistogram.recordTimesHistogram(
"Android.WebView.Startup.CreationTime.Stage2.ProviderInit.Warm",
SystemClock.uptimeMillis() - startTime);
}
}
}
某种意义上来说,WebViewProvider 才是真正处理 WebView 能力的核心部分,而WebView 只是一个代理:
public void loadUrl(@NonNull String url, @NonNull Map<String, String> additionalHttpHeaders) {
checkThread();
mProvider.loadUrl(url, additionalHttpHeaders);
}