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