如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456
Android WebView常见问题解决方案汇总:
就目前而言,如何应对版本的频繁更新呢,又如何灵活多变地展示我们的界面呢,这又涉及到了web app与native app之间孰优孰劣的争论. 于是乎,一种混合型的app诞生了,灵活多变的部分,如淘宝商城首页的活动页面,一集凡客诚品中我们都可以见到web 页面与native页面的混合,既利用了web app的灵活易更新,也借助了native app本身的效率.
当然,就会用到webview这样的一个控件,这里,我把自己使用过程中遇到的一些问题整理下来.
首先上张图对WebView进行一个基本的回顾:
以上思维导图原文件下载地址:
http://download.csdn.net/detail/t12x3456/6509195
然后看一下具体的问题及解决方案:
1.为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
32
33
34
35
36
|
/**
* 显示自定义错误提示页面,用一个View覆盖在WebView
*/
protected
void
showErrorPage() {
LinearLayout webParentView = (LinearLayout)mWebView.getParent();
initErrorPage();
while
(webParentView.getChildCount() >
1
) {
webParentView.removeViewAt(
0
);
}
LinearLayout.LayoutParams lp =
new
LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT);
webParentView.addView(mErrorView,
0
, lp);
mIsErrorPage =
true
;
}
protected
void
hideErrorPage() {
LinearLayout webParentView = (LinearLayout)mWebView.getParent();
mIsErrorPage =
false
;
while
(webParentView.getChildCount() >
1
) {
webParentView.removeViewAt(
0
);
}
}
protected
void
initErrorPage() {
if
(mErrorView ==
null
) {
mErrorView = View.inflate(
this
, R.layout.online_error,
null
);
Button button = (Button)mErrorView.findViewById(R.id.online_error_btn_retry);
button.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
mWebView.reload();
}
});
mErrorView.setOnClickListener(
null
);
}
}
|
1
2
3
|
CookieSyncManager.createInstance(
this
);
CookieSyncManager.getInstance().startSync();
CookieManager.getInstance().removeSessionCookie();
|
1
2
|
webView.clearCache(
true
);
webView.clearHistory();
|
1
2
3
4
|
getScrollY()方法返回的是当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离.
getHeight()或者getBottom()方法都返回当前WebView 这个容器的高度
getContentHeight 返回的是整个html 的高度,但并不等同于当前整个页面的高度,因为WebView 有缩放功能, 所以当前整个页面的高度实际上应该是原始html 的高度再乘上缩放比例. 因此,更正后的结果,准确的判断方法应该是:
if
(WebView.getContentHeight*WebView.getScale() == (webview.getHeight()+WebView.getScrollY())){
//已经处于底端 }
|
5.URL拦截:
Android WebView是拦截不到页面内的fragment跳转的。但是url跳转的话,又会引起页面刷新,H5页面的体验又下降了。只能给WebView注入JS方法了。
6.处理WebView中的非超链接请求(如Ajax请求):
有时候需要加上请求头,但是非超链接的请求,没有办法再shouldOverrinding中拦截并用webView.loadUrl(String url,HashMap headers)方法添加请求头
目前用了一个临时的办法解决:
首先需要在url中加特殊标记/协议, 如在onWebViewResource方法中拦截对应的请求,然后将要添加的请求头,以get形式拼接到url末尾
在shouldInterceptRequest()方法中,可以拦截到所有的网页中资源请求,比如加载JS,图片以及Ajax请求等等
Ex:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@SuppressLint
(
"NewApi"
)
@Override
public
WebResourceResponse shouldInterceptRequest(WebView view,String url) {
// 非超链接(如Ajax)请求无法直接添加请求头,现拼接到url末尾,这里拼接一个imei作为示例
String ajaxUrl = url;
// 如标识:req=ajax
if
(url.contains(
"req=ajax"
)) {
ajaxUrl +=
"&imei="
+ imei;
}
return
super
.shouldInterceptRequest(view, ajaxUrl);
}
|
7.在页面中先显示图片:
1
2
3
4
5
6
7
8
9
|
@Override
public
void
onLoadResource(WebView view, String url) {
mEventListener.onWebViewEvent(CustomWebView.
this
, OnWebViewEventListener.EVENT_ON_LOAD_RESOURCE, url);
if
(url.indexOf(
".jpg"
) >
0
) {
hideProgress();
//请求图片时即显示页面
mEventListener.onWebViewEvent(CustomWebView.
this
, OnWebViewEventListener.EVENT_ON_HIDE_PROGRESS, view.getUrl());
}
super
.onLoadResource(view, url);
}
|
8.屏蔽掉长按事件 因为webview长按时将会调用系统的复制控件:
1
2
3
4
5
6
7
|
mWebView.setOnLongClickListener(
new
OnLongClickListener() {
@Override
public
boolean
onLongClick(View v) {
return
true
;
}
});
|
9.在WebView加入 flash支持:
1
2
3
4
5
6
7
8
|
String temp =
"<html><body bgcolor=\""
+
"black"
+
"\"> <br/><embed src=\""
+ url +
"\" width=\""
+
"100%"
+
"\" height=\""
+
"90%"
+
"\" scale=\""
+
"noscale"
+
"\" type=\""
+
"application/x-shockwave-flash"
+
"\"> </embed></body></html>"
;
String mimeType =
"text/html"
;
String encoding =
"utf-8"
;
web.loadDataWithBaseURL(
"null"
, temp, mimeType, encoding,
""
);
|
我们在native与网页相结合开发的过程中,难免会遇到关于WebView一些共通的问题。就我目前开发过程中遇到的问题以及最后得到的优化方案都将在这里列举出来。有些是老生常谈,有些则是个人摸索得出解决方法。下面就是整理得到的些干货。
默认情况html代码下载到WebView后,webkit开始解析网页各个节点,发现有外部样式文件或者外部脚本文件时,会异步发起网络请求下载 文件,但如果在这之前也有解析到image节点,那势必也会发起网络请求下载相应的图片。在网络情况较差的情况下,过多的网络请求就会造成带宽紧张,影响 到css或js文件加载完成的时间,造成页面空白loading过久。解决的方法就是告诉WebView先不要自动加载图片,等页面finish后再发起 图片加载。
故在WebView初始化时设置如下代码:
1
2
3
4
5
6
7
|
public
void
int
() {
if
(Build.VERSION.SDK_INT >=
19
) {
webView.getSettings().setLoadsImagesAutomatically(
true
);
}
else
{
webView.getSettings().setLoadsImagesAutomatically(
false
);
}
}
|
同时在WebView的WebViewClient实例中的onPageFinished()方法添加如下代码:
1
2
3
4
5
6
|
@Override
public
void
onPageFinished(WebView view, String url) {
if
(!webView.getSettings().getLoadsImagesAutomatically()) {
webView.getSettings().setLoadsImagesAutomatically(
true
);
}
}
|
从上面的代码,可以看出我们对系统API在19以上的版本作了兼容。因为4.4以上系统在onPageFinished时再恢复图片加载时,如果存在多张图片引用的是相同的src时,会只有一个image标签得到加载,因而对于这样的系统我们就先直接加载。
当WebView加载页面出错时(一般为404 NOT FOUND),安卓WebView会默认显示一个卖萌的出错界面。但我们怎么能让用户发现原来我使用的是网页应用呢,我们期望的是用户在网页上得到是如原 生般应用的体验,那就先要从干掉这个默认出错页面开始。当WebView加载出错时,我们会在WebViewClient实例中的 onReceivedError()方法接收到错误,我们就在这里做些手脚:
1
2
3
4
5
6
|
@Override
public
void
onReceivedError (WebView view,
int
errorCode, String description, String failingUrl) {
super
.onReceivedError(view, errorCode, description, failingUrl);
loadDataWithBaseURL(
null
,
""
,
"text/html"
,
"utf-8"
,
null
);
mErrorFrame.setVisibility(View.VISIBLE);
}
|
从上面可以看出,我们先使用loadDataWithBaseURL清除掉默认错误页内容,再让我们自定义的View得到显示(mErrorFrame为蒙在WebView之上的一个LinearLayout布局,默认为View.GONE)。
当我们做类似上拉加载下一页这样的功能的时候,页面初始的时候需要知道当前WebView是否存在纵向滚动条,如果有则不加载下一页,如果没有则加载下一页直到其出现纵向滚动条。首先继承WebView类,在子类添加下面的代码:
1
2
3
|
public
boolean
existVerticalScrollbar () {
return
computeVerticalScrollRange() > computeVerticalScrollExtent();
}
|
computeVerticalScrollRange得到的是可滑动的最大高度,computeVerticalScrollExtent得到的是滚动把手自身的高,当不存在滚动条时,两者的值是相等的。当有滚动条时前者一定是大于后者的。
同样我们在做上拉加载下一页这样的功能时,也需要知道当前页面滚动条所处的状态,如果快到底部,则要发起网络请求数据更新网页。同样继承WebView类,在子类覆盖onScrollChanged方法,具体如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Override
protected
void
onScrollChanged(
int
newX,
int
newY,
int
oldX,
int
oldY) {
super
.onScrollChanged(newX, newY, oldX, oldY);
if
(newY != oldY) {
float
contentHeight = getContentHeight() * getScale();
// 当前内容高度下从未触发过, 浏览器存在滚动条且滑动到将抵底部位置
if
(mCurrContentHeight != contentHeight && newY >
0
&& contentHeight <= newY + getHeight() + mThreshold) {
// TODO Something...
mCurrContentHeight = contentHeight;
}
}
}
|
上面mCurrContentHeight用于记录上次触发时的网页高度,用来防止在网页总高度未发生变化而目标区域发生连续滚动时会多次触发TODO,mThreshold是一个阈值,当页面底部距离滚动条底部的高度差<=这个值时会触发TODO。
当我们在WebView中加载出从web服务器上拿取的内容时,是无法访问本地资源的,如assets目录下的图片资源,因为这样的行为属于跨域行 为(Cross-Domain),而WebView是禁止的。解决这个问题的方案是把html内容先下载到本地,然后使用 loadDataWithBaseURL加载html。这样就可以在html中使用 file:///android_asset/xxx.png 的链接来引用包里面assets下的资源了。示例如下:
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
|
private
void
loadWithAccessLocal(
final
String htmlUrl) {
new
Thread(
new
Runnable() {
public
void
run() {
try
{
final
String htmlStr = NetService.fetchHtml(htmlUrl);
if
(htmlStr !=
null
) {
TaskExecutor.runTaskOnUiThread(
new
Runnable() {
@Override
public
void
run() {
loadDataWithBaseURL(htmlUrl, htmlStr,
"text/html"
,
"UTF-8"
,
""
);
}
});
return
;
}
}
catch
(Exception e) {
Log.e(
"Exception:"
+ e.getMessage());
}
TaskExecutor.runTaskOnUiThread(
new
Runnable() {
@Override
public
void
run() {
onPageLoadedError(-
1
,
"fetch html failed"
);
}
});
}
}).start();
}
|
上面有几点需要注意:
如果你的多个WebView是放在ViewPager里一个个加载出来的,那么就会遇到这样的问题。ViewPager首屏WebView的创建是在前台,点击时没有问题;而其他非首屏的WebView是在后台创建,滑动到它后点击页面会出现如下错误日志:
20955-20968/xx.xxx.xxx E/webcoreglue﹕ Should not happen: no rect-based-test nodes found
解决这个问题的办法是继承WebView类,在子类覆盖onTouchEvent方法,填入如下代码:
1
2
3
4
5
6
7
|
@Override
public
boolean
onTouchEvent(MotionEvent ev) {
if
(ev.getAction() == MotionEvent.ACTION_DOWN) {
onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
}
return
super
.onTouchEvent(ev);
}
|
4.0以上的系统我们开启硬件加速后,WebView渲染页面更加快速,拖动也更加顺滑。但有个副作用就是,当WebView视图被整体遮住一块, 然后突然恢复时(比如使用SlideMenu将WebView从侧边滑出来时),这个过渡期会出现白块同时界面闪烁。解决这个问题的方法是在过渡期前将 WebView的硬件加速临时关闭,过渡期后再开启,代码如下:
1
2
3
|
if
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webview.setLayerType(View.LAYER_TYPE_SOFTWARE,
null
);
}
|
使用开源项目Safe Java-JS WebView Bridge可以很好替代addJavaScriptInterface方法,同时增加了异步回调等支持,并且不存在了安全风险。
在开发过程中你可能会遇到这样一种情况。端里面使用ViewPager嵌套了多个WebView页面,同时某一个WebView中的页面元素需要响应TouchMove事件。详细解决方案请移步:http://www.pedant.cn/2014/09/23/webview-touch-conflict