中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

gradle中需要知道的事情

發布時間:2021-09-15 10:10:27 來源:億速云 閱讀:165 作者:小新 欄目:移動開發

這篇文章主要介紹了gradle中需要知道的事情,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

前言

gradle的定義(來自維基百科)

Gradle是一個基于Apache Ant和Apache Maven概念的項目自動化建構工具。它使用一種基于Groovy的特定領域語言來聲明項目設置,而不是傳統的XML。當前其支持的語言限于Java、Groovy和Scala,計劃未來將支持更多的語言。

通俗的理解:gradle是一種構建工具,我們可以用他來對多工程進行各種管理(依賴,打包,部署,發布,各種渠道的差異管理);

有些時候,我們會有一些個性化的構建需求,比如我們引入了第三方庫,或者我們想要在通用構建過程中做一些其他的事情,這時我們就要自己在系統默認構建規則上做一些修改。這時候我們就要自己向Gradle”下命令“了,這時候我們就需要用Gradle能聽懂的話了,也就是Groovy。

我們在開頭處提到“Gradle是一種構建工具”。實際上,當我們想要更靈活的構建過程時,Gradle就成為了一個編程框架——我們可以通過編程讓構建過程按我們的意愿進行。也就是說,當我們把Gradle作為構建工具使用時,我們只需要掌握它的配置腳本的基本寫法就OK了;而當我們需要對構建流程進行高度定制時,就務必要掌握Groovy等相關知識了。

遭遇的問題

我們在實時多項目構建的時候經常遇到以下這些問題:

1、同時依賴了不同版本的某個庫,編譯時出現duplicate class錯誤;

2、gradle 不同版本api報錯;

3、不會寫gradle配置,看不懂gradle語法,不知道從何學起;

4、對編譯過程中gradle的報錯無從下手;

等等…

我們接下來將從實際項目出發一步一步來學習gradle的這些事,本文主旨在于學習gradle的思路,深度細節將會忽略;

揭開Gradle的面紗

一、理解打包命令 gradle clean assembleDebug/assembleRelease

以上這條命令可以分解為三個部分,gradle,clean, assembleDebug;實際上就和我們執行腳本一樣,gradle是執行器,而clean 和 assembleDebug是入參, 在這里它們兩個代表不同的task,就類似gradle task1 task2 這樣。

二、什么是task?

在build.gradle寫上

task task1 {
 println "===>task 1"
}
task task2 {
 println "===>task 2"
}

這樣就定義了兩個task;當我們執行gradle task1 task2 -q的時候(-q是設置日志級別),理論上會看到日志輸出:

===>task 1
===>task 2

task的關系有dependsOn,mustRunAfter等等,由于項目中用的比較少這里先跳過這部分;

這里我們簡單講一下閉包的概念:

閉包在groovy中是一個處于代碼上下文中的開放的,匿名代碼塊。它可以訪問到其外部的變量或方法,
更詳細的請自行google

然而,當我們在項目里執行gradle task1 task2 -q的時候,我們發現輸出是這樣的:

 SeeyouClient git:(SeeyouClient-dev) ? gradle task1 task2 -q
