Android WebView 初始化流程

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, nullfalse);
    }

    // ...
}

对外暴露的构造方法并没有真实的初始化逻辑,而是在对内的构造方法中:

    @UnsupportedAppUsage
    protected WebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
            @Nullable Map<String, Object> JavaScriptInterfaces, boolean privateBrowsing)
 
{
        this(context, attrs, defStyleAttr, 0JavaScriptInterfaces, 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 的构造方法中是核心的初始化逻辑,在这个方法中的逻辑如下:

  1. 处理一些重要的属性
  2. 检查 context 是否存在
    1. 不存在抛出 IllegalArgumentException
  3. 检查 WebView 的线程是否存在 Looper
    1. 不存在抛出异常 RuntimeException
  4. 读取当前 targetVersion 版本是否高于 Android 4.3,供后续检查线程时使用。
  5. 检查线程是否合法
  6. 确保创建 WebViewProvider
  7. WebViewProvider 调用 init 进行初始化
  8. 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(thisnew PrivateAccess());
        }
    }

getFactory

mProvider 的初次赋值,通过 getFactory() 进行:

@UnsupportedAppUsage
private static WebViewFactoryProvider getFactory() {
    return WebViewFactory.getProvider();
}

这里直接调用 WebViewFactory 的静态方法:

@UnsupportedAppUsage
static WebViewFactoryProvider getProvider() {
    synchronized (sProviderLock) {
        if (sProviderInstance != nullreturn 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(nullnew WebViewDelegate());
                return sProviderInstance;
            }
        } catch (Exception e) {
            throw new AndroidRuntimeException(e);
        }
    }
}

getProvider 方法中的逻辑是:

  1. 单例对象不为空,无需重新初始化直接返回对象
  2. 检查进程是否允许 WebView 正常运行
  3. 检查 WebView 的状态
  4. 尝试通过 getProviderClass 方法,通过反射的方式获取 Provider 的类实例 (Class<WebViewFactoryProvider>
  5. 尝试通过反射框架调用 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 PacProceSSOgetPacProcessor() {
        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);
    }
}

在这个方法中执行了四个重要的步骤:

  1. 获取 Context 并设置 Provider 需要的一些信息和状态。
  2. 这个步骤主要是获取 ClassLoader 。
  3. WebViewFactory.loadNativeLibrary(),加在 Native 的一些 Library 。
  4. 通过 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(thisnew 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 的构造方法中的初始化逻辑:

  1. 处理一些重要的属性
  2. 检查 context 是否存在
    1. 不存在抛出 IllegalArgumentException
  3. 检查 WebView 的线程是否存在 Looper
    1. 不存在抛出异常 RuntimeException
  4. 读取当前 targetVersion 版本是否高于 Android 4.3,供后续检查线程时使用。
  5. 检查线程是否合法
  6. 确保创建 WebViewProvider
  7. WebViewProvider 调用 init 进行初始化
  8. 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);
    }


原文始发于微信公众号(八千里路山与海):Android WebView 初始化流程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/85065.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!