前言
所谓这个混合开发,也就是比较流行hybird,就是一些简单的html5和native 代码之间的交互。很多电商之类的app里面都有类似的功能,其优点就是可以实现跨平台,有新功能也或bug不需要再重新发版本。
概括
本章介绍基础属性WebSettings的使用,它可以设置webview所支持的功能,如页码缩放、支持JS交互、支持多窗口等。另外,webview自身也公开了一些方法提供调用,如加载某个Url网页、页码回退和前进、拦截url等。重点讲解Android与JS交互技术,这也是面试最必问的技术点。最后简单介绍了webview的优化及缓存处理。
一、WebSettings 设置属性
在开发中,我们一般都会先进行设置属性,通过 WebView.getSettings(); 可以获取webSettings的对象实例。
(1)支持webview进行页面缩放;
webSettings.setBuiltInZoomControls(true);
*这里有个坑,为了防止webview缩放时退出崩溃,最好写上如下代码:
@Override
public void finish() {
ViewGroup view = (ViewGroup) getWindow().getDecorView();
view.removeAllViews();
super.finish();
}
(2) 支持JS;
webSettings.setJavaScriptEnabled(true);
(3)支持页面多窗口;
webSettings.setSupportMultipleWindows(true);
(4)支持JS打开对话窗口;
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
(5)支持启动技术;
webSettings.setUseWideViewPort(true);
(6)webSettings.setLoadWithOverviewMode(true); //支持加载时相关页面适配
(7)String cacheDirPath = //设置缓存路径
mWebView.getSettings().setDatabasePath(cacheDirPath); //设置数据库缓存路径
mWebView.getSettings().setAppCachePath(cacheDirPath); //设置 应用 缓存目录
mWebView.getSettings().setDomStorageEnabled(true); //开启 DOM 存储功能
mWebView.getSettings().setDatabaseEnabled(true); //开启 数据库 存储功能
mWebView.getSettings().setAppCacheEnabled(true); //开启 应用缓存 功能
二、网页返回&前进
(1)是否可以后退,返回值Boolean
Webview.canGoBack()
(2)后退
Webview.goBack()
(3)是否可以前进,返回值Boolean
Webview.canGoForward()
(4)前进
Webview.goForward()
(5)以当前的页码为起始点,前进或者后退到历史记录中指定的steps。如果steps为负数则为后退,正数则为前进。
Webview.goBackOrForward(int steps)
案例,通常会拦截返回按键按钮:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && mWebView.canGoBack()) { //手机返回键
mWebView.goBack();//后退网页
return true;
}
return super.onKeyDown(keyCode, event);
}
三、Android与JS交互
JS是什么???即JavaScript,它不是Java程序的设计语言,而是一种脚本语言。JS是可以一行一行运行的脚本程序代码,可以直接内嵌于HTML网页,属于HTML网页的一部分。本章介绍的交互有两种方式。分别是Android调用JS方法和JS调用Android的方法。
(1)Android调用JS的方法:有2种
1、WebView的loadUrl()//会重新刷新页面
// 调用js中的函数:jsFun(msg)
webView.loadUrl("javascript:jsFun('" + msg + "')");
2、evaluateJavascript()// 该方法的执行不会使页面刷新
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
}
小结论:
建议两种方法混合使用,即Android 4.4以下使用loadUrl,Android 4.4以上evaluateJavascript
(2)JS调用Android的方法:有三种
1、通过WebView的addJavascriptInterface()
// 参数1:Android的本地对象
// 参数2:JS的对象
mWebView.addJavascriptInterface(new MyJSInterface(),"androidJsInterface");
private static class MyJSInterface{
public void showMessage(String msg){
Toast.makeText(WebViewActivity.this, "成功"+ msg, Toast.LENGTH_LONG).show();
}
}
原理: 通过对象映射将Android中的本地对象和JS中的对象进行关联,从而实现JS调用Android的对象和方法。
漏洞:因为addJavascriptInterface绑定了一个Java对象实例,根据Java的反射机制,就可以获得更多方法,而且间接获得更多的实例对象。
解决:
- Android 4.2版本之后,Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击:
private static class MyJSInterface{
@JavascriptInterface
public void showMessage(String msg){
Toast.makeText(WebViewActivity.this, "成功"+ msg, Toast.LENGTH_LONG).show();
}
}
- Android 4.2版本以前,需要采用拦截prompt()的方式进行漏洞修复,下面?就是讲解内容。
2、通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调,拦截JS对话框alert()、confirm()、prompt() 消息。JS的消息方法如下:
原理:Android通过 WebChromeClient
的onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回调分别拦截JS对话框 ,得到他们的消息内容,然后分析意图执行相应操作。(需要和前端开发人员做好约定好消息内容的意图)
选择:常用拦截prompt()
方法,因为onJsPrompt调用较少,而onJsAlert、onJsConfirm调用频繁。只有prompt()
可以返回任意类型的值,而alert()没有返回值、confirm()只能返回两种状态(确定 / 取消)两个值。
3、通过WebViewClient.shouldOverrideUrlLoading 拦截url,分析意图执行相应操作
网易云音乐的app 里面的积分商城就是使用这种方式实现H5与native交互。微信也是和网易一样通过拦截url 分析url 意图来执行相应的操作的,native 回调js代码也是走的js里的_handleMessageFromWeixin 这种方法。但你其实想一想, 微信这个方法也是有缺陷的,因为url是可以伪造的,好在微信自己会在native代码里 验证他的appid。所以一定程度上可以避免大部分的攻击。
//步骤1. 定义Webview组件
Webview webview = (WebView) findViewById(R.id.webView1);
//步骤2. 加载一个网页:
webView.loadUrl("http://www.google.com/");
//2:apk包中的html页面 webView.loadUrl("file:///android_asset/test.html");
//3:本地页面webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
//步骤3. 复写shouldOverrideUrlLoading()方法
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//使得打开网页时不调用系统浏览器, 而是在本WebView中显示
view.loadUrl(url);
return true;
}
});
2、当网页加载后有一个url,可以进行判断。需要与前端人员沟通如何定义Url,此url可以作为内部交互的意图。
a.比如,点击网页按钮关闭Activity。这里url字符串查找是否包含“finish_app”关键字。
if(url.contains("finish_app")) {//退出Activity
finish();
}
b.比如,这个是从WebView页面跳转另外一个Activity页面。
if (url.contains("MyLuckLog")){//会员页面
startActivity(new Intent(WebViewActivity.this,NewActivity.class));
}
四、WebView 优化
1、内存优化
@Override
protected void onDestroy() {
super.onDestroy();
webView.removeAllViews();
webView.destroy();
}
//清除网页访问留下的缓存
//由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
Webview.clearCache(true);
//清除当前webview访问的历史记录
//只会webview访问历史记录里的所有记录除了当前访问记录
Webview.clearHistory();
//这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据
Webview.clearFormData();
2、播放视频时,返回页面,声音依然存在问题。
@Override
protected void onResume() {
try {
webView.getClass().getMethod("onResume").invoke(webView,(Object[])null);//继续播放视频
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
super.onResume();
}
@Override
protected void onPause() {
try {
webView.getClass().getMethod("onPause").invoke(webView,(Object[])null);//停止视频播放
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
super.onPause();
}
3、后台耗电问题 :activity不可见时要停用webview
4、webview硬件加速导致页面渲染问题-白屏展示(关闭硬件加速)
五、WebView 缓存
1、缓存的是url、css、图片、Js等,有两种方式,一种是网页数据缓存,一种是H5AppCache缓存 ,但都会缓存到应有所在的数据库表中。
2、五种模式:
- Load_Cache_only 只读取缓存;
- Load_Default 根据Cache_Control决定是否网络获取;
- Load_Cache_Noraml API 17后 与 2相同;
- Load_No_Cache 不使用缓存只从网络获取;
- Load_Cache_Else_Netwrok 只要本地存在缓存 无论是否过期或no_cache,都用缓存。
3、正确设置缓存模式:
if (NetWorkUtils.isNetworkConnected(this)){
webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
}else{
webView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
4、清除WebView缓存
/**
* 清除WebView缓存
*/
public void clearWebViewCache() {
/**清理Webview缓存数据库,缓存文件由程序自动生成
* /data/data/package_name/database/webview.db
* /data/data/package_name/database/webviewCache.db
**/
try {
//因为他们都是文件,所以可以用io方式删除,具体方法可以自己写
deleteDatabase("webview.db");
deleteDatabase("webviewCache.db");
} catch (Exception e) {
e.printStackTrace();
}
//WebView 缓存文件
File webviewCacheDir = new File(APP_CACAHE_DIRNAME);
//删除webview 缓存目录
if (webviewCacheDir.exists()) {
//具体的方法自己写
deleteFile(webviewCacheDir);
}
}