背景說明
在業務開發過程當中常常會進入一些三方sdk,這些三方的sdk引入so庫,有些so庫文件還比較大,這時候咱們就須要考慮so庫從網絡獲取異步加載,減小發布包的體積java
傳統方案
關於so異步加載方案,網上的資料隨便搜下大把,核心思想
so庫文件放到網絡->下載到本地沙盒->經過System.load載入
看起來挺簡單的 然而挺多資料沒提到的是android
so庫存在依賴關係
好比當你把libunity.so下載下來經過
System.load("/data/data/com.example.soload/files/libunity.so")
加載的時候會獲得以下異常git
java.lang.UnsatisfiedLinkError: dlopen failed: library 「libmain.so」 not found
at java.lang.Runtime.loadLibrary0(Runtime.java:1071)
at java.lang.Runtime.loadLibrary0(Runtime.java:1007)
at java.lang.System.loadLibrary(System.java:1667)
緣由分析github
解決方案編程
載入libunity.so以前須要先載入libmain.sojson
擴展思考api
咱們寫代碼的時候怎麼控制加載順序呢
https://github.com/facebook/S...
https://github.com/KeepSafe/R...
以上2個開源庫 都有處理相關邏輯
核心思想就是解析so庫ELF格式,分析依賴並遞歸,直到依賴庫都加載完成再載入自身
這裏不擴展開來 有興趣的夥伴能夠看源碼研究下網絡
text relocations問題
當你把liblbs.so下載下來經過app
System.load("/data/data/com.example.soload/files/liblbs.so")的時候又出現意外了
java.lang.UnsatisfiedLinkError: dlopen failed: 「/data/data/com.example.soload/files/liblbs.so」 has text relocations ( https://android.googlesource....
at java.lang.Runtime.load0(Runtime.java:938)
at java.lang.System.load(System.java:1631)==
緣由分析:異步
https://blog.csdn.net/chjqxxx...
擴展思考:
咱們能夠經過命令進行自查
有源碼的能夠從新編譯,沒有源碼的只能經過下降targetSdkVersion處理(然而也是治標不治本)
進階方案
除了System.load方案 還有沒有其餘方案呢
答案固然是確定的
https://github.com/Tencent/ti...
核心思路:經過反射把自定義的native庫path插入nativeLibraryDirect ories最前面,即便安裝包libs目錄裏面有同名的so,也優先加載指定路徑的外部so
這裏截取tinker部分代碼
com.tencent.tinker.lib.library.TinkerLoadLibrary.java
這個方案是否是看起來更簡單了,因爲這個方案是很是規手段,存在兼容風險, 咱們須要驗證該方案的兼容性,寫個helloword的so,經過該方案加載,提早找個版本線上帶上去,進行埋點統計
截至目前最新版上線2天 統計到數據以下
其中失敗的1例是上線前自測故意讓校驗失敗產生的
也就是說截至目前能夠認爲改方案是靠譜的
固然了若是後續有新的Android系統版本更新
咱們還須要關注新版本的兼容狀況
項目應用
上面鋪墊了那麼多,如今進入正題
技術方案是一回事,落實到實際項目是另外一回事
如今開始咱們思考下以下幾個問題
- 項目中引用的so庫都是那些功能再用什麼時候載入
- 三方jar(aar)引入的so庫加載時機不受咱們代碼邏輯控制
- 使用的so庫那些適合作異步加載
- so庫文件沒有下載完成以前 用戶用相關功能如何處理
- 版本迭代so文件升級如何處理
- 打包階段如何把so文件進行剔除
解決問題一、二、3
咱們能夠經過切面編程的思路解決,這裏用到了開源庫https://github.com/HujiangTec...
因爲so庫的加載都是經過調用系統函數System.loadLibrary進行,
那麼咱們全局攔截該函數的調用並打印調用鏈,
運行app,配合日誌,就能分析so庫的具體使用狀況
這裏咱們須要根據本身業務場景分析,好比app進入到首頁依賴的庫不建議進行異步加載,須要異步加載的庫最好是完成一個功能(咱們須要按照經驗進行邏輯分組 參考文章開頭的圖)
解決問題四、5
Android是依據Activity做爲活動單位,啓動Activity是經過startActivity函數調用進行的,那麼咱們就能夠攔截織入本身的邏輯
大體思路以下
對應實現部分代碼截圖
經過規則配置化+字節碼攔截的邏輯 對原有業務無侵入便可實現動態加載
研發關注正常的業務邏輯 不須要針對性編寫代碼
後續引入新的so庫,修改配置文件便可
在實現的過程當中有些細節是須要考慮的
好比校驗下載文件的完整性,下載要不要支持斷點續傳,so文件須要更新如何處理等
解決問題6
經過上面的介紹,咱們實現了so文件的異步加載
可是打包的時候 咱們如何剔除so 減小最終發佈包的大小
能夠在build.gradle進行配置
可是上面提到了咱們是經過json配置的規則
這樣再重複配置一遍
並且還有可能2處配置不一致
並且咱們的大原則是解耦
那麼咱們就繼續hook打包流程
彙總小結
經過上面的介紹大體說明了原理
有興趣的夥伴能夠評論區留言,提出本身的想法
目前還在灰度驗證階段,等線上驗證穩定後在評論區放出相關代碼
so異步加載方案大同小異,該方案跟其餘方案相比
我的感受特點就是侵入性低
接入只須要3個步驟
未完待續
目前該方案還存在幾個點有待優化
====================
問題1
目前用的是進階方案實現的so庫加載 比較理想的方式是傳統方案和進階方案都應該支持 一開始其實我想用傳統方案去實現 可是該方案在unity那塊有問題(unity相關so的加載邏輯有點特殊 存在經過jni直接加載so的狀況 因爲沒有源碼 內部邏輯不太清楚 ) 後續還須要研究完善 畢竟官方System.load是官方提供的api 更可靠
問題2
進度加載的dialog是依附於調用startActivity的窗口
若是startActivuty以後裏面 立馬調用finish會致使dialog泄露
這個是須要注意的一個點 也就意味着對原有邏輯存在必定依賴
這個能夠經過啓動一個FLAG_ACTIVITY_NEW_TASK類型的activity
代理解決 可是感受這樣有點重 目前還有點糾結
問題3
目前文件只是放到了阿里雲,用戶的網絡環境複雜多變,須要考慮支持騰訊雲,七牛雲等更多的服務商。
問題4
上傳到oss的動做目前是動手完成的,須要作成腳本。