欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

嗶哩嗶哩Android項(xiàng)目編譯優(yōu)化

 更新時(shí)間:2022年06月22日 09:03:33   作者:張揚(yáng)  
這篇文章主要為大家介紹了嗶哩嗶哩Android項(xiàng)目編譯優(yōu)化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

嗶哩嗶哩的安卓項(xiàng)目的工程結(jié)構(gòu)是Monorepo(單倉(cāng))變種,也就是所有的代碼都在一個(gè)工程結(jié)構(gòu)下編譯。我們認(rèn)為Monorepo(單倉(cāng))是一個(gè)非常適合我們的開發(fā)模式,主要是因?yàn)槠涮峁┑脑犹峤?,可見性,參與度,切片的穩(wěn)定性等等優(yōu)點(diǎn),這些都是我們選擇Monorepo的原因。但是因?yàn)闄?quán)限管控,ijkplayer等雙端通用工程的原因,還是拆開了多個(gè)git倉(cāng)庫。通過git權(quán)限的方式來拆分了工程結(jié)構(gòu),然后通過Gradle Plugin的形式進(jìn)行了多工程的組合,在CI打包環(huán)境上讓工程具備Monorepo的能力。

點(diǎn)擊可了解多倉(cāng)和單倉(cāng)的差別

隨著代碼長(zhǎng)時(shí)間迭代,業(yè)務(wù)模塊數(shù)量增多,當(dāng)前工程有500+的模塊以及19個(gè)復(fù)合構(gòu)建。如果所有的模塊都用源代碼編譯,打一個(gè)包本地可能需要大概30分鐘的時(shí)間才能完成編譯。而且嗶哩嗶哩的安卓工程是一個(gè)上百人同時(shí)開發(fā)的項(xiàng)目,如果一小個(gè)改動(dòng)都需要30分鐘的時(shí)間投入編譯中,對(duì)于開發(fā)同學(xué)來說可能心態(tài)都要崩了。

下圖是工程在CI上全量編譯的情況下,編譯耗時(shí)大概是16.6分鐘,打包機(jī)的性能是優(yōu)于本地電腦的,所以速度會(huì)更快一點(diǎn)。

讓編譯速度變得更快也就迫在眉睫,而且這個(gè)模式是針對(duì)開發(fā)同學(xué),讓他們可以快速對(duì)模塊變更的代碼負(fù)責(zé)。同時(shí)也希望這個(gè)模式是在不影響當(dāng)前的工程結(jié)構(gòu),讓他們的打包速度能變得更快。

編譯優(yōu)化

我們通過添加--scan參數(shù),觀察了全源碼情況下的編譯流程。在沒有編譯緩存的情況下,每個(gè)模塊都是源代碼,所以都需要執(zhí)行將源代碼編譯成字節(jié)碼的過程。同時(shí)還需要完成所有工程的依賴配置等等展開,這些過程都是比較耗時(shí)的。而工程編譯的大部分耗時(shí)都集中在將源碼編譯字節(jié)碼產(chǎn)物的過程中,也就是Gradle Task階段。下圖就是我從buildScan中找出來耗時(shí)相對(duì)較長(zhǎng)的Task任務(wù),可以看出來有的編譯任務(wù)的耗時(shí)在2min以上。

如果能將模塊直接變更成aar產(chǎn)物,那么就可以跳過這些模塊的編譯任務(wù),直接使用他們的二進(jìn)制產(chǎn)物。但是直接使用aar產(chǎn)物是有風(fēng)險(xiǎn)的,會(huì)犧牲一部分代碼的正確性,同時(shí)代碼可能會(huì)有滯后性。另外編譯階段會(huì)進(jìn)行很多語法校驗(yàn)的操作,而直接更換成aar產(chǎn)物了就會(huì)跳過這部分檢查。

而我們快編的做法就是犧牲一部分代碼正確性,將沒有變更的源代碼變成aar產(chǎn)物,在編譯階段直接采用aar產(chǎn)物進(jìn)行編譯,這樣就可以跳過部分源代碼編譯環(huán)節(jié)。但是因?yàn)樘^了源代碼編譯階段,所以有一部分編譯時(shí)的常量?jī)?yōu)化,方法簽名變更或者其他問題就可能會(huì)出現(xiàn)。

在快編模式下編譯的平均耗時(shí)優(yōu)化到了6min左右。因?yàn)閏i的特性,每次都需要清空文件夾之后重新clone工程,這樣會(huì)有額外的工程準(zhǔn)備時(shí)間。另外在變更的模塊比較少的情況下,編譯速度則會(huì)更快一點(diǎn)。

另外這些問題使用快編的人是否可以接受?我們目的主要是優(yōu)化下開發(fā)編譯速度,能讓他們可以更快的出包,更快的調(diào)試代碼,所以我們認(rèn)為這種取舍還是合理的。

如何可以及時(shí)的發(fā)現(xiàn)這些方法簽名變更的問題呢?下面讓我們慢慢展開我們的編譯優(yōu)化方案。

工作流程

我們要先從gradle build的生命周期開始展開了,可以分為三個(gè)大流程,一個(gè)是初始化階段,一個(gè)是配置階段,另外一個(gè)是task執(zhí)行階段。一般配置階段會(huì)加載完成工程的依賴關(guān)系以及配置信息等工作。

我們會(huì)在生命周期的不同階段,執(zhí)行一些的代碼,然后篡改一些編譯相關(guān)的屬性,從而做到源代碼和aar產(chǎn)物之間的替換操作。下面是我列出來我們的項(xiàng)目的快編的工作流程。

andruid是當(dāng)前的工程名,因?yàn)橐龆鄠€(gè)業(yè)務(wù)之間的代碼隔離,所以我們還是把單個(gè)倉(cāng)庫拆分成了多個(gè)工程結(jié)構(gòu),如果一個(gè)有所有倉(cāng)庫權(quán)限的人或者CI機(jī)器可以獲取到所有代碼倉(cāng)庫的權(quán)限,并全源碼打包。

babel commit 是負(fù)責(zé)來存放這些倉(cāng)庫的穩(wěn)定切片信息,會(huì)在當(dāng)前分支push到遠(yuǎn)端之后pipeline完成之后生成,然后以git commit的形式存放在andruid工程下。當(dāng)沒有對(duì)應(yīng)倉(cāng)庫代碼權(quán)限則會(huì)使用babel commit內(nèi)存放的version版本信息。

  • Gradle 配置階段之前先根據(jù)緩存信息獲取所有子倉(cāng)切片
  • 同步多個(gè)業(yè)務(wù)倉(cāng)庫配置
  • 通過遍歷展開文件樹,生成工程對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)
  • 基于當(dāng)前模塊的commit來生成version,嘗試下載aar
  • 基于下載結(jié)果選擇使用源碼還是aar進(jìn)行編譯

快編插件

上面我們講完了大體的流程是如何的,下面我會(huì)拋出一些問題,就以下幾個(gè)問題來看我們是如何解決的:

  • 如何確保代碼相對(duì)來說的準(zhǔn)確性呢?以什么來作為唯一標(biāo)識(shí)符?

  • 如何在gradle的配置階段就完成源碼到aar的替換呢?

  • 當(dāng)前的aar產(chǎn)物出現(xiàn)了方法簽名變更的情況,我們有沒有辦法快速的刷新所有的緩存呢?

  • 有沒有辦法讓同步流程也變得更快?

  • 在什么情況下誰來生成上傳這個(gè)aar產(chǎn)物?

獲取工程樹結(jié)構(gòu)

我們要在settings.gradle執(zhí)行的階段就先獲取到當(dāng)前目錄下有多少個(gè)工程,然后我們才能基于這個(gè)工程數(shù)據(jù)結(jié)構(gòu),先去嘗試下載每個(gè)模塊的遠(yuǎn)端aar產(chǎn)物,如果無法下載到該模塊,則意味著模塊已經(jīng)出現(xiàn)了變更。然后在gradle配置階段之前進(jìn)行替換操作替換掉能下載到aar的模塊。

這里我們需要先定義出一個(gè)數(shù)據(jù)結(jié)構(gòu)來負(fù)責(zé)存放我們所需要收集的模塊編譯信息。先盤點(diǎn)下我們所要收集的數(shù)據(jù)結(jié)構(gòu)Project,Project 數(shù)據(jù)結(jié)構(gòu)如下表格所示。后面我們會(huì)多次使用到這個(gè)數(shù)據(jù)結(jié)構(gòu),通過改變其中的值屬性來變更我們的編譯流程。

字段名

含義

dir

文件路徑

group

組名

name

