WebView 相关概念
一、背景
学习一个东西,首先得知道为什么学习它,其次才是它的一些使用方法。那么为什么要使用 WebView 呢?我们知道像电商这类的 App 经常会有一些活动啥的,如果使用原生的 Android 开发,既耗时又耗力,App 更新频繁。但是如果使用 WebView 的话,只需要更改 HTML 页面即可,极大地节省了成本。
二、简介
WebView 是一个基于 webkit 引擎、展现 web 页面的控件。
WebView 的作用如下:
- 在 App 中显示和渲染 Web 页面。
- 直接使用 HTML 作为布局。
- 可以与 JS 进行交互。
WebView 使用详解
WebView 既可以自己单独使用,也可以联合其工具类一起使用,下面主要分为三个部分来进行介绍:
- WebView 自身常见的方法。
- WebView 组合使用的的工具类:WebSettings、WebViewClient、WebChromeClient。
- Android 和 JS 的交互。
一、WebView 自身常见的方法
-
加载 URL
//从网页加载 webview.loadUrl("https://google.com") //加载 apk 下的 html webview.loadUrl("file://android_asset/test.html") //加载手机本地的 html webview.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html") 复制代码
-
WebView 的状态
//激活 WebView 的状态,可以执行网页的响应 webview.onResume() //失去焦点不可见,通知内核暂停所有的行动 webview.onPause() //暂停所有 WebView 的行动,降低消耗 webview.pauseTimer() //恢复行动 webview.resumeTimers() 复制代码
-
关于 前进/后退 网页
//是否可以后退 Webview.canGoBack() //后退网页 Webview.goBack() //是否可以前进 Webview.canGoForward() //前进网页 Webview.goForward() //以当前的index为起始点前进或者后退到历史记录中指定的steps //如果steps为负数则为后退,正数则为前进 Webview.goBackOrForward(intsteps) 复制代码
-
解决 Back 键 Finish Browser
//按手机的 back 会 finish() 自身而结束,下面的代码解决这个问题 public boolean onKeyDown(int keyCode, KeyEvent event) { if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) { mWebView.goBack(); return true; } return super.onKeyDown(keyCode, event); } 复制代码
二、WebView 组合使用的工具类
1、WebSettings 类
-
创建 WebView 的两种方式
// 1、直接在 Activity 中生成 WebView webview = new WebView(this) // 2、在 XML 布局中定义,在 Activity 获取对象 WebView webview = findViewById(R.id.webview) 复制代码
-
利用 WebSettings 进行配置
//声明WebSettings子类 WebSettings webSettings = webView.getSettings(); //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript webSettings.setJavaScriptEnabled(true); // 若加载的 html 里有JS 在执行动画等操作,会造成资源浪费(CPU、电量) // 在 onStop 和 onResume 里分别把 setJavaScriptEnabled() 给设置成 false 和 true 即可 //支持插件 webSettings.setPluginsEnabled(true); //设置自适应屏幕,两者合用 webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小 webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小 //缩放操作 webSettings.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。 webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放 webSettings.setDisplayZoomControls(false); //隐藏原生的缩放控件 //其他细节操作 // 只要本地有,无论是否过期,或者 no-cache,都使用缓存中的数据 webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); // 不使用缓存,只从网络中获取数据 webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); // (默认)根据 cache-control 或者 Last-Modified 决定是否从网络中获取数据 webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // 不使用网络,只读取本地缓存数据 webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY); webSettings.setAllowFileAccess(true); //设置可以访问文件 webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口 webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片 webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式 复制代码
2、WebViewClient 类
-
作用: 处理各种通知 & 请求事件
-
常用方法:
webview.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(View view,String url){ view.loadUrl(url); } }); // 打开网页是不调用浏览器,而是直接在本 WebView 中显示 shouldOverrideUrlLoading() // 开始载入页面时进行调用 onPageStarted() // 页面加载结束时调用 onPageFinish() // 在页面加载资源时调用,每一资源都会调用一次 onLoadResource() // 加载页面的服务器出现错误时调用 onReceivedError() 复制代码
3、WebChromeClient 类
-
作用: 辅助 WebView 处理 JS 的对话框、网站图标、网站标题等。
-
常用方法:
// 获取网页的加载进度并进行显示 onProgressChanged() // 获取 Web 页的标题 onReceivedTitle() // 支持 JS 的警告框 onJsAlter() //..... 复制代码
三、WebView 和 JavaScript 交互
- Android 调用 JS 代码总结
- 通过 WebView 的 loadUrl()。
- 通过 WebView 的 evaluateJavaScript()。
- JS 调用 Android 代码总结
- 通过调用 WebView 的 addJavascriptInterface() 进行对象映射。
- 通过 WebViewClient 的 onJsAlter()、onJsConfirm()、onJsPrompt()方法回调拦截 JS 对话框 alter()、confirm()、prompt()
三、WebView 内存泄漏问题
1、如何避免内存泄漏
-
不在 XML 中定义 WebView,而在是需要的时候在 Activity 创建,并且Context 使用 getApplicationContext()
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mWebView = new WebView(getApplicationContext()); mWebView.setLayoutParams(params); mLayout.addView(mWebView); 复制代码
-
在 Activity 销毁的时候,先让 WebView 加载 null,然后在移除 WebView,最后置空。
@Override protected void onDestroy() { if (mWebView != null) { //先加载 null 内容 mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); mWebView.clearHistory(); //移除 WebView ((ViewGroup) mWebView.getParent()).removeView(mWebView); //销毁 mWebView.destroy(); //置空 mWebView = null; } super.onDestroy(); } 复制代码
2、探究内存泄漏的原因(TODO)
这是由 WebView 的内核引起的,一般在低版本的 Android 才会出现,高版本已经进行修复。原因具体如下:
org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。 org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow; 系统会在attach和detach处进行注册和反注册 component callback
相关文章参考:juejin.cn/post/690148…
四、WebView 相关的性能优化
WebView 给人的感觉就是它的加载速度很慢,比原生的 Native 慢了很多,往往加载一个页面需要耗费大量时间。这就需要我们对 WebView 进行相关的优化,首先得了解 WebView 的启动过程:
1、WebView 初始化
只有当 WebView 都创建并初始化完成以后,才开始加载页面的资源,那么怎样才能解决这样的情况。有下面几种常用的解决方案:
-
获取全局 的 Context
减少 WebView 在 App 首次打开时初始化的时间,但是增加了额外的内存消耗。同时,页面间进行跳转需要清除上一个页面留存的痕迹,更加容易引起内存泄漏。
-
客户端代理数据请求
- 在客户端初始化 WebView 的同时,直接由native开始网络请求数据;
- 当页面初始化完成后,向 native 获取其代理请求的数据。
此方法虽然不能减少 WebView 的初始化时间,但是数据请求和 WebView 初始化同时进行,总体的页面加载时间缩短了。
2、建立连接/服务器处理
-
DNS 采用和客户端 API 相同的域名
客户端首次打开会对一个域名发起请求,其 DNS 将会被缓存,当打开 WebView 的时候,又会对一个新的 Api 进行请求,就会重新发起 DNS 请求,增加了时间的消耗。
-
同步渲染采用 chunk 编码
同步渲染时如果后端请求时间过长,可以考虑采用 chunk 编码,将数据放在最后,优先将静态内容加载出来。对于传统的后段渲染,往往都是采用【浏览器】-> 【Web API】-> 【业务 API】的加载模式,其中后段时间就是指【Web API】的处理时间。这里的 Web API 一般有两个作用:
- 确定静态资源的版本。
- 根据用户的请求,去业务 API 获取数据。
而确定静态资源的版本基本上是无耗时的,后端请求时间基本上花费在了业务 API 的请求上。那么如何对这段时间进行优化呢?我们可以在header中设置
transfer-encoding:chunked
使得页面可以分块输出,如果合理设计页面,让head部分都是确定的静态资源版本相关内容,而body部分是业务数据相关内容,那么我们可以在用户请求的时候,首先将Web API可以确定的部分先输出给浏览器,然后等API完全获取后,再将API数据传输给浏览器。由下图可以看清楚 chunk 分块请求和 完全输出的区别:
3、页面框架渲染
通常情况下,前端的的 CSS 加载 和 JS 脚本的执行都不会阻塞页面的解析,但是如果同时出现的顺序不当,则会阻塞页面的解析,执行情况是这样的:
- CSS 不会阻止页面继续向下解析。
- 内联的 JS 会很快执行完成,然后继续解析文档。
- 但是 CSS 的加载会阻塞后面 JS 的执行,从而间接阻塞 HTML 的解析。
参考文章: