--- WebView --- #### 目录 1. 思维导图 2. WebView 的基本使用 - WebView - WebSettings - WebViewClient - WebChromeClient 3. WebView 与 JS 交互 - Android 去调用 JS 代码 - JS 调用 Android 代码 4. WebView 常见问题汇总 5. WebView 优化 6. 参考 #### 思维导图 ![](https://github.com/Omooo/Android-Notes/blob/master/images/WebView.png?raw=true) #### 基本使用 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)