Android WebView常见问题及解决方案汇总

如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456

 

Android WebView常见问题解决方案汇总:

就目前而言,如何应对版本的频繁更新呢,又如何灵活多变地展示我们的界面呢,这又涉及到了web app与native app之间孰优孰劣的争论. 于是乎,一种混合型的app诞生了,灵活多变的部分,如淘宝商城首页的活动页面,一集凡客诚品中我们都可以见到web 页面与native页面的混合,既利用了web app的灵活易更新,也借助了native app本身的效率.
当然,就会用到webview这样的一个控件,这里,我把自己使用过程中遇到的一些问题整理下来.

首先上张图对WebView进行一个基本的回顾:


Android WebView常见问题及解决方案汇总_第1张图片

以上思维导图原文件下载地址:

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 );
        }
    }

 

2.WebView cookies清理:

?
1
2
3
CookieSyncManager.createInstance( this );
CookieSyncManager.getInstance().startSync();
CookieManager.getInstance().removeSessionCookie();

3.清理cache 和历史记录:

?
1
2
webView.clearCache( true );
webView.clearHistory();

回到顶部

4.判断WebView是否已经滚动到页面底端:

?
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,  "" );

 

Android WebView开发问题及优化汇总

我们在native与网页相结合开发的过程中,难免会遇到关于WebView一些共通的问题。就我目前开发过程中遇到的问题以及最后得到的优化方案都将在这里列举出来。有些是老生常谈,有些则是个人摸索得出解决方法。下面就是整理得到的些干货。

1.加快HTML网页装载完成的速度

默认情况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标签得到加载,因而对于这样的系统我们就先直接加载。

回到顶部

2.自定义出错界面

当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)。

回到顶部

3.是否存在滚动条

当我们做类似上拉加载下一页这样的功能的时候,页面初始的时候需要知道当前WebView是否存在纵向滚动条,如果有则不加载下一页,如果没有则加载下一页直到其出现纵向滚动条。首先继承WebView类,在子类添加下面的代码:

?
1
2
3
public boolean existVerticalScrollbar () {
return computeVerticalScrollRange() > computeVerticalScrollExtent();
}


computeVerticalScrollRange得到的是可滑动的最大高度,computeVerticalScrollExtent得到的是滚动把手自身的高,当不存在滚动条时,两者的值是相等的。当有滚动条时前者一定是大于后者的。

回到顶部

4.是否已滚动到页面底部

同样我们在做上拉加载下一页这样的功能时,也需要知道当前页面滚动条所处的状态,如果快到底部,则要发起网络请求数据更新网页。同样继承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。

回到顶部

5.远程网页需访问本地资源

当我们在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();
}


上面有几点需要注意:

  • 从网络上下载html的过程应放在工作线程
  • html下载成功后渲染出html的步骤应放在UI主线程,不然WebView会报错
  • html下载失败则可以使用我们前面讲述的方法来显示自定义错误界面

    完整的demo项目代码我已放到:http://yunpan.cn/cgQPvJQxxkCBj (提取码:6712)。
回到顶部

6.ViewPager里非首屏WebView点击事件不响应

如果你的多个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);
}


该方法的最先提出在 WebView in ViewPager not receive user inputs

回到顶部

7.WebView硬件加速导致页面渲染闪烁

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 );
}


回到顶部

8.避免addJavaScriptInterface带来的安全问题

使用开源项目Safe Java-JS WebView Bridge可以很好替代addJavaScriptInterface方法,同时增加了异步回调等支持,并且不存在了安全风险。

回到顶部

9.WebView与上层父元素的TouchMove事件冲突

在开发过程中你可能会遇到这样一种情况。端里面使用ViewPager嵌套了多个WebView页面,同时某一个WebView中的页面元素需要响应TouchMove事件。详细解决方案请移步:http://www.pedant.cn/2014/09/23/webview-touch-conflict

来自: http://www.pedant.cn/2014/09/10/webview-optimize-points/

你可能感兴趣的:(Android WebView常见问题及解决方案汇总)