目前 Android 工程的默認(rèn)構(gòu)建工具為 Gradle砍艾,我們?cè)跇?gòu)建 APK 的時(shí)候往往會(huì)執(zhí)行 ./gradlew assembleDebug
這樣的命令雹洗。揖庄。
那么這個(gè)命令到底代表著什么含義呢业稼?命令的執(zhí)行究竟是在做什么事情呢伏钠?我們能不能在命令執(zhí)行的過(guò)程中做一些自己的操作呢横漏?接下來(lái)我們來(lái)具體的進(jìn)行分析。
Gradle 的構(gòu)建過(guò)程
Gradle Wrapper 是個(gè)啥
當(dāng)我們?cè)?Android Studio 中新建一個(gè)工程時(shí)熟掂,你會(huì)發(fā)現(xiàn)在工程的根目錄下會(huì)創(chuàng)建以下幾個(gè)文件:
實(shí)際上這幾個(gè)文件是通過(guò)執(zhí)行 $ Gradle Wrapper
生成的缎浇。Gradle Wrapper,顧名思義就是對(duì) Gradle 構(gòu)建工具的一層封裝赴肚。
在和其他同事共同管理某個(gè) Android 工程的時(shí)候素跺,肯定會(huì)存在同事 A 電腦上的 Gradle 版本和同事 B 電腦上的 Gradle 版本不一樣,那么這個(gè)不一樣可能導(dǎo)致的問(wèn)題是需要在 build.gradle 文件中添加不同的配置誉券,甚至有的 Gradle 版本都無(wú)法成功跑通工程指厌。
所以,Gradle 的工程師們將 Gradle 添加了一層簡(jiǎn)單的封裝踊跟,Linux 用戶可以通過(guò)執(zhí)行 gradlew
來(lái)代替 gradle
命令踩验,Windows 用戶可以通過(guò) gradlew.bat 來(lái)代替,實(shí)際上這倆文件就是個(gè)可執(zhí)行腳本商玫,我們可以直接打開(kāi)這個(gè)腳本來(lái)看里面到底有什么箕憾。
# 只貼上主要代碼 gradlew 文件
...
JAVACMD="java"
...
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
...
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
從里面的代碼我們可以看到,實(shí)際上 gradlew 的腳本也只是執(zhí)行了一下 Java 命令拳昌,assembleDebug 只是執(zhí)行腳本的參數(shù)袭异。我們修改一下腳本打印出來(lái)這個(gè)命令:
java -Xdock:name=Gradle -Xdock:icon=/media/gradle.icns -Dorg.gradle.appname=gradlew -classpath /xxx/food/gradle/wrapper/gradle-wrapper.jar
org.gradle.wrapper.GradleWrapperMain assembleDebug -s
從這里可以看到,腳本所做的事情就是通過(guò) Java 命令來(lái)執(zhí)行 /gradle/wrapper/ 下的 gradle-wrapper.jar 包炬藤。通過(guò) JD-GUI 工具打開(kāi)這個(gè)可執(zhí)行 jar 包御铃,找到那個(gè) jar 包的入口類(lèi),也就是 org.gradle.wrapper.GradleWrapperMain沈矿,我們大致看一下這個(gè) jar 包到底干了什么上真。
// org.gradle.wrapper.GradleWrapperMain文件
public static void main(String[] args) throws Exception {
File wrapperJar = wrapperJar(); // 獲取這個(gè)可執(zhí)行jar包的具體路徑
File propertiesFile = wrapperProperties(wrapperJar); // 獲取gradle-wrapper.properties的位置
File rootDir = rootDir(wrapperJar); // 獲取工程的根目錄
// 下面的一段代碼用來(lái)解析命令行,我們暫時(shí)不去管它
CommandLineParser parser = new CommandLineParser();
...
WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
// 下面這個(gè)方法便是解析gradle/wrapper/gradle-wrapper.properties文件里面的內(nèi)容羹膳,然后根據(jù)里面的配置到相應(yīng)的地址需下載另一個(gè)可執(zhí)行jar包
wrapperExecutor.execute(
args,
new Install(logger, new Download(logger, "gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)),
new BootstrapMainStarter());
}
通過(guò)分析 execute() 方法的執(zhí)行谷羞,我們會(huì)發(fā)現(xiàn),這個(gè)方法執(zhí)行了 gradle-wrapper.properties 文件的解析和下載工作,我們先看一下這個(gè)文件里面有什么:
// gradle/wrapper/gradle-wrapper.properties 文件配置
distributionBase=GRADLE_USER_HOME // GRADLE_USER_HOME的地址默認(rèn)在用戶目錄下的.gradle/文件夾里
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
如上所示湃缎,代碼會(huì)找到 distributionUrl 這個(gè)路徑犀填,然后下載相應(yīng)的文件。在這里嗓违,工程會(huì)指定固定的一個(gè) Gradle 版本九巡,然后所有的開(kāi)發(fā)者都會(huì)在相同的路徑下下載到相同的 ZIP 包,這樣也就保證了在不同電腦上執(zhí)行構(gòu)建操作結(jié)果的一致性蹂季。下載文件的過(guò)程就不多述了冕广,因?yàn)楸容^簡(jiǎn)單。
文件下載以后偿洁,當(dāng)前就會(huì)去執(zhí)行這個(gè)文件:
// org.gradle.wrapper.BootstrapMainStarter
public class BootstrapMainStarter {
public void start(String[] args, File gradleHome) throws Exception {
File gradleJar = findLauncherJar(gradleHome);
URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
Thread.currentThread().setContextClassLoader(contextClassLoader);
// 通過(guò)反射找到org.gradle.launcher.GradleMain 進(jìn)行具體的調(diào)用
Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain");
Method mainMethod = mainClass.getMethod("main", String[].class);
mainMethod.invoke(null, new Object[]{args});
if (contextClassLoader instanceof Closeable) {
((Closeable) contextClassLoader).close();
}
}
}
如上所示撒汉,Java 代碼找到下載的 jar 包,然后通過(guò) ClassLoader 加載到內(nèi)存里涕滋,加載以后通過(guò)反射調(diào)用里面的入口類(lèi)睬辐。
到此,Gradle Wrapper 的整個(gè)調(diào)用過(guò)程結(jié)束了宾肺,它的功能就是保證多個(gè)電腦上能夠以相同的 Gradle 版本構(gòu)建 Android 的工程代碼溯饵,后續(xù)的執(zhí)行則是開(kāi)啟了 Gradle 的真正構(gòu)建過(guò)程,我們接下來(lái)進(jìn)行分析锨用。
Gradle 的構(gòu)建生命周期
Gradle 的整個(gè)構(gòu)建過(guò)程共分為三個(gè)階段:init 初始化階段丰刊、config 配置階段和 build 執(zhí)行階段。下面簡(jiǎn)單說(shuō)一下這三個(gè)階段分別做什么工作增拥。
init 初始化階段
初始化階段主要是解析 settings.gradle 文件啄巧,查看該工程引入了多少個(gè) module。如下所示掌栅,可以在 settings.gradle 文件下定義需要引入的 module 和其對(duì)應(yīng)的目錄:
include ':app'
include ':library'
project(':library').projectDir = new File('../library')
config 階段
在 config 階段便是去解析每個(gè) module 里的 build.gradle 文件秩仆,并逐行執(zhí)行,完成對(duì) project 的配置渣玲,并構(gòu)造 Task 任務(wù)依賴關(guān)系圖以便在執(zhí)行階段按照依賴關(guān)系執(zhí)行 Task。
build 執(zhí)行階段
執(zhí)行階段便是根據(jù) config 階段生成的 Task 依賴關(guān)系圖弟晚,來(lái)挨個(gè)地去執(zhí)行各個(gè) Task忘衍。每個(gè) Task 可以看做是一個(gè)功能體,比如說(shuō)卿城,在構(gòu)建過(guò)程中 Java 文件需要先轉(zhuǎn)換為 class 文件枚钓,然后 class 文件要再次轉(zhuǎn)換成 dex 文件,然后 dex 文件最終組合生成 APK瑟押,這個(gè)過(guò)程中每一步都是由一個(gè) Task 來(lái)執(zhí)行的搀捷。后續(xù)在介紹自定義 Gradle 插件的時(shí)候會(huì)講到 Task 相關(guān)的東西。
Gradle 構(gòu)建過(guò)程代碼分析
剛才梳理了一下 Gradle 構(gòu)建過(guò)程的生命周期,分為上面那三個(gè)階段嫩舟,那么氢烘,具體到代碼是如何實(shí)現(xiàn)這三個(gè)聲明周期的呢?我們具體進(jìn)行一下分析家厌。
在 Gradle Wrapper 的末尾播玖,我們提到構(gòu)建過(guò)程走到了通過(guò)反射找到 org.gradle.launcher.GradleMain 進(jìn)行具體的調(diào)用。那么我們就繼續(xù)跟著源碼走饭于。
// org.gradle.launcher.GradleMain
public class GradleMain {
public static void main(String[] args) throws Exception {
new ProcessBootstrap().run("org.gradle.launcher.Main", args);
}
}
org.gradle.launcher.GradleMain 是真正執(zhí)行構(gòu)建過(guò)程的入口類(lèi)蜀踏,深入到 new ProcessBootstrap().run() 方法中繼續(xù)執(zhí)行。
private void runNoExit(String mainClassName, String[] args) throws Exception {
ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(new DefaultModuleRegistry(CurrentGradleInstallation.get())));
ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory();
ClassPath antClasspath = classPathRegistry.getClassPath("ANT");
ClassPath runtimeClasspath = classPathRegistry.getClassPath("GRADLE_RUNTIME");
ClassLoader antClassLoader = classLoaderFactory.createIsolatedClassLoader(antClasspath);
// 我們發(fā)現(xiàn)通過(guò)新建classLoader掰吕,在classLoader增加了新的依賴項(xiàng)果覆,但這些依賴項(xiàng)不知道是什么。不過(guò)這不是我們關(guān)注的重點(diǎn)殖熟,我們繼續(xù)代碼的執(zhí)行
ClassLoader runtimeClassLoader = new VisitableURLClassLoader(antClassLoader, runtimeClasspath);
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(runtimeClassLoader);
try {
// 在此處通過(guò)反射調(diào)用了org.gradle.launcher.Main這個(gè)類(lèi)的run方法局待,
Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName);
Object entryPoint = mainClass.newInstance();
Method mainMethod = mainClass.getMethod("run", String[].class);
mainMethod.invoke(entryPoint, new Object[]{args});
} finally {
...
}
}
主流程是通過(guò)反射調(diào)用了 org.gradle.launcher.Main 這個(gè)類(lèi)的 run 方法,我們具體看一下這段代碼吗讶。
// org.gradle.launcher.Main
public class Main extends EntryPoint {
public static void main(String[] args) {
new Main().run(args);
}
protected void doAction(String[] args, ExecutionListener listener) {
UnsupportedJavaRuntimeException.assertUsingVersion("Gradle", JavaVersion.VERSION_1_7);
createActionFactory().convert(Arrays.asList(args)).execute(listener);
}
CommandLineActionFactory createActionFactory() {
return new CommandLineActionFactory();
}
}
org.gradle.launcher.Main 這個(gè)類(lèi)實(shí)際上繼承了 org.gradle.launcher.bootstrap.EntryPoint 這個(gè)類(lèi)燎猛,而它的 run 方法實(shí)際也就是提供了對(duì) Main 中 doAction 方法的回調(diào)。
// org.gradle.launcher.cli.CommandLineActionFactory
public Action<ExecutionListener> convert(List<String> args) {
ServiceRegistry loggingServices = createLoggingServices();
LoggingConfiguration loggingConfiguration = new DefaultLoggingConfiguration();
return new WithLogging(loggingServices,
buildLayoutFactory,
args,
loggingConfiguration,
new ExceptionReportingAction(
new ParseAndBuildAction(loggingServices, args),
new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData())));
}
CommandLineActionFactory.convert 返回 WithLogging 的實(shí)例照皆,然后調(diào)用了 WithLogging 的 execute 方法重绷。
然后經(jīng)過(guò)各種回調(diào)等,最終調(diào)用的是 ParseAndBuildAction 的 execute 方法(中間的回調(diào)過(guò)程略過(guò)膜毁,有興趣的同學(xué)可以自己查看一下昭卓,我們只分析主流程的代碼):
// org.gradle.launcher.cli.CommandLineActionFactory#ParseAndBuildAction
public void execute(ExecutionListener executionListener) {
List<CommandLineAction> actions = new ArrayList<CommandLineAction>();
// BuildInActions 是處理help version 這些命令的
actions.add(new BuiltInActions());
// 如果不是上面的兩條命令行參數(shù),則執(zhí)行BuildActionsFactory里的參數(shù)瘟滨。
createActionFactories(loggingServices, actions);
CommandLineParser parser = new CommandLineParser();
for (CommandLineAction action : actions) {
action.configureCommandLineParser(parser);
}
Action<? super ExecutionListener> action;
try {
ParsedCommandLine commandLine = parser.parse(args);
action = createAction(actions, parser, commandLine);
} catch (CommandLineArgumentException e) {
action = new CommandLineParseFailureAction(parser, e);
}
action.execute(executionListener);
}
private Action<? super ExecutionListener> createAction(Iterable<CommandLineAction> factories, CommandLineParser parser, ParsedCommandLine commandLine) {
for (CommandLineAction factory : factories) {
// 根據(jù)命令行參數(shù)選中相關(guān)的處理的Action候醒,比如說(shuō)在gradle參數(shù)后面跟著 help 或者version 參數(shù),則選中BuiltInActions
Runnable action = factory.createAction(parser, commandLine);
if (action != null) {
return Actions.toAction(action);
}
}
throw new UnsupportedOperationException("No action factory for specified command-line arguments.");
}
如果在 gradlew 參數(shù)后面加 help 或者 version 時(shí)杂瘸,將交給 BuiltInActions 進(jìn)行處理倒淫,其它的則會(huì)交給 BuildActionsFactory 類(lèi)處理。我們直接查看一下 BuildActionsFactory 的執(zhí)行代碼败玉。
// org.gradle.launcher.cli.BuildActionsFactory
public Runnable createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
Parameters parameters = parametersConverter.convert(commandLine, new Parameters());
parameters.getStartParameter().setInteractive(ConsoleStateUtil.isInteractive());
parameters.getDaemonParameters().applyDefaultsFor(jvmVersionDetector.getJavaVersion(parameters.getDaemonParameters().getEffectiveJvm()));
// 下面是通過(guò)各種不同的參數(shù)
if (parameters.getDaemonParameters().isStop()) { /
return stopAllDaemons(parameters.getDaemonParameters(), loggingServices);
}
if (parameters.getDaemonParameters().isStatus()) {
return showDaemonStatus(parameters.getDaemonParameters(), loggingServices);
}
if (parameters.getDaemonParameters().isForeground()) {
DaemonParameters daemonParameters = parameters.getDaemonParameters();
ForegroundDaemonConfiguration conf = new ForegroundDaemonConfiguration(
UUID.randomUUID().toString(), daemonParameters.getBaseDir(), daemonParameters.getIdleTimeout(), daemonParameters.getPeriodicCheckInterval());
return new ForegroundDaemonAction(loggingServices, conf);
}
if (parameters.getDaemonParameters().isEnabled()) {
return runBuildWithDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
}
if (canUseCurrentProcess(parameters.getDaemonParameters())) {
return runBuildInProcess(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
}
return runBuildInSingleUseDaemon(parameters.getStartParameter(), parameters.getDaemonParameters(), loggingServices);
}
后三個(gè)方法中都會(huì)調(diào)用到 runBuildAndCloseServices敌土,這也是執(zhí)行 Gradle 構(gòu)建的方法。
最終代碼會(huì)執(zhí)行到 RunBuildAction 的 run 方法运翼。
// org.gradle.launcher.cli.RunBuildAction
public void run() {
try {
// 這個(gè)executer實(shí)際上是 InProcessBuildActionExecuter 的實(shí)例
executer.execute(
new ExecuteBuildAction(startParameter),
new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(clientMetaData, startTime), new DefaultBuildCancellationToken(), new NoOpBuildEventConsumer()),
buildActionParameters,
sharedServices);
} finally {
if (stoppable != null) {
stoppable.stop();
}
}
}
最終查看 InProcessBuildActionExecuter 執(zhí)行的代碼返干。
public Object execute(BuildAction action, BuildRequestContext buildRequestContext, BuildActionParameters actionParameters, ServiceRegistry contextServices) {
// 最終通過(guò)調(diào)用獲取到了GradleLauncher的實(shí)例,他的一個(gè)實(shí)現(xiàn)類(lèi)DefaultGradleLauncher
GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(action.getStartParameter(), buildRequestContext, contextServices);
try {
RootBuildLifecycleListener buildLifecycleListener = contextServices.get(ListenerManager.class).getBroadcaster(RootBuildLifecycleListener.class);
buildLifecycleListener.afterStart();
try {
GradleBuildController buildController = new GradleBuildController(gradleLauncher);
buildActionRunner.run(action, buildController);
return buildController.getResult();
} finally {
buildLifecycleListener.beforeComplete();
}
} finally {
gradleLauncher.stop();
}
}
在 GradleLauncher 的實(shí)現(xiàn)類(lèi)中血淌,我們看到了熟悉的東西:
private enum Stage {
Load, Configure, Build
}
然后代碼便會(huì)調(diào)用到 DefaultGradleLauncher 的 run ()->doBuild () 方法
private BuildResult doBuild(final Stage upTo) {
// TODO:pm Move this to RunAsBuildOperationBuildActionRunner when BuildOperationWorkerRegistry scope is changed
final AtomicReference<BuildResult> buildResult = new AtomicReference<BuildResult>();
WorkerLeaseService workerLeaseService = buildServices.get(WorkerLeaseService.class);
workerLeaseService.withLocks(workerLeaseService.getWorkerLease()).execute(new Runnable() {
@Override
public void run() {
Throwable failure = null;
try {
// 開(kāi)始構(gòu)建之前
buildListener.buildStarted(gradle);
// 開(kāi)始構(gòu)建
doBuildStages(upTo);
} catch (Throwable t) {
failure = exceptionAnalyser.transform(t);
}
buildResult.set(new BuildResult(upTo.name(), gradle, failure));
// 構(gòu)建完成之后
buildListener.buildFinished(buildResult.get());
if (failure != null) {
throw new ReportedException(failure);
}
}
});
return buildResult.get();
}
然后我們看一下開(kāi)始構(gòu)建時(shí)的代碼:
private void doBuildStages(Stage upTo) {
if (stage == Stage.Build) {
throw new IllegalStateException("Cannot build with GradleLauncher multiple times");
}
if (stage == null) {
// Evaluate init scripts
initScriptHandler.executeScripts(gradle);
// 初始化階段矩欠,解析Settings.gradle文件夾
settings = settingsLoader.findAndLoadSettings(gradle);
stage = Stage.Load;
}
if (upTo == Stage.Load) {
return;
}
if (stage == Stage.Load) {
// 配置階段
buildOperationExecutor.run(new ConfigureBuild());
stage = Stage.Configure;
}
if (upTo == Stage.Configure) {
return;
}
stage = Stage.Build;
// 繪制task的依賴樹(shù)
buildOperationExecutor.run(new CalculateTaskGraph());
// 執(zhí)行task。
buildOperationExecutor.run(new ExecuteTasks());
}
到最后一步,感覺(jué)所有的努力都沒(méi)有白費(fèi)癌淮,終于看到了熟悉的 Gradle 構(gòu)建的三個(gè)階段躺坟。原來(lái) Gradle 的構(gòu)建也是用代碼寫(xiě)出來(lái)的,并沒(méi)有想象的那么高深该默。
Gradle 插件
什么是 Gradle 插件
我們上面講解過(guò)瞳氓,Gradle 的構(gòu)建過(guò)程實(shí)際上就是各個(gè) Task 的執(zhí)行過(guò)程,那么這些執(zhí)行的 Task 從哪里來(lái)呢栓袖?答案就是從 Gradle 插件里來(lái)匣摘。
我們發(fā)現(xiàn)當(dāng)我們新建一個(gè) Android 工程時(shí),在 App 這個(gè) module 的 build.gradle 文件的第一行里會(huì)有以下代碼:
apply plugin: 'com.android.application'
這句代碼的作用便是將構(gòu)建 Android 應(yīng)用的所有需要 Task 都加載進(jìn)來(lái)了裹刮。所以我們看到音榜,Gradle 生命周期的三個(gè)階段僅僅是個(gè)殼子,如果想構(gòu)建 Android 工程捧弃,那么就用 apply plugin: 'com.android.application'
引入所有的構(gòu)建 Android 應(yīng)用所需要的 Task赠叼;如果想要構(gòu)建 Java 工程,那么只需要通過(guò) apply plugin: 'java'
來(lái)引入 Java 工程所需要的 Task 便可违霞。
那么知道了 Gradle 插件的強(qiáng)大功能嘴办,我們將如何按照自己的需要自定義 Gradle 插件呢?我們下面來(lái)進(jìn)行講解买鸽。
自定義 Gradle 插件
我們?cè)谧远x Gradle 插件的時(shí)候涧郊,需要解決以下問(wèn)題:
- 問(wèn)題一:如何自定義一個(gè) Gradle Plugin?
- 問(wèn)題二:Gradle Plugin 怎么調(diào)試眼五?
- 問(wèn)題三:Gradle Plugin 的 apply 方法是什么時(shí)候觸發(fā)的妆艘?
下面以實(shí)際的例子來(lái)介紹如何自定義 Gradle 插件,并對(duì) Gradle 插件進(jìn)行調(diào)試看幼。在這個(gè)實(shí)際的例子中批旺,Gradle 插件的定義和使用分別在兩個(gè)不同的工程中,這樣定義出來(lái)的 plugin 能夠供外部使用诵姜。
問(wèn)題一:自定義一個(gè)插件
新建兩個(gè)工程 CustomPlugin汽煮、GradleProject。前者是定義插件的地方棚唆,后者是使用插件的地方暇赤。我們先定義插件。
下面是完成插件自定義以后的目錄結(jié)構(gòu)瑟俭,我們先來(lái)一個(gè)總覽翎卓。
新建一個(gè) module契邀,刪除里面的所有文件摆寄,然后新建成如上圖所示的目錄結(jié)構(gòu)。其中 MyCustomPlugin 是定義的插件類(lèi),而 mycustomplugin.properties 是配置的插件屬性微饥。
首先在 build.gradle 文件中添加如下代碼逗扒。
// 應(yīng)用另外兩個(gè)插件
apply plugin:"groovy"
apply plugin: "maven"
dependencies {
// 使用gradle的api
compile gradleApi()
// 使用groovy的api
compile localGroovy()
}
repositories {
// 下載api相關(guān)文件的倉(cāng)庫(kù)
mavenCentral()
}
添加以后點(diǎn)擊 Sync Project with Gradle 按鈕,就是這個(gè):
然后 Android Studio 就會(huì)識(shí)別出 groovy 文件夾欠橘,groovy 文件夾就變成了藍(lán)色矩肩。
然后在 MyCustomPlugin.groovy 添加如下代碼,在 apply 方法中具體執(zhí)行我們想要這個(gè)插件去做的事情肃续。
class MyCustomPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println("start mycustomplugin")
println(project.name)
}
}
最后一步黍檩,在 mycustomplugin.properties 文件中添加如下代碼,用來(lái)指明插件的處理類(lèi):
implementation-class=com.dianping.myplugin.MyCustomPlugin
其中 mycustomplugin.properties 中的 mycustomplugin始锚,代表著這個(gè)插件在使用時(shí)的名稱刽酱,例如,使用時(shí)就是 apply plugin:'mycustomplugin’
瞧捌。使用方通過(guò)名稱找到這個(gè)插件的配置文件棵里,然后根據(jù)配置文件找到這個(gè)插件具體執(zhí)行的類(lèi)。
到此姐呐,自定義一個(gè)插件的基本工作就完成了殿怜,下面就講一講如何使用。
如何使用
打包
在 myplugin 這個(gè) module 中的 build.gradle 文件中添加一些代碼曙砂,添加后整個(gè)代碼結(jié)構(gòu)如下头谜。
apply plugin:"groovy"
apply plugin: "maven"
dependencies {
compile gradleApi()
compile localGroovy()
}
repositories {
mavenCentral()
}
// 此處為新添加的代碼
// 定義組
group='com.dianping.myplugin'
//定義版本
version='1.0.0'
uploadArchives {
repositories {
mavenDeployer {
// 定義插件打包后上傳的位置,可以隨意指定麦轰,但是在使用時(shí)需要指定同樣的文件才能找到
repository(url: uri('../../repo'))
}
}
}
添加完成后再次點(diǎn)擊:
然后會(huì)在 Gradle project 面板中出現(xiàn) uploadArchives 的 Task乔夯。
雙擊它,就會(huì)在 …/…/repo 目錄下出現(xiàn)相關(guān)文件款侵。
使用
在 GradleProject 的根級(jí)別的 build.gradle 中 buildscript 節(jié)點(diǎn)上添加代碼末荐,添加完后如下所示:
buildscript {
repositories {
google()
jcenter()
// 添加的代碼
maven {
url uri('../repo') // 指定路徑,這個(gè)路徑和上面的生成路徑是一致的
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
// 添加的代碼新锈。myplugin是上面定義的時(shí)候module的名稱甲脏,com.dianping.myplugin是group。
classpath 'com.dianping.myplugin:myplugin:1.0.0'
}
}
最后在 app 這個(gè) module 中添加插件使用妹笆。
apply plugin: 'mycustomplugin'
添加完后執(zhí)行 ./gradlew :app:assembleDebug
就能看到打印結(jié)果:
start mycustomplugin
app
至此块请,使用上也講完了。下面該講一講如何調(diào)試了拳缠。
問(wèn)題二:如何調(diào)試
在剛才的 CustomPlugin 工程下在菜單欄中選擇 Run >> Edit Configurations墩新。然后點(diǎn)擊 remote,新建遠(yuǎn)程調(diào)試窟坐,其他東西都不用改海渊,直接點(diǎn)擊 OK 就行绵疲。
新建完成以后再 GradleProject 中運(yùn)行如下命令:
./gradlew :app:assembleDebug -Dorg.gradle.debug=true
然后在 CustomPlugin 需要斷點(diǎn)的地方打上斷點(diǎn),點(diǎn)擊下面紅框里的按鈕臣疑,啟動(dòng)調(diào)試盔憨。斷點(diǎn)處就會(huì)終止執(zhí)行。
至此讯沈,插件的開(kāi)發(fā)也能夠調(diào)試了郁岩。
問(wèn)題三:apply 方法什么時(shí)候執(zhí)行
Gradle 構(gòu)建的過(guò)程總共分為三個(gè)階段:初始化階段、配置階段缺狠、運(yùn)行階段问慎。初始化階段是執(zhí)行 settings.gradle 文件中的內(nèi)容,看看這個(gè) Project 需要構(gòu)建哪幾個(gè) module挤茄。在配置階段是從根 Project 依次遍歷 module蝴乔,并為每個(gè) module 生成一個(gè) Project 對(duì)象。配置階段完成時(shí)就形成了一個(gè)完整的 Task 依賴圖驮樊。然后就是執(zhí)行階段執(zhí)行相關(guān)的 Task薇正。
那么 apply 方法是什么時(shí)候執(zhí)行的呢?是在配置階段遇到 apply plugin:'mycustomplugin’
就開(kāi)始執(zhí)行囚衔,我們可以在前后打 log 來(lái)驗(yàn)證挖腰。結(jié)果和預(yù)期一樣。apply 方法中傳入的 Project 對(duì)象就是某個(gè)使用該插件的 Project 的對(duì)象练湿。
println 'before'
apply plugin: 'mycustomplugin'
println 'after'
非獨(dú)立工程定義和使用插件
如果想要在自己的工程里面使用 Gradle 插件猴仑,那么更加簡(jiǎn)單。
新建一個(gè) Project肥哎,叫做 PluginDemo, 在 app 的 build.gradle 中寫(xiě)上如下代碼:
class ApkDistPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.task("apkdist") << {
println 'hello world'
}
}
}
apply plugin: ApkDistPlugin
命令行輸入:
./gradlew -q -p app/ apkdist
// 輸出結(jié)果為:
hello world
讓插件是可以配置的
大多數(shù)插件都需要在 build script 中獲取到一定的配置信息辽俗。其中一個(gè)方法就是通過(guò) Extension 類(lèi)來(lái)進(jìn)行,Project 類(lèi)中持有了 ExtensionContainer 對(duì)象篡诽,包含了對(duì)這個(gè) Project 所有的配置崖飘。那么我們就可以通過(guò)它來(lái)添加我們自己的配置。下面是一個(gè)例子杈女。
class ApkDistExtension {
Closure nameMap = null
String destDir = null
}
class GreetingPluginExtension {
String message = null
}
class ApkDistPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.create("apkdistconf",ApkDistExtension)
def extension = project.extensions.create("greet",GreetingPluginExtension)
project.task("apkdist") << {
def closure = project['apkdistconf'].nameMap
closure('hello world closure')
println 'hello world'
println project['apkdistconf'].destDir
println extension.message
}
}
}
apply plugin: ApkDistPlugin
apkdistconf {
nameMap { name ->
println "$name haha"
}
destDir 'heiheihei'
}
greet.message = "greet"
下面是運(yùn)行結(jié)果
// 執(zhí)行的命令
./gradlew -q -p app/ apkdist
// 運(yùn)行的結(jié)果
hello world closure haha
hello world
heiheihei
greet