配色: 字号:
那些年在WebView上踩过的坑
2016-12-12 | 阅:  转:  |  分享 
  
那些年在WebView上踩过的坑

1.WebView的内存泄露问题

问题描述:

webview内存泄露的情况还是很严重的,尤其是当你加载的页面比较庞大的时候。

解决方案:

1)展示webview的activity可以另开一个进程,这样就能和我们app的主进程分开了,即使webview产生了oom崩溃等问题也不会影响到主程序,如何实现呢,其实很简单,在Androidmanifest.xml的activity标签里加上Android:process=”packagename.web”就可以了,并且当这个进程结束时,请手动调用System.exit(0)。



2)如果实在不想用开额外进程的方式解决webview内存泄露的问题,那么下面的方法很大程度上可以避免这种情况



publicvoidreleaseAllWebViewCallback(){

if(android.os.Build.VERSION.SDK_INT<16){

try{

Fieldfield=WebView.class.getDeclaredField("mWebViewCore");

field=field.getType().getDeclaredField("mBrowserFrame");

field=field.getType().getDeclaredField("sConfigCallback");

field.setAccessible(true);

field.set(null,null);

}catch(NoSuchFieldExceptione){

if(BuildConfig.DEBUG){

e.printStackTrace();

}

}catch(IllegalAccessExceptione){

if(BuildConfig.DEBUG){

e.printStackTrace();

}

}

}else{

try{

FieldsConfigCallback=Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");

if(sConfigCallback!=null){

sConfigCallback.setAccessible(true);

sConfigCallback.set(null,null);

}

}catch(NoSuchFieldExceptione){

if(BuildConfig.DEBUG){

e.printStackTrace();

}

}catch(ClassNotFoundExceptione){

if(BuildConfig.DEBUG){

e.printStackTrace();

}

}catch(IllegalAccessExceptione){

if(BuildConfig.DEBUG){

e.printStackTrace();

}

}

}

}

在webview的destroy方法里调用这个方法就行了。



2.慎重在shouldoverrideurlloading中返回true



当设置了WebviewClient时,在shouldoverrideurlloading中如果不需要对url进行拦截做处理,而是简单的继续加载此网址,则建议采用返回false的方式而不是loadUrl的方式进行加载网址。

1)当请求的方式是”POST”方式时这个回调是不会通知的。

2)因为如果采用loadUrl的方式进行加载,那么对于加载有跳转的网址时,进行webview.goBack就会特别麻烦。

例如加载链接如下:

A1->(A2->A3->A4)->A5括号内为跳转

如果采用returnfalse的方式,那么在goBack的时候,可以从第二步直接回到A1网页。从A5回到A1只需要执行两次goBack

而如果采用的是loadUrl,则没办法直接从第二步回到A网页。因为loadUrl把第二步的每个跳转都认为是一个新的网页加载,因此从A5回到A1需要执行四次goBack



只有当不需要加载网址而是拦截做其他处理,如拦截tel:xxx等特殊url做拨号处理的时候,才应该返回true。



3.getSettings().setBuiltInZoomControls(true)引发的crash。

问题描述:

这个方法调用以后如果你触摸屏幕弹出的提示框还没消失的时候你如果activity结束了就会报错了。3.0以上4.4以下很多手机会出现这种情况



解决方案:

在activity的onDestroy方法里手动的将webiew设置成setVisibility(View.GONE)



4.onPageFinished函数的问题

问题描述:

你永远无法确定当WebView调用这个方法的时候,网页内容是否真的加载完毕了。当前正在加载的网页产生跳转的时候这个方法可能会被多次调用,多数开发者都是参考的http://stackoverflow.com/questions/3149216/how-to-listen-for-a-webview-finishing-loading-a-url-in-android这个上面的高票答案,但其中列举的解决方法并不完美。



解决方案:

当你的WebView需要加载各种各样的网页并且需要在页面加载完成时采取一些操作的话,可能WebChromeClient.onProgressChanged()比WebViewClient.onPageFinished()都要靠谱一些。如果哪位大神有更好的解决方法,欢迎留言。



5.WebView后台耗电问题。



问题描述:

当你的程序调用了WebView加载网页,WebView会自己开启一些线程,如果你没有正确地将WebView销毁的话,这些残余的线程会一直在后台运行,由此导致你的应用程序耗电量居高不下。



解决方案:

在Activity.onDestroy()中直接调用System.exit(0),使得应用程序完全被移出虚拟机,这样就不会有任何问题了。



6.后台无法释放js导致耗电



问题描述:

在有的手机里,你如果webview加载的html里有一些js一直在执行比如动画之类的东西,如果此刻webview挂在了后台,这些资源是不会被释放用户也无法感知,导致一直占有cpu耗电特别快。



解决方案:

在Activity的onstop和onresume里分别把setJavaScriptEnabled();给设置成false和true。



7.怎么用网页的标题来设置自己的标题栏?



WebChromeClientmWebChromeClient=newWebChromeClient(){

@Override

publicvoidonReceivedTitle(WebViewview,Stringtitle){

super.onReceivedTitle(view,title);

txtTitle.setText(title);

}

};



mWedView.setWebChromeClient(mWebChromeClient());

但是发现在部分的手机上,当通过webview.goBack()回退的时候,并没有触发onReceiveTitle(),这样会导致标题仍然是之前子页面的标题,没有切换回来.



这里可以分两种情况去处理:

1)可以确定webview中子页面只有二级页面,没有更深的层次,这里只需要判断当前页面是否为初始的主页面,可以goBack的话,只要将标题设置回来即可.

2)webview中可能有多级页面或者以后可能增加多级页面,这种情况处理起来要复杂一些:

因为正常顺序加载的情况onReceiveTitle是一定会触发的,所以就需要自己来维护webviewloading的一个url栈及url与title的映射关系。那么就需要一个ArrayList来保持加载过的url,一个HashMap保存url及对应的title。

正常顺序加载时,将url和对应的title保存起来,webview回退时,移除当前url并取出将要回退到的web页的url,找到对应的title进行设置即可。



这里还要说一点,当加载出错的时候,比如无网络,这时onReceiveTitle中获取的标题为找不到该网页,因此建议当触发onReceiveError时,不要使用获取到的title.



8.怎么隐藏缩放控件?



if(DeviceUtils.hasHoneycomb()){

mWebView.getSettings().setDisplayZoomControls(false);

}

最好把这两句加上

mWebView.getSettings().setSupportZoom(true);

mWebView.getSettings().setBuiltInZoomControls(true);

9.怎么知道WebView是否已经滚动到页面底端?



if(mWebView.getContentHeight()mWebView.getScale()

==(mWebView.getHeight()+mWebView.getScrollY())){

}

附上官网对几个方法的注释



getContentHeight()@returntheheightoftheHTMLcontent

getScale()@returnthecurrentscale

getHeight()@returnTheheightofyourview

getScrollY()@returnThetopedgeofthedisplayedpartofyourview,inpixels.

10.怎么清理cache和历史记录?



mWebView.clearCache(true);

mWebView.clearHistory();

11.怎么清理Cookie?



CookieSyncManager.createInstance(this);

CookieSyncManager.getInstance().startSync();

CookieManager.getInstance().removeSessionCookie();

12.为什么打包之后JS调用失败?

原因:没在proguard-rules.pro中添加混淆吧



-keepclasscon.demo.activity.web.utils.JsShareInterface{

publicvoidshare(java.lang.String);

}

13.WebView页面中播放了音频,退出Activity后音频仍然在播放



需要在Activity的onDestory()中调用以下方法



1.webView.destroy();

但可能会出现报错:



10-1015:01:11.402:E/ViewRootImpl(7502):sendUserActionEvent()mView==null

10-1015:01:26.818:E/webview(7502):java.lang.Throwable:Error:WebView.destroy()calledwhilestillattached!

10-1015:01:26.818:E/webview(7502):atandroid.webkit.WebViewClassic.destroy(WebViewClassic.java:4142)

10-1015:01:26.818:E/webview(7502):atandroid.webkit.WebView.destroy(WebView.java:707)

10-1015:01:26.818:E/webview(7502):atcom.didi.taxi.ui.webview.OperatingWebViewActivity.onDestroy(OperatingWebViewActivity.java:236)

10-1015:01:26.818:E/webview(7502):atandroid.app.Activity.performDestroy(Activity.java:5543)

10-1015:01:26.818:E/webview(7502):atandroid.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1134)

10-1015:01:26.818:E/webview(7502):atandroid.app.ActivityThread.performDestroyActivity(ActivityThread.java:3619)

10-1015:01:26.818:E/webview(7502):atandroid.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3654)

10-1015:01:26.818:E/webview(7502):atandroid.app.ActivityThread.access$1300(ActivityThread.java:159)

10-1015:01:26.818:E/webview(7502):atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:1369)

10-1015:01:26.818:E/webview(7502):atandroid.os.Handler.dispatchMessage(Handler.java:99)

10-1015:01:26.818:E/webview(7502):atandroid.os.Looper.loop(Looper.java:137)

10-1015:01:26.818:E/webview(7502):atandroid.app.ActivityThread.main(ActivityThread.java:5419)

10-1015:01:26.818:E/webview(7502):atjava.lang.reflect.Method.invokeNative(NativeMethod)

10-1015:01:26.818:E/webview(7502):atjava.lang.reflect.Method.invoke(Method.java:525)

10-1015:01:26.818:E/webview(7502):atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)

10-1015:01:26.818:E/webview(7502):atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

10-1015:01:26.818:E/webview(7502):atdalvik.system.NativeStart.main(NativeMethod)

webview调用destory时,webview仍绑定在Activity上.这是由于自定义webview构建时传入了该Activity的context对象,因此需要先从父容器中移除webview,然后再销毁webview:



rootLayout.removeView(webView);

webView.destroy();

14.处理WebView中的非超链接请求(如Ajax请求)

有时候需要加上请求头,但是非超链接的请求,没有办法再shouldOverrinding中拦截并用webView.loadUrl(Stringurl,HashMapheaders)方法添加请求头

目前用了一个临时的办法解决:

首先需要在url中加特殊标记/协议,如在onWebViewResource方法中拦截对应的请求,然后将要添加的请求头,以get形式拼接到url末尾

在shouldInterceptRequest()方法中,可以拦截到所有的网页中资源请求,比如加载JS,图片以及Ajax请求等等



@SuppressLint("NewApi")

@Override

publicWebResourceResponseshouldInterceptRequest(WebViewview,Stringurl){

//非超链接(如Ajax)请求无法直接添加请求头,现拼接到url末尾,这里拼接一个imei作为示例



StringajaxUrl=url;

//如标识:req=ajax

if(url.contains("req=ajax")){

ajaxUrl+="&imei="+imei;

}



returnsuper.shouldInterceptRequest(view,ajaxUrl);



}

15.屏蔽webview长按事件,因为webview长按时将会调用系统的复制控件



mWebView.setOnLongClickListener(newOnLongClickListener(){



@Override

publicbooleanonLongClick(Viewv){

returntrue;

}

});

16.在页面中先显示图片



@Override

publicvoidonLoadResource(WebViewview,Stringurl){

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

}

17.为WebView自定义错误显示界面

覆写WebViewClient中的onReceivedError()方法:



/

显示自定义错误提示页面,用一个View覆盖在WebView

/

protectedvoidshowErrorPage(){

LinearLayoutwebParentView=(LinearLayout)mWebView.getParent();



initErrorPage();

while(webParentView.getChildCount()>1){

webParentView.removeViewAt(0);

}

LinearLayout.LayoutParamslp=newLinearLayout.LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT);

webParentView.addViewwww.baiyuewang.net(mErrorView,0,lp);

mIsErrorPage=true;

}

protectedvoidhideErrorPage(){

LinearLayoutwebParentView=(LinearLayout)mWebView.getParent();



mIsErrorPage=false;

while(webParentView.getChildCount()>1){

webParentView.removeViewAt(0);

}

}





