精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

掌控 Android 編譯利器,攜程火車票AAR 編譯速度優化實踐

開發 新聞
本次編譯速度優化采用的方案是模塊AAR方案, 優化目標為: 優化后一次干凈的全量編譯時間縮減為原來編譯時間的50%以下。

作者簡介

小明,攜程移動開發工程師,專注于移動端框架和基建開發;

黃拉歡,攜程移動開發經理,專注于移動端框架建設及性能調優。

一、 背景

Android 項目一般使用 Gradle 作為構建打包工具,隨著業務需求的不斷迭代,代碼量提升的同時,Gradle 編譯耗時也在不斷的增長,而編譯速度會直接決定開發流程效率的高低,影響面主要涉及到開發和測試階段。 

對于火車票項目,經過長期的迭代過程導致模塊眾多工程龐大,優化前一次干凈的全量編譯時間可達到10m39s,造成開發和測試都需要長時間等待編譯出包,嚴重影響到開發和測試的效率。因此對火車票 App 進行編譯速度優化是件亟待解決的事情。

本次編譯速度優化采用的方案是模塊AAR方案, 優化目標為: 優化后一次干凈的全量編譯時間縮減為原來編譯時間的50%以下。

二、 模塊AAR方案介紹

Google 官網提供了優化構建速度的幾種方案,但基本上效果都不明顯。業內常用的編譯加速方案一般有模塊aar、控制增量編譯、transform 優化、dexBuilder 優化等,其中有些方案侵入性太強或者 ROI 不高,模塊aar方案侵入性低,預計收益顯著,并且在大廠已經成為標配方案,可以作為本次編譯優化的主方向。

AAR(Android ARchive)文件為 Android 庫編譯產物,其中包括 class、資源和清單文件,可用作 Android 應用模塊依賴項。在Android 的構建流程中,源碼編譯時會將依賴的 aar 庫和應用模塊合并,在通過 apkbuilder 輸出 apk 產物。

圖片

 圖1:android的構建流程圖

Android 項目大多都是多模塊 Gradle 項目,構建項目時所有的子模塊需要參與編譯,導致全量編譯時間冗長。實際上,對于大部分的開發人員來說,并不需要關注所有的模塊,只需要負責功能相關的業務模塊即可,因此這也提供了模塊aar方案的切入點。

一般來說,aar方案都大同小異,涉及到模塊aar發布,project和module依賴的切換,傳遞依賴的處理等幾個方面。依賴切換我們采用的是 Gradle 官方文檔中直接自定義依賴項的解析方案,通過定義依賴替換規則來更改依賴項;aar的發布使用的是maven-publish插件;傳遞依賴使用的mavenPom文件管理。

火車票項目經過多次模塊化的重構后現有20個子模塊,可將這些子模塊獨立打包aar并發布至遠程maven倉庫中,在需要打包時用aar替換項目的構建,就能將編譯的時間給節省下來,以提高打包效率。 

圖片

圖2: 模塊aar開發示意圖

三、改造過程和遇到的問題

3.1 構建測量指標

良好的優化基于數據的呈現,首先第一步需要做的是分析構建的性能。此處采用的是官方提供的Gradle --profile選項的方式統計數據。基本步驟為:

  • gradlew clean在每個build之間執行干凈編譯,確保剖析完整的構建流程;
  • gradlew--profile--offline--rerun-tasks assembleFlavorDebug為某個變種task啟用性能剖析,并確保gradle使用的是緩存依賴項,且強制gradle重新運行所有任務;
  • 構建完成后,會生成html格式的性能剖析報告,可用于分析構建信息和前后構建對比;

3.2 模塊aar的改造

因存在單個模塊發布的過程,模塊間需要盡量的解耦,而解耦首先要進行模塊的劃分;其次,存在依賴關系的模塊間不能互相使用資源,這會造成編譯的失敗,所以理清模塊間的關系是當務之急。

同時,我們需意識到當我們完成 Gradle 腳本的編寫后,我們得到的應該是一個app殼+其他模塊的依賴樹組合,可通過以下步驟進行改造:

1)模塊間依賴樹重構

首先分析項目各模塊依賴關系, 可使用開源腳本工具projectDependencyGraph.gradle生成依賴圖,示意圖如下:

圖片

 圖3: 模塊間依賴樹混亂示意圖

可直觀的看出模塊間依賴關系冗余,模塊間的依賴不清晰,且基礎庫一般固定卻呈現網狀結構。故需對網狀結構進行整理,將不經常改動的庫直接發布遠程,如下:

圖片

 圖4: 模塊間依賴樹整理示意圖

2)歷史Gradle腳本重構

