最近幾年,騰訊對於開源事業也是愈來愈支持,今天要說的就是在騰訊被普遍使用的Shadow框架,一個通過線上億級用戶量檢驗的反射全動態Android插件框架。 首先,讓咱們來看一下官方對於Shadow的簡介:android
Shadow是一個騰訊自主研發的Android插件框架,通過線上億級用戶量檢驗。 Shadow不只開源分享了插件技術的關鍵代碼,還完整的分享了上線部署所須要的全部設計。git
與市面上其餘插件框架相比,Shadow主要具備如下特色:github
除此以外, Shadow支持的特性有:bash
衆所周知,Android 9.0出現限制非公開SDK接口訪問以後,能夠說當時咱們已知的全部插件框架實現都或多或少的出現了適配問題。你們的應對方式基本上都是一種對抗的思想,有的去破解限制,有的經過和Google溝通淺灰名單有效期暫時續命。框架
遇到了這個問題,咱們沒有選擇和這個策略進行對抗,咱們很是理解Google爲何限制使用非公開SDK接口。因此咱們從新Review了插件框架的本質原理和設計缺陷,進而設計了全新的插件框架Shadow。Shadow沒有使用任何非公開SDK接口,實現了和本來在用的使用了大量非公開SDK接口的實現同樣的功能。工具
在Shadow的Sample中,能夠添加以下所示的代碼來開啓嚴格檢查模式運行,而其餘插件框架並不能作到。測試
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
builder.penaltyDeath();
builder.detectNonSdkApiUsage();
StrictMode.setVmPolicy(builder.build());
複製代碼
好比,咱們看到的一款也宣傳未使用非公開SDK接口,支持Android 9.0的插件框架,在它的Sample中開啓嚴格模式運行後,出現了以下Crash信息:gradle
W/.xxx.sampl: Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (light greylist, reflection)
W/System.err: StrictMode VmPolicy violation with POLICY_DEATH; shutting down.
複製代碼
可見,即便它的實現代碼中沒有出現任何非公開SDK的引用,實際上它依賴的第三方組件內部也使用了非公開SDK接口。ui
所謂全動態,指的就是除了插件代碼以外,插件框架自己的全部邏輯代碼也都是動態的。而且,Shadow框架實際上也作到了這一點,即插件框架的代碼咱們是和插件打包在一塊兒發佈的。spa
全動態化插件框架有多重要呢?其實它比無Hack、零反射實現還要重要!由於有了這個特性以後,就算是咱們用了Hack的方案,須要兼容各類手機廠商的系統。咱們也不須要等宿主App更新才能解決問題。
實際上,Shadow的這個特性是更早實現的。咱們早在2015年就開始大量使用插件技術了。在2016年就實現了這個特性。憑藉這個特性不斷的動態發佈插件框架的代碼,去適配各類兼容性問題。在今年更是應用這個特性,在徹底不跟宿主版本的前提下,將本來的具備上百個反射Hack調用的舊實現更新爲了Shadow無Hack實現。新的Shadow天然也具有這個特性。
在實際使用過程當中,咱們的宿主對於業務接入在增量上有極其苛刻的要求。Shadow接入時只使用了15.1KB,160個方法。而咱們已知的其餘插件框架對宿主的增量通常在110KB,900個方法到370KB,2300個方法之間。
之前的插件框架老是想用一些Hack手段去修改系統行爲,找到系統的漏洞達到目的。Shadow的原則是不去跟系統對抗。既然只是限制非公開SDK接口訪問,而沒有限制動態加載代碼。那麼確定有辦法在不使用非公開SDK接口的前提下實現原來的目的。由於咱們插件技術的目的本質上來講仍是動態加載代碼。
那麼一個重要的原則就是,若是一個組件須要安裝才能使用,那麼就別在沒安裝的狀況下把它交給系統。咱們已知的插件框架中,作的最好的也不符合這個原則,因此儘管它的Hook點少,但就是因爲它將沒有安裝的Activity交給系統了,因此後面就不得不作一些Hack的事修補。
因此套一個殼子的方案就很是好。這種思路其餘框架很早就有了,可是它們一直想把一個插件Activity套在一個宿主Activity之中,而後想辦法實現一個轉調關係。若是插件Activity是一個真的Activity,那這個插件就能夠正常編譯安裝運行,對開發插件或者直接上架插件App很是有利。可是因爲它是個系統的Activity子類,它就有不少方法不能直接調用,甚至還可能須要避免它的super方法被調用。若是插件Activity不是一個真的Activity,只是一個跟Activity有差很少方法的普通類,這件事就簡單多了,只須要讓殼子Activity持有它,轉調它就好了。但這種插件的代碼正常編譯成獨立App安裝運行會比較麻煩,代碼中可能會出現不少插件相關的if-else,也很差。
Shadow作了一個很是簡單事,經過運用AOP思想,利用字節碼編輯工具,在編譯期把插件中的全部Activity的父類都改爲一個普通類,而後讓殼子持有這個普通類型的父類去轉調它就不用Hack任何系統實現了。雖說是很是簡單的事,實際上這樣修改後還帶來一些額外的問題須要解決,好比getActivity()方法返回的也不是Activity了。不過Shadow的實現中都解決了這些問題。
Shadow框架的原理示意圖以下:
第一次clone Shadow的代碼到本地後,建議先在命令行編譯一次。
在命令行測試編譯時能夠執行以下編譯任務:
./gradlew build
複製代碼
若是沒有出錯,再嘗試用Android Studio打開工程。
而後就能夠在IDE中選擇sample-host模塊直接運行便可,以下: