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

相关文章
|
6月前
|
API Android开发 数据安全/隐私保护
解决android webview 加载http url 失败 net::ERR_CLEARTEXT_NOT_PERMITTED 错误
解决android webview 加载http url 失败 net::ERR_CLEARTEXT_NOT_PERMITTED 错误
238 0
|
2月前
|
XML Android开发 数据格式
安卓和webview交互
安卓和webview交互
25 0
|
4月前
|
JavaScript 前端开发 Android开发
android开发,使用kotlin学习WebView(详细)
android开发,使用kotlin学习WebView(详细)
130 0
|
4月前
|
XML Android开发 数据格式
安卓和webview交互
安卓和webview交互
57 1
|
5月前
|
定位技术 Android开发
[√]Android webview的url scheme
[√]Android webview的url scheme
408 0
|
5月前
|
小程序 Android开发 iOS开发
在钉钉小程序中安卓无法打开webview
在钉钉小程序中安卓无法打开webview
116 1
|
6月前
|
JavaScript 前端开发 Android开发
Android AgentWeb WebView 与js交互总结
Android AgentWeb WebView 与js交互总结
183 0
|
8月前
|
JavaScript 前端开发 Android开发
Android 中WebView的使用详解
Android 中WebView的使用详解
235 0
|
5天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
27天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
14 0