parent
fdfd932cb4
commit
4b77e97993
@ -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…
Reference in new issue