摘要:上周我們在系列入門篇文章中已經將在項目中的結構過了一遍。但是不同的是它可以改變其自身的代理。這正常,因為我們還沒有聲明它。與將通過對象進行。至于是的中的一個抽象感念,它申明在中。的知識點還有很多,這只是對有關的一部分進行分析。
上周我們在Android Gradle系列-入門篇文章中已經將gradle在項目中的結構過了一遍。對于gradle,我們許多時候都不需要修改類似與*.gradle文件,做的最多的應該是在dependencies中添加第三方依賴,或者說修改sdk版本號,亦或者每次發版本改下versionCode與versionName。即使碰到問題也是直接上google尋找答案,而并沒有真正理解它為什么要這么做,或者它是如何運行的?
今天,我會通過這篇文章一步一步的編寫gradle文件,從項目的創建,到gradle的配置。相信有了這篇文章,你將對gradle的內部運行將有一個全新的認識。
Groovy在講gradle之前,我們還需明白一點,gradle語法是基于groovy的。所以我們先來了解一些groovy的知識,這有助于我們之后的理解。當然如果你已經有groovy的基礎你可以直接跳過,沒有的也不用慌,因為只要你懂java就不是什么難題。
syntax下面我將通過code的形式,列出幾點
當調用的方法有參數時,可以不用(),看下面的例子
def printAge(String name, int age) { print("$name is $age years old") } def printEmptyLine() { println() } def callClosure(Closure closure) { closure() } printAge "John", 24 //輸出John is 24 years old printEmptyLine() //輸出空行 callClosure { println("From closure") } //輸出From closure
如果最后的參數是閉包,可以將它寫在括號的外面
def callWithParam(String param, Closureclosure) { closure(param) } callWithParam("param", { println it }) //輸出param callWithParam("param") { println it } //輸出param callWithParam "param", { println it } //輸出param
調用方法時可以指定參數名進行傳參,有指定的會轉化到Map對象中,沒有的將按正常傳參
def printPersonInfo(Mapperson) { println("${person.name} is ${person.age} years old") } def printJobInfo(Map job, String employeeName) { println("${employeeName} works as ${job.name} at ${job.company}") } printPersonInfo name: "Jake", age: 29 printJobInfo "Payne", name: "Android Engineer", company: "Google"
你會發現他們的調用都不需要括號,同時printJobInfo的調用參數的順序不受影響。
Closure在gradle中你會發現許多閉包,所以我們需要對閉包有一定的了解。如果你熟悉kotlin,它與Function literals with receiver類似。
在groovy中我們可以將Closures當做成lambdas,所以它可以直接當做代碼塊執行,可以有參數,也可以有返回值。但是不同的是它可以改變其自身的代理。例如:
class DelegateOne { def callContent(String content) { println "From delegateOne: $content" } } class DelegateTow { def callContent(String content) { println "From delegateTwo: $content" } } def callClosure = { callContent "I am bird" } callClosure.delegate = new DelegateOne() callClosure() //輸出From delegateOne: I am bird callClosure.delegate = new DelegateTow() callClosure() //輸出From delegateTow: I am bird
通過改變callClosure的delegate,讓其調用不同的callContent。
如果你想了解更多,可以直接閱讀groovy文檔
在上篇文章中已經提到有關gradle的腳步相關的知識,這里就不再累贅。
下面我們來一步一步構建gradle。
首先我們新建一個文件夾example,cd進入該文件夾,在該目錄下執行gradle projects,你會發現它已經是一個gradle項目了
$ gradle projects > Task :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project "example" No sub-projects To see a list of the tasks of a project, run gradle:tasks For example, try running gradle :tasks BUILD SUCCESSFUL in 5s
因為這里不是在Android Studio中創建的項目,所以如果你本地沒有安裝與配置gradle環境,將不會有gradle命令。所以這一點要注意一下。
每一個android項目在它的root project下都需要配置一個settings.gradle,它代表著項目的全局配置。同時使用void include(String[] projectPaths)方法來添加子項目,例如我們為example添加app子項目
$ echo "include ":app"" > settings.gradle $ gradle projects > Task :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project "example" --- Project ":app" To see a list of the tasks of a project, run gradle:tasks For example, try running gradle :app:tasks BUILD SUCCESSFUL in 1s 1 actionable task: 1 executed
:app中的:代表的是路徑的分隔符,同時在settings.gradle中默認root project是該文件的文件夾名稱,也可以通過rootProject.name = name來進行修改。
搭建Android子項目現在需要做的是將子項目app構建成Android項目,所以我們需要配置app的build.gradle。因為gradle只是構建工具,它是根據不同的插件來構建不同的項目,所以為了符合Android的構建,需要申明應用的插件。這里通過apply方法,它有以下三種類型
void apply(Closure closure) void apply(Mapoptions) void apply(Action super ObjectConfigurationAction> action)
這里我們使用的是第二種,它的map參數需要與ObjectConfigurationAction中的方法名相匹配,而它的方法名有以下三種
from: 應用一個腳本文件
plugin: 應用一個插件,通過id或者class名
to: 應用一個目標代理對象
因為我們要使用android插件,所以需要使用apply(plugin: "com.android.application"),又由于groovy的語法特性,可以將括號省略,所以最終在build.gradle中的表現可以如下:
$ echo "apply plugin: "com.android.application"" > app/build.gradle
添加完以后,再來執行一下
$ gradle app:tasks FAILURE: Build failed with an exception. * Where: Build file "/Users/idisfkj/example/app/build.gradle" line: 1 * What went wrong: A problem occurred evaluating project ":app". > Plugin with id "com.android.application" not found. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 6s
發現報錯了,顯示com.android.application的插件id找不到。這正常,因為我們還沒有聲明它。所以下面我們要在project下的build.gradle中聲明它。為什么不直接到app下的build.gradle聲明呢?是因為我們是android項目,project可以有多個sub-project,所以為了防止在子項目中重復聲明,統一到主項目中聲明。
project的build.gradle聲明插件需要在buildscript中,而buildscript會通過ScriptHandler來執行,以至于sub-project也能夠使用。所以最終的申明如下:
buildscript { repositories { google() jcenter() } dependencies { classpath "com.android.tools.build:gradle:3.3.2" } }
上面的buildscript、repositories與dependencies方法都是以Closure作為參數,然后再通過delegate進行調用
buildscript(Closure)在Project中調用,通過ScriptHandler來執行Closure
repositories(Closure)在ScriptHandler中調用,通過RepositoryHandler來執行Closure
dependencies(Closure)在ScriptHandler中調用,通過DependencyHandler來執行Closure
相應的google()與jcenter()會在RepositoryHandler執行,classpaht(String)會在DependencyHandler(*)執行。
如果你想更詳細的了解可以查看文檔
讓我們再一次執行gradle projects
$ gradle projects FAILURE: Build failed with an exception. * What went wrong: A problem occurred configuring project ":app". > compileSdkVersion is not specified. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 1s
發現報沒有指定compileSdkVersion,因為我們還沒有對app進行相關的配置,只是引用了android插件。所以我們現在來進行基本配置,在app/build.gradle中添加
android { buildToolsVersion "28.0.1" compileSdkVersion 28 }
我們在android中進行聲明,android方法會加入到project實例中。buildToolsVersion與compileSdkVersion將通過Closure對象進行delegate。
Extensionsandroid方法會是如何與project進行關聯的?在我們聲明的Android插件中,會注冊一個AppExtension類,這個extension將會與android命名。所以gradle能夠調用android方法,而在AppExtension中已經聲明了各種方法屬性,例如buildTypes、defaultConfig與signingConfigs等。這也就是為什么我們能夠在android方法中調用它們的原因。下面是extension的創建部分源碼
@Override void apply(Project project) { super.apply(project) // This is for testing. if (pluginHolder != null) { pluginHolder.plugin = this; } def buildTypeContainer = project.container(DefaultBuildType, new BuildTypeFactory(instantiator, project.fileResolver)) def productFlavorContainer = project.container(GroupableProductFlavorDsl, new GroupableProductFlavorFactory(instantiator, project.fileResolver)) def signingConfigContainer = project.container(SigningConfig, new SigningConfigFactory(instantiator)) extension = project.extensions.create("android", AppExtension, this, (ProjectInternal) project, instantiator, buildTypeContainer, productFlavorContainer, signingConfigContainer) setBaseExtension(extension) ... }Dependencies
android方法下面就是dependencies,下面我們再來看dependencies
dependencies { implementation "io.reactivex.rxjava2:rxjava:2.0.4" testImplementation "junit:junit:4.12" annotationProcessor "org.parceler:parceler:1.1.6" }
有了上面的基礎,應該會容易理解。dependencies是會被delegate給DependencyHandler,不過如果你到DependencyHandler中去查找,會發現找不到上面的implementation、testImplementation等方法。那它們有到底是怎么來的呢?亦或者如果我們添加了dev flavor,那么我又可以使用devImplementation。這里就涉及到了groovy的methodMissing方法。它能夠在runtime(*)中捕獲到沒有定義的方法。
至于(*)是gradle的methodMissing中的一個抽象感念,它申明在MethodMixIn中。
對于DependencyHandler的實現規則是:
在DependencyHandler中如果我們回調了一個沒有定義的方法,且它有相應的參數;同時它的方法名在configuration(*)中;那么將會根據方法名與參數類型來調用doAdd的相應方法。
對于configuration(*),每一個plugin都有他們自己的配置,例如java插件定義了compile、compileClassPath、testCompile等。而對于Android插件在這基礎上還會定義annotationProcessor,(variant)Implementation、(variant)TestImplementation等。對于variant則是基于你設置的buildTypes與flavors。
另一方面,由于doAdd()是私用的方法,但add()是公用的方法,所以在dependencies中我們可以直接使用add
dependencies { add("implementation", "io.reactivex.rxjava2:rxjava:2.0.4") add("testImplementation", "junit:junit:4.12") add("annotationProcessor", "org.parceler:parceler:1.1.6") }
注意,這種寫法并不推薦,這里只是為了更好的理解它的原理。
gradle的知識點還有很多,這只是對有關Android的一部分進行分析。當我們進行gradle配置的時,不至于對gradle的語法感到魔幻,或者對它的一些操作感到不解。
我在github上建了一個倉庫Android精華錄,收集Android相關的文章,如果有需要的可以去看一下,有好的文章可以加我微信fan331100推薦給我。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74516.html
摘要:全稱應用性能管理監控后面我會通過一系列的文章來介紹的原理框架設計與實現等等。在應用構建期間,通過修改字節碼的方式來進行字節碼插樁就是實現自動化的方案之一。 showImg(https://segmentfault.com/img/bVbbRX6?w=1995&h=1273); 歡迎關注微信公眾號:BaronTalk,獲取更多精彩好文! 一. 前言 性能問題是導致 App 用戶流失的罪魁...
摘要:如果你有新建一個項目的經歷,那么你將看到推薦的方案在的中使用來定義版本號全局變量。例如之前的版本號就可以使用如下方式實現因為使用的是語言,所以以上都是語法例如版本控制,上面代碼的意思就是將有個相關的版本依賴放到的變量中,同時放到了中。 showImg(https://segmentfault.com/img/bVbsh3m?w=2560&h=1280); 上篇文章我們已經將Gradle...
閱讀 3615·2021-11-22 09:34
閱讀 3186·2021-11-15 11:38
閱讀 3039·2021-10-27 14:16
閱讀 1233·2021-10-18 13:35
閱讀 2424·2021-09-30 09:48
閱讀 3429·2021-09-29 09:34
閱讀 1626·2019-08-30 15:54
閱讀 1818·2019-08-26 11:57