動態下發 so 庫在 Android APK 安裝包瘦身方面的應用

現建立了一個Android開發水友圈,圈內會不定時更新一些Android中高級的進階資料,歡迎你們帶着技術問題來討論,共同成長進步!(包含資深UI工程師,Android底層開發工程師,Android架構師,原生性能優化及混合優化,flutter專精);但願有技術的大佬加入,水圈內解決的問題越多得到的權利越大!java

衆所周知 Android 加載 so 文件自己就是一種運行時動態加載可執行代碼的行爲,因此把 so 作成動態下發的沒有什麼技術風險,不過要把這項技術穩定落地到實際生產項目中仍是有很多麻煩的問題。本文根據實際項目經驗,分享一些 so 動態化關鍵技術點和須要避免的坑。

需求價值


以爲有幫助的能夠關注轉發下~android

通常來講,越是成熟的 Android 項目,Native 代碼的貢獻量就越多,以往 APK 體積的主要佔比大都是資源文件,不過如今 Native 代碼帶來的 so 體積佔比也很可觀了,因此 so 動態化的價值愈來愈凸顯。另外一方面,如今支持 arm64 的 Android 項目也愈來愈多,Google Play 更是強制要求支持 arm64,因此有的 Android 項目須要內置兩種甚至以上 abi 支持(好比 B 站客戶端項目就同時支持 arm32/arm64/x86 三種,以往還支持 arm5),結果就是 so 體積成倍地上漲。所以,能不能將非主要的 abi 相關的 so 文件動態化,也成爲了國內 Android 項目瘦身優化不得不優先考慮的問題。面試

此外,一些第三方 SDK 庫也自帶了很多 so 庫(好比騰訊視頻 SDK,之前我在接入這個 SDK 的時候,項目自己才 15 MB 體積,而 SDK 自身 so 已經佔了 17 MB),或許是爲了精簡第三方 SDK 帶來的體積,或許是爲了隔離第三方 SDK 的 API(項目只自身依賴本身定義的業務相關性 API,經過依賴注入的方式訪問第三方 SDK 的實現,這樣之後更換 SDK 的時候只須要切換依賴注入的形式便可),都須要具體的 so 動態化方案提供技術支撐。後端

動態化須要解決的問題數組


動態下發 so 庫,看上只是把本來就算運行時動態加載的 so 文件,從 APK 安裝包裏面抽離出來,工做流程上變化不大,但實際上這也是一種完備的插件化技術,也就是說全部插件須要面臨問題的問題咱們通通須要考慮。如下我針對實際投產時遇到的問題進行一一分析講解。安全

1. 安全性問題性能優化

動態化本質上就是運行時加載可執行代碼,而全部可執行代碼在拷貝安裝到安全路徑(好比 Android 的 data/data 內部路徑)以前,都有被劫持或者破壞的風險。so 動態化也不得不考慮這個安全性問題,最好的作法是每次加載 so 庫以前都對其作一次安全性校驗。考慮到檢查帶來的時間成本,能夠假設內部路徑是無條件可信的(對 Android 來講, data/data 路徑在設備 root 狀況下是不安全的;並且除了劫持風險外,內部路徑文件有可能被應用自身一些不當文件操做給破壞致使插件不完整,所以若是要考慮絕對安全,內部路徑插件被加載也必須作安全檢查),在 so 文件拷貝到內部路徑後單獨作一次檢查,檢查失敗就丟棄文件走 fail 邏輯,檢查經過就生成一個 flag 文件做爲標誌,之後經過判斷 flag 標誌是否存在來決定是否須要執行安全檢查。服務器

怎麼校驗安全性呢?最簡單的方式是記錄 so 文件的 MD5 或者 CRC 等 Hash 信息(粒度能夠是每一個單獨的 so 文件,或者一批 so 文件的壓縮包),將信息內置到 APK 內部或者服務器(若是保存在服務器,客戶端須要經過相似 HTTPS 之類的可信通道獲取這些數據),經過校驗 so 文件 Hash 信息是否一致來確保安全性。不過 Hash 信息通常都會隨之 so 文件的變更而改變,每次都須要調整這些數據比較麻煩,我想到的優化方案是「經過相似 APK 安裝包簽名校驗的方式來確保安全性」:將 so 文件打包成 APK 格式的插件包並使用 Android Keystore 進行簽名,將 Keystore 的指紋信息保存在宿主包內部,安全檢驗環節只須要校驗插件包的簽名信息是否和內置的指紋信息一致便可。(一種優化的方案是,使用和宿主包同樣的 Keystore 給插件包簽名,檢驗環節只須要檢查插件和宿主的簽名信息是否一致。)架構

具體代碼實現能夠進圈獲取併發

2. 版本控制問題

和通常的插件化方案同樣,so 動態化也必須處理好版本控制問題:從 APK 裏把 so 剝離出來後,咱們除了要保證 so 文件的安全性,還要保證 so 文件和依賴它的宿主代碼是 API 兼容的(嚴格上必需要求版本一致,至少作到向前兼容)。若是不須要通常插件那樣考慮升降級問題,那也必須作到 so 文件和 APK 包版本是一致的:宿主下載相應版本的 so 文件後,安裝到指定的版本路徑;宿主版本升級後必須再次下載新版本的 so 文件而不能受到存量舊版本 so 文件的干擾(若是須要作到動態升降級,還須要保留最近一兩個版本的存量 so 文件,用於 fallback 邏輯須要)。

版本控制除了解決插件的 API 兼容問題,還能夠實現「即時吊銷」策略。設想咱們發佈了某一個版本宿主 APK 和與之對應的 so 插件包,而這個版本的 so 是有 Bug 的可能致使 APP 崩潰。經過版本控制流程,咱們能夠在服務端禁用這個版本的 so 插件,從而使客戶端進入「so 插件不可用」的邏輯,而不至於執行有問題的代碼。(若是 so 插件支持動態升降級,還能夠配置讓客戶端強制更新到 fix 插件版本,或者 fallback 回沒有問題的存量舊版。)

從框架設計上,版本控制涉及動態化的 Update 和 Install 兩個環節,具體實現代碼能夠參考圈內連接

3. abi 兼容性判斷

abi 兼容性是 so 插件特有的動態化問題,除了考慮 so 插件是否安全以外,咱們還須要檢查 so 插件包裏的 so 庫 abi 信息是否與宿主目前運行時的 abi 一致。考慮這麼一種狀況:宿主 APK 裏面內置了 ARM32 和 AMR64 兩種 so 文件,一樣插件包裏也內置這兩種 so 文件,當宿主 APK 安裝在 ARM32 的設備上,動態加載 so 插件的時候,咱們必須只解壓並加載相應 AMR32 的 so 插件,對於 ARM64 的設備也是一樣的道理。也就是說:一樣的 APK 宿主,一樣的 so 插件,安裝在不一樣 abi 設備上時,動態化框架的插件處理行爲是不同的。

這個問題也但是說是版本控制問題上面的一個分支問題。考慮到框架的完備性,框架自身應該能自動設別和處理好 abi 兼容問題,而不是經過 so 插件的打包流程來規避這個問題(容錯)。

4. System#load 加載代碼侵入問題

侵入性問題也是 so 插件特有的問題,這個問題跟 Android Framework 加載 so 庫的具體方式有關。Framework 通常不讓用戶直接經過 dlopen 函數加載動態連接庫,而是封裝瞭如下兩種加載 so 庫的方式(實際上第二種最終也是須要經過 libName 找到具體的 so 文件路徑,再經過文件路徑加載 so 庫,與第一種方式異曲同工):

public final class System {
    // 方式一:經過 so 文件路徑加載
    public static void load(String filename) {
Runtime.getRuntime().load0(VMStack.getStackClass1(), filename);
    }
    // 方式二:經過 so 庫名加載
    public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
    }
}

一般狀況下,咱們是經過 方式二 以 System.loadLibrary("xxx") 的方式來加載 so 文件 libxxx.so,而將 so 文件動態化以後,咱們須要將 so 文件安裝到內部安全路徑,在經過 方式一 以 System.load("{安全路徑}/libxxx.so") 的方式來加載。這種方案是大部分 so 動態化項目採用的方案,一直以來也都能穩定工做,不過咱們也在這個方案裏發現了很多麻煩。

採用 方式一 做爲 so 動態化的方案,意味着代碼裏要寫死 System.load("{安全路徑}/libxxx.so") 。這樣一來,首先咱們在代碼調節階段就蛋疼了,Native 代碼在開發階段徹底能夠用傳統的內置方案進行調試,在集成階段再按動態化的方案打包,這也就意味着咱們必須頻繁地在 方式一 和 方式二 直接來回修改,代碼侵入性問題很是嚴重。然而這還不是最麻煩的問題,對於第三方的 SDK 項目的動態化問題,若是 SDK 項目自己的 so 庫是以 方式二 的方式加載(正常的開發方式,對於一些自身就帶有 so 文件下載邏輯的 SDK 項目,則極可能是以 方式一 加載的,這種狀況下反而問題不大),則可能須要藉助 ASM 這種「曲線救國」的方式來把 SDK 項目裏 so 加載的相關代碼修改爲 方式一;或者選擇在準備好 so 插件以後當即以 方式一 把插件裏的全部 so 文件加載進宿主,這樣能夠兜住插件裏 方式二 的加載代碼(若是目標 so 庫已經加載過一次,則 方式二 加載代碼變成一個空實現)。

