Android WebView的使用、实现与渲染引擎的关联启动过程

简介: 什么是Android WebViewWebView 是一个用来显示 Web 网页的控件,继承自 AbsoluteLayout,和使用系统其他控件没什么区别,只是 WeView 控件方法比较多比较丰富。

什么是Android WebView

WebView 是一个用来显示 Web 网页的控件,继承自 AbsoluteLayout,和使用系统其他控件没什么区别,只是 WeView 控件方法比较多比较丰富。因为它就是一个微型浏览器,包含一个浏览器该有的基本功能,例如:滚动、缩放、前进、后退下一页、搜索、执行 Js等功能。

有个比较重要的变化是:

 
在 Android 4.4 之前使用 WebKit 作为渲染内核,4.4 之后采用 chrome 内核。Api 使用兼容低版本。 

Android WebView的主要方法

  • void loadUrl(String url):加载网络链接 url
  • removeJavascriptInterface(String interfaceName):删除interfaceName 对应的注入对象
  • addJavascriptInterface(Object object,String interfaceName):注入 java 对象。
  • boolean canGoBack():判断 WebView 当前是否可以返回上一页
  • goBack():回退到上一页
  • boolean canGoForward():判断 WebView 当前是否可以向前一页
  • goForward():回退到前一页
  • onPause():类似 Activity 生命周期,页面进入后台不可见状态
  • pauseTimers():该方法面向全局整个应用程序的webview,它会暂停所有webview的layout,parsing,JavaScript Timer。当程序进入后台时,该方法的调用可以降低CPU功耗。
  • onResume():在调用 onPause()后,可以调用该方法来恢复 WebView 的运行。
  • resumeTimers():恢复pauseTimers时的所有操作。(注:pauseTimers和resumeTimers 方法必须一起使用,否则再使用其它场景下的 WebView 会有问题)
  • destroy():销毁 WebView
  • clearHistory():清除当前 WebView 访问的历史记录。
  • clearCache(boolean includeDiskFiles):清空网页访问留下的缓存数据。需要注意的时,由于缓存是全局的,所以只要是WebView用到的缓存都会被清空,即便其他地方也会使用到。该方法接受一个参数,从命名即可看出作用。若设为false,则只清空内存里的资源缓存,而不清空磁盘里的。
  • reload():重新加载当前请求
  • setLayerType(int layerType, Paint paint):设置硬件加速、软件加速
  • removeAllViews():清除子view。
  • clearSslPreferences():清除ssl信息。
  • clearMatches():清除网页查找的高亮匹配字符。
  • setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled):设置垂直方向滚动条。
  • setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled):设置横向滚动条。
  • loadUrl(String url, Map additionalHttpHeaders):加载制定url并携带http header数据。
  • evaluateJavascript(String script, ValueCallback resultCallback):Api 19 之后可以采用此方法之行 Js。
  • stopLoading():停止 WebView 当前加载。
  • loadUrl("about:blank")来实现这个功能,阴雨需要重新加载一个页面自然时间会收到影响。
  • freeMemory():释放内存,不过貌似不好用。
  • clearFormData():清除自动完成填充的表单数据。需要注意的是,该方法仅仅清除当前表单域自动完成填充的表单数据,并不会清除WebView存储到本地的数据。

Android WebView的具体实现与Chromium渲染引擎启动过程

这里以Android 8.0的源码为来说明,是先找到WebView的真正的构造函数:

 
protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { .... ensureProviderCreated(); mProvider.init(javaScriptInterfaces, privateBrowsing); .... } 

这个构造函数会调用另外一个成员函数ensureProviderCreated()确保Chromium动态库已经加载。在Chromium动态库已经加载的情况下,WebView类的成员函数ensureProviderCreated还会创建一个WebViewProvider对象,并且保存在成员变量mProvider中。这个WebViewProvider其实才是真正用来实现WebView的功能的幕后大佬。上诉那些Android WebView主要的方式基本都是通过mProvider来实现的,例如loadUrl(String url)等方法。

 
public void loadUrl(String url) { checkThread(); mProvider.loadUrl(url); } 