因歷史原因,火車票項目的 Gradle 邏輯冗余繁瑣,無統一管理略顯臃腫。為提高 Gradle 的擴展性和靈活性,需要重構 Gradle 代碼:

  • 對現有 Gradle 腳本按功能職責拆分成單個Gradle文件,需要使用的地方通過apply關鍵字應用即可;
  • 基于原插件com.android.applicationcom.android.library建立兩套基準Gradle文件,統一控制不同類型的模塊;
  • 抽取共有基礎依賴項作為獨立的 Gradle 文件,在subprojects中讓所有模塊都應用這些依賴項; 
  • 整理libs目錄中本地的aar文件,移動至一個目錄,統一依賴本地aar;
  • 建立app_bundle.json文件記錄所有模塊項目的信息及類型,便于模塊項目是源碼還是aar的切換控制;
  • 開發一套模擬參數機制,支持合并環境輸入參數和local.properties參數 以便于 Gradle 功能的控制;如開啟某個transform,指定哪些模塊為aar等功能;
  • 重構 flavor 變體功能,動態控制 flavor 的創建。

3)發布和依賴aar版本的控制

當我們發布aar至遠程倉庫后,需要一個文件記錄已發布模塊aar的版本號,打包時通過此文件查找對應版本的aar,此處使用的是 MCD 平臺采用 json 格式的 versionPath 文件。示意圖如下:

圖片

 圖5:通過versionPath文件控制版本

模塊aar的核心在于對依賴項的控制。我們想要的是一個app殼+部分源碼模塊+部分aar模塊+其他依賴的結構,因此下面幾點是要考慮到的:

  • 在所有子模塊發布 maven 成功后,本地編譯使用aar模塊且環境參數沒有傳遞 versionPath 參數時,構建腳本會自動拉取Mcd最新的versionPath.json鏈接作為默認的模塊aar信息表。
  • 開發階段需切換源碼/aar,可對項目中所有模塊進行分類,分別為:入口模塊,源碼模塊,aar發布模塊,可移除模塊。另外,合并 gradle 環境參數和local.properties參數共同作為控制參數,然后修改app_bundle.json指定模塊類型以達到靈活切換的目的。例如,定義一個sourceProject參數指定哪些模塊作為源碼構建。
  • 為模塊之間的依賴關系建立一個json文件aar_config.json,便于查看和修改模塊間依賴關系。

整個模塊aar打包流程圖如下:

圖片

 圖6:模塊aar下的打包流程

4)抽取app多余代碼,使其成為真正的"殼"

App入口項目中存在的代碼會影響編譯效率,為盡可能降低編譯時間,將app中大部分 main 目錄代碼移動至子模塊中,保證app模塊只剩一個空殼而已。

5)自定義依賴項的替換規則

通過app_bundle.json得知目標模塊是aar格式后,我們可使用 Gradle 提供的自定義依賴替換規則來實現aar的切換功能。 

依賴替換規則允許項目和模塊依賴項透明地替換為指定的替換項,項目和模塊依賴可互換地替換。對應于源碼中ResolutionStrategyApi中的DependencySubstitutions接口,此功能常用于多項目構建中靈活的組裝子項目。通過允許從存儲庫下載而不是構建項目依賴項的子集,這對于加快大型多項目構建的開發非常有用。 

自定義依賴項替換規則如下:

//此處采用偽代碼形式,正式場景最佳實踐應抽取到plugin中并提供extension和log功能
//通過app_bundles.json信息決定是否應用替換規則
gradle.ext.appBundles.each { bundleName, bundleInfo ->
    if (bundleInfo.buildType == 'aar') {
        gradle.ext.subsitituteMap[":$bundleName"] = "$specialGroupID:$bundleName:${bundleInfo.versionName}"
    }  
}
//制定自定義解析規則,用模塊替換項目依賴項
configurations.all { conf ->
    resolutionStrategy.dependencySubstitution {
        gradle.ext.subsitituteMap.each {
            substitute project(it.key) with module(it.value)
        }
    }
}

6)自定義pom文件管理傳遞依賴

此處通過maven-publish插件發布模塊aar,發布aar時不可混淆以防止依賴報錯。

通過聲明pom文件來管理依賴及傳遞依賴是個很好的方式。發布模塊aar時,本身項目的子模塊aar不建議寫入pom文件,否則在模塊切換源碼時會存在遠程aar和源碼沖突的情況,此時需要添加額外的exclude操作。

自定義pom文件如下:

//使用maven-publish插件發布aar
apply plugin: 'maven-publish'
...
publishing{
...
//自定義pom文件處理傳遞依賴
  pom {
      packaging = "aar"
      withXml {
          pomGenerate(it)
      }
  }
}
//獲取配置的依賴 configuration => dependenciesMap
def ascribeDependencies(String confName) {
    def dependencySet = project.configurations.getByName(confName).getAllDependencies()
    def dependenciesMap = [:]
    //獲取module類型依賴,待寫入pom文件
    dependencySet.withType(ExternalModuleDependency)
        .findAll {
            if (it.group != null && it.name != null && it.version != null) {
                return it
            }
            return null
        }
        .each {
            ...
            dependenciesMap.put("${it.group}:${it.name}", it)
        }
    return dependenciesMap
}
//拼接pom文件
def pomGenerate(XmlProvider xp) {
    def dependenciesMap = [:]
    dependenciesMap.putAll(ascribeDependencies("implementation"))
    dependenciesMap.putAll(ascribeDependencies("api"))
    ...
        // 添加節點的閉包
        def addDependencyNode = { rootNode, entry ->
...
            Dependency target = entry.value
            //寫入基本依賴信息
            def node = rootNode.appendNode('dependency')
            node.appendNode('groupId', target.group)
            node.appendNode('artifactId', target.name)
            node.appendNode('version', target.version)
            node.appendNode('scope', 'compile')
            //如果有,寫入classifier
            def artifacts = target.getArtifacts()
            if (!artifacts.isEmpty()) {
                DependencyArtifact artifact = artifacts.first()
                artifact.type && node.appendNode("type", artifact.type)
                artifact.classifier && node.appendNode("classifier", artifact.classifier)
            }
            //如果有,寫入exclude規則
            Set<ExcludeRule> excludes = target.getExcludeRules()
            if (!excludes.isEmpty()) {
                def exclusions = node.appendNode("exclusions")
                excludes.each { rule ->
                    def exclusion = exclusions.appendNode("exclusion")
                    rule.group && exclusion.appendNode("groupId", rule.group)
                    rule.module && exclusion.appendNode("artifactId", rule.module)
                }
            }
        }
        //添加dependencies節點
        def dependenciesNode = xp.asNode().appendNode('dependencies')
        dependenciesMap.each {
            addDependencyNode(dependenciesNode, it)
        }
}

7)其他核心Gradle腳本代碼一覽

  • setting入口處合并參數
//加載local.properties屬性
gradle.ext.localProperties = gradle.ext.loadProperties("$rootDir/local.properties")
//因為gradle版本兼容問題,startParameter.projectProperties不能注入直接注入,
//類型為com.google.common.collect.ImmutableMap,需要統一。
def inputProjectProperties = new HashMap()
if (startParameter.projectProperties != null) {
    for (key in startParameter.projectProperties.keySet()) {
        def value = startParameter.projectProperties.get(key)
        if (value != null) {
            println "注入ProjectProperty >> $key :${utf(value)}"
            inputProjectProperties.put(key, value)
        }
    }
}
//local property 參數注入,優先級高于環境參數
if (gradle.ext.localProperties) {
    gradle.ext.localProperties.each { k, v ->
        println "注入LocalProperty >> $k :${utf(v)}"
        inputProjectProperties.put(k, utf(v))
    }
}
  • 入口處更新全模塊信息app_bundle.json指定模塊打包類型
//解析初始模塊信息app_bundles.json
gradle.ext.appBundles = gradle.ext.getConfigJson("$rootDir/app_bundles.json")
//利用輸入參數更新模塊信息
gradle.ext.appBundles.each { bundleName, bundleInfo ->
      ...
        // App混合編譯打包
        if (gradle.ext.sourceProjects.contains(bundleName)) {
            //強制指定源碼編譯的模塊
            bundleInfo.buildType = 'lib'
            bundleInfo.include = true
        } else if ((!bundleInfo.required) && gradle.ext.excludeProjects.contains(bundleName)) {
            //指定可移除模塊
            bundleInfo.buildType = 'lib'
            bundleInfo.include = false
        } else if (gradle.ext.versionPathConfig) {
            // AAR 混合編譯,versionPath決定aar信息
            def that = gradle.ext.versionPathConfig.find { item -> bundleName == item.bundleCode }
            if (that) {
                // AAR編譯的模塊
                bundleInfo.buildType = 'aar'
                bundleInfo.include = true
                bundleInfo.versionName = that.version
            } else {
                // 其他模塊
                bundleInfo.buildType = 'lib'
                bundleInfo.include = false
                if (bundleInfo.required) {
                    println("Build bundle missing,bundleName:" + bundleName)
                }
            }
        } else {
            // 純源碼編譯
            bundleInfo.buildType = 'lib'
            bundleInfo.include = true
        }
}
  • 在各個模塊的ext擴展中定義統一的模塊依賴方法,方便修改
//ext擴展(接口為ExtraPropertiesExtension.java)中定義統一方法
def compileBundle(Project project, String depName = null) {
    def projectName = depName ?: project.name
    def dependenciesHandler = project.configurations.implementation
    if (projectName != project.name) {
        //指定單個模塊依賴
        def findProject = project.rootProject.findProject(projectName)
        if (findProject != null) {
            dependenciesHandler.dependencies.add(
                    project.dependencies.create(project.rootProject.project(projectName))
            )
            println("compileBundle direct >> ${project.name} $projectName")
        } else {
            println("compileBundle direct skip >> ${project.name} $projectName")
        }
    } else {
        //傳遞模塊依賴,moduleTransitives即為解析的aar_config.json對象
        if (gradle.ext.moduleTransitives != null) {
            def depList = gradle.ext.moduleTransitives[projectName]
            depList?.each {
                def findProject = project.rootProject.findProject(it.depName)
                if (it.depName != null && findProject != null) {
                    dependenciesHandler.dependencies.add(
                            project.dependencies.create(project.rootProject.project(it.depName))
                    )
                    println("compileBundle relationship >> ${projectName} ${it.depName}")
                } else {
                    println("compileBundle relationship skip >> ${projectName} ${it.depName}")
                }
            }
        }
    }
}
//模塊gradle使用處
dependencies {
    ...
    compileBundle(project)
}

