master
songshicong 6 years ago
parent fdfd932cb4
commit 4b77e97993
  1. 449
      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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
<script>
function callJS(){
alert("Android调用了 JS 的 callJS() 方法");
}
</script>
<p3>
WebView 与 JS 交互!
</p3>
</head>
</html>
```
第一种方式: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<String>() {
@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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Demo</title>
<script>
function callAndroid(){
test.hello("js调用了android中的hello方法");
}
</script>
</head>
<body>
<p2>JS 调用 Android 方法</p2>
<button type="button" id="button1" onclick="callAndroid()">点击按钮调用 Android 的 hello 方法</button>
</body>
</html>
```
然后定义一个 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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Demo</title>
<script>
function callAndroid(){
document.location = "js://webview?arg1=2333&arg2=222";
}
</script>
</head>
<body>
<p2>JS 调用 Android 方法</p2>
<button type="button" id="button1" onclick="callAndroid()">点击按钮调用 Android 的方法</button>
</body>
</html>
```
```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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Demo</title>
<script>
function callAndroid(){
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}
</script>
</head>
<body>
<p2>JS 调用 Android 方法</p2>
<button type="button" id="button1" onclick="callAndroid()">点击按钮调用 Android 的方法</button>
</body>
</html>
```
```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)
Loading…
Cancel
Save