模塊名

version

版本號(hào)

change

是否變更

a8Change

方法簽名是否變更

maven.yaml負(fù)責(zé)存放當(dāng)前工程的group+name 信息

數(shù)據(jù)結(jié)構(gòu)的生成規(guī)則如下,我們會(huì)以當(dāng)前文件夾作為根節(jié)點(diǎn),之后遍歷展開當(dāng)前的文件樹形結(jié)構(gòu),每當(dāng)檢測(cè)到一個(gè)文件夾下面同時(shí)含有build.gralde和一個(gè)maven.yaml的文件的情況下,我們就會(huì)生成一個(gè)Project的數(shù)據(jù)結(jié)構(gòu),之后加入列表中。

當(dāng)這次文件夾遍歷操作完成之后,我們就會(huì)得到當(dāng)前工程下有多少個(gè)模塊,然后他們的group+name是什么,另外通過計(jì)算出緩存的version版本是多少。通過這些信息來幫助我們?nèi)ネ瓿晌覀兊目炀庍壿?。但是?dāng)前這個(gè)操作需要耗費(fèi)大概1分鐘的時(shí)間。

version版本

我們沒有按照Gradle標(biāo)準(zhǔn)的1.0.0這種版本方式來定義模塊版本,這種方式很難和當(dāng)前的代碼變更結(jié)合到一起,而且需要一套全局version來進(jìn)行版本管理。另外也很難達(dá)到準(zhǔn)確表達(dá)當(dāng)前分支下的真實(shí)緩存情況。

為什么要用commit的sha這個(gè)作為版本號(hào)呢?因?yàn)榇髠}(cāng)是基于一個(gè)穩(wěn)定切片的編程模式。既然切片是穩(wěn)定的情況下,那么也就是當(dāng)前的每個(gè)Project的commit提交也都是穩(wěn)定的。

我們通過git指令去獲取到當(dāng)前的Project數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)的文件夾下的commit的sha值,一定是每一個(gè)Project下面的最接近的commit。然后根據(jù)上面的加鹽規(guī)則來生成這個(gè)版本號(hào),之后作為數(shù)據(jù)結(jié)構(gòu)的一部分。

static def getGitSha(String file) {
      def text = "git rev-parse --show-toplevel".execute(null, new File(file)).text.trim()
      if (text.length() == 0) {
          return ""
      }
      def releaPath = file.replace(text + "/", "")
      def cmd = 'git log --max-count=1 --pretty=%H '
      if (releaPath.length() > 0) {
          cmd = cmd + " " + releaPath
      }
      def sha = cmd.execute(null, new File(text)).text.trim()
      if (sha.startsWith("HEAD")) {
          return ""
      }
      if (sha.startsWith("fatal:")) {
          return ""
      }
      return sha
  }

當(dāng)前的aar產(chǎn)物出現(xiàn)了方法簽名變更的情況下,我們有沒有辦法快速的刷新所有的緩存呢?

所以我們引入了一個(gè)鹽值,然后將這個(gè)鹽值和sha結(jié)合到一起作為當(dāng)前倉(cāng)庫的真實(shí)的version。當(dāng)我們的鹽值發(fā)生變化就會(huì)導(dǎo)致當(dāng)前所有的緩存失效,然后就會(huì)觸發(fā)重新生成新的version版本了。所以只要修改鹽值的值就可以將所有aar的版本進(jìn)行統(tǒng)一的升級(jí)。

還需要對(duì)variant也加入version版本生成的邏輯中。不同的變種的代碼也都是不同的,需要區(qū)分變種來選擇下載不同的aar版本。

源碼orAAR

等完成上述步驟之后,我們的工程的快編的前置工作就準(zhǔn)備的差不多了,接下來就是嘗試性去下載這個(gè)version版本的aar。如果這個(gè)版本存在,意味著當(dāng)前倉(cāng)庫并沒有發(fā)生實(shí)際的變更,可以用該產(chǎn)物直接替換掉當(dāng)前的源代碼。

如果當(dāng)前的version的版本無法下載,可以認(rèn)為這個(gè)commit在遠(yuǎn)端aar并不存在,之后我們就可以將Project標(biāo)記為已經(jīng)發(fā)生變更,然后它將直接使用源碼編譯。