解決 so 動態化的 System#load 代碼侵入問題,要借鑑 Android 熱修復技術方案的思路:按 方式二,即經過 System#loadLibrary("xxx" ) 加載 so 庫, Android Framework 會遍歷當前上下文的 ClassLoader 實例裏的 nativeLibraryDirectories 數組,在數組裏全部的文件路徑下查找文件名爲 libxxx.so 的文件,因此咱們的解決思路就是在安裝好 so 插件以後,將其所在的內部安全路徑注入到這個 nativeLibraryDirectories 數組裏,便可實現經過 方式二 加載。(思路雖然簡單清晰,不過 在實際應用中仍是有很多問題,之後在具體的解決方案中進行詳細說明。)

如下篇幅 對 so 動態化的方案和具體技術細節給出咱們的分析和答案。

具體方案


1. 系統加載 so 庫的工做流程

當咱們調用 System#loadLibrary("xxx" ) 後,Android Framework 都幹了些了啥?

簡單來講,Android 的 so 加載機制,大體能夠分爲如下四個環節:

  1. PMS install:安裝 APK 包的時候,PackageManagerService 根據當前設備的 abi 信息,從 APK 包裏拷貝相應的 so 文件。
  2. Native classpath:啓動 APP 的時候, Android Framework 建立應用的 ClassLoader 實例,並將當前應用相關的全部 so 文件所在目錄注入到當前 ClassLoader 相關字段。
  3. so loading:調用 System.loadLibrary("xxx"), Android Framework 從當前上下文 ClassLoader 實例(或者用戶指定)的目錄數組裏查找並加載名爲 libxxx.so 的文件。
  4. jni calling:調用 so 相關 JNI 方法。

大體流程示意圖以下:

具體流程以及方法調用鏈這裏不作深刻討論根據這個流程以及上面提到的「加載代碼侵入問題」,按照 System.loadLibrary("xxx") 加載代碼和 JNI 方法相關類(如下統稱 JNI 代碼)所在的 ClassLoader 實例不一樣,so 動態化技術能夠分爲「JNI 代碼隔離」和「JNI 代碼內置」兩種解決方案。

2. JNI 代碼隔離方案

顧名思義,就是將涉及到的 JNI 代碼拆解到一個獨立的模塊,一同打包進 so 插件包裏。運行時動態加載 so 庫的時候,先給 so 插件建立一個插件 ClassLoader,在插件 ClassLoader 內部執行「so loading」和「jni calling」。代碼隔離方案的優勢是是可以作到插件模塊編譯隔離,其餘模塊的代碼沒法 Reference 插件裏面的相關 JNI 方法,不容易干擾 JNI 調用的生命週期,後續維護成本低(這也是通常的插件化方案須要作到的目標)。同時缺點也是很是明顯的:根據項目歷史包袱的具體狀況,模塊拆解成本可能比動態化改造的收益還大。所以,代碼隔離方案比較適合新增的 Native 模塊,一開始就奔着動態化、延遲加載的方向去。

3. JNI 代碼內置方案

考慮到拆解 JNI 模塊的技術成本,能夠考慮先單獨把 so 文件單獨打包進插件包,JNI 代碼保留在宿主代碼內部,so 插件共用宿主的 ClassLoader 實例,「so loading」和「jni calling」依舊保留在宿主內部執行。這種「偷懶」的 JNI 代碼內置方案相對於隔離方案來講改造難度要小得多,相應地因爲沒有把代碼拆解乾淨,很是容易形成代碼污染問題,後續維護成本大。考慮到時間成本,我相信大部分項目只能選擇 JNI 代碼內置方案。畢竟代碼污染問題,能夠經過 Code Review、Lint 靜態檢查等方式來增強「代碼准入」門檻,緩解問題。這裏須要特別強調的是,相比於代碼隔離方案,JNI 代碼內置方案有個特有的技術問題不得不解決:向 nativeLibraryDirectories 注入 so 插件路徑帶來的 集合併發修改 問題。因爲 nativeLibraryDirectories 的具體實現是一個 ArrayList 實例,其元素讀寫操做自身是不保證線程安全的,而咱們在 Worker 線程加載 so 插件的環節最後須要將新的 so 文件路徑注入到 ArrayList 集合裏,若是這時候恰好有另外一個線程由於執行「so loading」操做而正在遍歷集合元素,則會拋出 ConcurrentModificationException(ArrayList 內部實現)。

