主頁 > 移動端開發 > Gradle的構建程序都不會?帶你全面了解Android如何自定義Gradle 插件

Gradle的構建程序都不會?帶你全面了解Android如何自定義Gradle 插件

2020-09-22 19:22:48 移動端開發

目前 Android 工程的默認構建工具為 Gradle,我們在構建 APK 的時候往往會執行 ./gradlew assembleDebug 這樣的命令,,

那么這個命令到底代表著什么含義呢?命令的執行究竟是在做什么事情呢?我們能不能在命令執行的程序中做一些自己的操作呢?接下來我們來具體的進行分析,

Gradle 的構建程序

Gradle Wrapper 是個啥

當我們在 Android Studio 中新建一個工程時,你會發現在工程的根目錄下會創建以下幾個檔案:

在這里插入圖片描述

實際上這幾個檔案是通過執行 $ Gradle Wrapper 生成的,Gradle Wrapper,顧名思義就是對 Gradle 構建工具的一層封裝,

在和其他同事共同管理某個 Android 工程的時候,肯定會存在同事 A 電腦上的 Gradle 版本和同事 B 電腦上的 Gradle 版本不一樣,那么這個不一樣可能導致的問題是需要在 build.gradle 檔案中添加不同的配置,甚至有的 Gradle 版本都無法成功跑通工程,

所以,Gradle 的工程師們將 Gradle 添加了一層簡單的封裝,Linux 用戶可以通過執行 gradlew 來代替 gradle 命令,Windows 用戶可以通過 gradlew.bat 來代替,實際上這倆檔案就是個可執行腳本,我們可以直接打開這個腳本來看里面到底有什么,

# 只貼上主要代碼 gradlew 檔案
...
JAVACMD="java"
...
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
...
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 

從里面的代碼我們可以看到,實際上 gradlew 的腳本也只是執行了一下 Java 命令,assembleDebug 只是執行腳本的引數,我們修改一下腳本列印出來這個命令:

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 

從這里可以看到,腳本所做的事情就是通過 Java 命令來執行 /gradle/wrapper/ 下的 gradle-wrapper.jar 包,通過 JD-GUI 工具打開這個可執行 jar 包,找到那個 jar 包的入口類,也就是 org.gradle.wrapper.GradleWrapperMain,我們大致看一下這個 jar 包到底干了什么,

// org.gradle.wrapper.GradleWrapperMain檔案
public static void main(String[] args) throws Exception {

  File wrapperJar = wrapperJar(); // 獲取這個可執行jar包的具體路徑
  File propertiesFile = wrapperProperties(wrapperJar); // 獲取gradle-wrapper.properties的位置
  File rootDir = rootDir(wrapperJar); // 獲取工程的根目錄

  // 下面的一段代碼用來決議命令列,我們暫時不去管它
  CommandLineParser parser = new CommandLineParser(); 

  ... 

  WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
  // 下面這個方法便是決議gradle/wrapper/gradle-wrapper.properties檔案里面的內容,然后根據里面的配置到相應的地址需下載另一個可執行jar包
  wrapperExecutor.execute(
    args,
    new Install(logger, new Download(logger, "gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)),
    new BootstrapMainStarter());
} 

通過分析 execute() 方法的執行,我們會發現,這個方法執行了 gradle-wrapper.properties 檔案的決議和下載作業,我們先看一下這個檔案里面有什么:

// gradle/wrapper/gradle-wrapper.properties 檔案配置
distributionBase=GRADLE_USER_HOME // GRADLE_USER_HOME的地址默認在用戶目錄下的.gradle/檔案夾里
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-4.4-all.zip 

如上所示,代碼會找到 distributionUrl 這個路徑,然后下載相應的檔案,在這里,工程會指定固定的一個 Gradle 版本,然后所有的開發者都會在相同的路徑下下載到相同的 ZIP 包,這樣也就保證了在不同電腦上執行構建操作結果的一致性,下載檔案的程序就不多述了,因為比較簡單,

檔案下載以后,當前就會去執行這個檔案:

// 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);
       // 通過反射找到org.gradle.launcher.GradleMain 進行具體的呼叫
        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 包,然后通過 ClassLoader 加載到記憶體里,加載以后通過反射呼叫里面的入口類,

到此,Gradle Wrapper 的整個呼叫程序結束了,它的功能就是保證多個電腦上能夠以相同的 Gradle 版本構建 Android 的工程代碼,后續的執行則是開啟了 Gradle 的真正構建程序,我們接下來進行分析,

Gradle 的構建生命周期

Gradle 的整個構建程序共分為三個階段:init 初始化階段、config 配置階段和 build 執行階段,下面簡單說一下這三個階段分別做什么作業,

init 初始化階段

初始化階段主要是決議 settings.gradle 檔案,查看該工程引入了多少個 module,如下所示,可以在 settings.gradle 檔案下定義需要引入的 module 和其對應的目錄:

include ':app'

include ':library'
project(':library').projectDir = new File('../library') 
config 階段

在 config 階段便是去決議每個 module 里的 build.gradle 檔案,并逐行執行,完成對 project 的配置,并構造 Task 任務依賴關系圖以便在執行階段按照依賴關系執行 Task,

build 執行階段

執行階段便是根據 config 階段生成的 Task 依賴關系圖,來挨個地去執行各個 Task,每個 Task 可以看做是一個功能體,比如說,在構建程序中 Java 檔案需要先轉換為 class 檔案,然后 class 檔案要再次轉換成 dex 檔案,然后 dex 檔案最終組合生成 APK,這個程序中每一步都是由一個 Task 來執行的,后續在介紹自定義 Gradle 插件的時候會講到 Task 相關的東西,

Gradle 構建程序代碼分析

剛才梳理了一下 Gradle 構建程序的生命周期,分為上面那三個階段,那么,具體到代碼是如何實作這三個宣告周期的呢?我們具體進行一下分析,

在 Gradle Wrapper 的末尾,我們提到構建程序走到了通過反射找到 org.gradle.launcher.GradleMain 進行具體的調用,那么我們就繼續跟著原始碼走,

// 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 是真正執行構建程序的入口類,深入到 new ProcessBootstrap().run() 方法中繼續執行,

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);
  // 我們發現通過新建classLoader,在classLoader增加了新的依賴項,但這些依賴項不知道是什么,不過這不是我們關注的重點,我們繼續代碼的執行
  ClassLoader runtimeClassLoader = new VisitableURLClassLoader(antClassLoader, runtimeClasspath);
  ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
  Thread.currentThread().setContextClassLoader(runtimeClassLoader);

  try {
    // 在此處通過反射呼叫了org.gradle.launcher.Main這個類的run方法,
    Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName);
    Object entryPoint = mainClass.newInstance();
    Method mainMethod = mainClass.getMethod("run", String[].class);
    mainMethod.invoke(entryPoint, new Object[]{args});
  } finally {
    ...
  }
} 

主流程是通過反射呼叫了 org.gradle.launcher.Main 這個類的 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 這個類實際上繼承了 org.gradle.launcher.bootstrap.EntryPoint 這個類,而它的 run 方法實際也就是提供了對 Main 中 doAction 方法的回呼,

// 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 的實體,然后呼叫了 WithLogging 的 execute 方法,

然后經過各種回呼等,最終呼叫的是 ParseAndBuildAction 的 execute 方法(中間的回呼程序略過,有興趣的同學可以自己查看一下,我們只分析主流程的代碼):

// org.gradle.launcher.cli.CommandLineActionFactory#ParseAndBuildAction
public void execute(ExecutionListener executionListener) {
  List<CommandLineAction> actions = new ArrayList<CommandLineAction>();
  // BuildInActions 是處理help version 這些命令的
  actions.add(new BuiltInActions());
  // 如果不是上面的兩條命令列引數,則執行BuildActionsFactory里的引數,
  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) {
    // 根據命令列引數選中相關的處理的Action,比如說在gradle引數后面跟著 help 或者version 引數,則選中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 引數后面加 help 或者 version 時,將交給 BuiltInActions 進行處理,其它的則會交給 BuildActionsFactory 類處理,我們直接查看一下 BuildActionsFactory 的執行代碼,

// 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()));
  // 下面是通過各種不同的引數
  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);
} 

后三個方法中都會呼叫到 runBuildAndCloseServices,這也是執行 Gradle 構建的方法,

最終代碼會執行到 RunBuildAction 的 run 方法,

// org.gradle.launcher.cli.RunBuildAction
public void run() {
  try {
    // 這個executer實際上是 InProcessBuildActionExecuter 的實體
    executer.execute(
      new ExecuteBuildAction(startParameter),
      new DefaultBuildRequestContext(new DefaultBuildRequestMetaData(clientMetaData, startTime), new DefaultBuildCancellationToken(), new NoOpBuildEventConsumer()),
      buildActionParameters,
      sharedServices);
  } finally {
    if (stoppable != null) {
      stoppable.stop();
    }
  }
} 

最終查看 InProcessBuildActionExecuter 執行的代碼,

public Object execute(BuildAction action, BuildRequestContext buildRequestContext, BuildActionParameters actionParameters, ServiceRegistry contextServices) {
  // 最終通過呼叫獲取到了GradleLauncher的實體,他的一個實作類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 的實作類中,我們看到了熟悉的東西:

private enum Stage {
  Load, Configure, Build
} 

然后代碼便會呼叫到 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 {
        // 開始構建之前
        buildListener.buildStarted(gradle);
        // 開始構建
        doBuildStages(upTo);
      } catch (Throwable t) {
        failure = exceptionAnalyser.transform(t);
      }
      buildResult.set(new BuildResult(upTo.name(), gradle, failure));
      // 構建完成之后
      buildListener.buildFinished(buildResult.get());
      if (failure != null) {
        throw new ReportedException(failure);
      }
    }
  });
  return buildResult.get();
} 

然后我們看一下開始構建時的代碼:

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的依賴樹
   buildOperationExecutor.run(new CalculateTaskGraph());
   // 執行task,
   buildOperationExecutor.run(new ExecuteTasks());
 } 

到最后一步,感覺所有的努力都沒有白費,終于看到了熟悉的 Gradle 構建的三個階段,原來 Gradle 的構建也是用代碼寫出來的,并沒有想象的那么高深,

Gradle 插件

什么是 Gradle 插件

我們上面講解過,Gradle 的構建程序實際上就是各個 Task 的執行程序,那么這些執行的 Task 從哪里來呢?答案就是從 Gradle 插件里來,

我們發現當我們新建一個 Android 工程時,在 App 這個 module 的 build.gradle 檔案的第一行里會有以下代碼:

apply plugin: 'com.android.application' 

這句代碼的作用便是將構建 Android 應用的所有需要 Task 都加載進來了,所以我們看到,Gradle 生命周期的三個階段僅僅是個殼子,如果想構建 Android 工程,那么就用 apply plugin: 'com.android.application' 引入所有的構建 Android 應用所需要的 Task;如果想要構建 Java 工程,那么只需要通過 apply plugin: 'java' 來引入 Java 工程所需要的 Task 便可,

那么知道了 Gradle 插件的強大功能,我們將如何按照自己的需要自定義 Gradle 插件呢?我們下面來進行講解,

自定義 Gradle 插件

我們在自定義 Gradle 插件的時候,需要解決以下問題:

  • 問題一:如何自定義一個 Gradle Plugin?

  • 問題二:Gradle Plugin 怎么除錯?

  • 問題三:Gradle Plugin 的 apply 方法是什么時候觸發的?

下面以實際的例子來介紹如何自定義 Gradle 插件,并對 Gradle 插件進行除錯,在這個實際的例子中,Gradle 插件的定義和使用分別在兩個不同的工程中,這樣定義出來的 plugin 能夠供外部使用,

問題一:自定義一個插件

新建兩個工程 CustomPlugin、GradleProject,前者是定義插件的地方,后者是使用插件的地方,我們先定義插件,

下面是完成插件自定義以后的目錄結構,我們先來一個總覽,

在這里插入圖片描述

新建一個 module,洗掉里面的所有檔案,然后新建成如上圖所示的目錄結構,其中 MyCustomPlugin 是定義的插件類,而 mycustomplugin.properties 是配置的插件屬性,

首先在 build.gradle 檔案中添加如下代碼,

// 應用另外兩個插件
apply plugin:"groovy"
apply plugin: "maven"

dependencies {
    // 使用gradle的api
    compile gradleApi()
    // 使用groovy的api
    compile localGroovy()
}

repositories {
    // 下載api相關檔案的倉庫
    mavenCentral()
} 

添加以后點擊 Sync Project with Gradle 按鈕,就是這個:

在這里插入圖片描述

然后 Android Studio 就會識別出 groovy 檔案夾,groovy 檔案夾就變成了藍色,

然后在 MyCustomPlugin.groovy 添加如下代碼,在 apply 方法中具體執行我們想要這個插件去做的事情,

class MyCustomPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {

        println("start mycustomplugin")

        println(project.name)
    }
} 

最后一步,在 mycustomplugin.properties 檔案中添加如下代碼,用來指明插件的處理類:

implementation-class=com.dianping.myplugin.MyCustomPlugin 

其中 mycustomplugin.properties 中的 mycustomplugin,代表著這個插件在使用時的名稱,例如,使用時就是 apply plugin:'mycustomplugin’,使用方通過名稱找到這個插件的組態檔,然后根據組態檔找到這個插件具體執行的類,

到此,自定義一個插件的基本作業就完成了,下面就講一講如何使用,

如何使用

打包

在 myplugin 這個 module 中的 build.gradle 檔案中添加一些代碼,添加后整個代碼結構如下,

apply plugin:"groovy"
apply plugin: "maven"

dependencies {
    compile gradleApi()
    compile localGroovy()
}

repositories {
    mavenCentral()
}
// 此處為新添加的代碼
// 定義組
group='com.dianping.myplugin'
//定義版本
version='1.0.0'

uploadArchives {
    repositories {
        mavenDeployer {
            // 定義插件打包后上傳的位置,可以隨意指定,但是在使用時需要指定同樣的檔案才能找到
            repository(url: uri('../../repo'))
        }
    }
} 

添加完成后再次點擊:

在這里插入圖片描述

然后會在 Gradle project 面板中出現 uploadArchives 的 Task,

在這里插入圖片描述

雙擊它,就會在 …/…/repo 目錄下出現相關檔案,

使用

在 GradleProject 的根級別的 build.gradle 中 buildscript 節點上添加代碼,添加完后如下所示:

buildscript {

    repositories {
        google()
        jcenter()
        // 添加的代碼
        maven {
            url uri('../repo') // 指定路徑,這個路徑和上面的生成路徑是一致的
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        // 添加的代碼,myplugin是上面定義的時候module的名稱,com.dianping.myplugin是group,
        classpath 'com.dianping.myplugin:myplugin:1.0.0'

    }
} 

最后在 app 這個 module 中添加插件使用,

apply plugin: 'mycustomplugin' 

添加完后執行 ./gradlew :app:assembleDebug 就能看到列印結果:

start mycustomplugin
app 

至此,使用上也講完了,下面該講一講如何除錯了,

問題二:如何除錯

在剛才的 CustomPlugin 工程下在選單欄中選擇 Run >> Edit Configurations,然后點擊 remote,新建遠程除錯,其他東西都不用改,直接點擊 OK 就行,

在這里插入圖片描述

新建完成以后再 GradleProject 中運行如下命令:

./gradlew :app:assembleDebug -Dorg.gradle.debug=true 

然后在 CustomPlugin 需要斷點的地方打上斷點,點擊下面紅框里的按鈕,啟動除錯,斷點處就會終止執行,

在這里插入圖片描述

至此,插件的開發也能夠除錯了,

問題三:apply 方法什么時候執行

Gradle 構建的程序總共分為三個階段:初始化階段、配置階段、運行階段,初始化階段是執行 settings.gradle 檔案中的內容,看看這個 Project 需要構建哪幾個 module,在配置階段是從根 Project 依次遍歷 module,并為每個 module 生成一個 Project 物件,配置階段完成時就形成了一個完整的 Task 依賴圖,然后就是執行階段執行相關的 Task,

那么 apply 方法是什么時候執行的呢?是在配置階段遇到 apply plugin:'mycustomplugin’ 就開始執行,我們可以在前后打 log 來驗證,結果和預期一樣,apply 方法中傳入的 Project 物件就是某個使用該插件的 Project 的物件,

println 'before'
apply plugin: 'mycustomplugin'
println 'after' 

非獨立工程定義和使用插件

如果想要在自己的工程里面使用 Gradle 插件,那么更加簡單,

新建一個 Project,叫做 PluginDemo, 在 app 的 build.gradle 中寫上如下代碼:

class ApkDistPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.task("apkdist") << {
            println 'hello world'
        }
    }
}
apply plugin: ApkDistPlugin
命令列輸入:
./gradlew -q -p app/ apkdist

// 輸出結果為:

hello world 

讓插件是可以配置的

大多數插件都需要在 build script 中獲取到一定的配置資訊,其中一個方法就是通過 Extension 類來進行,Project 類中持有了 ExtensionContainer 物件,包含了對這個 Project 所有的配置,那么我們就可以通過它來添加我們自己的配置,下面是一個例子,

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" 

下面是運行結果

// 執行的命令
./gradlew -q -p app/ apkdist

// 運行的結果

hello world closure haha
hello world
heiheihei
greet

現在都說互聯網寒冬,其實只要自身技術能力夠強,咱們就不怕!我這邊專門針對Android開發工程師整理了一套【Android進階學習視頻】、【全套Android面試秘籍】、【Android知識點PDF】,如有需要獲取資料檔案的朋友,可以點擊我GitHub免費獲取!

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/106817.html

標籤:其他

上一篇:Sagit.Framework For IOS 開發框架入門教程16:螢屏旋轉、螢屏強制旋轉功能。

下一篇:移動端初級相關解決方案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more