有了这个mProvider之后,WebView类的构造函数就会继续调用mProvider.init(javaScriptInterfaces, privateBrowsing)启动网页渲染引擎。对于基于Chromium实现的WebView来说,它使用的WebViewProvider是一个WebViewChromium对象。当这个WebViewChromium对象的成员函数init被调用的时候,它就会启动Chromium的网页渲染引擎。 所以,我们接下来看一下ensureProviderCreated的实现:

 
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()); } } 

WebView类的成员函数ensureProviderCreated首先调用成员函数checkThread确保它是在WebView的创建线程中调用的,接下来又会判断成员变量mProvider的值是否为null。如果为null,就表示它还没有当前创建的WebView创建过Provider。在这种情况下,它首先会调用成员函数getFactory获得一个WebViewFactory。有了这个WebViewFactory之后,就可以调用它的成员函数createWebView创建一个WebViewProvider。

接下来我们再看一下getFactory()方法以及它的实现:

 
private static WebViewFactoryProvider getFactory() { return WebViewFactory.getProvider(); } 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 || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID || uid == android.os.Process.BLUETOOTH_UID) { throw new UnsupportedOperationException( "For security reasons, WebView is not allowed in privileged processes"); } StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { Class<WebViewFactoryProvider> providerClass = getProviderClass(); Method staticFactory = null; try { staticFactory = providerClass.getMethod( CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class); } catch (Exception e) { if (DEBUG) { Log.w(LOGTAG, "error instantiating provider with static factory method", e); } } Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation"); try { sProviderInstance = (WebViewFactoryProvider) staticFactory.invoke(null, 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); } } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); StrictMode.setThreadPolicy(oldPolicy); } } } 

getFactory返回的WebView Factory是通过调用WebViewFactory类的静态成员函数getProvider获得的,getProvider首先是判断静态成员变量sProviderInstance的值是否等于null。如果等于null,那么就说明当前的App进程还没有加载过Chromium动态库。在这种情况下,就需要加载Chromium动态库,并且创建一个WebView Factory,保存在静态成员变量sProviderInstance。接下来我们就先分析Chromium动态库的加载过程,然后再分析WebView Factory的创建过程。

加载Chromium动态库是通过调用WebViewFactory类的静态成员函数loadNativeLibrary实现的:

 
private static int loadNativeLibrary(ClassLoader clazzLoader, PackageInfo packageInfo) { if (!sAddressSpaceReserved) { Log.e(LOGTAG, "can't load with relro file; address space not reserved"); return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED; } String[] args = getWebViewNativeLibraryPaths(packageInfo); int result = nativeLoadWithRelroFile(args[0] /* path32 */, args[1] /* path64 */, CHROMIUM_WEBVIEW_NATIVE_RELRO_32, CHROMIUM_WEBVIEW_NATIVE_RELRO_64, clazzLoader); if (result != LIBLOAD_SUCCESS) { Log.w(LOGTAG, "failed to load with relro file, proceeding without"); } else if (DEBUG) { Log.v(LOGTAG, "loaded with relro file"); } return result; } 

loadNativeLibrary首先会调用成员函数getWebViewNativeLibraryPaths获得要加载的Chromium动态库的文件路径,然后再调用另外一个静态成员函数nativeLoadWithRelroFile对它进行加载。在加载的时候,会指定一个Chromium GNURELRO Section文件。这个Chromium GNURELRO Section文件是系统启动时候,通过启动一个临时进程生成的。其中静态成员函数nativeLoadWithRelroFile是一个JNI方法,它由C++层的函数LoadWithRelroFile实现:

 
jboolean LoadWithRelroFile(JNIEnv* env, jclass, jstring lib32, jstring lib64, jstring relro32, jstring relro64) { #ifdef __LP64__ jstring lib = lib64; jstring relro = relro64; (void)lib32; (void)relro32; #else jstring lib = lib32; jstring relro = relro32; (void)lib64; (void)relro64; #endif jboolean ret = JNI_FALSE;  const char* lib_utf8 = env->GetStringUTFChars(lib, NULL); if (lib_utf8 != NULL) { const char* relro_utf8 = env->GetStringUTFChars(relro, NULL); if (relro_utf8 != NULL) { ret = DoLoadWithRelroFile(lib_utf8, relro_utf8); env->ReleaseStringUTFChars(relro, relro_utf8); } env->ReleaseStringUTFChars(lib, lib_utf8); } return ret; } 

LoadWithRelroFile判断自己是32位还是64位的实现,然后从参数lib32和lib64中选择对应的Chromium动态库进行加载。这个加载过程是通过调用另外一个函数DoLoadWithRelroFile实现的:

 
jboolean DoLoadWithRelroFile(const char* lib, const char* relro) {  int relro_fd = TEMP_FAILURE_RETRY(open(relro, O_RDONLY)); ...... android_dlextinfo extinfo; extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_USE_RELRO; extinfo.reserved_addr = gReservedAddress; extinfo.reserved_size = gReservedSize; extinfo.relro_fd = relro_fd; void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo); close(relro_fd); ...... return JNI_TRUE; } 

函数DoLoadWithRelroFile的实现是通过Linker导出的函数androiddlopenext在Zyogote进程保留的地址空间中加载Chromium动态库的。注意,App进程是Zygote进程fork出来的,因此它同样会获得Zygote进程预留的地址空间。不过,函数DoLoadWithRelroFile会将告诉函数androiddlopenext在加载Chromium动态库的时候,将参数relro描述的Chromium GNURELRO Section文件内存映射到内存来,并且代替掉已经加载的Chromium动态库的GNURELRO Section。这是通过将指定一个ANDROIDDLEXTUSERELRO标志实现的。之所以可以这样做,是因为参数relro描述的Chromium GNURELRO Section文件对应的Chromium动态库的加载地址与当前App进程加载的Chromium动态库的地址一致。只要两个相同的动态库在两个不同的进程中的加载地址一致,它们的链接和重定位信息就是完全一致的,因此就可以通过文件内存映射的方式进行共享。共享之后,就可以达到节省内存的目的了。

这一步执行完成之后,App进程就加载完成Chromium动态库了。回到前面分析的WebViewFactory类的静态成员函数getProvider,它接下来继续创建一个WebViewFactory。这个WebViewFactory以后就可以用来创建WebViewProvider。

WebViewFactory类的静态成员函数getProvider首先要确定要创建的WebView Factory的类型。这个类型是通过调用另外一个静态成员函数getFactoryClass获得的:

 
private static Class<WebViewFactoryProvider> getProviderClass() { Context webViewContext = null; Application initialApplication = AppGlobals.getInitialApplication(); try { Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getWebViewContextAndSetProvider()"); try { webViewContext = getWebViewContextAndSetProvider(); } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " + sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")"); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()"); try { initialApplication.getAssets().addAssetPathAsSharedLibrary( webViewContext.getApplicationInfo().sourceDir); ClassLoader clazzLoader = webViewContext.getClassLoader(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()"); loadNativeLibrary(clazzLoader, sPackageInfo); Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()"); try { return getWebViewProviderClass(clazzLoader); } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } } catch (ClassNotFoundException e) { Log.e(LOGTAG, "error loading provider", e); throw new AndroidRuntimeException(e); } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); } } catch (MissingWebViewPackageException e) { // If the package doesn't exist, then try loading the null WebView instead. // If that succeeds, then this is a device without WebView support; if it fails then // swallow the failure, complain that the real WebView is missing and rethrow the // original exception. try { return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY); } catch (ClassNotFoundException e2) { // Ignore. } Log.e(LOGTAG, "Chromium WebView package does not exist", e); throw new AndroidRuntimeException(e); } } 

从这里可以看到,WebViewFactory类的静态成员函数getFactoryClass返回的WebView Factory的类型为com.android.webview.chromium.WebViewChromiumFactoryProviderForO。这个com.android.webview.chromium.WebViewChromiumFactoryProviderForO类是由前面提到的WebView Package提供的。这意味着WebViewFactory类的静态成员函数getProvider创建的WebView Factory是一个WebViewChromiumFactoryProvider对象:

 
public WebViewChromiumFactoryProvider() { ... AwBrowserProcess.loadLibrary(); ... 

WebViewChromiumFactoryProvider类的构造函数会调用AwBrowserProcess类的静态成员函数loadLibrary对前面加载的Chromium动态库进行初始化:

 
public abstract class AwBrowserProcess { ... public static void loadLibrary() { ... try { LibraryLoader.loadNow(); } catch (ProcessInitException e) { throw new RuntimeException("Cannot load WebView", e); } } ... } 

AwBrowserProcess类的静态成员函数loadLibrary又调用LibraryLoader类的静态成员函数loadNow对前面加载的Chromium动态库进行初始化:

 
public class LibraryLoader { ... public static void loadNow() throws ProcessInitException { loadNow(null, false); } ... } 

LibraryLoader类的静态成员函数loadNow又调用另外一个重载版本的静态成员函数loadNow对前面加载的Chromium动态库进行初始化:

 
public class LibraryLoader { ... public static void loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries) throws ProcessInitException { synchronized (sLock) { loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries); } } ... } 

