本文来自于CSDN博客,作者:Shawn_Dut,已获授权,版权归原作者全部,未经作者同意,请勿转载。
欢迎同有博客好文章的作者加微信(ID:tm_forever_miss)或直接邮件(mobilehub@csdn.net)投稿、约稿、给文章纠错。
这篇博客重要来先容WebView的相干利用方法,常见的几个弊端,开辟中大概碰到的坑和末了办理相应弊端的源码,以及针对该源码的分析。
由于博客内容长度,这次将分为上下两篇,上篇详解WebView的利用,下篇报告WebView的弊端和坑,以及修复源码的分析。
下篇:AndroidWebView详解,常见弊端详解和安全源码(下)
AndroidHybrid和WebView分析
如今市面上的APP根据范例大抵可以分为3类:NativeAPP、WebAPP和HybridAPP,而HybridAPP兼具“NativeAPP精良用户交互体验的上风”和“WebAPP跨平台开辟的上风”,如今很多的主流应用也是利用Hybrid模式开辟的。
Hybrid的上风与原生的体验差距
Hybrid的上风
为什么要利用Hybrid开辟呢,这就要提到native开辟的限定:
1.客户端发板周期长
众所周知,客户端的发板周期在正常环境下比力长,就算是创业公司的迭代也在一到两个星期一次,大公司的迭代周期一样平常都在月这个数量级别上,而且Android还好,iOS的考核就算变短了也有几天,而且大概会有考核不通过的不测环境出现,所谓为了应对业务的快速发展,很多业务比如一些活动页面就可以利用H5来举行开辟。
2.客户端巨细体积受限
假如全部的东西都利用native开辟,比如上面提到的活动页面,就会造成大量的资源文件要参加到APK中,这就造成APK巨细增长,而且有的活动页面更新很快,造成资源文件大概只会利用一个版本,假如不及时整理,就会造成资源文件的残留。
3.web页面的体验题目
利用纯Web开辟,比从前迭代快速很多,但是从某种程度上来说,还是不如原生页面的交互体验好;
4.无法跨平台
一样平常环境下,同一样的页面在android和iOS上必要写两份差别的代码,但是如今只必要写一份即可,Hybrid具有跨平台的上风。
以是综上这两种方式单独处理惩罚都不是特别好,思量到发版周期不定,而且体验交互上也不能很差,以是就把两种方式综合起来,让终端和前端共同开辟一个APP,如许一些迭代很稳固的页面就可以利用原生,增长体验性;一些迭代很快速的页面就可以利用H5,让两种长处连合起来,补充原来单个开辟模式的缺点。
H5与Native的体验差距
H5和Native的体验差距重要在两个方面:
1.页面渲染瓶颈
第一个是前端页面代码渲染,受限于JS的分析服从,以及手机硬件装备的一些性能,以是从这个角度来说,我们应用开辟者是很难从根本上办理这个题目的;
2.资源加载迟钝
第二个方面是H5页面是从服务器上下发的,客户端的页面在内存内里,在页面加载时间上面,根据网络状态的差别,H5页面的体验和Native在很多环境下相比差距还是不小的,但是这种题目从某种程度上来说也是可以补充的,比如说我们可以做一些资源预加载的方案,在资源预加载方面,着实也有很多种方式,下面重要罗列了一些:
第一种方式是利用WebView自身的缓存机制:
假如我们在APP内里访问一个页面,短时间内再次访问这个页面的时间,就会感觉到第二次打开的时间顺畅很多,加载速率比第一次的时间要短,这个就是由于WebView自身内部会做一些缓存,只要打开过的资源,他都会试着缓存到本地,第二次必要访问的时间他直接从本地读取,但是这个读取着实是不太稳固的东西,关掉之后,大概说这种缓存失效之后,体系会主动把它打扫,我们没办法举行控制。基于这个WebView自身的缓存,有一种资源预加载的方案就是,我们在应用启动的时间可以开一个像素的WebView,事先去访问一下我们常用的资源,后续打开页面的时间假如再用到这些资源他就可以从本地获取到,页面加载的时间会短一些。
第二种方案是,我们本身去构建,本身管理缓存:
把这些必要预加载的资源放在APP内里,他大概是预先放进去的,也大概是后续下载的,题目在于前端这些页面怎么去缓存,两个方案,第一种是前端可以在H5打包的时间把内里的资源URL举行更换,如许可以直接访问本地的地点;第二种是客户端可以拦截这些网页发出的全部哀求做更换:
这个是美团利用的预加载方案(详情请看:美团大众点评Hybrid化建立),归属于第二种加载方案,每当WebView发起资源哀求的时间,我们会拦截这些资源的哀求,去本地查抄一下我们这些静态资源本地离线包有没有。针对本地的缓存文件我们有些战略可以或许及时的去更新它,为了安全思量,也必要同时做一些预下载和安全包的加密工作。预下载有以下几点上风:
我们拦截了WebView内里发出的全部的哀求,但是并没有更换内里的前端应用的任何代码,前端这套页面代码可以在APP内,大概其他的APP内里都可以直接访问,他不必要为我们APP做定制化的东西;
这些URL哀求,他会直接带上先前用户操纵所留下的Cookie,由于我们没有更改资源原始URL地点;
整个前端在用离线包和缓存文件的时间是完全无感知的,前端只用管写一个本身的页面,客户端会帮他处理惩罚好如许一些静态资源预加载的题目,有这个离线包的话,加载速率会变快很多,特别是在弱网环境下,没有这些离线包加载速率会慢一些。而且假如本地离线包的版本不能跟H5匹配的话,H5页面也不会发生什么题目。
实际资源预下载也确实可以或许有效的增长页面的加载速率,具体的对比可以去看美团的那片文章。
那么什么地方必要利用Native开辟,什么地方必要利用H5开辟呢:一样平常来说Hybrid是用在一些快速迭代试错的地方,别的一些非重要产物的页面,也可以利用Hybrid去做;但是假如是一些很紧张的流程,利用频率很高,特别核心的功能,还是应该利用Native开辟,让用户得到一个极致的产物体验。
WebView具体先容
我们来看看Google官网关于WebView的先容:
AViewthatdisplayswebpages.ThisclassisthebasisuponwhichyoucanrollyourownwebbrowserorsimplydisplaysomeonlinecontentwithinyourActivity.ItusestheWebKitrenderingenginetodisplaywebpagesandincludesmethodstonavigateforwardandbackwardthroughahistory,zoominandout,performtextsearchesandmore.
可以看到WebView是一个表现网页的控件,而且可以简单的表现一些在线的内容,而且基于WebKit内核,在Android4.4(APILevel19)引入了一个基于Chromium的新版本WebView,这让我们的WebView能支持HTML5和CSS3以及Java,有一点必要留意的是由于WebView的升级,对于我们的程序也带来了一些影响,假如我们的targetSdkVersion设置的是18大概更低,singleandnarrowcolumn和defaultzoomlevels不再支持。Android4.4之后有一个特别方便的地方是可以通过setWebContentDebuggingEnabled()方法让我们的程序可以举行长途桌面调试。
WebView加载页面
WebView有四个用来加载页面的方法:
loadUrl(Stringurl)
loadUrl(Stringurl,MapString,StringadditionalHttpHeaders)
loadData(Stringdata,StringmimeType,Stringencoding)
loadDataWithBaseURL(StringbaseUrl,Stringdata,StringmimeType,Stringencoding,StringhistoryUrl)
利用起来较为简单,loadData方法会有一些坑,在下面的内容会先容到。
WebView常见设置
利用WebView的时间,一样平常都会对其举行一些设置,我们来看看常见的设置:
WebSettingswebSettings=webView.getSettings();
//设置了这个属性后我们才华在WebView里与我们的Js代码举行交互,对于WebApp黑白常紧张的,默认是false,
//因此我们必要设置为true,这个本身会有弊端,具体的下面我会讲到
webSettings.setJavaEnabled(true);
//设置JS是否可以打开WebView新窗口
webSettings.setJavaCanOpenWindowsAutomatically(true);
//WebView是否支持多窗口,假如设置为true,必要重写
//WebChromeClient#onCreateWindow(WebView,boolean,boolean,Message)函数,默以为false
webSettings.setSupportMultipleWindows(true);
//这个属性用来设置WebView是否可以或许加载图片资源,必要留意的是,这个方法会控制全部图片,包罗那些利用dataURI协议嵌入的图片。利用setBlockNetworkImage(boolean)方法来控制仅仅加载利用网络URI协议的图片。必要提到的一点是假如这个设置从false变为true之后,全部被内容引用的正在表现的WebView图片资源都会主动加载,该标识默认值为true。
webSettings.setLoadsImagesAutomatically(false);
//标识是否加载网络上的图片(利用http大概https域名的资源),必要留意的是假如getLoadsImagesAutomatically()
//不返回true,这个标识将没有作用。这个标识和上面的标识会相互影响。
webSettings.setBlockNetworkImage(true);
//表现WebView提供的缩放控件
webSettings.setDisplayZoomControls(true);webSettings.setBuiltInZoomControls(true);
//设置是否启动WebViewAPI,默认值为false
webSettings.setDatabaseEnabled(true);
//打开WebView的storage功能,如许JS的localStorage,sessionStorage对象才可以利用
webSettings.setDomStorageEnabled(true);
//打开WebView的LBS功能,如许JS的geolocation对象才可以利用
webSettings.setGeolocationEnabled(true);webSettings.setGeolocationDatabasePath("");
//设置是否打开WebView表单数据的生存功能
webSettings.setSaveFormData(true);
//设置WebView的默认userAgent字符串
webSettings.setUserAgentString("");
//设置是否WebView支持“viewport”的HTMLmetatag,这个标识是用来屏幕自顺应的,当这个标识设置为false时,页面布局的宽度被不停设置为CSS中控制的WebView的宽度;假如设置为true而且页面含有viewportmetatag,那么被这个tag声明的宽度将会被利用,假如页面没有这个tag大概没有提供一个宽度,那么一个宽型viewport将会被利用。
webSettings.setUseWideViewPort(false);
//设置WebView的字体,可以通过这个函数,改变WebView的字体,默认字体为"sans-serif"
webSettings.setStandardFontFamily("");
//设置WebView字体的巨细,默认巨细为16
webSettings.setDefaultFontSize(20);
//设置WebView支持的最小字体巨细,默以为8
webSettings.setMinimumFontSize(12);
//设置页面是否支持缩放
webSettings.setSupportZoom(true);
//设置文本的缩放倍数,默以为100
webSettings.setTextZoom(2);
然后尚有最常用的WebViewClient和WebChromeClient,WebViewClient重要辅助WebView实行处理惩罚各种相应哀求变乱的,比如:
Resource
onPageStart
onPageFinish
onReceiveError
onReceivedHttpAuthRequest
shouldOverrideUrlLoading
WebChromeClient重要辅助WebView处理惩罚Java的对话框、网站Logo、网站title、load进度等处理惩罚:
onCloseWindow(关闭WebView)
onCreateWindow
onJsAlert
onJsPrompt
onJsConfirm
onProgressChanged
onReceivedIcon
onReceivedTitle
onShowCustomView
WebView只是用来处理惩罚一些html的页面内容,只用WebViewClient就行了,假如必要更丰富的处理惩罚结果,比如JS、进度条等,就要用到WebChromeClient,我们接下来为了处理惩罚在特定版本之下的js弊端题目,就必要用到WebChromeClient。
接着尚有WebView的几种缓存模式:
LOAD_CACHE_ONLY
不利用网络,只读取本地缓存数据;
LOAD_DEFAULT
根据cache-control决定是否从网络上取数据;
LOAD_CACHE_NORMAL
APIlevel17中已经废弃,从APIlevel11开始作用同LOAD_DEFAULT模式;
LOAD_NO_CACHE
不利用缓存,只从网络获取数据;
LOAD_CACHE_ELSE_NETWORK
只要本地有,无论是否逾期,大概no-cache,都利用缓存中的数据。
www.baidu.com的cache-control为no-cache,在模式LOAD_DEFAULT下,无论怎样都会从网络上取数据,假如没有网络,就会出现错误页面;在LOAD_CACHE_ELSE_NETWORK模式下,无论是否有网,只要本地有缓存,都会加载缓存。本地没有缓存时才从网络上获取,这个和Http缓存同等,我不在过多先容,假如你想自界说缓存战略和时间,可以实行下,volley就是利用了http界说的缓存时间。
清空缓存和清空汗青记录,CacheManager来处理惩罚webview缓存相干:mWebView.clearCache(true);;清空汗青记录mWebview.clearHistory();,这个方法要在onPageFinished()的方法之后调用。
WebView与native的交互
利用Hybrid开辟的APP根本都必要Native和web页面的JS举行交互,下面先容一下交互的方式。
js调用native
怎样让web页面调用native的代码呢,有三种方式:
第一种方式:通过addJavaInterface方法举行添加对象映射
这种是利用最多的方式了,起首第一步我们必要设置一个属性:
mWebView.getSettings().setJavaEnabled(true);
这个函数会有一个告诫,由于在特定的版本之下会有非常伤害的弊端,我们下面将会偏重先容到,设置完这个属性之后,Native必要界说一个类:
publicclassJSObject{privateContextmContext;publicJSObject(Contextcontext){mContext=context;}@JavaInterfacepublicStringshowToast(Stringtext){Toast.show(mContext,text,Toast.LENGTH_SHORT).show();return"success";}}...
//特定版本下会存在弊端
mWebView.addJavaInterface(newJSObject(this),"myObj");必要留意的是在API17版本之后,必要在被调用的地方加上@addJavaInterface束缚注解,由于不加上注解的方法是没有办法被调用的,JS代码也很简单:
functionshowToast(){
varresult=myObj.showToast("我是来自web的Toast");}
可以看到,这种方式的长处在于利用简单明白,本地和JS的约定也很简单,就是对象名称和方法名称约定好即可,缺点就是下面要提到的弊端题目。
第二种方式:利用WebViewClient接口回调方法拦截url
这种方式着实实现也很简单,利用的频次也很高,上面我们先容到了WebViewClient,此中有个回调接口shouldOverrideUrlLoading(WebViewview,Stringurl),我们就是利用这个拦截url,然后分析这个url的协议,假如发现是我们预先约定好的协议就开始分析参数,实行相应的逻辑,我们先来看看这个函数的先容:
GivethehostapplicationachancetotakeoverthecontrolwhenanewurlisabouttobeloadedinthecurrentWebView.IfWebViewClientisnotprovided,bydefaultWebViewwillaskActivityManagertochoosetheproperhandlerfortheurl.IfWebViewClientisprovided,returntruemeansthehostapplicationhandlestheurl,whilereturnfalsemeansthecurrentWebViewhandlestheurl.ThismethodisnotcalledforrequestsusingthePOST"method".
留意这个方法在API24版本已经废弃了,必要利用shouldOverrideUrlLoading(WebViewview,WebResourceRequestrequest)更换,利用方法很雷同,我们这里就利用shouldOverrideUrlLoading(WebViewview,Stringurl)方法来先容一下:
publicbooleanshouldOverrideUrlLoading(WebViewview,Stringurl){
//假定传入进来的url="js://openActivity?arg1=111arg2=222",代表必要打开本地页面,而且带入相应的参数Uriuri=Uri.parse(url);
Stringscheme=uri.getScheme();
//假如scheme为js,代表为预先约定的js协议if(scheme.equals("js")){
//假如authority为openActivity,代表web必要打开一个本地的页面if(uri.getAuthority().equals("openActivity")){
//分析web页面带过来的相干参数HashMapString,Stringparams=newHashMap();SetStringcollection=uri.getQueryParameterNames();
for(Stringname:collection){params.put(name,uri.getQueryParameter(name));}Intentintent=newIntent(getContext(),MainActivity.class);intent.putExtra("params",params);getContext().startActivity(intent);}
//代表应用内部处理惩罚完成returntrue;}
returnsuper.shouldOverrideUrlLoading(view,url);}
代码很简单,这个方法可以拦截WebView中加载url的过程,得到对应的url,我们就可以通过这个方法,与网页约定好一个协议,假如匹配,实行相应操纵,我们看一下JS的代码:
functionopenActivity(){
document.location="js://openActivity?arg1=111arg2=222";}
这个代码实行之后,就会触发本地的shouldOverrideUrlLoading方法,然后举行参数分析,调用指定方法。这个方式不会存在第一种提到的弊端题目,但是它也有一个很繁琐的地方是,假如web端想要得到方法的返回值,只能通过WebView的loadUrl方法去实行JS方法把返回值转达归去,相干的代码如下:
//java
mWebView.loadUrl("java:returnResult("+result+")");//java
functionreturnResult(result){alert("resultis"+result);}
以是说第二种方式在返回值方面还是很繁琐的,但是在不必要返回值的环境下,比如打开Native页面,还是很符合的,订定好相应的协议,就可以或许让web端具有打开全部本地页面的本领了。
第三种方式:利用WebChromeClient回调接口的三个方法拦截消息
这个方法的原理和第二种方式原理一样,都是拦截相干接口,只是拦截的接口不一样:
@OverridepublicbooleanonJsAlert(WebViewview,Stringurl,Stringmessage,JsResultresult){
returnsuper.onJsAlert(view,url,message,result);}@OverridepublicbooleanonJsConfirm(WebViewview,Stringurl,Stringmessage,JsResultresult){
returnsuper.onJsConfirm(view,url,message,result);}@OverridepublicbooleanonJsPrompt(WebViewview,Stringurl,Stringmessage,StringdefaultValue,JsPromptResultresult){
//假定传入进来的message="js://openActivity?arg1=111arg2=222",代表必要打开本地页面,而且带入相应的参数Uriuri=Uri.parse(message);Stringscheme=uri.getScheme();
if(scheme.equals("js")){
if(uri.getAuthority().equals("openActivity")){HashMapString,Stringparams=newHashMap();SetStringcollection=uri.getQueryParameterNames();
for(Stringname:collection){params.put(name,uri.getQueryParameter(name));}Intentintent=newIntent(getContext(),MainActivity.class);intent.putExtra("params",params);getContext().startActivity(intent);
//代表应用内部处理惩罚完成result.confirm("success");}returntrue;}
returnsuper.onJsPrompt(view,url,message,defaultValue,result);}
和WebViewClient一样,这次添加的是WebChromeClient接口,可以拦截JS中的几个提示方法,也就是几种样式的对话框,在JS中有三个常用的对话框方法:
onJsAlert方法是弹出告诫框,一样平常环境下在Android中为Toast,在文本内里参加n就可以换行;
onJsConfirm弹出确认框,会返回布尔值,通过这个值可以判定点击时确认还是取消,true表现点击了确认,false表现点击了取消;
onJsPrompt弹出输入框,点击确认返回输入框中的值,点击取消返回null。
但是这三种对话框都是可以本地拦截到的,以是可以从这里去做一些更改,拦截这些方法,得到他们的内容,举行分析,比如假如是JS的协议,则阐明为内部协议,举行下一步分析然后举行相干的操纵即可,prompt方法调用如下所示:
functionclickprompt(){
varresult=prompt("js://openActivity?arg1=111arg2=222");alert("openactivity"+result);}
这里必要留意的是prompt内里的内容是通过message转达过来的,并不是第二个参数的url,返回值是通过JsPromptResult对象转达。为什么要拦截onJsPrompt方法,而不是拦截其他的两个方法,这个从某种意义上来说都是可行的,但是假如必要返回值给web端的话就不可了,由于onJsAlert是不能返回值的,而onJsConfirm只可以或许返回确定大概取消两个值,只有onJsPrompt方法是可以返回字符串范例的值,操纵最全面方便。
以上三种方案的总结和对比
以上三种方案都是可行的,在这里总结一下:
第一种方式:
是如今如今最广泛的用法,方便简便,但是唯一的不敷是在4.2体系以下存在弊端题目;
第二种方式:
通过拦截url并分析,假如是已经约定好的协议则举行相应规定好的操纵,缺点就是协议的束缚必要记录一个规范的文档,而且从Native层往Web层转达值比力繁琐,长处就是不会存在弊端,iOS7之下的版本就是利用的这种方式。
第三种方式:
和第二种方式的头脑着实是雷同的,只是拦截的方法变了,这里拦截了JS中的三种对话框方法,而这三种对话框方法的区别就在于返回值题目,alert对话框没有返回值,confirm的对话框方法只有两种状态的返回值,prompt对话框方法可以返回恣意范例的返回值,缺点就是协议的订定比力贫苦,必要记录具体的文档,但是不会存在第二种方法的弊端题目。
native调用js
第一种方式
native调用js的方法上面已经先容到了,方法为:
//java
mWebView.loadUrl("java:show("+result+")");//java
type="text/java"
functionshow(result){alert("result"=result);
return"success";}/
必要留意的是名字肯定要对应上,要否则是调用不乐成的,而且尚有一点是JS的调用肯定要在onPageFinished函数回调之后才华调用,要否则也是会失败的。
第二种方式
假如如今有需求,我们要得到一个Native调用Web的回调怎么办,Google在Android4.4为我们新增长了一个新方法,这个方法比loadUrl方法更加方便简便,而且比loadUrl服从更高,由于loadUrl的实行会造成页面革新一次,这个方法不会,由于这个方法是在4.4版本才引入的,以是我们利用的时间必要添加版本的判定:
finalintversion=Build.VERSION.SDK_INT;
if(version18){mWebView.loadUrl(jsStr);}else{mWebView.evaluateJava(jsStr,newValueCallbackString(){@OverridepublicvoidonReceiveValue(Stringvalue){
//此处为js返回的结果}});}
两种方式的对比
一样平常最常利用的就是第一种方法,但是第一种方法获取返回的值比力贫苦,而第二种方法由于是在4.4版本引入的,以是范围性比力大。
WebView常见弊端和坑
常见弊端和坑请看下篇博客:androidWebView详解,常见弊端详解和安全源码(下)
源码
源码分析请看下篇博客:androidWebView详解,常见弊端详解和安全源码(下)
下载源码:https://github.com/zhaozepeng/SafeWebView;
参考自:https://github.com/yushiwo/WebViewBugDemo,在此底子上做了一些优化。
引用
https://group.jobbole.com/26417/?utm_source=android.jobbole.comutm_medium=sidebar-group-topic
https://blog.csdn.net/jiangwei0910410003/article/details/52687530
https://blog.csdn.net/leehong2005/article/details/11808557
https://github.com/yushiwo/WebViewBugDemo/blob/master/src/com/lee/webviewbug/WebViewEx.java
https://blog.csdn.net/sk719887916/article/details/52402470
https://zhuanlan.zhihu.com/p/24202408
https://github.com/lzyzsd/JsBridge
https://www.jianshu.com/p/93cea79a2443#
https://www.codexiu.cn/android/blog/33214/
https://github.com/pedant/safe-java-js-webview-bridge
https://blog.sina.com.cn/s/blog_777f9dbb0102v8by.html
https://www.cnblogs.com/chaoyuehedy/p/5556557.html
https://blogs.360.cn/360mobile/2014/09/22/webview%E8%B7%A8%E6%BA%90%E6%94%BB%E5%87%BB%E5%88%86%E6%9E%90/
https://my.oschina.net/zhibuji/blog/100580
https://www.cnblogs.com/punkisnotdead/p/5062631.html?utm_source=tuicoolutm_medium=referral
相识最新移动开辟、VR/AR干货技能分享,请关注mobilehub微信公众号(ID:mobilehub)。
我要评论