【安卓開發(fā)】TextView顯示帶圖片HTML踩坑記錄
需求是顯示一個(gè)網(wǎng)頁,并且內(nèi)容要可控,可以跟原生UI交互。
我用的是Compose,原生的Text是沒有這種功能的,從0開始造輪子也不是不行,但是View體系已經(jīng)有兩個(gè)完整的解決方案了,沒那個(gè)必要:
WebView
可以,但沒必要,我只需要顯示一個(gè)網(wǎng)頁,并不需要太多交互,也不用跳轉(zhuǎn),WebView過于臃腫;其次就是交互很麻煩,得通過js hook,因此駁回。
TextView
可以通過Html.fromHtml()得到一個(gè)可以直接設(shè)置給TextView的對象,而且交互比較方便。
選擇第二種方案以后,就開啟了踩坑之路:
TextView無法滾動(dòng)
解決方案:

2.無法顯示圖片
????這里坑就比較多了,我按時(shí)間順序整理一下:
????a.異步加載
????????Html.fromHtml()的第三個(gè)參數(shù)是一個(gè)ImageGetter接口,要實(shí)現(xiàn)一個(gè)getDrawable方法,給你一個(gè)url,讓你返回Drawable。
那么問題來了,這個(gè)方法是在主線程調(diào)用的,如果你在主線程加載圖片,則卡死,如果? ? ? ? ? ? 你開IO線程,那就沒辦法返回結(jié)果。
仔細(xì)想一下就會(huì)發(fā)現(xiàn),這個(gè)問題無解,這一次加載一定會(huì)阻塞主線程,因此需要設(shè)計(jì)一套緩存機(jī)制,在第一次加載的時(shí)候記錄下url,加載完成以后再次調(diào)用getDrawable,返回已經(jīng)緩存到本地的圖片。
所以我就寫了一個(gè)幫助類:
????????
首先是為什么不寫成單例類,因?yàn)榭赡苡卸鄠€(gè)頁面都要使用,而且這個(gè)類的生命周期是跟頁面綁定的,所以不能寫成單例。
其次是圖片數(shù)量限制,因?yàn)闆]辦法控制HTML的內(nèi)容,為了防止OOM把最大圖片數(shù)量限制在了10張。
然后這里雖然涉及到了多線程訪問,但是不同線程并不會(huì)互相干擾,所以沒用ConcurrentHashMap,也沒加鎖。
scope是協(xié)程上下文,基本就是ViewModel的scope,前面說過這個(gè)類的生命周期跟頁面綁定,所以頁面銷毀的時(shí)候,viewModel銷毀,所有協(xié)程也跟著銷毀。
以及頁面沒有銷毀,但是需要加載新的HTML,也得調(diào)用reset()刷新緩存。
我預(yù)想中的調(diào)用流程是這樣的:
getDrawable內(nèi)部查詢緩存,不為空直接使用,否則調(diào)用load,load會(huì)立即返回一個(gè)Drawable作為占位圖片,然后去加載網(wǎng)絡(luò)圖片,加載完成以后緩存,然后回調(diào)onFinished,回調(diào)函數(shù)內(nèi)部再次給TextView設(shè)置HTML,于是上述流程被重復(fù)。
因?yàn)槊恳淮卧O(shè)置HTML都會(huì)加載所有圖片,所以為了防止某個(gè)圖片在加載過程中被重復(fù)加載,直接把占位符緩存進(jìn)去,所以開頭加了判斷,直接返回占位符。
剩下沒什么好說的,非常質(zhì)樸的緩存。
b.ImageGetter()的缺陷
????ImageGetter會(huì)把img的src屬性傳到getDrawable里,但我的src屬性長這樣:

真正的url在src里。
一開始我想修改ImageGetter類,或者別的什么,但是明顯官方?jīng)]給這個(gè)接口,反射也不是很好操作;然后想著要不提前把圖片加載好,但是這樣getDrawable內(nèi)部依舊拿不到作為key的url,只能根據(jù)順序決定使用哪一張圖片,不太穩(wěn)定;最后索性直接修改了HTML文檔,把src替換成了src,原來的src換成別的不影響顯示的屬性。
c.Drawable
一切都弄好以后,圖片還是不顯示,然后發(fā)現(xiàn)Drawable還需要設(shè)置Rect。這個(gè)值直接給圖片原大小的話,會(huì)顯示不全,所以簡單的縮放了兩倍:

后續(xù)可以判斷一下,超出屏幕就鋪滿寬度,然后根據(jù)寬度決定高度的縮放比例,就完美了。
中間還有一些小問題,省略。
以上。
過了很多天,發(fā)現(xiàn)一個(gè)新的問題:
頁面銷毀,不意味著HTML會(huì)改變,同一段HTML的圖片不應(yīng)該被多次加載,浪費(fèi)流量。
不過好在coil是自帶緩存的,替我兜了個(gè)底,不然還是得寫成單例,然后根據(jù)當(dāng)前url來決定是否清理緩存。