今日快訊
昨天向大家推送了我這個公眾號的第一篇推廣文章,最後的反響算是褒貶不一吧。我後來自己有反思一下,覺得還是應該給大家道個歉,這畢竟是我所接的第一篇推廣,很多方面都考慮不周。其中最大的失誤就是只做了宣傳,沒有做福利。僅僅向大家介紹了這樣一個付費的面試專欄,卻忘記申請優惠折扣了,這樣的話大家雖說從我這裡得知了這個產品,卻沒有享受到任何額外的好處。以後我會謹記這點,如果再做付費產品推廣的話,一定要向推廣方申請優惠,沒有優惠的話我就拒絕幫他推廣。
再者就是幫小專欄平台說句公道話吧,昨天被評論區里的幾位朋友帶了節奏,因為我向來都是把正負面評論全放出來的。小專欄作為一個技術社區,付費專欄只是其中一部分,裡面也有大量的免費專欄的。昨天推廣的《Android面試指南》專欄確實是十幾位技術大牛花了不少時間寫出來的面試總結、職業生涯規劃等內容,是具有一定的價值的。大家也真沒必要一聽付費就談虎色變,覺得文章就應該都是免費的。如果每個作者都願意把自己寫的文章免費分享出來那當然是最好的事,但如果有作者希望自己的文章要付費才能閱讀,我覺得我們也應該去理解,然後根據自己的需要,選擇付費還是忽略,真的不用一聽是要錢的就開噴,希望我們每個人都盡量理性一點吧。
作者簡介
本篇來自lucky_billy的投稿,分享了android 組件開發中一種更高效的組件自動註冊方案,一起來看看!
lucky_billy的博客地址:
前言
組件自動註冊方案: 在編譯時,掃描即將打包到 apk 中的所有類,將所有組件類收集起來,通過修改位元組碼的方式生成註冊代碼到組件管理類中,從而實現編譯時自動註冊的功能,不用再關心項目中有哪些組件類了。
特點:不需要註解,不會增加新的類;性能高,不需要反射,運行時直接調用組件的構造方法;能掃描到所有類,不會出現遺漏;支持分級按需載入功能的實現。
最近在公司做 android 組件化開發框架的搭建,採用組件匯流排的方式進行通信:提供一個基礎庫,各組件( IComponent 介面的實現類)都註冊到組件管理類(組件匯流排:ComponentManager)中,組件之間在同一個 app 內時,通過 ComponentManager 轉發調用請求來實現通信(不同 app 之間的通信方式不是本文的主題,暫且略去)。但在實現過程中遇到了一個問題:
如何將不同 module 中的組件類自動註冊到 ComponentManager 中?
自動註冊到 ComponentManager
目前市面上比較常用的解決方案是使用 annotationProcessor:通過編譯時註解動態生成組件映射表代碼的方式來實現。但嘗試過後發現有問題,因為編譯時註解的特性只在源碼編譯時生效,無法掃描到 aar 包里的註解( project 依賴、maven 依賴均無效),也就是說必須每個 module 編譯時生成自己的代碼,然後要想辦法將這些分散在各aar種的類找出來進行集中註冊。
ARouter 的解決方案是:
運行時通過讀取所有 dex 文件遍歷每個 entry 查找指定包內的所有類名,然後反射獲取類對象。這種效率看起來並不高。
ActivityRouter 的解決方案是( demo 中有2個組件名為 』app』 和 』sdk』):
在主 app module 中有一個@Modules({"app", "sdk"})註解用來標記當前 app 內有多少組件,根據這個註解生成一個 RouterInit 類
在 RouterInit 類的 init 方法中生成調用同一個包內的 RouterMapping_app.map
每個 module 生成的類( RouterMapping_app.java 和 RouterMapping_sdk.java )都放在 com.github.mzule.activityrouter.router 包內(在不同的 aar 中,但包名相同)
在RouterMapping_sdk類的 map()方法中根據掃描到的當前 module 內所有路由註解,生成了調用 Routers.map(…) 方法來註冊路由的代碼
在 Routers 的所有 api 介面中最終都會觸發 RouterInit.init() 方法,從而實現所有路由的映射表註冊
這種方式用一個 RouterInit 類組合了所有 module 中的路由映射表類,運行時效率比掃描所有 dex 文件的方式要高,但需要額外在主工程代碼中維護一個組件名稱列表註解: @Modules({「app」, 「sdk」})
有沒有一種方式可以更高效地管理這個列表呢?
高效地管理映射表
聯想到之前用 ASM 框架自動生成代碼的方式做了個 AndAop 插件用於自動插入指定代碼到任意類的任意方法中,於是寫了一個自動生成註冊組件的 gradle 插件。
大致思路是:在編譯時,掃描所有類,將符合條件的類收集起來,並通過修改位元組碼生成註冊代碼到指定的管理類中,從而實現編譯時自動註冊的功能,不用再關心項目中有哪些組件類了。不會增加新的 class,不需要反射,運行時直接調用組件的構造方法。
性能方面:由於使用效率更高的 ASM 框架來進行位元組碼分析和修改,並過濾掉android/support 包中的所有類(還支持設置自定義的掃描範圍),經公司項目實測,未代碼混淆前所有 dex 文件總計12MB 左右,掃描及代碼插入的總耗時在2s-3s之間,相對於整個apk 打包所花3分鐘左右的時間來說可以忽略不計(運行環境:MacBookPro 15 吋高配 Mid 2015)。
開發完成後,考慮到這個功能的通用性,於是升級組件掃描註冊插件為通用的自動註冊插件 AutoRegister,支持配置多種類型的掃描註冊,使用方式見 github 中的 README 文檔。此插件現已用到組件化開發框架 CC 中
升級後,AutoRegister 插件的完整功能描述是:
在編譯期掃描即將打包到 apk 中的所有類,並將指定介面的實現類(或指定類的子類)通過位元組碼操作自動註冊到對應的管理類中。尤其適用於命令模式或策略模式下的映射表生成。
在組件化開發框架中,可有助於實現分級按需載入的功能:
在組件管理類中生成組件自動註冊的代碼
在組件框架第一次被調用時載入此註冊表
若組件中有很多功能提供給外部調用,可以將這些功能包裝成多個 Processor,並將它們自動註冊到組件中進行管理
組件被初次調用時再載入這些 Processor
我們看下具體的實現過程。
準備工作
首先要知道如何使用 Android Studio 開發 Gradle 插件:
了解 TransformAPI:Transform API 是從 Gradle 1.5.0版本之後提供的,它允許第三方在打包 Dex 文件之前的編譯過程中修改 java 位元組碼(自定義插件註冊的 transform 會在 ProguardTransform 和 DexTransform 之前執行,所以自動註冊的類不需要考慮混淆的情況)。參考文章有:Android 熱修復使用 Gradle Plugin1.5改造 Nuwa 插件(主要看前半部分關於TransformAPI的介紹,Nuwa 相關的內容可先忽略):
位元組碼修改框架(相比於 Javassist 框架 ASM 較難上手,但性能更高,但相學習難度阻擋不了我們對性能的追求):
ASM 英文文檔 :
http://download.forge.objectweb.org/asm/asm4-guide.pdf
ASM API 文檔 :
http://asm.ow2.org/asm50/javadoc/user/index.html
Android 熱修復方案 Tinker (七) 插樁實現(主要看關於 ASM 使用的介紹及與 transformAPI的結合):
構建插件工程
按照如何使用 Android Studio 開發 Gradle 插件文章中的方法創建好插件工程並發布到本地maven 倉庫(我是放在工程根目錄下的一個文件夾中),這樣我們就可以在本地快速調試了。
build.gradle 文件的部分內容如下:
根目錄的 build.gradle 文件中要添加本地倉庫的地址及 dependencies:
在 Transform 類的 transform 方法中添加類掃描相關的代碼:
CodeScanProcessor 是一個工具類,其中 CodeScanProcessor.scanJar(src, dest) 和CodeScanProcessor.scanClass(file) 分別是用來掃描 jar 包和 class 文件的
掃描的原理是利用 ASM 的 ClassVisitor 來查看每個類的父類類名及所實現的介面名稱,與配置的信息進行比較,如果符合我們的過濾條件,則記錄下來,在全部掃描完成後將調用這些類的無參構造方法進行註冊:
記錄目標類所在的文件,因為我們接下來要修改其位元組碼,將註冊代碼插入進去:
掃描完成後,開始修改目標類的位元組碼(使用 ASM 的 MethodVisitor 來修改目標類指定方法,若未指定則默認為 static 塊,即 方法),生成的代碼是直接調用掃描到的類的無參構造方法,並非通過反射。
class 文件: 直接修改此位元組碼文件(其實是重新生成一個 class 文件並替換掉原來的文件)
jar 文件:複製此 jar 文件,找到 jar 包中目標類所對應的 JarEntry,修改其位元組碼,然後替換原來的jar文件
接收擴展參數,獲取需要掃描類的特徵及需要插入的代碼
找了很久沒找到 gradle 插件接收自定義對象數組擴展參數的方法,於是退一步改用List 接收後再進行轉換的方式來實現,以此來接收多個掃描任務的擴展參數
在 Application 中配置擴展參數
application 中配置自動註冊插件所需的相關擴展參數,在主 app module 的 build.gradle 文件中添加擴展參數,示例如下:
總結
本文介紹了 AutoRegister 插件的功能及其在組件化開發框架中的應用。重點對其原理做了說明,主要介紹了此插件的實現過程,其中涉及到的技術點有 TransformAPI、ASM、groovy 相關語法、gradle 機制。
本插件的所有代碼及其用法 demo 已開源到 github上,歡迎 fork、start
接下來就用這個插件來為我們自動管理註冊表吧!
我的GitHub:
https://github.com/luckybilly/AutoRegister
喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!
本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧! 請您繼續閱讀更多來自 郭霖 的精彩文章: