一、WebView 简单使用
WebView 是 Android 系统中的原生控件,其主要功能与前端页面进行响应交互,快捷省时地实现如期的功能,相当于增强版的内置浏览器。使用时需要在配置文件里设置网络权限,定义布局大小和样式,绑定和操作控件。
manifest.xml
<uses-permission android:name="android.permission.INTERNET" />
activity_main.xml
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
MainActivity.java
String url = "https://www.baidu.com";
WebView webView = (WebView) findViewById(R.id.web_view);
webView.loadUrl(url);
网页跳转,包括前进、后退、自定义。
boolean back = webview.canGoBack(); -> 判断网页是否可以回退
webview.goBack(); -> 回退一页
boolean forward = webview.canGoForward(); -> 判断网页是否可以前进
webview.goForward(); -> 前进一页
webview.goBackOrForward(1); -> 正数为前进
webview.goBackOrForward(-1); -> 负数为后退
复制代码
二、WebView 生命周期
onResume():活跃状态,可以正常执行网页的响应。
onPause():暂停状态,页面被失去焦点,暂停所有进行中的动作,如:DOM解析、CSS和JavaScript执行等。
pauseTimers():全局WebView暂停状态,如:layout、parsing、javascripttimer等。
resumeTimers():恢复到pauseTimers()执行前的状态。
destroy():销毁状态,释放资源。
注意:使用WebView不当容易引起内存泄漏,所以WebView的生命周期方法应跟随Activity的生命周期的方法来调用。
复制代码
三、WebSettings
控件的相关配置
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); -> 是否开启JS支持
webSettings.setPluginsEnabled(true); -> 是否开启插件支持
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); -> 是否允许JS打开新窗口
webSettings.setUseWideViewPort(true); -> 缩放至屏幕大小
webSettings.setLoadWithOverviewMode(true); -> 缩放至屏幕大小
webSettings.setSupportZoom(true); -> 是否支持缩放
webSettings.setBuiltInZoomControls(true); -> 是否支持缩放变焦,前提是支持缩放
webSettings.setDisplayZoomControls(false); -> 是否隐藏缩放控件
webSettings.setAllowFileAccess(true); -> 是否允许访问文件
webSettings.setDomStorageEnabled(true); -> 是否节点缓存
webSettings.setDatabaseEnabled(true); -> 是否数据缓存
webSettings.setAppCacheEnabled(true); -> 是否应用缓存
webSettings.setAppCachePath(uri); -> 设置缓存路径
webSettings.setMediaPlaybackRequiresUserGesture(false); -> 是否要手势触发媒体
webSettings.setStandardFontFamily("sans-serif"); -> 设置字体库格式
webSettings.setFixedFontFamily("monospace"); -> 设置字体库格式
webSettings.setSansSerifFontFamily("sans-serif"); -> 设置字体库格式
webSettings.setSerifFontFamily("sans-serif"); -> 设置字体库格式
webSettings.setCursiveFontFamily("cursive"); -> 设置字体库格式
webSettings.setFantasyFontFamily("fantasy"); -> 设置字体库格式
webSettings.setTextZoom(100); -> 设置文本缩放的百分比
webSettings.setMinimumFontSize(8); -> 设置文本字体的最小值(1~72)
webSettings.setDefaultFontSize(16); -> 设置文本字体默认的大小
webSettings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); -> 按规则重新布局
webSettings.setLoadsImagesAutomatically(false); -> 是否自动加载图片
webSettings.setDefaultTextEncodingName("UTF-8"); -> 设置编码格式
webSettings.setNeedInitialFocus(true); -> 是否需要获取焦点
webSettings.setGeolocationEnabled(false); -> 设置开启定位功能
webSettings.setBlockNetworkLoads(false); -> 是否从网络获取资源
复制代码
四、WebViewClient
控件客户端,用于处理各种通知和请求事件。
onPageStarted():页面开始加载时调用,这时候可以显示加载进度条,让用户耐心等待页面的加载。
onPageFinished():页面完成加载时调用,这时候可以隐藏加载进度条,提醒用户页面已经完成加载。
onLoadResource():页面每次加载资源时调用。
shouldOverrideUrlLoading():WebView加载url默认会调用系统的浏览器,通过重写该方法,实现在当前应用内完成页面加载。
onReceivedError():页面加载发生错误时调用,这时候可以跳转到自定义的错误提醒页面,总比系统默认的错误页面美观,优化用户体验。
onReceivedHttpError():页面加载请求时发生错误。
onReceivedSslError():页面加载资源时发生错误。
shouldOverrideKeyEvent():覆盖按键默认的响应事件,这时候可以根据自身的需求在点击某些按键时加入相应的逻辑。
onScaleChanged():页面的缩放比例发生变化时调用,这时候可以根据当前的缩放比例来重新调整WebView中显示的内容,如修改字体大小、图片大小等。
shouldInterceptRequest():可以根据请求携带的内容来判断是否需要拦截请求。
WebViewClient webViewClient = new WebViewClient(){
@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 boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true; -> 消费事件终止传递
}
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl){
view.loadUrl("file:///android_assets/error.html"); -> assets目录下放置文件
}
webView.setWebViewClient(webViewClient);
复制代码
五、WebChromeClient
浏览器客户端,用于处理网站图标、网站标题、网站弹窗等。
onProgressChanged():页面加载进度发生变化时调用,可以通过该方法实时向用户反馈加载情况,如显示进度条等。
onReceivedIcon():接收Web页面的图标,可以通过该方法把图标设置在原生的控件上,如Toolbar等。
onReceivedTitle():接收Web页面的标题,可以通过该方法把图标设置在原生的控件上,如Toolbar等。
onJsAlert():处理JS的Alert对话框。
onJsPrompt():处理JS的Prompt对话框。
onJsConfirm():处理JS的Confirm对话框。
onPermissionRequest():Web页面请求Android权限时调用。
onPermissionRequestCanceled():Web页面请求Android权限被取消时调用。
onShowFileChooser():Web页面上传文件时调用。
getVideoLoadingProgressView():自定义媒体文件播放加载时的进度条。
getDefaultVideoPoster():设置媒体文件默认的预览图。
onShowCustomView():媒体文件进入全屏时调用。
onHideCustomView():媒体文件退出全屏时调用。
WebChromeClient webChromeClient = new WebChromeClient();
复制代码
六、Js 调用 Android
注:该方法针对Android 4.2(API 17)及以上,只有标有@JavascriptInterface注解的public方法才能从js调用。而对targetSdkVersion为API Level 16及以下的app,js可以调用Java所有的public方法。
前置设定:webview.getSettings().setJavaScriptEnabled(true)
复制代码
addJavascriptInterface()安全风险及解决办法
Android 代码
public class Bridge{
@JavascriptInterface
public void jsInvoke(String jsonParams) {
//获取js传递过来的数据,并解析出方法名
try {
JSONObject jsonObject = new JSONObject(json);
final String callback = jsonObject.optString("callbackMethod");
//根据获得的方法名,安卓进行回调,回调前封装数据过去
//封装数据
final JSONObject data = new JSONObject();
data.put("name", "zhangsan");
data.put("age", "38");
// Toast.makeText(mContext,json,Toast.LENGTH_LONG).show();
mHandler.post(new Runnable() {
@Override
public void run() {
//回调,需要保证在主线程中
mWebView.loadUrl("javascript:" + callbackMethod + "(" + data.toString() + ")");
}
});
} catch (JSONException e) {
e.printStackTrace();
}
}
//JavascriptInterface 可以标注多个方法
@JavascriptInterface
public void xxxx(String jsonParams) {
}
}
Bridge bridge = new Bridge();
mWebView.addJavascriptInterface(bridge,"androidJsInterface");
复制代码
Js 代码
//将需要 安卓回调的方法名 以callbackMethod方式传递 action 可自定义操作类型 callbackMethod App可调用的JS方法,用于Js的回调
// 注:androidJsInterface 由安卓传递的对象别名,也就是Js能拿到java端的桥对象
例子1:function xxx(){
var json = {"action":"startScan","callbackMethod":"getScanResult","port":"xxx"};
window.androidJsInterface.jsInvoke(JSON.stringify(json));
}
例子2: function xxx(){
var json = {"action":"otherAction","callbackMethod":"otherCallback","port":"xxx"};
window.androidJsInterface.jsInvoke(JSON.stringify(json));
}
例子3:function xxx(){
var json = {"action":"noCallback","callbackMethod":"","port":"xxx"};
window.androidJsInterface.jsInvoke(JSON.stringify(json));
}
复制代码
注:这个方法的本来是拦截所有WebView的Url跳转的。我们可以通过这个API 构造一个特殊自定义格式的Url跳转,shouldOverrideUrlLoading拦截Url后判断其格式,然后Native 解析到对应的方法和数据再去执行本地方法。
复制代码
Android 代码
webview.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.i("qcl0228", "拦截到的url:" + url);
//url如果以qiushi开头,就是h5和我们定义的传值协议
if (url.startsWith("qiushi")) {
Uri uriRequest = Uri.parse(url);
String scheme = uriRequest.getScheme();
String action = uriRequest.getHost();
String query = uriRequest.getQuery();
if ("qiushi".equals(scheme)) {
if (!TextUtils.isEmpty(query)) {
//把url携带的参数存到一个map里
HashMap maps = new HashMap();
Set<String> names = uriRequest.getQueryParameterNames();
for (String name : names) {
maps.put(name, uriRequest.getQueryParameter(name));
}
JSONObject jsonObject = new JSONObject(maps);
if ("setH5Info".equals(action)) {
if (jsonObject != null && jsonObject.has("params")) {
String h5InfoParams = jsonObject.optString("params");
Log.i("qcl0228", "拦截到的参数:" + h5InfoParams);
}
}
}
}
} else {
view.loadUrl(url);
}
return true;
}
});
复制代码
拦截到的 url:qiushi://setH5Info?param=%7B%22title%22:%22%E5%95%86%E5%93%81%E6%A0%87%E9%A2%98%22%7D 拦截到的参数:{"title":"商品标题"}
Js 代码
//简单重定向
<div id="div" onclick="chongdingxiang()"style="width:100px; height:100px; background-color:#099;"></div>
<script type="text/javascript">
function chongdingxiang(){
//模拟重定向
window.location.href="qiushi://setH5Info?params=%7B%22title%22%3A%22%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%22%7D";
}
</script>
复制代码
七、Android 调用 Js
注: 这是无JS返回值的调用
且js方法不能有返回值,如下js代码 若有返回值,web则会发生请求重定向问题
function jsMethod(jsonParams){
return "123456";
};
复制代码
若调用的JS有返回值,webView会发生请求重定向
android 代码
String jsonParams = "123456";
//String url = "javascript:jsMethod()";//不拼接参数,直接调用js的jsMethod函数
String url = "javascript:jsMethod(" + jsonParams + ")";//拼接参数,就可以把数据传递给js
webview.loadUrl(url);
复制代码
js 代码
例子1 function jsMethod(){
console.log("callbackMethod执行了");
};
例子2 function jsMethod(jsonParams){
console.log("callbackMethod执行了");
alert(JSON.stringify(jsonParams));
};
例子3 fun HyJsBridge.onComplete(port,json){
};
复制代码
注:可以获取js方法的返回值,仅支持android4.4及以上
复制代码
android 代码
callback =new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.i("qcl0228", "js返回的数据" + value);
}
});
mWebView.evaluateJavascript("javascript:jsMethod("+ jsonParams +")",callback);
或者不需要回调
mWebView.evaluateJavascript("javascript:jsMethod("+ jsonParams +")",null);
复制代码
js 代码
// js中的方法 --> 将会被Android端调用
例子1 function jsMethod(jsonParams){
return "123456";
};
复制代码
js 返回的数据:123456
八、Html Demo
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Untitled Document</title>
</head>
<body>
<div id="div" onclick="getAndroidValue()"style="width:100px; height:100px; background-color:#099;"></div>
<script type="text/javascript">
function chongdingxiang(){
//模拟重定向
window.location.href="qiushi://setH5Info?params=%7B%22title%22%3A%22%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%22%7D";
}
function jsMethod(jsonParams){
document.getElementById("div").style.backgroundColor='red';
document.getElementById("div").innerHTML=jsonParams;
return '123456';
}
function getAndroidValue(){
document.getElementById("div").innerHTML=window.androidObject.androidMethod();
}
</script>
</body>
</html>
复制代码
九、Webview 漏洞及安全设置
JS调用Android代码是通过addJavascriptInterface接口进行对象映射。
复制代码
漏洞产生的原因
// java代码
public class AndroidJS extends Object {
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS调用了Android的hello方法");
}
}
//参数1:Android的本地对象 参数2:对象别名
webView.addJavascriptInterface(new AndroidJS(), "test");
复制代码
// JS代码
function callAndroid(){
test.hello("js调用了android中的hello方法");
}
复制代码
JS 通过 test 对象别名 映射到 Android 的地对象 AndroidJs(),从而实现 JS 调用 Android 的对象和方法。
漏洞产生原因是:当 JS 拿到 Android 这个对象后,就可以调用这个 Android 对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。如可以执行命令获取本地设备的 SD 卡中的文件等信息从而造成信息泄露
具体获取系统类的描述(结合 Java 反射机制):Android 中的对象有一公共的方法——getClass(),该方法可以获取到当前类类型 Class,该类有一关键的方法——Class.forName,该方法可以加载一个类(可加载 java.lang.Runtime 类),而该类是可以执行本地命令的。
以下是攻击的 JS 核心代码:
function execute(cmdArgs) {
// 步骤1:遍历window对象,目的是为了找到包含getClass()的对象,因为Android映射的JS对象在window中,所以肯定会遍历到
for (var obj in window) {
if ("getClass" in window[obj]) {
// 步骤2:利用反射调用forName方法得到Runtime类对象
alert(obj);
return window[obj].getClass().forName("java.lang.Runtime")
// 步骤3:以后就可以调用静态方法来执行一些命令,比如访问文件的命令
getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
// 从执行命令后返回的输入流中得到字符串,有很严重暴露隐私的危险
// 如执行完访问文件的命令之后,就可以得到文件名的信息了
}
}
}
复制代码
当一些 APP 通过扫描二维码打开一个外部网页时,攻击者就可以执行这段 JS 代码进行漏洞攻击。
解决方案:
方案 1:在 Android 4.2 版本之后,Google 在 Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface 进行注解从而避免漏洞攻击。
方案 2:Android 4.2 版本之前,采用拦截 prompt()进行漏洞修复。 具体如下:
每次当 WebView 加载页面前加载一段本地的 JS 代码,原理是:让 JS 调用一 Javascript 方法:该方法是通过调用 prompt 把 JS 中的信息(含特定标识,方法名称等)传递到 Android 端;在 Android 的 onJsPrompt 中 ,解析传递过来的信息,再通过反射机制调用 Java 对象的方法,这样实现安全的 JS 调用 Android 代码;(关于 Android 返回给 JS 的值,可通过 prompt 把 Java 中方法的处理结果返回到 JS 中)
javascript:(function JsAddJavascriptInterface_() {
// window.jsInterface表示在window上声明了一个Js对象,jsInterface = 注册的对象名
// 它注册了两个方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2),如果有返回值,就添加上return
if (typeof(window.jsInterface)!='undefined') {
console.log('window.jsInterface_js_interface_name is exist!!');
} else {
window.jsInterface = {
onButtonClick:function(arg0) {
// prompt()返回约定的字符串,该字符串可自己定义
// 包含特定的标识符MyApp和JSON字符串(方法名,参数,对象名等)
return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));
},
onImageClick:function(arg0,arg1,arg2) {
return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]}));
},
};
}
})()
复制代码
关闭密码保存提醒:webSettings.setSavePassword(false)
复制代码
如下代码:
public class WebViewActivity extends Activity {
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
webView = (WebView) findViewById(R.id.webView);
//webView.getSettings().setAllowFileAccess(false); (1)
//webView.getSettings().setAllowFileAccessFromFileURLs(true); (2)
//webView.getSettings().setAllowUniversalAccessFromFileURLs(true); (3)
Intent i = getIntent();
String url = i.getData().toString(); //url = file:///data/local/tmp/attack.html
webView.loadUrl(url);
}
}
复制代码
在清单文件中将 WebViewActivity 设置 android:exported = "true"属性,表示当前 Activity 是否可以被另一个 Application 的组件启动。即 A 应用可以通过 B 应用导出的 Activity 让 B 应用加载一个恶意的 file 协议的 url,从而可以获取 B 应用的内部私有文件,从而带来数据泄露威胁。
当其他应用启动可以允许外部调用的 Activity 时, intent 中的 data 直接被当作 url 来加载(假定传进来的 url 为 file:///data/local/tmp/attack.html),其它 APP 通过使用显式 ComponentName 或者其他类似方式就可以很轻松的启动该 WebViewActivity 并加载恶意 url。
对于不需要使用 file 协议的应用,禁用 file 协议:
setAllowFileAccess(false);
复制代码
对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript:
setAllowFileAccess(true);
// 禁止file协议加载 JavaScript
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
复制代码
文章引述及参考
webview参数详解 潜力之心—简书android与js通信 编程的小石头—知乎android与js通信 竖子敢尔—CSDNweb安全漏洞 橙子19911016—CSDN
评论