LibraryLoader类重载版本的静态成员函数loadNow又调用另外一个静态成员函数loadAlreadyLocked对前面加载的Chromium动态库进行初始化:

 
public class LibraryLoader { ... // One-way switch becomes true when the libraries are loaded. private static boolean sLoaded = false; ... private static void loadAlreadyLocked( Context context, boolean shouldDeleteOldWorkaroundLibraries) throws ProcessInitException { try { if (!sLoaded) { ... boolean useChromiumLinker = Linker.isUsed(); if (useChromiumLinker) Linker.prepareLibraryLoad(); for (String library : NativeLibraries.LIBRARIES) { Log.i(TAG, "Loading: " + library); if (useChromiumLinker) { Linker.loadLibrary(library); } else { try { System.loadLibrary(library); } catch (UnsatisfiedLinkError e) { ... } } } if (useChromiumLinker) Linker.finishLibraryLoad(); ... sLoaded = true; } } catch (UnsatisfiedLinkError e) { ... } ... } ... } 

由于并不是所有的系统都支持在加载动态库时,以文件内存映射的方式代替它的GNURELRO Section,因此Chromium自己提供了一个Linker。通过这个Linker加载动态库时,能够以文件内存映射的方式代替要加载的动态库的GNURELRO Section,也就是实现前面提到的函数androiddlopenext的功能。在高于Android 5.0中,由于系统已经提供了函数androiddlopenext,因此,Chromium就不会使用自己的Linker加载动态库,而是使用Android系统提供的Linker来加载动态库。通过调用System类的静态成员函数loadLibrary即可以使用系统提供的Linker来加载动态库。LibraryLoader类的静态成员函数loadAlreadyLocked要加载的动态库由NativeLibraries类的静态成员变量LIBRARIES指定:

 
public class NativeLibraries { ... static final String[] LIBRARIES = { "webviewchromium" }; ... } 

从这里可以知道,LibraryLoader类的静态成员函数loadAlreadyLocked要加载的动态库就是Chromium动态库。这个Chromium动态库前面已经加载过了,因此这里通过调用System类的静态成员函数loadLibrary再加载时,仅仅是只会触发它导出的函数JNIOnLoad被调用,而不会重新被加载。Chromium动态库导出的JNIOnLoad被调用的时候,Chromium动态库就会执行初始化工作:

 
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { ... content::SetContentMainDelegate(new android_webview::AwMainDelegate()); ... return JNI_VERSION_1_4; } 

其中的一个初始化操作是给Chromium的Content层设置一个类型为AwMainDelegate的Main Delegate。这个AwMainDelegate实现在Chromium的androidwebview模块中。Android WebView是通过Chromium的androidwebview模块加载和渲染网页的。Chromium加载和渲染网页的功能又是实现在Content层的,因此,Chromium的androidwebview模块又要通过Content层实现加载和渲染网页功能。这样,Chromium的androidwebview模块就可以设置一个Main Delegate给Content层,以便它们可以互相通信。给Chromium的Content层设置一个Main Delegate是通过调用函数SetContentMainDelegate实现的:

 
LazyInstance<scoped_ptr<ContentMainDelegate> > g_content_main_delegate = LAZY_INSTANCE_INITIALIZER; ...... void SetContentMainDelegate(ContentMainDelegate* delegate) { DCHECK(!g_content_main_delegate.Get().get()); g_content_main_delegate.Get().reset(delegate); } 

从前面的分析可以知道,参数delegate指向的是一个AwMainDelegate对象,这个AwMainDelegate对象会被函数SetContentMainDelegate保存在全局变量gcontentmain_delegate中。这一步执行完成后,Chromium动态库就在App进程中加载完毕,并且也已经完成了初始化工作。与此同时,系统也为App进程创建了一个类型为WebViewChromiumFactoryProvider的WebViewFactory。回到前面分析的WebView类的成员函数ensureProviderCreated中,这时候就它会通过调用上述类型为WebViewChromiumFactoryProvider的WebViewFactory的成员函数createWebView为当前创建的WebView创建一个WebView Provider:

 
public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider { ... @Override public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) { WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess); ... return wvc; } ... } 