解決併發修改問題的思路有兩種:

  1. 給「so loading」和「 so 文件路徑注入」這兩種操做同時上鎖,鎖的實例是 so 相關的 ClassLoader 實例。
  2. 在全部「so loading」操做以前(好比冷啓動初始化環節)就預先注入預留好的 so 文件路徑。

思路 1 比較簡單合理,不過加鎖的操做須要「侵入」其餘全部相關的 System.loadLibrary("xxx") 調用,一樣容易形成代碼污染問題;而思路 2 總感受有點違反程序設計的通常原則(有些 so 插件可能基本用不上,犯不着在一開始就把其路徑注入進來),具體取捨要看項目實際狀況。做爲補充,思路 1 能夠再優化一下:爲了不加鎖操做帶來的代碼污染,能夠繞個彎子在編譯階段經過 ASM 手段給自動給全部「so loading」上鎖;或者在往 ClassLoader 注入路徑的時候,不要在原有的 nativeLibraryDirectories 集合上作修改,而是從新 new 一個 List 實例把全部的路徑都拷貝到新集合上,最後再總體塞回去 ClassLoader,避免併發修改異常,代價是容許出現併發讀髒數據問題(不至於崩潰)。咱們這兩個思路都有嘗試,實際投產用的是思路 2,除了污染問題以外,主要是由於下面談到的「dlopen 問題」。

4. 處理 dlopen 問題

dlopen 是 Native 開發比較熟悉的一個函數,其功能是以指定模式加載指定的動態連接庫(使用 dlclose 來卸載打開的庫)。實際上,Android Framework 加載 so 庫的 System.loadLibrary("xxx") 調用,最後也是經過 dlopen 來實現 ,大體的調用路徑以下:

Sysytem#loadLibrary --> Sysytem#load --> Runtime#nativeLoad
                                           Java  +
                                                 |  Native
                                          dvmLoadNativeCode --> dlopen

在 NDK 開發中,若是咱們有兩個 so 文件:libxxx.so 和 liblog.so(後者是基礎庫,前者須要依賴後者的 API),xxx 須要動態連接 log,具體體如今 CMake 配置以下:

...
TARGET_LINK_LIBRARIES(xxx liblog.so)
...

則當咱們調用 System.loadLibrary("xxx") 的時候,Android Framework 會經過上面提到的調用鏈最終經過 dlopen 加載 libxxx.so 文件,並接着經過其依賴信息,自動使用 dlopen 加載 liblog.so(第二步沒有返回 System#load,而是直接在 Native 層面執行)。對於熟悉 Native 開發的同窗來講可能司空見慣,但對於只在第三方 SDK 裏接觸過 so 文件的同窗來講,應該不太知道這一點。然而偏偏正是這一點,給 so 動態化添加了很是大的困難,也讓咱們在具體的實踐項目中吃了很大的虧。

根據項目經驗,如今不管是插件化技術,或者是熱修復技術,裏面關於動態加載 so 文件的技術方案應該至關成熟,全部的坑都踩得七七八八,就算有沒有解決的坑,那應該也不不會嚴重到影響項目方案可行性的地步。因此一開始,咱們把動態化方案主要的風險評估放在模塊代碼拆解方面,而徹底沒有擔憂技術風險。實際上,在 Android N 之前,只要你將 libxxx.so 和 liblog.so 所在的文件目錄路徑都注入到當前 ClassLoader 的 nativeLibraryDirectories 裏,則在加載 so 插件的時候,這兩個文件都能正常被找到。而從 N 開始狀況就不同了:libxxx.so 能正常加載,而 liblog.so 會出現加載失敗錯誤。具體異常以下:

E/ExceptionHandler: Uncaught Exception java.lang.UnsatisfiedLinkError: dlopen failed: library "liblog.so" not found
at java.lang.Runtime.loadLibrary0(Runtime.java:xxx)
at java.lang.System.loadLibrary(System.java:xxx)
...

其主要緣由是,Android Native 用來連接 so 庫的 Linker.cpp dlopen 函數 的具體實現變化比較大(主要是引入了 Namespace 機制):以往的實現裏,Linker 會在 ClassLoder 實例的 nativeLibraryDirectories 裏的全部路徑查找相應的 so 文件;更新以後,Linker 裏檢索的路徑在建立 ClassLoader 實例後就被系統經過 Namespace 機制綁定了,當咱們注入新的路徑以後,雖然 ClassLoader 裏的路徑增長了,可是 Linker 裏 Namespace 已經綁定的路徑集合並無同步更新,因此出現了 libxxx.so 文件能找到,而 liblog.so 找不到的狀況。

至於 Namespace 機制的工做原理了,能夠簡單認爲是一個以 ClassLoader 實例 HashCode 爲 Key 的 Map,Native 層經過 ClassLoader 實例獲取 Map 裏存放的 Value(也就是 so 文件路徑集合)。

我以前琢磨着,Tinker 之因此一直沒有把 dlopen 問題暴露出來,主要是由於 Tinker 是熱修復框架,補丁插件裏須要的 liblog.so 文件,每每在宿主裏原本就有內置一份,因此只會致使熱修復部分失效,而不會出現 liblog.so 找不到問題。而實際上好巧不巧,Tinker 在解決 Android N 的混合編譯帶來的熱修復失敗問題時,在往 ClassLoader 注入插件 so 文件路徑的時候,會建立一個新的 AndroidNClassLoader 實例用來替換 APP 自身的 ClassLoader,這個替換的操做恰好一併兜住了 dlopen 問題。至於其餘插件化框架裏爲什麼沒有提到這個問題,大概是由於通常適合動態化改造的插件都比較輕量,通常不會有 Native 代碼(就算有也每每沒有 so 依賴)。

解決 dlopen 問題主要有如下幾個思路:

自定義 System#load,加載 libxxx.so 前,先解析 libxxx.so 的依賴信息,再遞歸加載其依賴的 so 文件(推薦參考開源方案 SoLoader[7] )。

自定義 Linker,徹底本身控制 so 文件的檢索邏輯(推薦參考開源方案ReLinker [8] )。

相似 Tinker,在合適的時機替換 ClassLoader 實例(這是咱們如今投產的方案)。

5. so 依賴分析工具

上面提到的都是 so 動態化方案中的具體技術難題,剩下的都是一些繁瑣的項目問題了(技術債務),好比上面提到的 so 依賴分析。想要把 so 動態化技術應用到 APK 的瘦身項目中來,除了分析哪些 so 文件體積佔比比較大以外,最好的作法是將其依賴的全部 so 文件必定挪到插件包裏。怎麼了解 APK 裏全部 so 文件具體的依賴信息呢?根據 so 文件模型手擼代碼解析依賴信息當然可行,不過那都是大神乾的活,吾等平凡之輩仍是選擇站在巨人的肩膀上。

這裏推薦一款 Google 開源的 APK 解析工具 android-classyshark [9],除了提供分析 APK dex/so 依賴信息以外,它還提供了 GUI 可視化界面,很是適合快速上手。

其餘問題


相關 JNI 類污染問題

JNI 方法須要在加載完成相應的 so 庫才能正常調用,因此有很多開發選擇將 System#loadLibrary("xxx" ) 之類的代碼寫在 JNI 類的靜態代碼塊,以保證在訪問 JNI 以前必定會先完成 so 庫加載。不過這實際上很是不「Best Practice」:一方面,加載 so 本來就屬於一種動態化技術,其自身就存在失敗的可能性,並且 Native 開發在 Android 上一直存在諸多「疑難雜症」,最好的辦法是考慮全部 so 加載和 JNI 方法調用失敗的可能性;另外一方面,加載 so 文件自己就有些許性能損耗,在靜態代碼塊中加載會加重性能問題。最麻煩的是,so 動態化改造以後,若是項目後續開發中有人不當心在 so 插件還沒有安裝完成以前引用了相關的 JNI 類(好比訪問靜態方法),哪怕沒有發生實際的方法調用,也會致使 JNI 類提早被 ClassLoader 加載,進而提早觸發 System#loadLibrary("xxx" ) 邏輯,觸發 Crash。

對於項目已有的 JNI 代碼,若是存在「靜態代碼塊加載 so 問題」,則在改形成動態化的時候,最好將相關加載代碼挪出靜態代碼塊,而且增長 so 加載失敗時候的 onFail 邏輯,確保全部 so 加載和 JNI 方法調用都不會出現崩潰問題。

代碼後續維護成本

這也是我目前比較頭疼的問題,因爲採用了「JNI 代碼內置方案」,沒有對 JNI 代碼進行編譯隔離,很是容易致使後續代碼維護過程當中,在不正確的生命週期裏訪問了動態化 so 相關的 JNI 方法,增長 Crash 的風險。

按照以往的動態化項目經驗,「比較穩定,代碼變化不大,模塊邊界比較內聚」的業務比較適合動態化改造,因此 so 動態化應該優先選擇這種類型的模塊,不管是改形成「JNI 代碼隔離方案」,仍是後續的維護成本,都相對要小許多。對於那些代碼耦合比較嚴重,版本迭代很是活躍的業務模塊,這是一個典型的「在高速行駛的火車上更換引擎」的問題:在動態化改造的同時,FT 代碼還在並行迭代,勢必會產生許多衝突;對於耦合比較嚴重的代碼,考慮投入產出比的話通常都會選擇「JNI 代碼內置」方案,沒有對 JNI 代碼進行編譯隔離,因此很是容易致使 Crash;改造完成後,後續 FT 代碼變更頻繁,後續代碼維護壓力大,並且多是。

目前我以爲比較靠譜的處理方案是從項目管理流程上找突破點,主要方向最好仍是讓 FT 開發本身負責本身模塊的動態化改造工做,下降維護成本(考慮到業務團隊跟質量團隊之間績效目標的衝突,可能難以推進)。同時須要儘可能根據項目的實際須要完善動態化框架以及相關配到的指導文檔,下降 FT 的接入成本。做爲輔助,還須要給容易產生代碼衝突的地方加上相應的靜態檢查 Case,以便及時發現問題。

持續集成、部署問題(CI/CD)

踩了上面一系列的坑,眼看着動態化技術方案完善得七七八八了,實際上 咱們纔剛剛開始而已!

首先,怎麼編譯出 so 插件包也是個技術活,這一點要根據具體的項目狀況選擇合適的方案(咱們選用的是 Gradle 插件在 PackageApplication 階段抽取目標資源文件)。這個是一個 CI 問題,換句話說就是咱們須要一個穩定靈活的流水線,用於穩定編譯咱們指定版本的 so 插件包,而不是每次都經過很是手工、笨拙的方式編包。其次,插件包編譯以後,不該該經過手工的方式把文件上傳到後端,在填寫相關的版本、依賴等配置信息。這是一個 CD 問題(Continuous Deployment),咱們應該採用自動化的手段(哪怕只是腳本),在集成階段以後收集須要的配置信息,自動上傳到一個內部環境的管理平臺(平臺上咱們能夠查看每一個版本的數據),在 Test/Release 階段根據須要將指定版本的配置信息「一鍵導入」到測試、預發佈環境,每一個環節上都要儘可能避免人工操做。

所以,從工程管理的角度來看,一個完備的動態化方案,必須涵蓋集成、部署、加載框架三個流程的內容,而前面的兩點是大多數動態化項目或者技術文章沒有說起到的,每每容易被忽視。

Play Store 動態代碼禁用問題

因爲一些衆所周知的緣由,包含有動態代碼的 APK 包是沒法上傳到 Play Store 的。不過實際上 Google 不是禁止動態代碼,而是禁止繞過 Play 渠道下發未通過審覈的動態代碼。通過諮詢,經過 Play 提供的 APK 拓展資源包 Expansion Files 服務,能夠向客戶端下發相關插件資源包,沒有政策風險(該服務主要是面向遊戲客戶端,能夠向 APK 客戶端下發綁定版本的「一個主資源包 + 一個 patch 包」,體積上限個 1G。須要說明的是,用戶發佈特定版本的 APK 以前必須先綁定資源包,一旦發佈就沒法修改)。

尾巴


本文主要是根據我自身實際投產的 Android 動態化項目經驗(SDK 插件、動態組件化)以及最近相關的 so 動態化實踐,分享一些動態加載 so 庫時須要考慮的問題。內容主要包括插件化方案的共同問題、abi 兼容性問題、代碼侵入性問題、併發修改問題,以及最重要也最容易忽視的 dlopen 問題。千言萬語匯成一句話:

插件有風險,投資須謹慎!

以爲有幫助的能夠關注下點個贊,如有任何疑問可經過 私信或者留言聯繫我,留言不必定回

有興趣的能夠點擊我主頁加圈,加圈就有免費的Android進階資料和麪試專題資料領。

相關文章
相關標籤/搜索