3.3 改造中遇到的問題

1)同名資源沖突

模塊AAR改造的過程中,進行了模塊依賴關系的調整,導致同名資源的覆蓋關系發生了改變。如果多個 AAR 庫之間發生沖突,系統會使用依賴項列表中首先列出的庫(靠近dependencies塊頂部)中的資源。同名資源沖突會導致兩種結果:

  • File資源的沖突導致在運行時會因為同名資源中 id 找不到而 crash。
  • Value資源的沖突導致 UI 不符合預期。

要解決這個問題,首先需要了解 Android 的資源打包流程,如圖所示:

圖片

 圖7:android資源打包流程

Android 資源打包工具 aapt 在編譯和打包資源的過程中,會對非 assets 資源做如下處理:

  • 賦予每一個非 assets 資源一個ID值,這些ID值以常量的形式定義在一個 R.java 文件中
  • 生成一個 resources.arsc 文件,用來描述那些具有ID值的資源的配置信息,它的內容就相當于是一個資源索引表

資源索引表對同名的資源只會生成一個id,因此項目中如果有同名的資源,構建工具會根據以下優先級順序(左側的優先級最高)選擇要保留的版本:

圖片

 圖8:同名資源合并優先級

為了避免常用的資源 ID 發生資源沖突,主要方案有兩種:

  • 使用對模塊具有唯一性(或在所有項目模塊之間具有唯一性)的前綴命名
  • 其他一致的命名方案

但是實際操作下來,發現還是存在幾個問題:

  • 一是如何找出沖突的資源進行重命名處理
  • 二是對什么樣的同名資源處理
  • 三方庫和三方庫中的資源沖突該怎么辦

為了解決這些問題,我們使用了CheckResourceConflict開源插件,原理是對工程做一個掃描,使用BaseVariantImpl.allRawAndroidResources.files接口可以在編譯期間獲取到所有的資源文件,然后檢測同名資源并分類輸出到一個可視化的html文件中,如圖:

圖片

 圖9:同名資源沖突的分析

現在,我們可以看到哪些資源有重復以及重復資源所在的位置,可以用唯一性命名來解決同名資源沖突的問題了。

針對第二個問題,如果是布局 ID 相差較大的File資源和會影響UI展示的 value 資源可通過唯一性命名來解決沖突;對于雖然同名但內容相同的資源可記錄不做處理。

第三個問題較為復雜,如果同名資源相差較大,可以考慮源碼的方式引入,或者直接在變體中覆蓋。

2)aar發布相關 

將模塊源碼發布成遠程aar需要注意不能混淆aar的代碼,否則開發階段依賴aar時不能識別其中的代碼,混淆階段放在app打包階段即可。

3)依賴替換中依賴標記不可添加classifier ,否則會拋出UnsupportedNotationException

dependencySubstitution聲明的module依賴標記只能有groupId,artifactId,version這三者,如果多于或者少于3,會直接拋出不支持的標記異常,拋出異常的標記轉換類為ModuleSelectorStringNotationConverter

//ModuleSelectorStringNotationConverter.java
@Override
protected ComponentSelector parseType(String notation) {
    ...
    String[] split = notation.split(":");
    if (split.length < 2 || split.length > 3) {
        throw new UnsupportedNotationException(notation);
    }
    ...
    return DefaultModuleComponentSelector.newSelector(moduleIdentifierFactory.module(group, name), DefaultImmutableVersionConstraint.of(version));
}

4)在應用依賴替換規則后遇到的 java.lang.StackOverflowError

此處用到的依賴替換規則為用模塊標記替換項目依賴。官網介紹在大型的多項目構建中,這個規則允許項目依賴的子集是從倉庫中下載而不是構建項目,對加速開發效率是很有用的;被替換的project必須在setting文件中被include,且為了去解析依賴配置,構建被替換依賴項的任務將不會執行。 

意思是被替換的project中的任務將不會執行,然而實際操作中,被替換Project的 dependencies 閉包中 api 動態方法會導致StackOverflowError異常,如果用implementation會導致Could not find method xxx on object DefaultDependencyHandler編譯異常。

為了查找原因和對Gradle有個形象的認知,當我們分析了 Gradle 依賴過程會發現:

  • Gradle 存在一個ServiceRegistry的注冊中心, 且 封裝了一系列類似于JavaSpi機制的Services類,其中一個DependencyServices類提供了DependencyFactory依賴工廠的構建方法;
  • 默認的DependencyFactory工廠實現類,包含通過靜態工廠方法創建的DependencyNotationParser對象,其中組裝了我們常用的StringNotation和ProjectNotation,返回一個組合的標記解析類NotationParser;

- 我們常用的group:artifact:version即為StringNotation;而project(':xxx')即為ProjectNotation;

  • 當我們通過 api或implement 聲明依賴時,實際上是通過Project中的DependencyHandler對象來進行處理的,這個對象就是ServiceRegistry提供的,這就和 DependencyServices 等功能類建立了聯系;
  • DependencyHandler 類中并沒有 implement和api 方法,因為Groovy是一個動態語言,允許運行時去catch未定義方法的調用。我們只需知道如果遇到這種找不到的方法會交給此類中的DynamicAddDependencyMethods對象去處理,最終會使用 DependencyNotationParser 中構建的標記解析類去解析。

至此,上述報錯的原因很有可能是因為依賴替換之后,改變了原本的DependencyHandlerNotationParser的聯系,導致原本正確的動態方法在替換后的功能類中找不到對應的方法或重復動態方法調用棧出錯。因此需要對模塊 Gradle 邏輯做一個跳過處理,不進入project的腳本邏輯。類似于:

//提供方法跳過替換后的模塊gradle邏輯
def isSubstitute() {
    return gradle.ext.subsitituteMap.containsKey(":${project.name}".toString())
}

5)aar 自動化構建方案的選擇

就構建這塊來說,aar編譯方式相比于之前,需要構建的模塊大大增加,以前不管代碼改動多少,只需要構建工程模塊即可;現在如果改動了模塊的代碼,需要先構建所有有變動的模塊,在這些模塊構建完成之后才能構建主包。如果這個過程全部由人手動來控制和觸發,其繁瑣程度不言而喻。

于是我們使用了webhook來實現自動化構建,在代碼 commit 的時候觸發相應的 webhook,來通知打包服務器執行自動構建的流程,打包服務器在接受到請求以后,先分析出發生改動的模塊是哪些,然后構建變動模塊的依賴樹,從依賴樹的底層開始從下而上觸發模塊構建,最后打包完成后通知相關人員。這樣一來,開發只需提交下代碼,剩下的就交給機器去做了。 

圖片

 圖10:自動化構建示意圖

但也因此出現了aar自動化的兩種方案:

  • 提交并同時構建所有的aar;此種方案優點是發布aar迅速,但因aar依賴順序問題會出現打包失敗,此時只能再打一次包解決此問題;
  • 提交并按依賴順序構建aar;此種方案能解決打包失敗的問題,但總的aar發布時間緩慢,導致整個打包流程的時間劇增,特定情況下會大幅超過源碼構建時間。此種情況可對模塊進行細粒度的拆分,減少開發業務時頻繁的進行aar模塊的傳遞性打包。

一般來說,對于不經常變動的模塊使用方案一,對于業務改動頻繁的模塊使用方案二。

6)構建過程的特征化標記或資源應放在App殼中

一般 CI/CD 系統中對構建的安裝包都有特征標記和資源注入,這些過程都應該放在app殼的Gradle腳本中,否則會出現資源不及時更新的問題。舉個例子:

問題描述:測試的時候發現,aar模式打的包拉不到新發布的rn。對打出來包進行分析后發現,包內的rn是工程里的原始rn,版本很老,服務那邊無法識別這么老的版本,導致rn增量下發失敗。

原因:gradle腳本下載rn資源的目錄為模塊aar的assets目錄,因此在aar模式下,總包rn的版本取決于模塊aar中的rn版本,而發布模塊aar打包腳本未更新rn,導致使用的rn為舊版。示意圖如下。

解決方案:修改下載rn資源的目錄為app殼的assets目錄,避免aar發布階段處理需要即時更新的邏輯。類似的問題還有AndroidMainifest清單文件中使用gradle參數,需要即時更新的資源或者參數需要放于app殼中執行,保證其實時性。

圖片

 圖11:動態資源未更新示意圖

3.4 其他優化

1)IDE插件的開發

為了方便開發同學本地開發時能夠靈活的進行 aar/源碼 的切換,我們引入了一個有圖形化界面的AS插件。基本原理是根據項目中的 json配置文件可視化的展示項目中的所有模塊,提供拖拽的功能方便模塊類型的變換,保存時改變本地的local.properties文件,達到控制參數的目的。 

另外,也可定制參數化打包,因為測試階段需要頻繁出包,這時如果要等aar包發布在打總包整個流程會變長,所以傳入sourceProject參數指定源碼模塊的參數化打包在測試階段尤為重要。

圖片

圖片

 圖12:AS插件靈活切換AAR/源碼

3.5 結果一覽

經過如上所述的改造后,對基于模塊aar方案的編譯時間進行統計。統計基于本地機器的干凈編譯時間,可得到比較精確的打包時間。

1)本地機器干凈編譯時間統計

操作步驟:

  • 開始第一次編譯

– as 進行一次 invalidate cache 清除緩存 + 進行一次 clean project

– 執行干凈編譯保證所有task都會執行gradlew--profile--offline--rerun-tasks assembleXXFlavorRelease

  • 開始第二次編譯

– as 進行一次 clean project

– 執行干凈編譯保證所有task都會執行gradlew--profile--offline--rerun-tasks assembleXXFlavorRelease

測試結果對照表:

圖片

 表1:干凈編譯耗時統計表


圖片

 表2:干凈編譯耗時對比

結果如上圖表所示,可直觀的得出結論:

  • 本地電腦測試去除干擾項后,第一次構建源碼平均耗時為 8m47s,aar平均耗時為 4m24s, 總包時間縮減率為 50%;第二次構建源碼平均耗時為 5m50s,aar平均耗時為 3m15s,總包時間縮減率為 44%。 

