Hooking into the Android plugin

When developing for Android, most tasks we want to influence are related to the Android plugin. It is possible to augment the behavior of tasks by hooking into the build process. In the previous example, we already saw how to add a dependency on a custom task to include a new task in the regular build process. In this section, we will look at some possibilities for Android-specific build hooks.

One way to hook into the Android plugin is to manipulate the build variants. Doing this is quite straightforward; you just need the following code snippet to iterate over all the build variants of an app:

android.applicationVariants.all { variant ->
  // Do something
}

To get the collection of build variants, you can use the applicationVariants object. Once you have a reference to a build variant, you can access and manipulate its properties, such as name, description, and so on. If you want to use the same logic for an Android library, use libraryVariants instead of applicationVariants.

Note

Notice that we iterate over the build variants with all() instead of the each() method that we mentioned earlier. This is necessary because each() is triggered in the evaluation phase before the build variants have been created by the Android plugin. The all() method, on the other hand, is triggered every time a new item is added to the collection.

This hook can be used to change the name of the APK before it is saved, to add the version number to the filename. This makes it easy to maintain an archive of APKs without manually editing file names. In the next section, we will see how to accomplish that.

Automatically renaming APKs

A common use case for manipulating the build process is to rename APKs to include the version number after they are packaged. You can do this by iterating over the build variants of the app, and changing the outputFile property of its outputs, as demonstrated in the following code snippet:

android.applicationVariants.all { variant ->
  variant.outputs.each { output ->
    def file = output.outputFile
    output.outputFile = new File(file.parent, file.name.replace(".apk", "-${variant.versionName}.apk"))
  }
}

Every build variant has a collection of outputs. The output of an Android app is just an APK. The output objects each have a property of the type File named outputFile. Once you know the path of the output, you can manipulate it. In this example, we add the version name of the variant to the file name. This will result in an APK named app-debug-1.0.apk instead of app-debug.apk.

Combining the power of build hooks for the Android plugin with the simplicity of Gradle tasks opens up a world of possibilities. In the next section, we will see how to create a task for every build variant of an app.

Dynamically creating new tasks

Because of the way that Gradle works and tasks are constructed, we can easily create our own tasks in the configuration phase, based on the Android build variants. To demonstrate this powerful concept, we will create a task to not just install, but also run any build variant of an Android app. The install task is a part of the Android plugin, but if you install an app from the command-line interface using the installDebug task, you will still need to start it manually when the installation is complete. The task we will create in this section will eliminate that last step.

Start by hooking into the applicationVariants property that we used earlier:

android.applicationVariants.all { variant ->
  if (variant.install) {
    tasks.create(name: "run${variant.name.capitalize()}", dependsOn: variant.install) {
        description "Installs the ${variant.description} and runs the main launcher activity."
      }
  }
}

For every variant, we check if it has a valid install task. This needs to be present because the new run task we are creating will depend on the install task. Once we have verified that the install task is present, we create a new task, and name it based on the variant's name. We also make our new task dependent on variant.install. This will trigger the install task before our task is executed. Inside the tasks.create() closure, start by adding a description, which is displayed when you execute gradlew tasks.

Besides adding the description, we also need to add the actual task action. In this example, we want to launch the app. You can launch an app on a connected device or emulator with the Android Debug Tool (ADB):

$ adb shell am start -n com.package.name/com.package.name.Activity

Gradle has a method called exec() that makes it possible to execute a command-line process. To make exec() work, we need to provide an executable that is present in the PATH environment variable. We also need to pass all the parameters with the args property, which takes a list of strings. Here is what that looks like:

doFirst {
    exec {
        executable = 'adb'
        args = ['shell', 'am', 'start', '-n', "${variant.applicationId}/.MainActivity"]
    }
}

To get the full package name, use the application ID of the build variant, which includes a suffix, if provided. There is one issue with suffixes in this case, though. Even if we add a suffix, the classpath to the activity is still the same. For example, consider this configuration:

android {
    defaultConfig {
        applicationId 'com.gradleforandroid'
    }

    buildTypes {
        debug {
            applicationIdSuffix '.debug'
        }
   }

The package name is com.gradleforandroid.debug, but the activity's path is still com.gradleforandroid.Activity. To make sure we get the right class to the activity, strip the suffix from the application ID:

doFirst {
    def classpath = variant.applicationId
    if(variant.buildType.applicationIdSuffix) {
        classpath -= "${variant.buildType.applicationIdSuffix}"
}
    def launchClass = "${variant.applicationId}/${classpath}.MainActivity"
    exec {
        executable = 'adb'
        args = ['shell', 'am', 'start', '-n', launchClass]
    }
}

First, we create a variable named classpath, based on the application ID. Then we find the suffix, provided by the buildType.applicationIdSuffix property. In Groovy, it is possible to subtract a string from another string, using the minus operator. These changes make sure running the app after installation does not fail when suffixes are used.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset