1. <nobr id="easjo"><address id="easjo"></address></nobr>

      <track id="easjo"><source id="easjo"></source></track>
      1. 
        

      2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
      3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>
          貴州做網站公司
          貴州做網站公司~專業!靠譜!
          10年網站模板開發經驗,熟悉國內外開源網站程序,包括DEDECMS,WordPress,ZBlog,Discuz! 等網站程序,可為您提供網站建設,網站克隆,仿站,網頁設計,網站制作,網站推廣優化等服務。我們專注高端營銷型網站,企業官網,集團官網,自適應網站,手機網站,網絡營銷,網站優化,網站服務器環境搭建以及托管運維等。為客戶提供一站式網站解決方案?。?!

          有贊 Android 編譯進階之路 —— 增量編譯提效方案Savitar

          來源:互聯網轉載 時間:2024-01-29 08:21:33

          作者:明天

          團隊:零售移動

          一、前言

          在前段時間的有贊移動沙龍中給大家分享了有贊移動 Android 團隊對于編譯提效的實踐,會上很多小伙伴對這部分十分感興趣,但由于時間關系沒有能進行一些細節上的交流,所以會后我們整理了兩篇文章分享給大家。關于第一部分全量編譯提效可以閱讀我們小伙伴分享的文章,今天給大家帶來第二部分:增量編譯提效方案Savitar。

          二、背景

          編譯慢一直都是成熟 Android 團隊難以回避的問題。有贊零售 Android 團隊隨著業務的發展,項目也到了一個比較大的規模:整個工程有 25 個業務模塊,擁有 45W+ 行源代碼(Java + Kotlin)以及多個構建 Flavor。小伙伴在進行需求開發時,平均的增量編譯構建時間達到了兩分鐘,再加上一些 Gradle 配置與APK安裝過程,基本上驗證一行代碼的修改需要近三分鐘(MacBook Pro 13-inch, 2016, i5-8G),這樣的情況大大降低了團隊的開發效率。為了解決這個問題,我們進行很多的探索與嘗試,由此就誕生了 Savitar。

          三、方案探索

          在 Savitar 誕生之前,我們曾嘗試在社區中尋求解決方案,希望通過接入某一個框架,達到在對工程結構不進行大面積改造的前提下,把增量編譯運行時間降低到 30 秒左右的目標,并且使用者不需要進行復雜的配置或者改變自己的開發習慣。帶著這個目標調研了很多方案,其中就有 BUCK、Freeline、InstantRun 等知名框架。

          3.1 調研結果

          通過調研之后,了解了每個框架能夠解決的問題和一些不滿足我們需求的地方:

          BUCK 自身有強大的構建系統,通過增量構建緩存機制,可以有效提升編譯的速度,但是其使用和配置過于復雜,對于工程的入侵比較大,且對于一些 Databinding、 Kotlin 等 Android 的特性支持還有欠缺。

          Freeline 以其極快的部署速度出名,但對我們來說致命缺點是不支持 Kotlin。

          InstantRun 是 Google 推薦的加速方式,擁有最全面的支持性,但由于我們是多進程的工程,并且 InstantRun 在編譯時的一些準備 Task 也會消耗一些時間,在實踐過程中發現加速并不明顯。

          由于篇幅關系在這里就不細致展開對每一個框架的解析,有興趣的同學可以通過每個框架的官網進行了解。最后這幾種方案都沒有采用,決定自己探索開發解決方案。但是調研的過程并非全無收獲,從幾個方案中我們發現針對于增量編譯加速場景,大家都是遵循 按需編譯,動態加載 的原則,將編譯與安裝的過程進行細致拆分,把編譯量降低到最小,再通過去除 APK 耗時的安裝過程,從而提升整個增量編譯安裝運行的速度。我們也朝著這個方向,并結合我們的實際場景最終完成了 Savitar 方案。

          四、方案實現

          Savitar 是有贊 Android 團隊增量編譯提效方案,它能夠有效減少模塊修改編譯時間,包含配套 IDE 插件,使用方便。

          類別

          支持內容

          代碼

          Java、Kotlin

          資源

          layout、values、assets、images

          擴展

          GUI界面

          其他

          調試、多分支管理(基于 Git)

          下面會從 Savitar 的設計與每個部分實現展開,描述我們是如何一步一步完成 Savitar 并解決 Android 增量編譯問題。

          4.1 結構設計

          如圖所示,Savitar 整體分成四個部分:

          • GUI 插件部分:面向使用者的 GUI 界面,內部包含了可運行 Jar(以下簡稱 Runner)的自動更新、各種檢查任務、編譯腳本調用執行
          • Runner 部分:一個 Jar 包,包含 Savitar 核心邏輯代碼,完成修改獲取、腳本生成、編譯執行等任務
          • 工程支持部分:一個 Gradle 插件,完成對工程信息的獲取和產物加載代碼的插入
          • 外部依賴部分:完成整個流程所需要的外部依賴程序

          下面是整體運行的流程圖,描述了從代碼修改到完成修改產物加載運行的過程:

          • 獲取改動信息:獲取代碼和資源修改,是整個過程的前提
          • 獲取工程信息:獲取當前工程的依賴信息,目錄信息和 Git 信息,為后續編譯做準備
          • 編譯生成產物:進行代碼、資源編譯,生成 Dex 產物和 Apk 產物
          • 重啟加載產物:完成對編譯產物的加載運行,完成整個加速過程

          下面將從各個子流程出發,詳細介紹內部實現。

          4.2 改動獲取

          改動獲取是 Savitar 最基礎但是十分重要的部分,是后續過程生成正確產物的前提。

          在實現的過程中,需要考慮以下幾個問題:

          • 如何正確獲取本地修改文件的信息
          • 如何支持多 Flavor
          • 如何支持多分支切換

          4.2.1 本地改動獲取

          Git 是現在廣泛使用的代碼版本管理工具,在 Git 諸多能力中,就包含改動檢測。于是我們一開始決定使用 Git 獲取文件改動信息。我們的需求是獲取修改文件的路徑,這個可以通過一個簡單的 Git 命令獲取到:

          $ git diff --name-only ${上次成功構建的commitId} HEAD

          其中 上次成功構建的commitId會在成功執行 Gradle 編譯命令后記錄,作為一個 Git 改動比較的基線,如果后面從遠端拉取了一些代碼到本地,就可以通過這個基線得出改動的文件信息。當這個 commitId 為空時,可以獲取到當前分支本地改動的信息。

          但是 Git 獲取改動存在一個問題,當本地有沒有添加到版本管理的新增文件時,通過 git diff 命令無法獲取到新增文件的信息,并且在對于本地正在修改的文件,Git 命令始終會返回這些文件,就算是這些文件已經包含在上次全量編譯產物中。所以 git diff 的結果并不是最佳的改動范圍結果,于是我們繼續尋找更好的方案。后來選擇了社區中成熟的文件修改監控工具 —— Watchman,它可以對某個文件夾下的文件改動監控,并支持使用命令獲取修改的文件的路徑信息,這個能力滿足對于文件修改獲取的要求。Watchman 可以通過下面的方式獲取改動文件信息。

          // 監控一個文件夾$ watchman watch ${文件夾}// 獲取改動文件$ watchman -j > ${diff信息保存文件} <<-EOT["query", "${文件夾}", {    "since": "${上次修改時間}",    "expression": ["exists"],    "fields": ["name"]}]EOT

          修改信息會以 JSON 格式保存在 ${diff信息保存文件}中。

          4.2.2 支持多分支切換

          Watchman 似乎可以替代 Git 完成改動獲取的工作,但在實踐中我們又發現了新的問題:在多分支切換的情況下面,從 A 分支切換到 B 分支,然后再從 B 分支切換回來,沒有修改一行代碼,但 Watchman 會產生 A,B 之間差異文件的改動記錄,此時 Watchman 的 diff 集合是不準確的,但 Git 就可以得出正確的修改記錄,于是,結合兩個工具的優勢,得出獲取改動的邏輯流程:

          通過上面的流程,可以準確獲取到本地修改文件的信息。

          4.2.3 Flavor 過濾改動文件

          當得到了本地修改的文件之后,是否就可以直接以這些文件進行下一步呢?答案是:NO!因為得到的修改信息有些可能是當前不需要的,例如我們客戶端存在 Pad 和 Phone 的 Flavor,在運行 Phone 的時候 Pad 的下面的修改是不需要的,所以在上面流程的最后,還需要添加一個過濾 Flavor 的流程,最終的流程如下:

          由此就實現了改動的獲取,獲取到本地的改動之后,還會進行不同文件類型的信息分類存儲,為后面不同文件的編譯做好準備。

          4.3 工程信息獲取

          獲取改動信息之后,需要完成這些改動文件的產物生成過程。本地的改動中會包含 Java、Kotlin 源代碼改動信息,還有 Xml,圖片等資源的改動信息,這些文件生成產物的方式是不一樣的,各自使用的工具以及需要的依賴也不同,所以,在真正編譯之前,還需要獲取到編譯過程中各種依賴信息和工程信息。

          需要獲取的信息:

          • 編譯依賴信息:包括全量編譯產物目錄、上個修改編譯產物目錄、三方庫依賴等
          • 工程信息:包括各個模塊包名、當前 Flavor、sourceSet、工程路徑等

          4.3.1 編譯依賴獲取

          以 Java 文件編譯為例子,在進行一個 Java 編譯時,需要為這個編譯過程提供當前 Java 文件中所引入的所有依賴配置,不管是本地的 Java 文件還是來自于三方庫中的 .class。

          對于本地的 Java 文件,只需要將工程下面所有的模塊下面的 build 目錄收集起來,傳遞到編譯的 classpath 中即可。

          對于三方庫依賴,可以在工程目錄下 .idea/libraries 文件夾中獲取到當前工程所有依賴的三方庫信息。

          下面是android.arch.core:common:1.1.0的例子,依賴的信息會以 Xml 的形式存儲,包含 Jar 或者 AAR 的地址信息。

          <component name="libraryTable">  <library name="Gradle: android.arch.core:common:1.1.0@jar">    <CLASSES>      <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/android.arch.core/common/1.1.0/8007981f7d7540d89cd18471b8e5dcd2b4f99167/common-1.1.0.jar!/" />    </CLASSES>    <JAVADOC />    <SOURCES>      <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/android.arch.core/common/1.1.0/f211e8f994b67f7ae2a1bc06e4f7b974ec72ee50/common-1.1.0-sources.jar!/" />    </SOURCES>  </library></component>

          從 Xml 中把需要的信息解析出來,這樣就可以獲取到所有的三方依賴了,再把 Jar 地址信息傳遞到編譯的 classpath 中,完成對于三方庫的依賴鏈接。

          4.3.2 工程信息獲取

          下面是對工程信息的抽象類圖,里面包含所有需要獲取的工程信息,這些信息是幫助完成編譯、產物加載甚至是前面修改獲取的必要信息。

          • MakeParam:信息集合保存類
          • ProjectParam:保存主工程信息,包括所有需要的路徑、主包名、啟動的 Activity、資源 ID 固定之后保存文件路徑、Android SDK 編譯版本等
          • ModuleParam:每個子模塊的信息,包括 packageId、當前 Flavor、sourceSet 等
          • SourceSetsParam:存儲每個 Flavor 下 source 的信息

          有了這些依賴和工程信息,產物編譯的前期準備就完成了。

          4.4 編譯實現

          這是 Savitar 中最關鍵的部分,會使用前面的依賴信息完成對改動文件的編譯產物生成。

          編譯對象:

          • 源代碼文件:Java、Kotlin
          • 資源文件:Xml(布局、String、Drawable等)、圖片

          4.4.1 源代碼編譯

          對于 Java 和 Kotlin 源代碼的編譯,需要使用到 javac 和 kotlinc 兩個工具。兩個工具的使用調用方式是類似的。

          # 執行kotlinc/javac 命令sh kotlinc{or javac} -classpath ${projectPath}/build/intermediates/classes/${Flavor}/debug: # build產物依賴${android_home}/platforms/android-${version}/android.jar: # Android SDK 以及其他三方庫Jar-d ${產物輸出目錄} @${kotlin修改文件集合.ch} @${java修改文件集合.ch} 

          Savitar 整個編譯流程、產物打包、推送加載都是通過 Shell 腳本完成,腳本由通過 Runner 動態生成,下面是生成腳本代碼的邏輯:

          Runner 生成腳本的原則是按需生成,只在檢測到存在相應的修改記錄之后才會生成對應的代碼,并且所有依賴也是在運行時生成,避免出現在依賴改變之后因腳本沒有更新導致編譯失敗的情況。

          在源代碼編譯流程中,值得注意的是 Java 與 Kotlin 之間的編譯順序。存在兩種文件修改時,需要先編譯 Kotlin 再編譯 Java,如果順序不對,可能會導致 Java 編譯失敗。例如存在 A.kt 與 B.java 文件存在依賴引用,如果先編譯 B.java 文件,就會出現 B.java 文件對于 A.kt 類依賴找不到的錯誤。這是為什么呢?其實是新老語言的兼容性不同,Kotlin 支持使用 Java 源代碼作為編譯依賴,但是反過來就不行,但是如果先把 A.kt 類編譯成 .class 文件,那么 B.java 文件就可以正常使用 .class 作為編譯依賴完成編譯了。

          4.4.2 資源編譯

          完成了源代碼編譯之后,就到了資源編譯。在介紹資源編譯之前,需要稍微講解一下資源 ID 固定。

          接觸過熱修復或者做過類似內容的同學知道,對于資源文件的熱修復,必須保持修復資源(非新增)與原有資源的 ID 一致,且新增資源的 ID 必須不能與已有資源 ID 重復,否則就會出現資源引用混亂的問題。為了保證資源編譯過后能夠與原有資源 ID 保持一致,必須提前把前面編譯的資源的 ID 保存固定下來,然后在后續的資源編譯中使用。資源 ID 固定可以通過在 Gradle 處理資源的Task中添加--emit-ids 參數并且指定一個 ID 保存文件完成。

          processResourcesTask.aaptOptions.additionalParameters("--emit-ids", idRecordPath)

          這個rocessResourcesTask可以通過獲取名字為 process${variantName}Resources的 Task 獲取到。

          rocessResourcesTask可以通過獲取名字為 process${variantName}Resources的 Task 獲取到。完成了資源 ID 固定之后,就可以開始資源編譯了。

          對于非 values 資源,基于 AAPT2 的 link 模式,將資源編譯后的 .flat 文件替換之前的 .flat 文件,再使用 link 命令完成打包即可。

          // 資源編譯aapt2 compile ${資源文件全路徑} -o ${資源文件編譯產物輸出目錄}// 資源APK生成aapt2 link ${.flat資源文件路徑} -o ${目標apk路徑} --manifest AndroidManifest.xml

          對于 values 資源,因為之前全量編譯的產物是合并過的,所以不能使用單個模塊的修改 .flat 替換合并過的 .flat,對于這種場景目前是會以 offline 模式重新執行一次處理資源的 Gradle Task。

          關于 AAPT2 的詳細使用,可以參考 Android 官網上的 AAPT2 文檔

          由此,就完成了 Savitar 中的編譯部分,相比使用 Android Stuio 直接編譯運行,Savitar 的編譯量更小,速度更快。

          4.5 產物加載

          這個部分會使用到熱修復的原理來完成對于產物的加載,不是很了解的同學可以先學習關于 Android 代碼和資源熱修復的原理。

          目前社區中有很多很成熟的熱修復框架,例如 Tinker、Sophix 等。一開始我們也在考慮是否需要把產物和現在使用的熱修復框架結合在一起(工程中使用的是 Tinker)。后來發現,其實在 Savitar 中,對于產物加載的要求沒有這么高,例如不需要像 Tinker 進行 dex 的差分操作,只需要簡單地把產物加載運行起來即可。所以這個方面只是參考了 Tinker 的 Loader 部分產物加載原理,然后簡化了一些流程,做了一個最簡可用版產物加載工具。

          上面是加載流程圖,整個流程其實并不復雜,但是能夠滿足對產物加載的需求。其中可能有人會疑問為什么需要在加載之后把產物刪除掉,這個不是下次啟動就沒有了么?這么做主要是為了能夠有途徑回到沒有產物的狀態,要不然每次都需要手動去刪除產物文件才能回到初始狀態,這樣操作會比較麻煩。

          五、使用體驗

          前面的部分詳細描述了 Savitar 具體實現,其中包含了很多的復雜流程和內容,但是對于使用者來說其實不需要關心這些,為了方便使用,我們為 Savitar 開發了一款 IDE 插件,只需要一鍵觸發就可以完成整個編譯打包流程,具體介紹如下:

          Android Studio -> Preference... -> Plugin -> Install plugin from disk -> 選擇本地 Savitar.jar (目前為內部使用,未上傳到 Jetbrains 插件中心),安裝完成后重啟 IDE,然后在 Android Studio 中工具欄就會出現 Savitar 的圖標(紅框部分)。

          點擊圖標后,可以在 Savitar Window 看到工具編譯、打包、推送整個運行過程,包含錯誤信息,如下圖:

          六、對一些問題的回答

          6.1 如何 Kotlinx 支持

          import kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        buttonCpu.setOnClickListener {            // 點擊事件        }    }}

          使用過 Kotlin 的 Android 同學對于上面的代碼肯定不陌生,利用 Kotlinx 特性,可以在 .kt 代碼中使用 Xml 中定義過組件Id直接獲取 View 實例進行操作,極大減少 UI 開發成本。

          但是上面代碼中的 import 并不是一個普通的形式,這樣的語法如果直接使用標準 kotlinc 進行編譯,會出現找不到 import 錯誤。

          import kotlinx.android.synthetic.mian.activity_main.*

          這個時候需要借助到 Kotlin 編譯器插件,在 Kotlin 編譯時傳入 Kotlinx 對應插件的 Jar 地址和參數,就可以完成包含 Kotlinx 語法的文件編譯。

          sh kotlinc-Xplugin=lib/android-extensions-compiler.jar-P plugin:org.jetbrains.kotlin.android:package=${package_name}-P plugin:org.jetbrains.kotlin.android:variant='${flovar};${resource_package}'

          文檔參考 Kotlin 編譯器插件

          6.2 為什么使用 Shell 腳本實現

          Shell 腳本可以直接在 Mac 系統下面執行,在 Shell 腳本里面可以方便地調用編譯過程中所需要的命令,并且調試運行也非常方便。

          6.3 Kotlinc 環境變量問題

          在使用 Android Studio 開發過程中,Kotlin 編譯所需的依賴包都是由 IDE 自動管理,但是 Savitar 是使用 Shell 實現,這樣的情況下面就需要關心這個編譯工具的問題了。我們將獲取 Kotlin 編譯依賴的邏輯放在 Savitar 運行環境檢測邏輯中,在檢測到沒有依賴包的情況下會自動從內網服務器下載對應版本的庫,完成 Kotlin 代碼編譯。

          七、結果與展望

          7.1 Savitar實踐成果

          使用了 Savitar 之后,我們的增量編譯速度得到了很大的提升。增量編譯時間從原來平均 110s 降低到 15s,提速 8 倍。

          從 2019 年 Q3 開始到目前為止,Savitar 在有贊內部使用超過 10,000 次,累計節省約 260 個小時編譯時間。隨著編譯時間的減少,Android 同學的開發體驗也越來越好了,媽媽再也不用擔心我因為編譯慢而加班了~

          7.2 未來計劃

          在未來,我們團隊在不斷改進和完善 Savitar 的同時,還會增加動態生成代碼、SO 庫等特性的支持,并逐漸往通用化方向進行架構設計,旨在支持所有的 Android 工程,最終開源,為 Android 編譯難題貢獻一份力量,并期待后續更多的開發者可以參與其中,一起共建。

          結語

          關于 Android 全量和增量加速方案的分享到此就告一段落了,但是我們對于開發效率提升的追求永不停止。再次感謝大家對我們移動技術沙龍支持,我們未來會產出更多關于移動技術方面的分享,希望大家持續關注。

          標簽:savitar-

          網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...

          在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...

          在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...

          ps4剛發布的時候賣多少錢?你好,。500克的給國行2200,1噸的給國行2600(除了一些限量發售,好像還沒有 尚未正式發布)。非國行的價格相對更隨意,但一般都比國行便宜。ps4多少錢?美版PS4售價399美元,約合2430元。上映日期是2013年11月15日。PS4的售價為3380港幣,約合2657元。上映日期是2013年12月17日。港版PS4《地帶:陰影墜落》中英文售價為3780港幣,約合...

          IT男是什么職業,IT又是什么意思呢?It男是指從事It行業的男性勞動者。他們的特點是長時間在電腦前工作。由于工作壓力的特殊性,這些人長時間坐不起來,甚至很少喝水和上廁所。因此,健康問題隨之而來:神經衰弱、視力下降、易肥胖、生育能力下降甚至不孕。它意味著信息技術。信息技術或it(英文:Information technology,簡稱it),主要用于管理和處理各種技術使用的信息。主要應用計算機科學...

          巴巴多斯國家簡介 請詳細介紹巴巴多斯這個國家?巴巴多斯島在哪里? 巴巴多斯(Barbados)珊瑚石灰巖島位于東加勒比海小安的列斯群島最東端。它被海洋包圍,西面與圣盧西亞、圣文森特、格林納丁斯和格林納達隔水相望。1966年11月30日,巴巴多斯擁有一個穩定的民主政權,獨立于此。他是英聯邦的成員,他的名字來自葡萄牙語,指的是野生無花果樹。巴巴多斯國內生產總值為48.21億美元,人均國內生產總值為...

          TOP
          国产初高中生视频在线观看|亚洲一区中文|久久亚洲欧美国产精品|黄色网站入口免费进人
          1. <nobr id="easjo"><address id="easjo"></address></nobr>

              <track id="easjo"><source id="easjo"></source></track>
              1. 
                

              2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
              3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>