綜上,對基于模塊AAR的編譯優化方案可得出如下結論:

  • 從總包構建時間上來看,通過模塊AAR方案改造后 release 總包時間縮減率可達50%左右,時間縮減明顯。開發階段關閉transform等耗時操作且支持部分模塊以AAR引入,編譯時間會進一步縮減,可有效提高開發效率。
  • 但從整個流程上來看,打包流程從傳統的源碼構建分成了發布aar和構建aar總包兩部分。其中發布aar的平均時間為2m左右,如果發布一個aar后再打總包,此時整個打包流程的時間縮減率在13%~27%之間,而自動化的aar打包會有依賴問題,存在多個aar順序打包后再打總包的情況,故整個流程上的時間縮減并不明顯并大有可能會超過源碼編譯時間。

四、總結與展望

本次主要對項目的工程結構和 gradle 腳本進行一定的優化,通過 gradle 參數支持模塊 aar/源碼 的切換,可在一定程度上提高開發的效率,全aar下可節省出包時間,同時模塊化也是其他優化方案的基礎。

通過對項目進行模塊aar的改造后,編譯速度上收益明顯,尤其通過as插件可視化的切換更增加了開發的靈活度,開發階段不用長時間的等待編譯完成。為了保持較快的編譯速度,后續還可以做到以下幾點:

  • 項目在保持良好的工程結構的同時,對業務模塊進行適當粒度的拆分,可以讓項目結構更清晰,更加有利于項目的更新;
  • 可對高耗時的task,如 dexBuilder,單獨做優化; 
  • 添加工程構建監控機制,及時對編譯過程分析和處理;
責任編輯:張燕妮 來源: 攜程技術
相關推薦

2023-07-07 14:18:57

攜程實踐

2022-09-09 15:49:03

攜程火車票組件化管理優化

2023-09-15 09:34:54

2023-06-28 14:01:13

攜程實踐

2023-10-20 09:17:08

攜程實踐

2023-06-09 09:54:36

攜程工具

2023-06-28 10:10:31

攜程技術

2024-01-30 08:55:24

2011-01-24 15:37:32

火車票

2016-08-31 13:26:24

PythonPython3工具

2012-01-05 13:14:42

火車票

2018-01-10 22:19:44

2022-07-08 09:38:27

攜程酒店Flutter技術跨平臺整合

2022-07-15 09:20:17

性能優化方案

2011-01-28 15:48:11

Chrome插件Page Monito火車票

2015-03-18 15:05:12

12306驗證碼

2020-11-11 13:44:00

攜程旅行點擊量

2022-04-27 13:36:18

12306鐵路12306

2018-12-29 16:24:58

Python12306火車票

2018-01-02 09:56:04

Python12306火車票
點贊
收藏

51CTO技術棧公眾號

