From 4b77e97993c9e2a55adf5d603bfec9864b1ddb85 Mon Sep 17 00:00:00 2001
From: songshicong <869759698@qq.com>
Date: Sun, 25 Nov 2018 08:50:16 +0800
Subject: [PATCH] update
---
blogs/WebView.md | 449 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 449 insertions(+)
create mode 100644 blogs/WebView.md
diff --git a/blogs/WebView.md b/blogs/WebView.md
new file mode 100644
index 0000000..277ea5e
--- /dev/null
+++ b/blogs/WebView.md
@@ -0,0 +1,449 @@
+---
+WebView
+---
+
+#### 目录
+
+1. 思维导图
+2. WebView 的基本使用
+ - WebView
+ - WebSettings
+ - WebViewClient
+ - WebChromeClient
+3. WebView 与 JS 交互
+ - Android 去调用 JS 代码
+ - JS 调用 Android 代码
+4. WebView 常见问题汇总
+5. WebView 优化
+6. 参考
+
+#### 思维导图
+
+![](https://i.loli.net/2018/11/25/5bf9f14c0204b.png)
+
+#### 基本使用
+
+WebView 是一个基于 webkit 引擎,展示 web 页面的空间。WebView 在低版本和高版本采用了不同的 webkit 内核版本,4.4 ( API 19 ) 之后直接使用了 Chrome。
+
+##### WebView 类
+
+```java
+ /**
+ * Back 键后退网页
+ * 如果又重写了 onBackPressed 方法,只会回调 onKeyDown
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
+ mWebView.goBack();
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+ mWebView.onPasue();
+ //清除缓存数据
+ mWebView.clearCache(true); //清除缓存
+ mWebView.clearHistory(); //清除浏览记录
+ mWebView.clearFormData(); //清除自动填充的表单数据
+```
+
+##### WebSetting 类
+
+对 WebView 进行配置和管理。
+
+```java
+ private void setWebViewSettings(WebView webView){
+ WebSettings webSettings=webView.getSettings();
+ webSettings.setJavaScriptEnabled(true); //支持 JS
+ webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过 JS 打开新的窗口
+ //设置自适应屏幕
+ webSettings.setUseWideViewPort(true);
+ webSettings.setLoadWithOverviewMode(true);
+
+ webSettings.setLoadsImagesAutomatically(true); //设置自动加载图片
+ webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); //不使用缓存
+ //...
+ }
+```
+
+##### WebViewClient 类
+
+处理各种通知和请求事件等等。
+
+```java
+//在当前 WebView 打开页面,而不是系统浏览器
+//如果不需要转发处理,只需要传递一个 WebViewClent 实例,根本不需要重写 shouldOverrideUrlLoading 方法
+public class MyWebViewClient extends WebViewClient {
+
+ private Context mContext;
+
+ public MyWebViewClient(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
+ //当前 WebView 处理
+ if (request.getUrl().getHost().equals("https://www.example.com")) {
+ return false;
+ }
+ //如果需要转发处理
+ mContext.startActivity(new Intent(Intent.ACTION_VIEW, request.getUrl()));
+ return true;
+ }
+
+ @Override
+ public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
+ switch (error.getErrorCode()){
+ case WebViewClient.ERROR_CONNECT: //连接失败
+ view.loadUrl("file:///android_asset/error.html");
+ break;
+ }
+ }
+
+ @Override
+ public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+ handler.proceed(); //等待证书响应
+ //handler.cancel(); //挂起连接 默认行为
+ }
+}
+
+mWebView.setWebViewClient(new MyWebViewClient(MainActivity.this));
+```
+
+##### WebChromeClient 类
+
+辅助 WebView 处理 JS 的对话框、网站标题等等。
+
+```java
+public class MyWebChromeClient extends WebChromeClient {
+
+ /**
+ * 网页加载进度
+ */
+ @Override
+ public void onProgressChanged(WebView view, int newProgress) {
+ super.onProgressChanged(view, newProgress);
+ }
+
+ /**
+ * 网页标题加载完毕回调
+ */
+ @Override
+ public void onReceivedTitle(WebView view, String title) {
+ super.onReceivedTitle(view, title);
+ }
+
+ /**
+ * 拦截输入框
+ */
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ return super.onJsPrompt(view, url, message, defaultValue, result);
+ }
+
+ /**
+ * 拦截确认框
+ */
+ @Override
+ public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
+ return super.onJsConfirm(view, url, message, result);
+ }
+
+ /**
+ * 拦截弹框
+ */
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
+ return super.onJsAlert(view, url, message, result);
+ }
+}
+
+
+mWebView.setWebChromeClient(new MyWebChromeClient());
+```
+
+#### WebView 与 JS 交互
+
+##### Android 调用 JS 代码
+
+- webView.loadUrl(url)
+- webView.evaluateJavascript()
+
+首先选准备一个静态文件:
+
+```html
+
+
+
+
+ Title
+
+
+ WebView 与 JS 交互!
+
+
+
+```
+
+第一种方式:loadUrl()
+
+```java
+ mWebView.loadUrl("javascript:callJS()");
+ mWebView.setWebChromeClient(new WebChromeClient(){
+ @Override
+ public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
+ AlertDialog dialog=new AlertDialog.Builder(WebViewContactActivity.this)
+ .setTitle("Title")
+ .setPositiveButton("确认", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ result.confirm();
+ }
+ })
+ .setCancelable(false)
+ .setMessage(message)
+ .create();
+ dialog.show();
+ return true;
+ }
+ });
+```
+
+可以看到,WebView 只是载体,内容的渲染还的通过 WebChromeClient 承载。
+
+第二种方式:evaluateJavascript()
+
+```java
+ mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback() {
+ @Override
+ public void onReceiveValue(String value) {
+ //JS 返回的结果
+ Toast.makeText(WebViewContactActivity.this, "value " + value, Toast.LENGTH_SHORT).show();
+ }
+ });
+```
+
+只是把上面的 loadUrl 换成 evaluateJavascript 方法而已。但是这种方法比第一种方式效率高,因为该方法的执行不会使页面刷新。
+
+两种方法的对比:
+
+| 调用方式 | 优点 | 缺点 | 使用场景 |
+| ------------------- | -------- | -------------------------- | ---------------------------------- |
+| loadUrl | 方便简洁 | 效率低 | 不需要获取返回值,对性能要求较低时 |
+| evaluatedJavascript | 效率高 | 向下兼容性差( API > 19 ) | API > 19 |
+
+当然也可以通过 Build.VERSION 来进行判断执行。
+
+##### JS 调用 Android 代码
+
+- 通过 WebView.addJavascriptInterface 进行对象映射
+- 通过 WebViewClient.shouldOverrideUrlLoading 方法回调拦截 url
+- 通过 WebChromeClient 的 onJsAlert、onJsConfirm、onJsPrompt 方法回调拦截 JS 对话框 alert、confirm、prompt 消息
+
+第一种方式:WebView.addJavascriptInterface 进行对象映射
+
+首先先准备好资源文件,用于模拟 WebView 加载的网页:
+
+```html
+
+
+
+
+ Demo
+
+
+
+JS 调用 Android 方法
+
+
+
+```
+
+然后定义一个 JS 对象映射关系的 Android 类:
+
+```java
+public class JSObject extends Object {
+
+ private Context mContext;
+
+ public JSObject(Context context) {
+ mContext = context;
+ }
+
+ @JavascriptInterface
+ public void hello(String msg){
+ Toast.makeText(mContext, "JS 调用了 Android 的 hello 方法", Toast.LENGTH_SHORT).show();
+ }
+}
+```
+
+最后就是通过 WebView 设置 Android 类与 JS 代码的映射:
+
+```java
+mWebView.loadUrl("file:///android_asset/js_to_android.html");
+mWebView.addJavascriptInterface(new JSObject(this),"test");
+```
+
+第二种方式:WebViewClient.shouldOverrideUrlLoading 方法回调拦截 url
+
+Android 通过 WebViewClient 的回调方法 shouldOverrideUrlLoading 拦截 url,解析该 url 协议,如果检测到是预先约定好的协议,就调用 Android 相应的方法。
+
+```html
+
+
+
+
+ Demo
+
+
+
+JS 调用 Android 方法
+
+
+
+```
+
+```java
+mWebView.loadUrl("file:///android_asset/js_call_android.html");
+ mWebView.setWebViewClient(new WebViewClient(){
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
+ if ("js".equals(request.getUrl().getScheme())){
+ if ("webview".equals(request.getUrl().getAuthority())){
+ Toast.makeText(WebViewContactActivity.this, "JS 调用 Android 方法,参数一为:"+request.getUrl().getQueryParameter("arg1"), Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+ return super.shouldOverrideUrlLoading(view, request);
+ }
+ });
+```
+
+第三种方式:通过 WebChromeClient 的 onJsAlert、onJsConfirm、onJsPrompt 方法回调拦截 JS 对话框的消息
+
+这里只示例 onJsPrompt 的回调,因为这个方法可以返回任意类型的值。
+
+```java
+
+
+
+
+ Demo
+
+
+
+JS 调用 Android 方法
+
+
+
+```
+
+```java
+ mWebView.loadUrl("file:///android_asset/js_call_android_demo.html");
+ mWebView.setWebChromeClient(new WebChromeClient(){
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ Uri uri=Uri.parse(message);
+ if ("js".equals(uri.getScheme())){
+ if ("demo".equals(uri.getAuthority())){
+ result.confirm("JS 调用了 Android 的方法");
+ }
+ return true;
+ }
+ return super.onJsPrompt(view, url, message, defaultValue, result);
+ }
+ });
+```
+
+三种方式的比较:
+
+| 调用方式 | 优点 | 缺点 | 使用场景 |
+| ------------------------------------------------------------ | ---------- | ------------------------ | ---------------------------------- |
+| WebView.addJavascriptInterface 对象映射 | 方便简洁 | Android 4.2 一下存在漏洞 | Android 4.2 以上相对简单的应用场景 |
+| WebViewClient.shouldOverrideUrlLoading 回调拦截 | 不存在漏洞 | 使用复杂,需要协议约束 | 不需要返回值情况下 |
+| WebChormeClient.onJsAlert / onJsConfirm / onJsPrompt 方法回调拦截 | 不存在漏洞 | 使用复杂,需要协议约束 | 能满足大多数场景 |
+
+#### WebView 常见问题
+
+1. WebView 销毁
+
+ ```java
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mWebView != null) {
+ mWebView.loadDataWithBaseURL("", null, "text/html", "utf-8", null);
+ mWebView.clearHistory();
+ ((ViewGroup) mWebView.getParent()).removeView(mWebView);
+ mWebView.destroy();
+ mWebView = null;
+ }
+ }
+ ```
+
+2. Android P 阻止加载任何 http 的请求
+
+ Mainfest 中加入:
+
+ ```
+ android:usesCleartextTraffic="true"
+ ```
+
+3. Android 5.0 之后 WebView 禁止加载 http 与 https 混合内容
+
+ ```java
+ if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
+ mWebView.getSettings().
+ setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
+ }
+ ```
+
+4. WebView 开启硬件加速导致的问题
+
+ 比如不能打开 PDF,播放视频花屏等等。
+
+ 关闭硬件加速,或者直接用第三方库吧。
+
+#### WebView 优化
+
+1. 给 WebView 加一个加载进度条
+
+ 重写 WebChromeClient 的 onProgressChanged 方法。
+
+2. 提高 HTML 网页加载速度,等页面 finsh 在加载图片
+
+ ```java
+ public void int () {
+ if(Build.VERSION.SDK_INT >= 19) {
+ webView.getSettings().setLoadsImagesAutomatically(true);
+ } else {
+ webView.getSettings().setLoadsImagesAutomatically(false);
+ }
+ }
+ ```
+
+3. 自定义 WebView 错误页面
+
+ 重写 WebViewClient 的 onReceivedError 方法。
+
+#### 参考
+
+[https://www.jianshu.com/p/b9164500d3fb](https://www.jianshu.com/p/b9164500d3fb)
\ No newline at end of file