WebViewChromiumFactoryProvider类的成员函数createWebView创建的是一个类型为WebViewChromium的WebView Provider。这个WebView Provider将会返回给WebView类的成员函数ensureProviderCreated。WebView类的成员函数ensureProviderCreated再将该WebView Provider保存在成员变量mProvider中。这样,正在创建的WebView就获得了一个类型为WebViewChromium的WebView Provider。以后通过这个WebView Provider,就可以通过Chromium来加载和渲染网页了。

欢迎加入Android进阶交流群;701740775。进群可免费领取一份最新技术大纲和Android进阶资料。请备注csdn

相关文章
|
29天前
|
程序员 开发工具 Android开发
Android|WebView 禁止长按,限制非白名单域名的跳转层级
如何限制 WebView 仅域名白名单网址能随意跳转,并禁用长按选择文字。
34 2
|
3月前
|
编解码 开发工具 Android开发
Android平台RTSP|RTMP播放器如何实现TextureView渲染
本文介绍了在Android平台上使用TextureView进行RTSP和RTMP视频流渲染的技术背景和实现方法。TextureView相较于SurfaceView具备更高性能、更强功能性和更灵活的绘制方式等优势,但也有必须在硬件加速环境下运行和较高内存占用等局限。文中详细展示了如何在SmartPlayerV2工程中创建和配置TextureView,并通过代码示例解释了如何根据视频分辨率信息调整显示比例,以及处理TextureView的各种生命周期回调。此外,还列举了该播放器SDK支持的多项高级功能,如多实例播放、多种编码格式支持、硬解码能力等,旨在帮助开发者更好地理解和实现高性能的直播播放器。
|
5月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
5月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
4月前
|
Web App开发 JavaScript 前端开发
Android端使用WebView注入一段js代码实现js调用android
Android端使用WebView注入一段js代码实现js调用android
123 0
|
5月前
|
Web App开发 移动开发 前端开发
52. 【Android教程】网页视图:WebView
52. 【Android教程】网页视图:WebView
93 1
|
5月前
|
安全 网络安全 API
kotlin安卓开发JetPack Compose 如何使用webview 打开网页时给webview注入cookie
在Jetpack Compose中使用WebView需借助AndroidView。要注入Cookie,首先在`build.gradle`添加WebView依赖,如`androidx.webkit:webkit:1.4.0`。接着创建自定义`ComposableWebView`,通过`CookieManager`设置接受第三方Cookie并注入Cookie字符串。最后在Compose界面使用这个自定义组件加载URL。注意Android 9及以上版本可能需要在网络安全配置中允许第三方Cookie。
|
6天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
11天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。