protectedvoidinitErrorPage(){

if(mErrorView==null){

mErrorView=View.inflate(this,R.layout.online_error,null);

Buttonbutton=(Button)mErrorView.findViewById(R.id.online_error_btn_retry);

button.setOnClickListener(newOnClickListener(){

publicvoidonClick(Viewv){

mWebView.reload();

}

});

mErrorView.setOnClickListener(null);

}

}



@Override

publicvoidonReceivedError(WebViewview,interrorCode,Stringdescription,StringfailingUrl){



mErrorView.setVisibility(View.VISIBLE);

super.onReceivedError(view,errorCode,description,failingUrl);

}

最后提一下WebView的一些小技巧:

1.webview的创建也是有技巧的,最好不要在layout.xml中使用webview,可以通过一个viewgroup容器,使用代码动态往容器里addview(webview),这样可以在onDestory()里销毁掉webview及时清理内存,另外需要注意创建webview需要使用applicationContext而不是activity的context,销毁时不再占有activity对象,这个大家应该都知道了,最后离开的时候需要及时销毁webview,onDestory()中应该先从viewgroup中remove掉webview,再调用webview.removeAllViews();webview.destory();



创建



ll=newLinearLayout(getApplicationContext());

ll.setOrientation(LinearLayout.VERTICAL);

wv=newWebView(getApplicationContext());

销毁



@Override

rotectedvoidonDestroy(){

ll.removeAllViews();

wv.stopLoading();

wv.removeAllViews();

wv.destroy();

wv=null;

ll=null;

super.onDestroy();

2.另外很多人不知道webview实际上有自己一套完整的cookie机制的,利用好这个可以大大增加对客户端的访问速度。

这里写图片描述

实际上cookie就是存放在这个表里的。

很多人都想要一个效果:网页更新cookie设置完cookie以后不刷新页面即可生效。这个在2.3以下和2.3以上要实现的方法不太一样,不过现在的安卓版本已经基本没有2.3的啦



publicvoidupdateCookies(Stringurl,Stringvalue){

if(Build.VERSION.SDK_INT<=Build.VERSION_CODES.GINGERBREAD_MR1){//2.3及以下

CookieSyncManager.createInstance(getContext().getApplicationContext());

}

CookieManagercookieManager=CookieManager.getInstance();

cookieManager.setAcceptCookie(true);

cookieManager.setCookie(url,value);

if(Build.VERSION.SDK_INT<=Build.VERSION_CODES.GINGERBREAD_MR1){

CookieSyncManager.getInstance().sync();

}

}

3.进一步的优化,activity被动被杀之后,最好能够保存webview状态,这样用户下次打开时就看到之前的状态了,嗯,就这么干,webview支持saveState(bundle)和restoreState(bundle)方法,所以就简单了,看看代码吧:

保存状态:



@Override

protectedvoidonSaveInstanceState(BundleoutState){

super.onSaveInstanceState(outState);

wv.saveState(outState);

Log.e(TAG,"savestate...");

}

恢复状态:

在activity的onCreate(bundlesavedInstanceState)里,这么调用:



if(null!=savedInstanceState){

wv.restoreState(savedInstanceState);

Log.i(TAG,"restorestate");

}else{

wv.loadUrl("http://3g.cn");

}

4.WebView图片延迟加载:

有些页面如果包含网络图片,在移动设备上我们等待加载图片的时间可能会很长,所以我们需要让图片延时加载,这样不影响我们加载页面的速度:

定义变量:



booleanblockLoadingNetworkImage=false;

在WebView初始化的时候设置:



blockLoadingNetworkImage=true;



webView.setWebViewClient(newWebViewClient(){

@Override

publicbooleanshouldOverrideUrlLoading(WebViewview,Stringurl){

//返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器

returntrue;

}



@Override

publicvoidonPageStarted(WebViewview,Stringurl,Bitmapfavicon){

super.onPageStarted(view,url,favicon);

if(!blockLoadingNwww.wang027.cometworkImage){

webView.getSettings().setBlockNetworkImage(true);

}

}



@Override

publicvoidonPageFinished(WebViewview,Stringurl){

super.onPageFinished(view,url);

if(blockLoadingNetworkImage){

webView.getSettings().setBlockNetworkImage(false);

}

}

});



献花(0)
+1
(本文系thedust79首藏)