Hybrid App(混合模式移动应用)是指介于web-app、native-app这两者之间的app,兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”,鉴于现在App应用需要对市场快速响应的需求,在Native中嵌入Webview的页面越来越被广泛的使用。本系列文章将从webview的基础,webview的原生接入,webview jsbridge(本系列打算介绍四种),webview安全,webview的优化五个维度来和大家一起对webview进行深入的剖析。
Android的SDK集成了WebView组件,也是View中的一种,它继承自AbsoluteLayout,展示网页的同时,也可以在其中放入其他的子View。从Android 4.4(KitKat)开始,原本基于WebKit的WebView开始基于Chromium内核,这一改动大大提升了WebView组件的性能以及对HTML5,CSS3,JavaScript的支持,同时修复了安全漏洞(Webview安全会详细讲解)。下面对Webview的基础知识进项详细介绍:
一、WebView加载:
(1)加载一个网页:
webView.loadUrl(“http://www.baidu.com/“);
(2)加载apk包中的一个html页面
webView.loadUrl(“file:///android_asset/my.html”);
此处有一个类似的场景:
当我们在WebView中加载出从web服务器上拿取的内容时,是无法访问本地资源的,如assets目录下的图片资源,因为这样的行为属于跨域行为(Cross-Domain),而WebView是禁止的。解决这个问题的方案是把html内容先下载到本地,然后用loadDataWithBaseURL加载html。这样就可以在html中使用 file:///android_asset/xxx.png 的链接来引用包里
面assets下的资源了。
private void loadWithAccessLocal(final String htmlUrl) {
//从网络上下载html的过程应放在工作线程中
new Thread(new Runnable() {
public void run() {
try {
final String htmlStr = NetService.getHtml(htmlUrl);
if (htmlStr != null) {
//html下载成功后渲染出html的步骤应放在UI主线程,不然WebView会报错
TaskExecutor.runTaskOnUiThread(new Runnable() {
@Override
public void run() {
loadDataWithBaseURL(htmlUrl, htmlStr, "text/html", "UTF-8", "");
}
});
return;
}
} catch (Exception e) {
Log.e("Exception:" + e.getMessage());
}
//html下载失败可以自定义错误界面
TaskExecutor.runTaskOnUiThread(new Runnable() {
@Override
public void run() {
onPageLoadedError(-1, "html get failed");
}
});
}
}).start();
}
(3)加载手机本地的一个html页面的方法:
webView.loadUrl(“content://com.android.html/sdcard/my.html”);
二、WebView基本组件:WebSettings、WebViewClient、WebChromeClient,它们的作用如下:
WebView: 主要负责解析和渲染网页
WebViewClient: 辅助WebView处理各种通知和请求事件
WebChromeClient: 用来辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等。
(1)WebSettings可以对WebView做如下设置:
(2)我们通过继承WebViewClient并重载它的方法可以实现不同功能的定制,如下所示(不贴图了,一页放不下):
private WebViewClient mWebViewClient = new WebViewClient(){
//在网页上的所有加载都经过这个方法,可以在此函数中做一些定制化的修改,比如获取url,查看url.equals(“xxx”)
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//打开网页时不调用系统浏览器, 在本WebView中显示最新的url
view.loadUrl(url);
return true;
}
//重写此方法处理在浏览器中的按键事件。
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
//个性化处理
return true;
}
//Key事件未被加载时调用
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
//个性化处理
}
//开始载入页面调用,可以设定一个loading的页面,告诉用户程序在等待网络响应。
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//个性化处理
}
//在页面加载结束时调用,我们可以配合上面效果关闭loading条,切换程序动作。
@Override
public void onPageFinished(WebView view, String url) {
//个性化处理
}
// 在加载页面资源时会调用,每一个资源(图片)的加载都会调用一次。
@Override
public void onLoadResource(WebView view, String url) {
//个性化处理
}
//错误信息报告
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
//个性化处理
}
//webview发生改变时调用
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
//个性化处理
}
//更新历史记录
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
//个性化处理
}
//应用程序重新请求
@Override
public void onFormResubmission(WebView view, Message dontResend, Message resend) {
//个性化处理
}
//重写此方法让Webview可以处理https请求
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
//个性化处理
}
//获取返回信息授权请求
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
//个性化处理
}
};
//把自定义的mWebViewClient设置给mWebView
mWebView.setWebViewClient(mWebViewClient );
(3)通过继承WebChromeClient并重载它的方法也可以实现不同功能的定制,如下图所示:
WebChromeClient mWebChromeClient = new WebChromeClient() {
//获得网页的加载进度,显示在右上角的TextView控件中,例如右上角可以显示一个Loading
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress < 100) {
//自定义
} else {
//自定义
}
}
//获取Web页中的title用来设置自己界面中的title,但当加载出错的时候,比如无网络,这时onReceiveTitle中获取的标题为 找不到该网页,
//因此建议当触发onReceiveError时,不要使用获取到的title
@Override
public void onReceivedTitle(WebView view, String title) {
//自定义
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
//自定义
}
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
//自定义
return true;
}
@Override
public void onCloseWindow(WebView window) {
}
//alert弹出框
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
//自定义
return true;
}
//处理confirm弹出框
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult
result) {
//自定义
return true;
}
//处理prompt弹出框
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
//自定义
return true;
}
};
//把自定义的mWebChromeClient设置给mWebView
mWebView.setWebChromeClient(mWebChromeClient);
三、WebView页面导航
(1)页面跳转:在WebView点击链接时, 默认的WebView会直接跳转到别的浏览器中, 如果想要实现在WebView内跳转就需要设置WebViewClient。如下所示:
mWebView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
//返回true: Android 系统会处理URL, 一般是唤起系统浏览器。
//返回false: 当前 WebView 处理URL。
return true;
}
});
(2)页面回退和前进:点击系统返回键同时判断webview是否可以回退,进行不同的逻辑处理
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}else{
...
}
return super.onKeyDown(keyCode, event);
}
其他相关方法:
goBack () //后退
goForward ()//前进
//以当前的index为起始点前进或者后退到历史记录中指定的steps,如果steps为负数则为后退,正数则为前进
goBackOrForward (int steps)
canGoForward () //是否可以前进
canGoBack () //是否可以后退
(3)页面滑动:关于页面滑动, 我们在做下拉刷新等功能时, 经常会去判断WebView是否滚动到顶部或者滚动到底部。请关注一下如下方法:
getScrollY() //方法返回的是当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离.
getHeight()或者getBottom() //方法都返回当前WebView 这个容器的高度
getContentHeight() 返回的是整个html 的高度,但并不等同于当前整个页面的高度,因为WebView 有缩放功能, 所以当前整个页面的高度实际上应该是原始html 的高度再乘上缩放比例. 因此,更正后的结果,准确的判断方法应该是:
if (webView.getContentHeight() * webView.getScale() == (webView.getHeight() + webView.getScrollY())) {
//已经处于底端
}
if(webView.getScrollY() == 0){
//处于顶端
}
四、WebView的状态
onResume () //激活WebView为活跃状态,能正常执行网页的响应
onPause () //当页面被失去焦点被切换到后台不可见状态,需要执行onPause动过, onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
pauseTimers () //当应用程序被切换到后台我们使用了webview, 这个方法不仅仅针对当前的webview而是全局的全应用程序的webview,它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
resumeTimers () //恢复pauseTimers时的动作。
destroy () //销毁,关闭了Activity时,音乐或视频,还在播放。就必须销毁。
但是注意:
webview调用destory时,webview仍绑定在Activity上.这是由于自定义webview构建时传入了该Activity的context对象,因此需要先从父容器中移除webview,然后再销毁webview。
rootLayout.removeView(webView);
webView.destroy();
五、WebView Cache && Cookie:在项目中如果使用到WebView控件, 当加载html页面时, 会在/data/data/包名目录下生成database与cache两个文件夹。请求的url记录是保存在WebViewCache.db, 而url的内容是保存在WebViewCache文件夹下。
(1)控制缓存行为,如下:
WebSettings webSettings = mWebView.getSettings();
//优先使用缓存
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
//只在缓存中读取
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
/不使用缓存
WwebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
清除缓存操作如下:
clearCache(true); //清除网页访问留下的缓存,由于内核缓存是全局的因此这个方法不仅仅针对webview而是针对整个应用程序.
clearHistory (); //清除当前webview访问的历史记录,只会webview访问历史记录里的所有记录除了当前访问记录.
clearFormData () //这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据。
(2)添加Cookie:
if (!DevUtil.hasLOLLIPOP()) {
CookieSyncManager.createInstance(mContext);
}
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
List<String> cookies = getCookies(customCookies);//项目中所使用到的所有cookie,因项目而异
for (String cookie : cookies) {
cookieManager.setCookie(uri.getHost(), cookie);
}
if (DevUtil.hasLOLLIPOP()) {
cookieManager.flush();
} else {
CookieSyncManager.getInstance().sync();
}
清除cookie:
CookieManager.getInstance().removeSessionCookie();