WebView是一个基于WebKit引擎,展现Web页面的控件。
主要提供以下功能:
- 显示和渲染Web页面
- 直接使用html文件做布局
- 可以和js进行交互
WebView基本使用
添加权限
AndroidManifest.xml1
| <uses-permission android:name="android.permission.INTERNET"/>
|
生成一个WebView组件
直接引入
1
| WebView webView = new WebView(this);
|
写在xml中引入
1 2 3 4 5 6 7
| <WebView android:id="@+id/wv" android:width="match_parent" android:height = "match_parent" />
WebView webview = (WebView) findViewById(R.id.wv);
|
设置WebView基本信息
主要利用WebSetting、WebViewClient,WebChromeClient
WebSetting
进行基本属性配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setPluginsEnabled(true);
webSettings.setUseWideViewPort(true); webSettings.setLoadWithOverviewMode(true);
webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webSettings.setDisplayZoomControls(false);
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); webSettings.setAllowFileAccess(true); webSettings.setJavaScriptCanOpenWindowsAutomatically(true); webSettings.setLoadsImagesAutomatically(true); webSettings.setDefaultTextEncodingName("utf-8");
|
WebViewClient
处理各种通知以及请求事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {① view.loadUrl(url); return true; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) {② } @Override public void onPageFinished(WebView view, String url) {③ } @Override public boolean onLoadResource(WebView view, String url) {④ } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){⑤ 22222 } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {⑥
} });
|
shouldOverrideUrlLoading
复写该方法,避免打开外部浏览器
1 2 3 4 5
| @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {① view.loadUrl(url); return true; }
|
onPageStarted
开始载入页面时回调该方法
onPageFinished
页面载入结束时回调该方法
onLoadResource
页面资源开始加载时调用,并且每次加载时都会调用
onReceivedError
加载页面出现错误时回调,可用于显示不同错误码的展示页面
onReceivedSslError
WebView默认是不处理HTTPs请求的,页面会显示空白
1 2 3 4 5 6
| @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); }
|
WebChromeClient
辅助WebView处理js、网站标题等属性
1 2 3 4 5 6 7 8 9 10 11 12
| webview.setWebChromeClient(new WebChromeClient(){
@Override public void onProgressChanged(WebView view, int newProgress) {① }); @Override public void onReceivedTitle(WebView view, String title) {② } }
|
onProgressChanged
获得当前页面的加载进度
onReceivedTitle
获得当前页面的标题
设置WebView要显示的网页
1 2 3 4
| webView.loadUrl("file:///android_asset/hellotest.html");
webView.loadUrl("http://www.google.com");
|
结束时销毁WebView
1 2 3 4 5 6 7 8 9 10 11 12
| @Override protected void onDestroy() { if (webView != null) { webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webView.clearHistory();
((ViewGroup) webView.getParent()).removeView(webView); webView.destroy(); webView = null; } super.onDestroy(); }
|
拓展
前进/后退网页
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) { webView.goBack(); return true; } 22return super.onKeyDown(keyCode, event); } ..................................
Webview.canGoForward()
Webview.goForward()
|
Android与Js的交互
Android与JS间的交互唯一桥梁就是WebView
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>WebView Test</title> <script> function callAndroid(){ myObj.hello("js调用了android中的hello方法"); } function callAndroidWithLoadUrl(){ document.location="js://showMsg?msg=android" } function callJS(){ alert("Android调用了JS的callJS方法"); } function callJsWithParams(params){ alert("Android调用了JS的callJsWithParams方法" + params); } function callJsWithParamsAndResult(params){ alert("Android调用了JS的callJsWithParamsAndResult" + params); return "Success"; } </script> </head> <body> <button type="button" id="button1" onclick="callAndroid()"></button> </body> </html>
|
Android调用JS
通过WebView.loadUrl()
调用
Js的调用一定要在onPageFinished()
回调之后才可调用,否则会导致失败。
1 2 3
| webView.loadUrl("javascript:callJS()");
webView.loadUrl("javascript:callJsWithParams("+params+")")
|
在4.4之前并没有直接提供调用js函数并获取返回值的方法,需要操作的是 Android调用Js,Js执行完毕后,再通过Js继续调用Android方法返回值。
通过WebView.evaluateJavascript()
调用(4.4后新增)
该方法效率高于loadUrl()
,不会导致页面的刷新。
1 2 3 4 5 6
| mWebView.evaluateJavascript("javascript:callJsWithParamsAndResult("+params+")", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { } });
|
在具体使用时,可以将两者进行结合使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| final int version = Build.VERSION.SDK_INT;
if (version < 18) { mWebView.loadUrl("javascript:callJS()"); } else { mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { } }); }
|
JS调用Android
通过WebView.addJavascriptInterface()
进行对象映射
1 2 3 4 5 6 7 8
| public class MyObject extends Object{ @JavascriptInterface public void hello(String msg){ } }
webView.addJavascriptInterface(new MyObject(), "myObj");
|
该方法在4.2以下存在严重的安全漏洞问题,下一节中会有相关的解决方法。
利用WebViewClient.shouldOverrideUrlLoading()
拦截url
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| mWebView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url); if ( uri.getScheme().equals("js")) {
if (uri.getAuthority().equals("webview")) {
System.out.println("js调用了Android的方法"); HashMap<String, String> params = new HashMap<>(); Set<String> collection = uri.getQueryParameterNames();
}
return true; } return super.shouldOverrideUrlLoading(view, url); } } ); }
|
利用WebChromeClient
回调接口的三个方法拦截消息
对相关的接口进行拦截,这里拦截的是 Js中的几个提示方法,也就是几种样式的对话框。
Js中方法 |
作用 |
返回值 |
对应拦截方法 |
alert() |
弹出警告框 |
没有 |
onJsAlert() |
confirm() |
弹出确认框 |
true/false |
onJsConfirm() |
prompt() |
弹出输入框 |
任意设置返回值输入内容 |
onJsPrompt() |
只有在onJsPrompt()
中可以返回任意字段,可以在其中进行拦截判断,以调用对应方法。
WebView执行漏洞
任意代码执行漏洞
WebView中的addJavascriptInterface()
接口
当Js获取到这个对象后,就可以调用到该对象的所有方法,导致漏洞产生。
WebView内置导出的searchBoxJavaBridge_
对象
Android3.0以下 系统默认通过searchBoxJavaBridge_
给WebView添加一个Js映射对象:searchBoxJavaBridge_
对象
WebView内置导出的accessibility
和accessibilityTraversal
对象
密码明文存储漏洞
当WebView开启密码保存功能时导致漏洞webView.setSavePassword(true)
,密码会被明文保存到/data/data/com.package.name/databases/webview.db
中,有泄漏危险。
通过设置webView.setSavePassword(false)
关闭密码保存提醒功能。
域控制不严格漏洞
A应用可以通过B应用导出的Activity让B应用家在一个恶意的file协议的url,从而获取到B应用的内部私有文件,带来数据泄露威胁。
对于不需要使用 file 协议的应用,禁用 file 协议;
1 2 3 4
| setAllowFileAccess(false); setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false);
|
对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript。
1 2 3 4 5 6 7 8 9 10 11
| setAllowFileAccess(true); setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false);
if (url.startsWith("file://") { setJavaScriptEnabled(false); } else { setJavaScriptEnabled(true); }
|
WebView优化
WebView内存泄漏
最好是可以去开启一个单独的进程去使用WebView并且当这个进程结束时,手动调用System.exit(0)
。
后台无法释放js导致耗电
在Js中可能会有一些动画或音频播放会一直执行,即时WebView挂在后台,这些资源也会继续使用,导致耗电加快。
1 2 3 4 5 6 7 8 9 10 11
| @Override public void onResume(){ super.onResume(); webView.setJavascriptEnabled(true); }
@Override public void onStop(){ super.onStop(); webView.setJavascriptEnabled(false); }
|
setBuiltInZoomControls
引起的Crash
当调用setsetBuiltInZoomControls(true)
时去触摸屏幕,然后显示一个缩放控制图标,这图标几秒后会自动消失,这时去主动退出Activity,就会发生ZoomButton
找不到依附Window导致异常使程序崩溃。
1 2 3 4 5 6
| @Override public void onDestroy(){ super.onDestroy(); webView.setVisibility(View.GONE); }
|
底部空白
当WebView嵌套在ScrollView里的时候,如果WebView先加载了一个高度很高的网页,再加载一个高度很低的网页,就会造成WebView的高度无法自适应,导致底部出现大量空白的情况。
通过JS注入的方式,获取页面内容的高度,获取到后赋值到WebView的高度上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| mWebView.setWebViewClient(new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { mWebView.loadUrl("javascript:App.resize(document.body.getBoundingClientRect().height)"); super.onPageFinished(view, url); } }); mWebView.addJavascriptInterface(this, "App");
@JavascriptInterface public void resize(final float height) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { mWebView.setLayoutParams(new LinearLayout.LayoutParams(getResources().getDisplayMetrics().widthPixels, (int) (height * getResources().getDisplayMetrics().density))); } }); }
|
WebView独立进程
WebView容易导致OOM问题,内存占用很大,还容易有内存泄漏的风险
由于Android版本的不同,4.0之前用的WebKit的内核,4.0之后就换了 chromium做内核了,内核的不同导致兼容性Crash
WebView和Native版本也不一致,导致Crash
多进程的好处:
- Android每个应用的内存占用是有限制的,占用内存越大越容易被杀死。实现多进程时,可有效减少主进程内存占用
- 子进程的崩溃不会影响到主进程的使用
- 独立的进程的启动与退出不依赖于用户的使用,可以完全独立控制,主进程的退出不影响其使用
//TODO 代码