這里還有些邊界條件,如果當(dāng)前的變更沒有提交的情況下,我們需要獲取當(dāng)前的git項(xiàng)目的變更內(nèi)容,然后基于文件路徑匹配到對(duì)應(yīng)的Project,將它切換到源碼編譯的情況下,獲取變更文件路徑代碼如下:

static List<String> getAllChangeFile(File file) {
      def text = "git status -u -s".execute(null, file).text
      String[] txts = text.split("\n")
      List<String> result = new ArrayList<>()
      txts.each { String item ->
          if (item.length() > 3) {
              result.add(item.substring(3))
          }
      }
      return result
  }

主動(dòng)Skip模塊

完成上述幾步之后,工程結(jié)構(gòu)就已經(jīng)是一個(gè)很清晰的狀態(tài)了,我們已經(jīng)知道當(dāng)前工程有多少個(gè)模塊,哪些模塊發(fā)生了變更哪些模塊沒有變更,另外沒有變更模塊的aar version版本,同時(shí)也拿到了下載的產(chǎn)物。

接下來我們需要做的操作是快編里面非常重要的,將當(dāng)前的沒有變更的源代碼移除。這樣工程所剩下來的就是發(fā)生了變更的模塊,通過這種方式就可以編譯最少的變更模塊了,從而縮短打包流程和避免無變更的模塊配置時(shí)間。

工程內(nèi)含有10+的復(fù)合構(gòu)建(Composing builds),所以這里我們需要支持兩個(gè)不同的場(chǎng)景,一個(gè)是當(dāng)前當(dāng)前工程下的settings.gradle下的include模塊進(jìn)行移除,還有一個(gè)就是移除沒有變更的includebuilding工程。

摘自 Gradle 文檔:復(fù)合構(gòu)建只是包含其他構(gòu)建的構(gòu)建。在許多方面,復(fù)合構(gòu)建類似于 Gradle 多項(xiàng)目構(gòu)建,不同之處在于,它包括完整的 builds ,而不是包含單個(gè) projects。

組合通常獨(dú)立開發(fā)的構(gòu)建,例如,在應(yīng)用程序使用的庫中嘗試錯(cuò)誤修復(fù)時(shí),將大型的多項(xiàng)目構(gòu)建分解為更小,更孤立的塊,可以根據(jù)需要獨(dú)立或一起工作。

復(fù)合構(gòu)建的屬性都是存放在Gradle Settings內(nèi)的IncludedBuildSpec屬性。通過groovy語言的動(dòng)態(tài)屬性,獲取到DefaultSettings下的getIncludedBuilds方法的返回值,從而獲取到當(dāng)前所有的復(fù)合構(gòu)建,然后根據(jù)IncludedBuildSpec的rootDir路徑來決定哪些復(fù)合構(gòu)建是不需要參與當(dāng)前編譯的。

還有就是Settings下的模塊通過調(diào)用settings.rootProject.children.clear(),include對(duì)應(yīng)的是Gradle Settings下的ProjectDescriptor,我們將所有的原始模塊數(shù)據(jù)的清空,然后基于Project數(shù)據(jù)結(jié)構(gòu)將變更的Project的文件路徑來插入列表,重新生成新的ProjectDescriptor。

Configuration策略

配置階段最后就是要將項(xiàng)目?jī)?nèi)的依賴版本更換到我們當(dāng)前Project數(shù)據(jù)結(jié)構(gòu)內(nèi)的version版本上去了。我們項(xiàng)目?jī)?nèi)的依賴版本只是起到一個(gè)占位的作用,全量編譯的時(shí)候源代碼,快速編譯的情況下就需要替換成對(duì)應(yīng)版本的aar產(chǎn)物。

gradle在配置階段后期,Configuration提供一個(gè)ResolutionStrategy策略,讓我們主動(dòng)的來篩選更改所需要的遠(yuǎn)端依賴庫的版本。

我們可以基于Project中的group,moduleName,version,然后通過ResolutionStrategy來進(jìn)行版本替換,簡(jiǎn)單的說就是當(dāng)group+name的組合符合的情況下替換成我們計(jì)算出來的version,之后再重新指向這個(gè)新版本就可以了。

