WebView
目录
- 思维导图
- WebView 的基本使用
- WebView
- WebSettings
- WebViewClient
- WebChromeClient
- WebView 与 JS 交互
- Android 去调用 JS 代码
- JS 调用 Android 代码
- WebView 常见问题汇总
- WebView 优化
- 参考
思维导图
基本使用
WebView 是一个基于 webkit 引擎,展示 web 页面的空间。WebView 在低版本和高版本采用了不同的 webkit 内核版本,4.4 ( API 19 ) 之后直接使用了 Chrome。
WebView 类
@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 进行配置和管理。
private void setWebViewSettings(WebView webView){
WebSettings webSettings=webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
webSettings.setLoadsImagesAutomatically(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
}
WebViewClient 类
处理各种通知和请求事件等等。
public class MyWebViewClient extends WebViewClient {
private Context mContext;
public MyWebViewClient(Context context) {
mContext = context;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
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();
}
}
mWebView.setWebViewClient(new MyWebViewClient(MainActivity.this));
WebChromeClient 类
辅助 WebView 处理 JS 的对话框、网站标题等等。
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 交互
WebView 与 JS 交互分为俩种情况,一种是JS 调用 Android 代码,另一种是JS 调用 Android 方法。
Android 调用 JS 代码
- webView.loadUrl(url)
- webView.evaluateJavascript()
首先选准备一个静态文件:
<!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()
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()
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
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 加载的网页:
<!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 类:
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 代码的映射:
mWebView.loadUrl("file:///android_asset/js_to_android.html");
mWebView.addJavascriptInterface(new JSObject(this),"test");
第二种方式:WebViewClient.shouldOverrideUrlLoading 方法回调拦截 url
Android 通过 WebViewClient 的回调方法 shouldOverrideUrlLoading 拦截 url,解析该 url 协议,如果检测到是预先约定好的协议,就调用 Android 相应的方法。
<!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>
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 的回调,因为这个方法可以返回任意类型的值。
<!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>
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 常见问题
-
WebView 销毁 @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;
}
}
-
Android P 阻止加载任何 http 的请求 Mainfest 中加入: android:usesCleartextTraffic="true"
-
Android 5.0 之后 WebView 禁止加载 http 与 https 混合内容 if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
mWebView.getSettings().
setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
-
WebView 开启硬件加速导致的问题 比如不能打开 PDF,播放视频花屏等等。 关闭硬件加速,或者直接用第三方库吧。
WebView 优化
-
给 WebView 加一个加载进度条 重写 WebChromeClient 的 onProgressChanged 方法。 -
提高 HTML 网页加载速度,等页面 finsh 在加载图片 public void int () {
if(Build.VERSION.SDK_INT >= 19) {
webView.getSettings().setLoadsImagesAutomatically(true);
} else {
webView.getSettings().setLoadsImagesAutomatically(false);
}
}
-
自定义 WebView 错误页面 重写 WebViewClient 的 onReceivedError 方法。
参考
大牛总结的非常到位,非常值得学习和借鉴。https://www.jianshu.com/p/b9164500d3fb
|