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