本文已獨(dú)家授權(quán) 郭霖 ( guolin_blog?) 公眾號(hào)發(fā)布陪腌!
擼完了上一篇Android-X5WebView簡(jiǎn)介 之后,有些大兄弟可能覺得不過癮吶铡原,說(shuō)你那樣的都是很基礎(chǔ)的啊(的確很基礎(chǔ))商叹,項(xiàng)目里面用起來(lái)不爽把嗫獭(的確很不爽),不能讓我直接CV捌鼠稀(的確不能直接復(fù)制粘貼)等等卵洗,那這篇文章的目標(biāo)就是怎么樣快速封裝X5WebView,如何有效的同步以及管理Cookie弥咪,如何使用IntentService優(yōu)化預(yù)加載过蹂,如何監(jiān)聽進(jìn)度條以及8.1系統(tǒng)的踩坑適配,等一些在項(xiàng)目中的常用功能聚至。
文章用到的源碼:項(xiàng)目地址
最近更新內(nèi)容:
X5Webview適配Android 8.1系統(tǒng)
Https與Http混合加載
功能需求:
需求一:客戶端賬號(hào)密碼登錄成功以后酷勺,調(diào)用H5(也就是使用X5webView,以下簡(jiǎn)稱X5)。H5界面也需要去記錄你的狀態(tài)扳躬。比如你客戶端本地登錄成功以后脆诉,H5界面也需要顯示登錄成功的狀態(tài)甚亭。那么,客戶端和H5如何去同步狀態(tài)击胜?
需求二:因?yàn)閄5加載的時(shí)候亏狰,會(huì)有一段時(shí)間會(huì)顯示空白或者卡頓,如何去監(jiān)聽并利用這個(gè)進(jìn)度并優(yōu)化偶摔?
需求三:如何簡(jiǎn)單封裝X5WebView基本功能暇唾,方便日后快速使用?
需求分析:
需求一:
(針對(duì)需求一辰斋,真的是參考了很多哥們的技術(shù)博客策州,然后由于筆者上周也就是3月9號(hào)接到的開發(fā)需求,其中遇到了很多坑亡呵,所以我希望這篇博客可以把這個(gè)需求寫的詳細(xì)抽活,盡可能的造福以后遇到同樣需求的朋友讓他們節(jié)約時(shí)間少走彎路)
對(duì)于Cookie,我們并不陌生锰什,如果不是很了解的建議首先參考Cookie下硕、Session、Token那點(diǎn)事兒? 先大致掌握一下汁胆。這里多提一嘴梭姓,Cookie 簡(jiǎn)單理解,它主要是用來(lái)進(jìn)程蹦勐耄活的誉尖,其具有時(shí)效性(持久化和非持久化),它是通過服務(wù)器的請(qǐng)求铸题,在響應(yīng)頭里面拿到铡恕,然后在第二次http請(qǐng)求上以請(qǐng)求頭的方式將參數(shù)帶過去,優(yōu)點(diǎn)是減少后臺(tái)查庫(kù)壓力等等丢间,更加具體的細(xì)節(jié)和說(shuō)明可以參考上面的鏈接文章探熔,那么,在Android中烘挫,也就是WebView中诀艰,我們?nèi)绾稳ス芾鞢ookie?
首先:CookieSyncManager與CookieManager
Android中關(guān)于Cookie的說(shuō)明:
早期的cookie是由CookieSyncManager進(jìn)行管理的饮六,之后CookieSyncManager被拋棄了其垄,換成了CookieManager來(lái)進(jìn)行管理。兩個(gè)版本的分割線就是Android SDK -- 21卤橄。
Android中Cookie的存儲(chǔ)位置:
目前Android系統(tǒng)WebView是將cookie存儲(chǔ)data/data/package_name/app_webview這個(gè)目錄下的一個(gè)叫Cookies的數(shù)據(jù)中绿满。
CookieSyncManager在內(nèi)存和存儲(chǔ)器之間同步瀏覽器的cookie。另外窟扑,CookieSyncManager的同步策略是在一個(gè)獨(dú)立的線程里定時(shí)進(jìn)行同步棒口。
注意:每次同步的時(shí)間間隔是5分鐘寄月。
CookieSyncManager類下的常用API介紹
cookie同步策略:
CookieSyncManager.createInstance(context);?
CookieSyncManager.getInstance().startSync();
cookie停止同步:
CookieSyncManager.getInstance().stopSync()
cookie立即同步:調(diào)用了該方法會(huì)立即進(jìn)行cookie的同步,代碼如下:
CookieSyncManager.getInstance().sync()
刪除cookie操作:
CookieSyncManager.createInstance(this);
CookieManager.getInstance().removeAllCookie();
CookieManager.getInstance().removeSessionCookie();
CookieSyncManager.getInstance().sync();
CookieSyncManager.getInstance().startSync();
CookieManager管理cookie:從sdk21之后无牵,webview已經(jīng)內(nèi)置了cookie的同步操作了漾肮。雖然不再需要關(guān)注cookie的同步,但是依然需要掌握刪除cookie的操作茎毁。
刪除cookie操作:底層實(shí)現(xiàn)是異步清除數(shù)據(jù)庫(kù)的記錄
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
立即同步:注意到這個(gè)flush()方法就是立即同步cookie的操作克懊,本質(zhì)上與CookieSyncManager中的sync()方法是一樣的。于是乎七蜘,關(guān)于同步cookie我們可以有如下簡(jiǎn)單的寫法:
然后谭溉,筆者就遇到了第一個(gè)坑,按照如下寫法以后橡卤,cookie居然神奇的不同步(下面是偽代碼扮念,下面是偽代碼)
之前筆者通過字符串拼接,也就是append字符串 碧库,拼接字符串以后柜与,我想直接通過cookieManager.setCookie(url, cookie); 在x5WebView.loadUrl(url);調(diào)用之前去設(shè)置cookie,
然后嵌灰,cookie就是同步不了弄匕。沒得辦法,打印日志之后發(fā)現(xiàn)沽瞭,手動(dòng)設(shè)置的cookie值迁匠,神奇的只有一個(gè)分號(hào) !
谷歌百度后,有哥們說(shuō)是因?yàn)閏ookie Value的值在讀取時(shí)驹溃,只會(huì)讀取到第一個(gè)分號(hào)時(shí)城丧,當(dāng)發(fā)現(xiàn)第一個(gè)分號(hào)即認(rèn)為讀取結(jié)束。所以分號(hào)后面的cookie的值將不會(huì)讀取豌鹤,實(shí)際測(cè)試確實(shí)是這樣亡哄。那么,我們?cè)撊绾谓鉀Q拼接cookie讀取失敗的問題傍药?
解決辦法如下圖磺平,我們可以一個(gè)個(gè)手動(dòng)設(shè)置cookie,即可拼接完整的Cookie魂仍。(這種辦法雖然笨拙拐辽,但的確可以有效解決分號(hào)切割問題)
當(dāng)然,我在公司項(xiàng)目里用的是Okhttp擦酌,(為什么這里用Okhttp俱诸,因?yàn)椋∵@樣就可以用Okhttp赊舶、Retrofit睁搭、OkGo等網(wǎng)絡(luò)框架赶诊,直接集成 我在項(xiàng)目中給大家提供的攔截器、自定義CookieJar使用了)园骆。通過自定義攔截器舔痪,實(shí)現(xiàn)CookieJar去完成同步客戶端和H5的cookie狀態(tài)。這里先上下最終效果圖:(筆者的代碼可能不是唯一實(shí)現(xiàn)功能需求的锌唾,但湊合還能用锄码。實(shí)現(xiàn)方式有很多種,寫的不好也請(qǐng)大家見諒)
關(guān)于AddCookiesInterceptor以及SaveCookiesInterceptor這兩個(gè)攔截器晌涕,主要就是存Cookie和使用Cookie滋捶,具體說(shuō)明可以參考 兩個(gè)攔截器的說(shuō)明 ,然后我們點(diǎn)進(jìn)自定義cookieJar中的 SaCookieManger
這個(gè)類定義了一個(gè)context構(gòu)造參數(shù)余黎,在保存cookie的saveFromResponse方法中重窟,調(diào)用了SaasCookieManager.loadCookie(cookies,url.host());方法,我們點(diǎn)進(jìn)SaasCookieManager
通過遍歷添加到集合里面惧财,然后一個(gè)個(gè)的setCookie( url ,cookie )巡扇、接著判斷SDK版本號(hào)進(jìn)行同步刷新即可,具體可以參考項(xiàng)目源代碼可缚。
當(dāng)然大家也可以在這里面根據(jù)開發(fā)需求去增加自己的實(shí)際功能霎迫。
通過上面的步驟,我們就可以簡(jiǎn)單的實(shí)現(xiàn) 客戶端與H5端同步cookie帘靡。
筆者的項(xiàng)目里面知给,是客戶端登錄成功以后,進(jìn)入H5頁(yè)面描姚,H5頁(yè)面上直接顯示已登錄狀態(tài)涩赢。
拓展:有部分手機(jī)使用后可能還是無(wú)法同步,那么我們可以嘗試轩勘,設(shè)置跨域讀取cookie筒扒,開啟webview對(duì)第三方cookie的支持。
當(dāng)頁(yè)面加載完畢的時(shí)候绊寻,我們可以通過下面的截圖代碼花墩,去獲取H5上面的cookie,我們也可以打印日志澄步、進(jìn)行同步
需求二:
監(jiān)聽進(jìn)度冰蘑,是這樣,WebView里面的WebChromeClient這個(gè)類村缸,里面有個(gè)onProgressChanged方法祠肥,重寫這個(gè)方法就可以獲取加載進(jìn)度。獲取到X5Webview的進(jìn)度之后梯皿,還需要通過WebView的setWebChromeClient方法仇箱,將我們自定義的WebChromeClient對(duì)象傳進(jìn)去县恕,即可完成進(jìn)度監(jiān)聽。
拓展:WebViewClient 內(nèi)置的shouldOverrideUrlLoading(WebView view, String url)這個(gè)函數(shù)的主要意思是指:檢查URL主機(jī)是否與特定域匹配剂桥。 如果它匹配忠烛,則該方法返回false以便不覆蓋URL加載(它允許WebView像往常一樣加載URL); 如果URL主機(jī)不匹配权逗,則Intent創(chuàng)建一個(gè)以啟動(dòng)默認(rèn)活動(dòng)以處理URL(解析為用戶的默認(rèn)Web瀏覽器)况木。
需求三:封裝X5Webview基本功能
常用設(shè)置:
比如設(shè)置對(duì)JS的支持等等一些比較常用的,我們可以直接這樣設(shè)置
滾動(dòng)條(內(nèi)側(cè)旬迹、外側(cè)的設(shè)置)火惊,隱藏或顯示的基本使用:
處理預(yù)加載:
有的小伙伴說(shuō),X5預(yù)加載不是很友好奔垦。在加載X5內(nèi)核的時(shí)候屹耐,X5內(nèi)核需要進(jìn)行一些初始化,這些初始化如果不明確指出運(yùn)行的線程椿猎,它就會(huì)在你啟動(dòng)頁(yè)面的時(shí)候惶岭,默認(rèn)在主線程中執(zhí)行,因此就會(huì)出現(xiàn)卡頓(這個(gè)現(xiàn)象時(shí)有時(shí)無(wú)犯眠,但是我們?cè)诖a層面盡可能的去規(guī)避使用風(fēng)險(xiǎn))按灶,所以,我們可以寫個(gè) IntentService 去幫我們管理預(yù)加載問題:
多提一嘴:IntentService是Service的子類筐咧,比普通的Service增加了額外的功能鸯旁。
IntentService會(huì)創(chuàng)建獨(dú)立的worker線程來(lái)處理所有的Intent請(qǐng)求;
會(huì)創(chuàng)建獨(dú)立的worker線程來(lái)處理onHandleIntent()方法實(shí)現(xiàn)的代碼量蕊,無(wú)需處理多線程的問題铺罢;
所有請(qǐng)求處理完成后,IntentService會(huì)自動(dòng)停止残炮,開發(fā)者無(wú)需手動(dòng)調(diào)用stopSelf()方法停止Service韭赘;
寫完之后我們?cè)谌プ远xApplication里面注冊(cè)服務(wù):(別忘了去清單文件配置Services)
返回鍵的處理:
這個(gè)就根據(jù)大家開發(fā)需求具體使用了,有的要求返回鍵按下兩次才允許退出等等
生命周期的處理势就、釋放資源的處理:這個(gè)就不說(shuō)了泉瞻,大家查閱資料集成功能即可
攔截廣告的處理:
有些哥們說(shuō),使用這個(gè)經(jīng)常會(huì)出現(xiàn)廣告苞冯,解決這個(gè)辦法有兩個(gè)辦法
1:使用Https
2:設(shè)計(jì)攔截url規(guī)則袖牙,對(duì)允許的url進(jìn)行放行加載,不允許的url抱完,WebView禁止加載
Android8.0系統(tǒng)適配:
2017年8月份,谷歌正式發(fā)布了Android8.0系統(tǒng),時(shí)隔3個(gè)多月谷歌正式發(fā)布了Android 8.1的正式版贼陶。但是目前,Android用戶持有的系統(tǒng)版本百分之85的系統(tǒng)是介于Android4.4-Android7.0這個(gè)系統(tǒng)版本之間,好了,這樣真機(jī)調(diào)試就會(huì)有一定的局限性∪信荩現(xiàn)在出現(xiàn)了這樣一種情況巧娱,使用X5去加載Url,8.1系統(tǒng)以下的都正常碉怔,8.1系統(tǒng)以上的的就會(huì)有問題。這不禁添,騰訊的X5社區(qū)也有別的開發(fā)者反饋了這個(gè)問題撮胧,但是沒人回答:
具體他是什么問題我不清楚(最后分析可能是遇到了同樣的問題),這里先細(xì)說(shuō)我遇到的問題老翘。還是文章內(nèi)部的代碼,到了8.1系統(tǒng)以后芹啥,我這邊通過X5WebView加載H5游戲就有問題。我們知道 初始化的時(shí)候,首先需要調(diào)用 QbSdk.PreInitCallback 這個(gè)回調(diào)函數(shù)铺峭,也就是onViewInitFinished ( boolean ininTag )這個(gè)回調(diào)函數(shù)墓怀,這個(gè)函數(shù)主要是完成x5內(nèi)核初始化,其中卫键,這里的ininTag為true則表示x5內(nèi)核加載成功傀履,false即表示x5內(nèi)核加載失敗,如果為false莉炉,X5內(nèi)核會(huì)自動(dòng)切換到系統(tǒng)內(nèi)核钓账。
?現(xiàn)在的一個(gè)情況是,加載到X5內(nèi)核以后,這個(gè)initTag有一定幾率會(huì)失敗。你可能會(huì)說(shuō)既然初始化失敗那就使用系統(tǒng)的WebView呀絮宁,嗯梆暮,沒錯(cuò),但是現(xiàn)在切換原生的WebView以后绍昂,就加載不出來(lái)了(蜜汁尷尬)這個(gè)原因后面也會(huì)說(shuō)啦粹,基于此這里引用一段谷歌針對(duì)8.0系統(tǒng)的官方文檔:
多個(gè) Android 應(yīng)用和服務(wù)可以同時(shí)運(yùn)行。 例如窘游,用戶可以在一個(gè)窗口中玩游戲卖陵,同時(shí)在另一個(gè)窗口中瀏覽網(wǎng)頁(yè),并使用第三個(gè)應(yīng)用播放音樂张峰。同時(shí)運(yùn)行的應(yīng)用越多泪蔫,對(duì)系統(tǒng)造成的負(fù)擔(dān)越大。 如果還有應(yīng)用或服務(wù)在后臺(tái)運(yùn)行喘批,這會(huì)對(duì)系統(tǒng)造成更大負(fù)擔(dān)撩荣,進(jìn)而可能導(dǎo)致用戶體驗(yàn)下降;例如饶深,音樂應(yīng)用可能會(huì)突然關(guān)閉餐曹。應(yīng)用在兩個(gè)方面受到限制:
后臺(tái)服務(wù)限制:處于空閑狀態(tài)時(shí),應(yīng)用可以使用的后臺(tái)服務(wù)存在限制敌厘。 這些限制不適用于前臺(tái)服務(wù)台猴,因?yàn)榍芭_(tái)服務(wù)更容易引起用戶注意。
廣播限制:除了有限的例外情況,應(yīng)用無(wú)法使用清單注冊(cè)隱式廣播饱狂。 它們?nèi)匀豢梢栽谶\(yùn)行時(shí)注冊(cè)這些廣播曹步,并且可以使用清單注冊(cè)專門針對(duì)它們的顯式廣播。
注:默認(rèn)情況下休讳,這些限制僅適用于針對(duì) O 的應(yīng)用讲婚。
在后臺(tái)中運(yùn)行的服務(wù)會(huì)消耗設(shè)備資源,這可能降低用戶體驗(yàn)俊柔。 為了緩解這一問題筹麸,系統(tǒng)對(duì)這些服務(wù)施加了一些限制。
系統(tǒng)可以區(qū)分?前臺(tái)?和?后臺(tái)?應(yīng)用雏婶。 (用于服務(wù)限制目的的后臺(tái)定義與內(nèi)存管理使用的定義不同物赶;一個(gè)應(yīng)用按照內(nèi)存管理的定義可能處于后臺(tái),但按照能夠啟動(dòng)服務(wù)的定義又處于前臺(tái)留晚。)如果滿足以下任意條件块差,應(yīng)用將被視為處于前臺(tái):
具有可見 Activity(不管該 Activity 已啟動(dòng)還是已暫停)。
具有前臺(tái)服務(wù)倔丈。
另一個(gè)前臺(tái)應(yīng)用已關(guān)聯(lián)到該應(yīng)用(不管是通過綁定到其中一個(gè)服務(wù)憨闰,還是通過使用其中一個(gè)內(nèi)容提供程序)。 例如需五,如果另一個(gè)應(yīng)用綁定到該應(yīng)用的服務(wù)鹉动,那么該應(yīng)用處于前臺(tái):
A:IME
B:壁紙服務(wù)
C:通知偵聽器
D:語(yǔ)音或文本服務(wù)
E:如果以上條件均不滿足,應(yīng)用將被視為處于后臺(tái)宏邮。
F:綁定服務(wù)不受影響泽示,這些規(guī)則不會(huì)對(duì)綁定服務(wù)產(chǎn)生任何影響。 如果您的應(yīng)用定義了綁定服務(wù)蜜氨,則不管應(yīng)用是否處于前臺(tái)械筛,其他組件都可以綁定到該服務(wù)。
為了降低發(fā)生這些問題的幾率飒炎,Android 8.0 對(duì)應(yīng)用在用戶不與其直接交互時(shí)可以執(zhí)行的操作施加了限制埋哟。
在 Android 8.0 之前,創(chuàng)建前臺(tái)服務(wù)的方式通常是先創(chuàng)建一個(gè)后臺(tái)服務(wù)郎汪,然后將該服務(wù)推到前臺(tái)赤赊。
Android 8.0 有一項(xiàng)復(fù)雜功能;系統(tǒng)不允許后臺(tái)應(yīng)用創(chuàng)建后臺(tái)服務(wù)煞赢。 因此抛计,Android 8.0 引入了一種全新的方法,即Context.startForegroundService()照筑,以在前臺(tái)啟動(dòng)新服務(wù)吹截。
以上文檔截取自8.0系統(tǒng)谷歌官方文檔瘦陈,那么這一段話主要的意思就是新系統(tǒng)限制了服務(wù),因此波俄,我們寫的IntentService就不要這樣寫了(谷歌有新的推薦方式我這里怎么快就怎么來(lái)吧)晨逝,直接將IntentService里面初始化X5內(nèi)核的代碼拷貝出來(lái),放在自定義Application里面的onCreate()即可弟断。
還有一點(diǎn),可能是手機(jī)或者種種問題趴生,我這邊初始化成功的概率大概是百分之90(測(cè)試了十次,有一次內(nèi)核沒有初始化成功),為了規(guī)避這種初始化失敗又切換到系統(tǒng)內(nèi)核加載不出來(lái)的風(fēng)險(xiǎn),我找了個(gè)笨方法阀趴,那就是如果初始化失敗就讓他繼續(xù)初始化直到X5初始化成功(這里使用的是EventBus去通知結(jié)果),初始化成功以后才使用X5Webview去加載url苍匆。
下面是代碼截圖:
當(dāng)然刘急,這只是筆者自己的解決方案(笨辦法),如果騰訊技術(shù)團(tuán)隊(duì)有最新的說(shuō)明或者開發(fā)者有更好的解決方案浸踩,請(qǐng)直接在評(píng)論區(qū)指出叔汁,歡迎斧正,謝謝检碗。
最新增加:Https與Http混合加載
我想据块,這個(gè)問題的出現(xiàn),是之前X5內(nèi)核在8.1系統(tǒng)上初始化失敗折剃,筆者做的一個(gè)實(shí)驗(yàn)嘗試另假。因?yàn)閄5內(nèi)核在8.1系統(tǒng)上初始化失敗(解決辦法可以參考上面的)怕犁,所以筆者又將WebView換成了原生的边篮。好的,老鐵沒毛病奏甫,用了原生的WebView以后戈轿,問題又出現(xiàn)了,H5游戲加載不出來(lái)阵子,這就尷尬到新的地步了思杯?
帶著黑人小哥三個(gè)問號(hào)的我,沒辦法吶挠进,花時(shí)間跟蹤調(diào)試智蝠,最后發(fā)現(xiàn):這個(gè)H5內(nèi)置的腳本資源,居然是Https和Http兩種JS腳本混合的D问帷h就濉!
但是H列搿F嶙病!X5內(nèi)部默認(rèn)實(shí)現(xiàn)了混合加載!8〔怠悍汛!所以現(xiàn)在切換到原生WebView以后,加載不出來(lái)了至会。因此針對(duì)這種特殊情況离咐,需要加上新的API。
我們知道Https簡(jiǎn)單講就是HTTP的安全版奉件,為什么它是安全的版本宵蛀?因?yàn)镠ttps提供了身份驗(yàn)證與加密通訊方法(確切的說(shuō)是SSL)但是,Https需要證書县貌,但是Android這邊針對(duì)WebView加載Https的一般做法是忽略SSL證書錯(cuò)誤术陶,繼續(xù)加載頁(yè)面。所以需要重寫WebViewClient的onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)方法煤痕,實(shí)現(xiàn)代碼如下:
? ? public WebViewClient client = new WebViewClient() {
? ? ? ? @Override
? ? ? ? public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
? ? ? ? ? ? //? 忽略SSL證書錯(cuò)誤梧宫,繼續(xù)加載頁(yè)面
? ? ? ? ? ? handler.proceed();
? ? ? ? }
}
證書的問題我們忽略以后,接下來(lái)還需要重寫WebSettings內(nèi)置的API摆碉,一行代碼搞定:
WebSettings? webSetting = getSettings();
//允許混合加載
webSetting.setAllowUniversalAccessFromFileURLs(true);
這樣就可以實(shí)現(xiàn)原生WebView混合加載的問題塘匣。
附錄:項(xiàng)目地址
如果這篇文章對(duì)您有開發(fā)or學(xué)習(xí)上的些許幫助,希望各位看官留下寶貴的star巷帝,謝謝馆铁。
Ps:著作權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)注明作者, 商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處(開頭或結(jié)尾請(qǐng)?zhí)砑愚D(zhuǎn)載出處锅睛,添加原文url地址),文章請(qǐng)勿濫用埠巨、開源項(xiàng)目?jī)H供學(xué)習(xí)交流、也希望大家尊重筆者的勞動(dòng)成果,謝謝现拒。