eachDependency { DependencyResolveDetails details ->
                               Logger.debug("requested    " + requested.group + ":" + requested.name + ":" + requested.version)
                               Logger.debug("target       " + details.getTarget())
                               def targetInfo = details.getTarget().toString().split(":")
                               String sVersion = compare.select(targetInfo[0], targetInfo[1], targetInfo[2])
                               if (sVersion != targetInfo[2]) {
                                   def tar = targetInfo[0] + ":" + targetInfo[1] + ":" + sVersion
                                   Logger.debug("git select   " + tar)
                                   details.useTarget(tar)
                                   details.because(" git flow ")
                               }
                               Logger.debug("target new   " + details.getTarget())
                           }

遠(yuǎn)端upload

那么是由誰來執(zhí)行這個(gè)aar版本的發(fā)布任務(wù)的呢?

當(dāng)我們的每個(gè)commit push提交到遠(yuǎn)端時(shí),gitlab的ci都會(huì)執(zhí)行一些pipeline任務(wù)。這里我們加了一個(gè)發(fā)布任務(wù),將所有變更的模塊發(fā)布到遠(yuǎn)端。

這部分和之前的version計(jì)算相同,先計(jì)算出工程所有的commit version的aar,然后嘗試下載,之后將下載不到的定義為變更模塊,最后將這部分發(fā)布到遠(yuǎn)端。upload階段就是通過maven-publish插件,發(fā)布aar到自定義遠(yuǎn)端地址,當(dāng)然內(nèi)部有些小改動(dòng)這里就不多展開了。

另外我們還進(jìn)行了一些小小的優(yōu)化調(diào)整,區(qū)分當(dāng)前到底是idea同步還是編譯任務(wù),只在編譯階段插入了maven-publish插件。這樣略微加快了一點(diǎn)點(diǎn)項(xiàng)目的同步速度。

R8 class check

當(dāng)把一大部分模塊切換到aar產(chǎn)物后,有可能會(huì)發(fā)生方法簽名常量?jī)?yōu)化等變更不同步的問題。這里我們就需要一種手段來檢測(cè)當(dāng)前apk中是否有一些危險(xiǎn)的方法調(diào)用,然后讓這些可能會(huì)崩潰的地方在代碼合入之前就暴露出來。

我們?cè)贏ndroid R8的基礎(chǔ)上,開發(fā)了一套專門針對(duì)于方法簽名檢查的任務(wù),我們叫做A8檢查。我們先來簡(jiǎn)單的了解下R8的一些小知識(shí)。

正常情況下混淆可以拿來壓縮代碼體積,其中包括刪除無效代碼等等。代碼縮減(也稱為“搖樹優(yōu)化”)是指移除 R8 確定在運(yùn)行時(shí)不需要的代碼的過程。此過程可以大大減小應(yīng)用的大小,例如,當(dāng)您的應(yīng)用包含許多庫依賴項(xiàng),但只使用它們的一小部分功能時(shí)。

也就是在R8進(jìn)行代碼壓縮的過程中,其實(shí)就已經(jīng)包含有所有語法樹信息了。我們?nèi)绻妙愃频臋C(jī)制,就可以獲取到對(duì)應(yīng)的方法簽名,然后通過遍歷循環(huán)之后檢測(cè)出是不是當(dāng)前類方法簽名存在問題。

public static void run(A8Command command) throws Throwable {
      AndroidApp app = command.getInputApp();
      InternalOptions options = command.getInternalOptions();
      ExecutorService executor = ThreadUtils.getExecutorService(options);
      new A8(options, command.forceReflectionError).run(app, executor, false);
  }

A8其中AndroidApp和R8內(nèi)的實(shí)現(xiàn)是一樣的,會(huì)根據(jù)當(dāng)前的Android Sdk版本以及需要檢查的文件先去生成AppView,然后我們基于這個(gè)AppView內(nèi)的appView.appInfo().classes(),之后遍歷展開,判斷當(dāng)前apk中是否含有一些非法的函數(shù)調(diào)用。

這種檢測(cè)方式的可以完全模擬出apk實(shí)際安裝情況下的一些函數(shù)調(diào)用情況,包括android源代碼內(nèi)的。一般的字節(jié)碼訪問之后生成的函數(shù)調(diào)用信息是難以模擬出安卓源代碼內(nèi)的api的,而且一些lambda因?yàn)槊撎呛笾?,可能也?huì)產(chǎn)生一系列的問題。

而且R8作為一個(gè)apk方法優(yōu)化工具,他原始提供的功能會(huì)比我們自己寫的更合理和靠譜,同時(shí)R8階段中已經(jīng)完成了脫糖,并轉(zhuǎn)化成了dex。所以我們認(rèn)為可靠性更高,這個(gè)也就是我們快編的最后一道屏障。

