Shadow爲何要求插件和宿主包名一致

緣由

咱們過去也用過基於各類反射實現的插件框架,用了3年左右時間,也維護了3年左右時間。在過去維護的經驗中,咱們就發現了插件使用單獨包名(ApplicationId,下同)帶來的問題。c#

ApplicationId通常是在build.gradle中設置的,在編譯時這個字符串會被記錄在2個位置。第1是記錄在應用的AndroidManifest.xml中,第2是記錄在應用的resources.arsc文件中。框架

記錄在AndroidManifest.xml中的包名主要用來構造應用的Context對象。在咱們開發中通常經過context.getPackageName()方法得到到當前應用設置的ApplicationId。可是重要的是,系統也會經過context獲取包名來識別context來自於哪一個安裝的應用。咱們知道系統不容許安裝多個相同ApplicationId的應用,這也是由於系統就是根據這個包名來區分安裝的應用的。經過這個ApplicationId,系統也能夠反向查找到應用安裝在系統中的apk文件路徑。ide

問題在於系統不是很是簡單的和咱們同樣只會調用context.getPackageName()方法得到應用的ApplicationId。還會調用一些私有API獲取,例如getOpPackageName()方法。因此咱們過去以Hack方式實現的插件框架中,只能不停的兼容各類OEM系統、各類Android版本,在Override了這些獲取PackageName的方法後,經過拋一個Throwable,查詢當前調用來自哪裏。若是來自於系統,咱們就會返回給系統宿主的包名。不然系統就會拿到一個沒有安裝的包名,拋出SecurityException,形成Crash。gradle

在設計Shadow時,咱們堅持一個原則來避免使用私有API,就是經過一層中間件將插件代碼變成宿主代碼的一部分。實際上這個過程是能夠經過手工將中間件和插件代碼都寫在宿主中達到相同效果的。因此在這個設計中,插件代碼實際上就是宿主代碼的一部分。既然是一部分,ApplicationId怎麼會不同呢?因此要求插件和宿主的ApplicationId保持一致,就永遠不會將插件代碼沒有安裝這件事暴露給系統。ui

記錄在resources.arsc的ApplicationId其實算一個小問題了。Resources對象上有一些API是接收包名做爲參數的,若是這個包名在獨立安裝和插件環境下動態獲取的不同,那麼有可能形成這些API失效,找不到想要的資源。插件

應對措施

Shadow代碼中並無什麼對包名的特殊處理邏輯,只有一處檢查包名是否一致的邏輯(com.tencent.shadow.core.loader.blocs.ParsePluginApkBloc#parse)。去掉這段邏輯大部分狀況下插件也是能夠運行的。只有在一些OEM手機的特殊場景會出問題,好比一些國產手機系統的WebView或者輸入欄中長按彈出菜單就有可能會Crash。因此去掉了這個限制後,就須要不停地去兼容各類OEM系統。這也不是不可行,由於Shadow有全動態的設計,插件框架的兼容代碼也能夠動態更新。設計

可是咱們認爲更合理的方法仍是保持插件和宿主包名一致。只須要有一套完善的自動構建CI/CD,針對不一樣渠道自動修改ApplicationId,編譯出ApplicationId不一樣的插件包去分發也不是很難的事情。關鍵是,這件事看起來麻煩,實際上長期來看不須要人工干預。而不停地兼容OEM系統,則須要長期投入人力人工分析解決。code

因此,建議你們保持插件和宿主包名一致。xml

相關文章
相關標籤/搜索