doPackage value:False
Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead.
==============anna apply start==================
configuration do:
include
**  onClick 
**  onItemClick 
**  onCheckedChanged 
**  onItemSelected 
**  onSwitchButtonCheck 
**  onItemLongClick 
**  onLongClick 
**  onPullRefresh 
**  OnRefresh 
configuration do:
exclude
org/conscrypt/ 
configuration do:
exceptions
java/lang/Exception 
java/lang/NullPointerException 
configuration do:
switch
custom 
need inject=false
==============anna apply end==================
Configuration 'provided' in project ':app' is deprecated. Use 'compileOnly' instead.
Configuration 'debugCompile' in project ':app' is deprecated. Use 'debugImplementation' instead.
===>task 1
===>task 2
DexKnife: Processing Variant
DexKnife: processSplitDex true
DexKnife: processing Task
----------------------tinker build warning ------------------------------------
tinker auto operation:
excluding annotation processor and source template from app packaging. Enable dx jumboMode to reduce package size.
enable dx jumboMode to reduce package size.
disable preDexLibraries to prevent ClassDefNotFoundException when your app is booting.
disable archive dex mode so far for keeping dex apply.
tinker will change your build configs:
we will add TINKER_ID=117 in your build output manifest file build/intermediates/manifests/full/*
if minifyEnabled is true
you will find the gen proguard rule file at build/intermediates/tinker_intermediates/tinker_proguard.pro
and we will help you to put it in the proguardFiles.
if multiDexEnabled is true
you will find the gen multiDexKeepProguard file at build/intermediates/tinker_intermediates/tinker_multidexkeep.pro
and we will help you to put it in the MultiDexKeepProguardFile.
if applyResourceMapping file is exist
we will build app apk with resource R.txt file
if resources.arsc has changed, you should use applyResource mode to build the new apk!
-----------------------------------------------------------------
Task spend time:

這是為什么呢?原因是gradle具有自己的生命周期:

初始化階段:負責判斷有多少個Projects參與構建:
 先執行settings.gradle
配置階段:負責對初始化階段創建的Projects完成配置:
 比如添加Task,修改Task的行為,閉包的內容會被執行,執行build.gradle的內容;
執行階段:根據配置階段的配置執行任務:
 執行task對應的內容,如doLast,doFirst之類的

因此gradle task1 task2 -q的輸出日志就可以理解了,其實按照task1和task2的寫法,執行gradle task1 task2 -q和gradle -q實際上效果是一樣的。

三、gradle clean assmebleDebug到底做了什么?(源碼追蹤和依賴分析出編譯流程)

1、打開gradle-4.5.1/bin/gradle文件可以看到執行了代碼:

 eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.launcher.GradleMain "$APP_ARGS"

 最終調用exec "$JAVACMD" "$@"來執行;所以入口就是:org.gradle.launcher.GradleMain

 具體細節可以參照:https://blog.csdn.net/yanbober/article/details/60584621

2,最終會調用DefaultGradleLauncher里,我們可以很明確的看到它的生命周期:

這邊最需要注意的時候,當我們只執行gradle -q這樣的時候,實際上每一次都會執行到TaskGraph的階段;也就是所有的tasks都已經梳理完成;

public class DefaultGradleLauncher implements GradleLauncher {
 //(這里是4.5.1的版本,生命周期更細致化)
 private enum Stage {
  Load, LoadBuild, Configure, TaskGraph, Build, Finished
 }
 //2.14.1的版本則是: 
 private enum Stage {
  Load, Configure, Build
 }
//核心方法
private void doBuildStages(Stage upTo) {
  try {
   loadSettings();
   if (upTo == Stage.Load) {
    return;
   }
   configureBuild();
   if (upTo == Stage.Configure) {
    return;
   }
   constructTaskGraph();
   if (upTo == Stage.TaskGraph) {
    return;
   }
   runTasks();
   finishBuild();
  } catch (Throwable t) {
   Throwable failure = exceptionAnalyser.transform(t);
   finishBuild(new BuildResult(upTo.name(), gradle, failure));
   throw new ReportedException(failure);
  }
 }
 
 //調用時機
  @Override
 public SettingsInternal getLoadedSettings() {
  doBuildStages(Stage.Load);
  return settings;
 }
 @Override
 public GradleInternal getConfiguredBuild() {
  doBuildStages(Stage.Configure);
  return gradle;
 }
 public GradleInternal executeTasks() {
  doBuildStages(Stage.Build);
  return gradle;
 }

四、知道編譯流程后有什么用呢?

1、我們經常在app/build.gradle看到這樣的代碼:

project.afterEvaluate {...}
android.applicationVariants.all {...}
gradle.addListener(new TaskListener())
apply from '../mvn.gradle'
...

這里我們介紹幾個概念:

project

對應gradle源碼的Project.java(按住control點擊project會自動跳轉),里邊提供一些對外的方法,如afterEvalute,beforeEvalue; 在理解編譯流程后,才能靈活的使用這些api;

android

對應gradle插件的AppExtension.java文件,提供了一些對外的參數和方法,我們可以使用android.xxx來訪問app/build.gradle里的任意參數和方法;

gradle

對應的gradle源碼里的Gradle.java對象,也是提供了一系列的方法給外部使用;

那么接下來假設我們有這樣一個需求:找到一個叫cleanBuildCache的task,找到之后添加一個action,打印一行字; 要實現這個需求,首先我們如何遍歷這個app的所有task:

有很多種寫法:

gradle.getTaskGraph().whenReady {
 project.tasks.each {
  task->
   println "taskName:"+task.getName()
 }
}
project.afterEvaluate {
 project.tasks.each {
  task->
   println "taskName:"+task.getName()
 }
}

執行gradle -q 感受一下。

接下看看如何添加action

project.afterEvaluate {
 project.tasks.each {
  task->
   // println "taskName:"+task.getName()
   if(task.getName().equals("cleanBuildCache")){
    println "find cleanBuildCache!!!!!!"
    List<Action<? super Task>> list = new ArrayList<>()
    list.add(new Action<Task>() {
     @Override
     void execute(Task task1) {
      println 'excute cleanBuildCache action !!!!!!'
     }
    })
    task.setActions(list)
   }
 }
}

執行gradle cleanBuildCache感受一下,

你會看到‘excute cleanBuildCache action !!!!!!'的打印字樣;

那為什么一定要放在afterEvaluate之后呢,因為這樣tasksGrap完成才有那么多task讓你遍歷,這就是理解生命周期所帶來的好處。

2、現在回顧我們之前主app寫的代碼:

processProductDebugManifest;

project.tasks.each {
  task->
   if(task.getName().equals("processZroTestDebugManifest")){
    println '!!!!!find processZroTestDebugManifest task:'
    task.outputs.files.each {
     file ->
      println 'file.getAbsolutePath():'+ file.getAbsolutePath()
      //file.getAbsolutePath():/Users/mu/MeiyouCode/PeriodProject/SeeyouClient/app/build/intermediates/manifests/instant-run/zroTest/debug
      //file.getAbsolutePath():/Users/mu/MeiyouCode/PeriodProject/SeeyouClient/app/build/intermediates/manifests/full/zroTest/debug
      //file.getAbsolutePath():/Users/mu/MeiyouCode/PeriodProject/SeeyouClient/app/build/outputs/logs/manifest-merger-zroTest-debug-report.txt
    }
    task.doLast {
     println '!!!!!excute processZroTestDebugManifest task'
     def dated = new Date().format("MMdd HH:mm")
     def manifestFile = "${buildDir}/intermediates/manifests/full/zroTest/debug/AndroidManifest.xml"
     def updatedContent = new File(manifestFile).getText('UTF-8')
       .replaceAll("MY_APP_PKGNAME", "${MY_APP_PKGNAME}")
       .replaceAll("MY_JPUSH_APPKEY", "${JPUSH_APPKEY}")
       .replaceAll("MY_HUAWEI_APPKEY", "${HUAWEI_APPKEY}")
       .replaceAll("MY_MEIZU_APPKEY", "${MEIZU_APPKEY}")
       .replaceAll("MY_MEIZU_APPID", "${MEIZU_APPID}")
     // .replaceAll("MY_XIAOMI_APPKEY", "${XIAOMI_APPKEY}")
     // .replaceAll("MY_XIAOMI_APPID", "${XIAOMI_APPID}")
     //.replaceAll("cn.jpush.android.service.PluginXiaomiPlatformsReceiver","com.meiyou.message.mipush.XiaomiReceiver")
       .replaceAll("MY_APK_VERSION", "${archivesBaseName}-${dated}")
     new File(manifestFile).write(updatedContent, 'UTF-8')
    }
   }
 }

實際上也可以寫成這樣(但是這樣因為變種不確定,寫死成zroTest/debug,所以還是用上面的方法比較好,直接替換所有的變種):

project.tasks.each {
  task->
   if(task.getName().equals("processZroTestDebugManifest")){
    println '!!!!!find processZroTestDebugManifest task:'+task.outputs.files.each {
     file ->
      file.getAbsolutePath();
    }
    task.doLast {
     println '!!!!!excute processZroTestDebugManifest task'
     def dated = new Date().format("MMdd HH:mm")
     def manifestFile = "${buildDir}/intermediates/manifests/full/zroTest/debug/AndroidManifest.xml"
     def updatedContent = new File(manifestFile).getText('UTF-8')
       .replaceAll("MY_APP_PKGNAME", "${MY_APP_PKGNAME}")
       .replaceAll("MY_JPUSH_APPKEY", "${JPUSH_APPKEY}")
       .replaceAll("MY_HUAWEI_APPKEY", "${HUAWEI_APPKEY}")
       .replaceAll("MY_MEIZU_APPKEY", "${MEIZU_APPKEY}")
       .replaceAll("MY_MEIZU_APPID", "${MEIZU_APPID}")
     // .replaceAll("MY_XIAOMI_APPKEY", "${XIAOMI_APPKEY}")
     // .replaceAll("MY_XIAOMI_APPID", "${XIAOMI_APPID}")
     //.replaceAll("cn.jpush.android.service.PluginXiaomiPlatformsReceiver","com.meiyou.message.mipush.XiaomiReceiver")
       .replaceAll("MY_APK_VERSION", "${archivesBaseName}-${dated}")
     new File(manifestFile).write(updatedContent, 'UTF-8')
    }
   }
 }

3、如何知道某個task干了什么呢,比如processZroTestDebugManifest或者Clean:

這些是com.android.tools.build到源碼里尋找或者直接compile ‘com.android.tools.build:gradle:3.0.1'直接從依賴庫里看源碼; 或者直接下載源碼(大概30G左右):

$ mkdir gradle_2.3.0
$ cd gradle_2.3.0
$ repo init -u https://android.googlesource.com/platform/manifest -b gradle_2.3.0
$ repo sync

大部分tasks都在com.android.build.gradle.tasks文件夾下,比如:ManifestProcessorTask和CleanBuildCache

具體可以:https://fucknmb.com/2017/06/01/Android-Gradle-Plugin源碼閱讀與編譯/

4、如何查找某個task的依賴呢,比如我想知道assmebleZroTestDebug執行后最終執行了哪些task;

1、編譯后打印;

gradle.addListener(new TaskListener())
class TaskListener implements BuildListener,TaskExecutionListener {
 private List<String> tasks = new ArrayList<>();
 @Override
 void buildStarted(Gradle gradle) {
 }
 @Override
 void settingsEvaluated(Settings settings) {
 }
 @Override
 void projectsLoaded(Gradle gradle) {
 }
 @Override
 void projectsEvaluated(Gradle gradle) {
 }
 @Override
 void buildFinished(BuildResult result) {
  StringBuilder stringBuilder = new StringBuilder();
  for(String taskName:tasks){
   stringBuilder.append(taskName).append("\n")
  }
  println("任務列表:\n"+stringBuilder.toString())
 }
 @Override
 void beforeExecute(Task task) {
 }
 @Override
 void afterExecute(Task task, TaskState state) {
  //println("===>Task:"+task.getName())
  tasks.add(task.getName())
 }
}

2、不用編譯直接打印;

void printTaskDependency(Task task, String divider) {
 divider += "-------"
 task.getTaskDependencies().getDependencies(task).any() {
  println(divider+ it.getPath())
  if (it.getPath().contains(":app")) {
   printTaskDependency(it,divider)
  }
 }
}
gradle.getTaskGraph().whenReady {
 project.tasks.all {
  //println("!!!!!!!!!! it:"+it.getName()+"==>it.getPath:"+it.getPath())
  if (it.getPath().equals(":app:assembleZroTestDebug")) {
   //println(it.getPath())
   printTaskDependency(it,"")
  }
 }
}

5、常用技能

1、gradle :app:dependencies > 1.txt 分析整個app的aar依賴

可以用于排查依賴庫異常的問題;

請注意!:對工程依賴無效;

2、productFlavors和buildType概念,組合成變種 如:

productFlavors {
 branchOne {
  applicationId "com.example.branchOne"
  buildConfigField "String", "CONFIG_ENDPOINT", "http://branchOne.com/android"
 }
 branchTwo {
  applicationId "com.example.branchTwo"
  buildConfigField "String", "CONFIG_ENDPOINT", "http://branchTwo.org"
 }
}
dependencies {
 compile 'com.android.support:support-v4:22.2.0'
 branchOneCompile 'com.android.support:appcompat-v7:22.2.0'//只為branchOne添加這個依賴
}

3、排除依賴和強制使用某個版本和強制排除某個庫:

configurations.all {
  resolutionStrategy {
//   force 'org.javassist:javassist:3.18.2-GA'
   // don't cache changing modules at all
   cacheChangingModulesFor 0, 'seconds'
//   //強制模塊使用指定版本號(防止其他模塊使用、跟主工程不匹配的版本:
   forcedModules = [
     "com.meiyou:peroid.base:${PERIOD_BASE_VERSION}",
     'org.javassist:javassist:3.18.2-GA'//"org.javassist:javassist:3.20.0-GA"//
     , 'com.google.guava:guava:18.0'//'com.google.guava:guava:19.0-rc2'//
   ]
   exclude group: 'com.squareup.okhttp3'
   exclude group: 'com.google.code.findbugs', module: 'annotations'
  }
 }

感謝你能夠認真閱讀完這篇文章,希望小編分享的“gradle中需要知道的事情”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

白城市| 兴业县| 北流市| 奉新县| 南部县| 东乡族自治县| 保德县| 平湖市| 仲巴县| 元江| 铅山县| 滦平县| 安宁市| 太和县| 措美县| 临朐县| 东辽县| 永城市| 永德县| 苍山县| 三亚市| 通辽市| 兰溪市| 耒阳市| 西峡县| 长泰县| 集贤县| 绥棱县| 石嘴山市| 大关县| 色达县| 盐池县| 泗水县| 孝义市| 岳普湖县| 南阳市| 南和县| 沈丘县| 崇文区| 汶上县| 湛江市|