我們會(huì)將這個(gè)屏障設(shè)置在gitlab ci的pipeline中,如果A8檢查沒有通過代碼是不允許被合入的。

Faster

在快編的基礎(chǔ)上,我們還是希望工程能有更快的編譯速度。我們當(dāng)前采用了云端編譯,獨(dú)立的編譯單元以及后續(xù)打算在編譯單元的基礎(chǔ)上構(gòu)造獨(dú)立的application殼工程來優(yōu)化我們的編譯速度。

在內(nèi)部碰撞的過程中,有過一些對(duì)于編譯優(yōu)化的設(shè)想,可以將選擇權(quán)交還給開發(fā)同學(xué),讓他們主動(dòng)來選擇當(dāng)前的開發(fā)模式,是只對(duì)自己當(dāng)前的模塊負(fù)責(zé),還是一些全局性的改動(dòng)。在不同的模式下他們可以選擇打開不同的工作路徑,更快速的切換開發(fā)模式。

當(dāng)前工程有大量的復(fù)合構(gòu)建邏輯,而復(fù)合構(gòu)建就是將多個(gè)本來完全獨(dú)立的工程結(jié)構(gòu)進(jìn)行組合編譯。每個(gè)業(yè)務(wù)都是一個(gè)獨(dú)立的Gradle Project,都具有一個(gè)settings.gradle文件,所以他們天生就具有獨(dú)立打開以及編譯的條件,但是因?yàn)閮?nèi)部依賴是源代碼,還是需要組合多個(gè)復(fù)合構(gòu)建。在這個(gè)前提的基礎(chǔ)上,也就誕生了在當(dāng)前模式下更獨(dú)立的編譯單元。

云編譯

云編譯的工作就是將本地的所有變更內(nèi)容和上一個(gè)babel commit進(jìn)行計(jì)算,之后將變更的內(nèi)容傳輸?shù)竭h(yuǎn)端打包機(jī),然后依托于遠(yuǎn)端的編譯緩存和遠(yuǎn)端更牛的機(jī)器進(jìn)行打包工作,等打包完成之后再將apk傳輸回本地之后安裝到手機(jī)中。

云編譯系統(tǒng)則不同于ci,可以保留當(dāng)前的build cache。平均的打包速度會(huì)更加快一點(diǎn),大概是3min左右。另外一個(gè)優(yōu)化點(diǎn)是獲取工程結(jié)構(gòu)能緩存的話,當(dāng)分支沒有變化的情況下直接使用緩存數(shù)據(jù),優(yōu)化完這部分應(yīng)該能更快。

云編譯的情況下平均的編譯速度會(huì)比快編更加快一點(diǎn),大概是3min。

獨(dú)立的編譯單元

當(dāng)全源代碼展開的時(shí)候,工程的同步速度會(huì)變得非常的慢,當(dāng)前工程有500+的模塊以及19個(gè)復(fù)合構(gòu)建,一次同步大概需要消耗半個(gè)小時(shí)左右的時(shí)間。并且當(dāng)切換分支都需要重新同步工程,這個(gè)也是當(dāng)前迫切需要解決的問題。對(duì)比了下單倉(cāng)和多倉(cāng)的優(yōu)劣,多倉(cāng)模式下的一個(gè)優(yōu)勢(shì)就是工程依賴都是aar依賴,工程可以獨(dú)立編譯并運(yùn)行起來。這也是單倉(cāng)所缺少的能力,那么有沒有辦法將單倉(cāng)也具有類似的能力呢?

基于前面的快編原理,可以挑選出當(dāng)前切片下所依賴的模塊的aar產(chǎn)物。接下來我們要做的就是讓每一個(gè)業(yè)務(wù)或者sdk等等具有可以獨(dú)立打開的能力就行了。

另外我們的工程結(jié)構(gòu)上也將基礎(chǔ)層,中間層等都進(jìn)行了獨(dú)立拆分,一個(gè)業(yè)務(wù)模塊 + common + framework 就是一個(gè)完全可以獨(dú)立編的工作單元。