日韩在线精品| 欧美xnxx| 久久久高清一区二区三区| 国产精品午夜国产小视频| 免费视频网站www| 在线日本制服中文欧美| 制服丝袜日韩国产| 久久久久久久久久久免费视频| av在线免费一区| 成人免费观看男女羞羞视频| 国产精品大陆在线观看| 国产 日韩 欧美 成人| 欧美少妇性xxxx| 亚洲第一页中文字幕| 蜜臀久久99精品久久久酒店新书 | 国产日韩在线观看视频| 精品动漫一区二区三区| 国产又粗又硬又长| 激情福利在线| av亚洲精华国产精华精华| 成人激情视频在线播放| 在线观看日本视频| 亚洲黄色一区| 九九九久久久久久| 午夜成人亚洲理伦片在线观看| 欧美大奶一区二区| 日韩一区和二区| 九色porny自拍| 韩国成人动漫| 狠狠色狠狠色综合日日五| 天天综合五月天| 3d成人动漫在线| 国产清纯在线一区二区www| 精品一区久久久| 人人妻人人澡人人爽精品日本| 狠狠v欧美v日韩v亚洲ⅴ| 国产精品最新在线观看| 国产99免费视频| 在线一区欧美| 97视频com| 日产亚洲一区二区三区| 精品动漫3d一区二区三区免费版 | 日本老太婆做爰视频| 一广人看www在线观看免费视频| 久久只精品国产| 久久久www免费人成黑人精品| 空姐吹箫视频大全| 成人精品gif动图一区| 国产福利不卡| 亚洲欧美另类一区| 成人激情综合网站| 国产乱码精品一区二区三区不卡| 亚洲国产精彩视频| 成人国产在线观看| 国产在线精品一区二区中文| 色婷婷激情五月| 91亚洲国产成人精品一区二三| 国产精品入口免费| 天堂资源中文在线| 久久综合九色综合97_久久久| 久久免费看av| 青青免费在线视频| 日本一区二区免费在线 | 日本中文在线观看| 亚洲色图在线看| 免费看日b视频| 超黄网站在线观看| 狠狠综合久久av一区二区小说 | 国产一二三区精品| 国产综合婷婷| 欧美又大又粗又长| 中文字幕在线网站| 国产酒店精品激情| 久久99欧美| 成人免费黄色网页| 亚洲美女屁股眼交| 日本a视频在线观看| xx欧美视频| 欧美高清视频在线高清观看mv色露露十八| 日韩在线不卡一区| 99久久人爽人人添人人澡| 亚洲精品久久久久久久久久久久久 | 欧美日韩中文字幕在线播放 | 国产精品久久久久91| 91久久久久国产一区二区| 夫妻av一区二区| 欧美一区二区三区精美影视| 成人无遮挡免费网站视频在线观看| 亚洲综合999| 成人3d动漫一区二区三区| 精品视频一区二区三区在线观看| 亚洲精品ady| 日本免费网站视频| 亚洲视频www| 91精品久久久久久久久久久久久| 黄色小视频免费观看| 欧美激情一二三区| 欧美视频在线观看视频| 成人在线免费| 日韩av影院在线观看| 天天鲁一鲁摸一摸爽一爽| 国产精品久久久免费 | 三级a在线观看| 成人福利一区| 色视频www在线播放国产成人| 日本少妇吞精囗交| 久久成人免费网| 秋霞毛片久久久久久久久| 伊人电影在线观看| 欧美浪妇xxxx高跟鞋交| www.久久av| 国产精品vip| 成人高h视频在线| 国产专区在线| 天天综合天天综合色| 国产精品久久久久野外| 色综合咪咪久久网| 日本久久久久久久久久久| 欧美视频久久久| 一区二区三区蜜桃| 亚洲涩涩在线观看| 欧美久久综合网| 国产91av在线| 少妇av在线播放| 亚洲综合一区在线| 天天干天天色天天干| 日韩电影二区| 国产精品白嫩初高中害羞小美女 | 夜夜骚av一区二区三区| 久久看人人爽人人| 久久精品视频16| 91亚洲无吗| 欧美国产极速在线| 国产叼嘿视频在线观看| 亚洲视频一区二区在线| 日本黄色福利视频| 91亚洲国产| 国产在线观看精品| 国产理论在线观看| 91精品国产综合久久精品app| 四虎地址8848| 久久99日本精品| 爱爱爱视频网站| **国产精品| 久青草国产97香蕉在线视频| 国产剧情精品在线| 亚洲人成在线播放网站岛国 | 91免费视频网| 97在线播放视频| 亚洲欧洲美洲国产香蕉| 日韩av电影国产| 黄色电影免费在线看| 欧美日韩中文字幕一区二区| www.涩涩爱| 国产一区 二区 三区一级| 超碰超碰超碰超碰超碰| 国产精品美女在线观看直播| 香蕉久久国产| 国产成人精品一区二区在线| 嫩草在线播放| 欧美日韩在线三区| tube国产麻豆| 不卡一区中文字幕| 男人天堂999| 国产一区二区在线| 国产综合视频在线观看| 免费在线播放电影| 日韩电影中文 亚洲精品乱码| 色老头在线视频| 《视频一区视频二区| 九色91porny| 国产欧美一区二区三区国产幕精品| 欧美一区二区福利| www.欧美| 51ⅴ精品国产91久久久久久| 国产精品天堂| 日韩午夜激情av| 国产剧情在线视频| 亚洲欧美国产77777| 少妇户外露出[11p]| 欧美aa在线视频| av日韩在线看| 国产永久精品大片wwwapp| 成人网在线观看| 成人bbav| 久久亚洲国产精品| 亚洲人成色777777老人头| 欧美狂野另类xxxxoooo| 可以免费看的av毛片| 国产精品欧美一区二区三区| 永久免费未满蜜桃| 精品一区二区三区久久| 久久成人免费观看| 欧美hd在线| 免费亚洲一区二区| 一区二区三区免费在线看| 国产成人一区二| а√在线中文网新版地址在线| 这里只有精品视频在线| 日本高清视频网站| 在线不卡免费av| 天天干,天天干| 一区2区3区在线看| 中文字幕在线观看二区| 91丝袜美腿高跟国产极品老师| 日本高清一区二区视频| 日韩精品成人一区二区在线| 亚洲熟妇av日韩熟妇在线| 综合天堂久久久久久久| 日韩三级电影| 曰本一区二区三区视频| 国产精品久久久久久久天堂第1集 国产精品久久久久久久免费大片 国产精品久久久久久久久婷婷 | 日韩欧美在线网站| 中文字幕免费观看视频| 日韩欧美第一页| 69精品久久久| 亚洲精品国产视频| 国产欧美小视频| 国产亚洲成aⅴ人片在线观看| 黑丝av在线播放| 国产盗摄女厕一区二区三区| 91亚洲精品久久久蜜桃借种| 首页欧美精品中文字幕| 亚洲熟妇av一区二区三区漫画| 欧美日韩国产亚洲一区| 吴梦梦av在线| 日韩视频在线观看| 婷婷精品国产一区二区三区日韩| 亚洲黄色录像| 久久久福利视频| 婷婷精品在线| 九九九九九精品| 好吊妞国产欧美日韩免费观看网站| 亚洲淫片在线视频| 久久中文字幕一区二区| 91夜夜未满十八勿入爽爽影院 | 精品国产视频一区二区三区| 国产精品午夜春色av| 性猛交娇小69hd| 国产丝袜欧美中文另类| 美女脱光内衣内裤| 国产亚洲欧美激情| 大胸美女被爆操| 国产精品美女www爽爽爽| 任你操精品视频| 亚洲欧洲日产国产综合网| 亚洲视频重口味| 亚洲欧美日韩国产另类专区| 波多野结衣爱爱视频| 亚洲乱码日产精品bd| 欧美人与禽zozzo禽性配| 一区二区三区免费网站| 日韩视频免费观看高清| 欧美日韩精品二区| 波多野结衣一区二区三区四区| 在线观看视频91| 一级特黄aaa| 日韩欧美成人一区| 日本韩国免费观看| 亚洲欧美制服丝袜| 亚洲成人三级| 欧美成人中文字幕| 9999精品成人免费毛片在线看| 51ⅴ精品国产91久久久久久| 国产精品亚洲d| 91九色视频导航| a级日韩大片| 欧美不卡在线一区二区三区| 成人综合专区| 久久www视频| 香蕉精品999视频一区二区| 中文字幕av专区| 国产成人午夜视频| 性欧美丰满熟妇xxxx性仙踪林| 国产精品五月天| 国产一级在线播放| 色哟哟国产精品免费观看| 88av在线视频| 亚洲国产精品中文| 免费黄色电影在线观看| 97色在线视频| 视频欧美精品| 极品日韩久久| 婷婷综合伊人| 国产在线精品91| 精品在线播放午夜| 一起草在线视频| 亚洲欧美在线另类| 国内精品福利视频| 国产三级一区| 国产精品视频999| 91精品国产乱码久久久竹菊| 日韩aⅴ视频一区二区三区| 影音先锋成人在线电影| 色婷婷综合久久久久中文字幕| 国产高清不卡一区二区| 韩国女同性做爰三级| 一区二区三区四区蜜桃| 自拍偷拍色综合| 欧美精品一区二区在线观看| 999国产在线视频| 欧美中文在线免费| 久久国产精品美女| 亚洲国产成人不卡| 国产日韩欧美三级| 日本女人黄色片| 中文av字幕一区| 在线观看日本网站| 精品久久久久久久久久久久包黑料| 日本综合在线| 国产成人精品一区二区三区| 视频小说一区二区| www.亚洲成人网| 国产一区二区三区蝌蚪| 娇妻被老王脔到高潮失禁视频| 偷窥少妇高潮呻吟av久久免费| a毛片在线免费观看| 最近2019年日本中文免费字幕 | 狠狠干成人综合网| 91亚洲精品久久久蜜桃借种| 中文字幕精品一区| 无码视频一区二区三区| 亚洲第一视频网站| wwwwxxxx在线观看| 成人免费视频网站入口| 亚洲综合色站| 国产毛片久久久久久| 国产精品美女一区二区三区| 免费精品一区二区| 亚洲性生活视频在线观看| 成人直播视频| 欧美日韩电影一区二区| 亚洲尤物影院| 国产伦精品一区二区三区妓女| 精品久久久精品| 日本中文字幕一区二区有码在线| 欧美亚洲另类在线| 色天下一区二区三区| 国产 福利 在线| 久久综合久久综合久久综合| 中文字幕免费观看| 亚洲视频在线看| 成人全视频在线观看在线播放高清| 日本中文不卡| 日本在线不卡一区| 2017亚洲天堂| 在线综合视频播放| 在线中文字幕-区二区三区四区 | 亚洲电影男人天堂| 久久国产乱子伦免费精品| 久久久高清一区二区三区| 精品国产www| 久久精品精品电影网| 国产999精品在线观看| 日本精品福利视频| 不卡一区在线观看| 无码人妻一区二区三区线 | h片在线观看视频免费| 国产一区二区三区奇米久涩 | 91蝌蚪视频在线| 一区二区三区产品免费精品久久75| 亚洲a视频在线| 欧美在线视频观看免费网站| 精品无人区麻豆乱码久久久| 污污动漫在线观看| 亚洲伊人色欲综合网| 午夜av免费观看| 国产精品日韩一区| 中文字幕一区二区三区久久网站 | 婷婷成人在线| 天天干天天综合| 亚洲中国最大av网站| 欧美日韩在线中文字幕| 国产精品一区=区| 欧美午夜免费影院| av网站免费在线看| 欧美一区二区三区婷婷月色| 24小时免费看片在线观看| 日本高清不卡一区二区三| 精品一区二区三区视频| 可以免费看的av毛片| 精品国产拍在线观看| 久久九九热re6这里有精品| 黑鬼大战白妞高潮喷白浆| 亚洲人成小说网站色在线 | 猫咪成人在线观看| 国产视频1区2区3区| 亚洲18女电影在线观看| 91露出在线| 久久久精彩视频| 激情成人综合网| www.国产一区二区| 欧美国产日韩xxxxx| 国产精品日韩一区二区| 久久精品嫩草影院| 国产情侣第一页| 国产亚洲综合性久久久影院| 99久久精品国产色欲| 人人爽久久涩噜噜噜网站| 午夜精品剧场|