Shadow解決插件和宿主有同名View的方法解析

問題描述

在「免安裝運行App」這個場景中,插件代碼一般和宿主是徹底不相關的。甚至項目都是獨立管理的,插件和宿主是不一樣團隊開發,不一樣版本發佈管理的。在這種狀況下,插件和宿主中出現相同名字的類是很是常見的。只要設計好ClassLoader的結構,將插件和宿主的ClassLoader隔離開,就能夠避免Java層面的類衝突問題。可是,Android系統中存在一個錯誤的設計,致使LayoutInflater在inflate的時候使用的View類並非簡單的從ClassLoader中讀取的,而是自行作了一層緩存,以View的類名做爲Key保存了Class對象。這個緩存存儲在一個靜態域上,所以在同一個進程中,不能存在兩個同名的View都使用LayoutInflater構造。這個問題常見於插件框架對於support包中的RecycleView支持上。無論這個問題的話,當宿主和插件都使用了support包中的RecycleView,就會出現提示信息相似於「RecycleView cannot cast to RecycleView」的Crash異常。git

傳統的解決方案

方法一,逃避法。我見過一些插件框架,在宿主和插件都用到support包時,就將插件用的support包去掉,讓插件直接使用宿主的support包。這樣作能夠避免前面例子中使用RecycleView等support包中的View的問題。可是既不能避免其餘宿主和插件中重名的自定義View的問題,也使得插件使用的support包版本和預期的不一致了。github

方法二,清空緩存法。還有一些插件框架,經過反射修改LayoutInflater的緩存,清空緩存達到目的。這樣作有兩個問題,一是宿主隨時可能再次inflate view,再次寫入緩存。插件框架可能要頻繁清空緩存,甚至每次使用LayoutInflater前都要清空緩存。這樣作會使得LayoutInflater失去緩存機制,形成性能降低。二是經過反射修改這個緩存是不安全的,須要兼容多種版本的Android系統,還會涉及訪問系統私有API。緩存

Shadow的解決方案

在開發Shadow的時候,咱們仔細學習了LayoutInflater的設計。發現經過LayoutInflater自帶的設計就能夠解決這個問題。安全

LayoutInflater是能夠繼承重寫的,並且插件中用到的LayoutInflater都是由插件框架中間層提供的Context返回的,所以咱們可讓插件中拿到的全部LayoutInflater都是咱們自定義的LayoutInflater。框架

LayoutInflater具備一個注入Factory的設計,若是設置了LayoutInflater的Factory,LayoutInflater在構造View時就會先試圖讓注入的Factory構造。若是Factory可以構造出來,就會優先使用。若是構造不出來,纔會走內置的構造邏輯。而咱們前面說的問題就是內置的構造邏輯形成的。所以,咱們就寫了一個自定義的Factory,而後複製了本來內置構造邏輯的代碼。在這段邏輯中,也有緩存機制。可是咱們將緩存的Key添加了標記插件apk的「partKey」做爲一部分,這樣相同名字的View在緩存中就是不一樣的Key了。所以,咱們既保留了LayoutInflater本來的緩存設計,還讓它支持了多插件。因此在Shadow中,不論是宿主和插件,仍是多插件之間,均可以使用相同名字的View。性能

這裏還有一個小的知識點,就是一個LayoutInflater對象只能set一次Factory。把咱們自定義的Factory設置進去了,插件的代碼拿到這個LayoutInflater就不能再次set Factory了。可是LayoutInflater的設計又容許clone一個LayoutInflater對象,保留它的的Factory。當新的LayoutInflater被set Factory時,一個內置的邏輯會將兩個Factory合併起來使用。所以在Shadow的代碼中,能夠看到ShadowLayoutInflater中包含了一個InnerInflater,套了兩層LayoutInflater,就是這個緣由。學習

這部分相關代碼能夠查看com.tencent.shadow.core.runtime.ShadowFactory2類和相關類的實現。優化

PS

最新版本的Android系統已經沒有這個Bug了。爲了兼容低版本的Android系統,這個問題仍是須要解決的。插件

github.com/Tencent/Sha…設計

歡迎你們參與到Shadow的開發中來,Shadow的代碼有很是多值得優化的地方,讓咱們開源共建起來!

相關文章
相關標籤/搜索