所以只需要把快編插件添加點(diǎn)簡(jiǎn)單的邏輯,生成一個(gè)子模板的插件。之后將插件引入到這個(gè)獨(dú)立工程的settings.gradle下。為了不影響?yīng)毩⒐こ痰倪壿?,這個(gè)插件只有在當(dāng)前工程作為root節(jié)點(diǎn)的情況下生效。

if (gradle.parent == null) {
    apply plugin: "fawkes.fast.build.sub.settings"
}

在這個(gè)工程目錄下的添加一個(gè)saints_row的文件,之后在插件內(nèi)反序列化數(shù)據(jù)結(jié)構(gòu),基于這個(gè)結(jié)構(gòu)來決定后續(xù)的編譯單元屬性,展開的目錄結(jié)構(gòu),以及編譯模式等等。

info:
 - path: "framework"
   src: false
 - path: "common"
   src: true
  
mode: "aarOnly"

其中我們可以通過src來任意關(guān)閉其中的一個(gè)工程進(jìn)行重新同步索引,工程關(guān)閉情況下依賴會(huì)被替換成快編的aar version。然后我們也設(shè)立了三種完全不同的模式,來輔助業(yè)務(wù)同學(xué)進(jìn)行日常的開發(fā)工作。

  • normal 基礎(chǔ)模式 全部源代碼展開。
  • aarOnly 引用快編的原理,將沒有變更的模塊變成aar,變更的模塊切換到源碼。
  • owner 將自己作為owner的工程源代碼展開,其他的和aarOnly一致。

一般情況下,開發(fā)同學(xué)只需要更改自己業(yè)務(wù)代碼就行了,所以他們可以將自己不需要的模塊直接切換成aar,這樣的同步速度對(duì)他們來說是最快的,所以我們把默認(rèn)模式都切換成aarOnly模式。

另外我們只是在原始的工程結(jié)構(gòu)下,開辟出了獨(dú)立的編譯單元,因?yàn)锳ndroid Studio打開的路徑不同,則選擇的模式就會(huì)出現(xiàn)差異,所以他并不會(huì)實(shí)際影響到當(dāng)前的工程結(jié)構(gòu)。

通過這兩張數(shù)據(jù)對(duì)比圖,可以看出我們?cè)谕降臅r(shí)間上,是有非常大的數(shù)據(jù)提升的。在這種模式下,可以讓工程找到相對(duì)準(zhǔn)確的aar版本,展開模塊數(shù)量變少,編譯和同步速度也就能更快的。對(duì)于開發(fā)來說,他也只需要對(duì)自己的業(yè)務(wù)代碼負(fù)責(zé)。

展望

后續(xù)我們計(jì)劃和自動(dòng)化初始化框架進(jìn)行配合,在每個(gè)可單獨(dú)打開的工程下都生成好一個(gè)殼工程,讓業(yè)務(wù)同學(xué)可以更快速感知到當(dāng)前代碼的變更并進(jìn)行調(diào)試。然后通過idea插件,或命令行工具可以快速的生成這個(gè)殼工程。這個(gè)插件后續(xù)也能跟隨著b站的工程迭代而持續(xù)更新,提供更多更便利的功能給到開發(fā)同學(xué)。

我們的任務(wù)就是提供更多的可能性,更多的便利性,將選擇的權(quán)利交給業(yè)務(wù)同學(xué)。讓他們能在大倉(cāng)的模式下快速穩(wěn)定的開發(fā)下去,可以選擇大倉(cāng),也可以選擇自己業(yè)務(wù)的獨(dú)立編譯路徑。

結(jié)語

我們是如何做編譯優(yōu)化的,到這里已經(jīng)聊得差不多了。項(xiàng)目中還有很多東西是可以繼續(xù)進(jìn)行優(yōu)化的。比如說文件訪問速度,編譯緩存,將工程粒度更細(xì)化,更多idea插件,快速幫開發(fā)定位代碼等等。

B站在單倉(cāng)的路上其實(shí)已經(jīng)走了很多年了,也碰到了很多的挑戰(zhàn)和問題,我們甚至一度想要放棄這種模式。但堅(jiān)持下來之后,我們還是認(rèn)為單倉(cāng)的優(yōu)點(diǎn)是要大于多倉(cāng)的。作為開發(fā)同學(xué),我們更多的時(shí)候應(yīng)該迎接挑戰(zhàn),然后思考如何去戰(zhàn)勝這些問題,更多關(guān)于嗶哩嗶哩Android編譯優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論