An overview of the

Build. Gradle has two scripts, one under project and one in app directory. As the project is iterated, We add a lot of dependencies to Gradle in app directory, but gradle under Project does not change very much. As a result, gradle files under App are getting bigger and bigger. Sometimes it is very inconvenient to find corresponding methods and tasks, especially after the integration of Tinker hotfix. I’ve had time recently to take a closer look at Gradle, groovy to be exact, and I’ve managed to decouple gradle from script dependencies to keep the gradle code in the app directory under 100 lines.

The body of the

Common configuration

The common practice is to create a new config.gradle file in the project directory as follows:

ext { android = [ compileSdkVersion: 25, buildToolsVersion: "SupportLibrary =" dependencies =" "Com. Android. Support: multidex: 1.0.1", "okhttp3" : "com. Squareup. Okhttp3: okhttp: 3.9.0"}Copy the code

Then reference it in the build.gradle file under project

apply from: "config.gradle"
Copy the code

Build. Gradle in your app directory and use it

apply plugin: 'com.android.application'
apply from: "package.gradle"

def cfg = rootProject.ext.android
def librarys = rootProject.ext.dependencies

android {
    compileSdkVersion cfg.compileSdkVersion
    buildToolsVersion cfg.buildToolsVersion
    dexOptions {
        jumboMode = true} dependencies {compile fileTree(include: ['*.jar'], dir: 'libs')
	compile librarys["multidex"]
	compile librarys["okhttp3"] // Omit ten thousand lines of code}Copy the code

Gradle can be managed in a unified way. Although it does not reduce the amount of build.gradle code in the app directory, it is sufficient. I am not familiar with Gradle based on Groovy. The amount of build.gradle code in app directory is increasing, especially after the integration of Tinker, packer-ng-plugin package, and some custom tasks are added. The code is very bloated, and sometimes it takes a long time to change something. Since I am not familiar with Groovy, I have read some articles on the Internet, which basically introduce the basic knowledge of Gradle and rely on unified management. In addition, there is no prompt to write code in Gradle, which makes me think that this may be the final version of Build. gradle. I then decided to simplify the Gradle code completely.

Url optimization

As per config, we will have at least two server addresses for development, which will be written in buildType for unified management

 buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            buildConfigField "String"."AlphaUrl"."\"releaseUrl1\""
// buildConfigField "String", "AlphaUrl", "\"releaseUrl2\""
        }
        debug {
            minifyEnabled true
              buildConfigField "String"."AlphaUrl"."\"debugUrl1\""
// buildConfigField "String", "AlphaUrl", "\"debugUrl2/\""}}Copy the code

When our server address is only a formal or a test, it completely OK, but if there are multiple addresses, each time you switch, all need to resynchronize, still have even if, collaborative development, many from Git server update code above every time, as long as the gradle updates to the app directory, All need to be resynchronized. Referring to config configuration, we only need to read the referenced URL each time if it is a reference, and only need to modify the code in config for switching. Here is the optimization: Add the code in config

    url = [
  "debug"  : "debugUrl1".//"debug" : "debugUrl2",
//"debug" : "debugUrl3",
   "release": "releaseUrl1".// "release": "releaseUrl2"

    ]
Copy the code

To quote

// get def url = rootproject.ext. url with //debug buildConfigField"String"."AlphaUrl"."\"${url["debug"]}\ ""
//release
buildConfigField "String"."AlphaUrl"."\"${url["release"]}\ ""

Copy the code

Depend on the optimization

Let’s take a look at the previous code

dependencies {
 compile fileTree(include: ['*.jar'], dir: 'libs')
 compile librarys["multidex"]
 compile librarys["supportAppcompat"]
 // Omit 10,000 lines of code here

Copy the code

Library is actually a Map, which translates into Java

 compile fileTree(include: ['*.jar'], dir: 'libs')
 HashMap<String,String> hashMap=new HashMap<>();
 compile hashMap.get("multidex")
 compile hashMap.get("supportAppcompat")
Copy the code

In fact, as you probably already know, we could actually write a loop to simplify this code, just like we did with HashMap

    dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    librarys.each { k, v -> compile v }
}
Copy the code

Modular configuration

In our regular Gradle configuration, we don’t add a lot of code to the build directory under project. We just create a new config.gradle file and add a dependency line to the project directory, so we can call the code in config. You can also configure tinker, Packer-ng-plugin, and custom task configuration files in a Gradle file and reference them in the app directory, in effect taking advantage of gradle plugin dependencies.

Tinker configuration

First, create a new tinker. Grale configuration file in the app directory. Why is it in the Tinker directory? Don’t forget to change tinker’s ID. I configured Tinker in the project directory, so I only need to call the code in config

// omit ten thousand lines of code defgetTinkerIdValue() {
    returnRootProject. Ext. Tinker. Id} / / is omitted ten thousand lines of codeCopy the code

Gradle file, because you can’t patch tinker if you rely on tinker. Gradle file. Tinker needs to read some information from your application. After buildTypes, which I had been debugging for quite a while.

apply plugin: 'com.android.application'def cfg = rootProject.ext.android def librarys = rootProject.ext.Dependencies def tinker = rootProject.ext.tinker def Url = rootproject.ext. url buildTypes {// omit ten thousand lines of code here} apply from:"tinker.gradle"

Copy the code

At this point, Tinker is ready to use

Packer configuration

Create a new package.gradle file

apply plugin: 'packer'
packer {
    archiveNameFormat = '${buildType}-v${versionName}-${channel}'
    archiveOutput = new File(project.rootProject.buildDir, "apks")
    channelList = ['xiaomi'.'meizu']}}Copy the code

Add dependencies to build.gradle in app

apply plugin: 'com.android.application'
apply from: "package.gradle"
Copy the code
Task allocation

Although the Application of Android Studio comes with many tasks, it cannot meet some of our requirements. For example, I need to use Python to upload the test package to FIR, so I need to customize the task. Therefore, I also plan to separate this task and create a new upload.gradle

ext {
   // Omit 10,000 lines of code here
    startUpload = this.&startUpload
}
// Upload to FIR
def startUpload() {
 // Omit 10,000 lines of code here
}

Copy the code

Reference in project

apply from: "upload.gradle"
Copy the code

It can be called directly from build.gradle of your app

task toFir << {
    startUpload()
}
Copy the code

ToFir << is the syntax of Gradle. If you do not add <<, this task will be executed at compile time. If you add <<, this task will be executed at compile time

With only 81 lines, you can debug your own gradle scripts without having to change them.

Run the test

Tinker test

Run gradlew tinkerpatchDebug or open the visual toolbar on the right and click tinkerpatchDebug under Tinker to run the test and run the result:

Result: final signed patch result: G:\Note\ChuangMei\app\build\outputs\tinkerPatch\debug\patch_signed.apk, size=2110
Result: final signed with 7zip patch result: G:\Note\ChuangMei\app\build\outputs\tinkerPatch\debug\patch_signed_7zip.apk, size=2439
Warning: patch_signed_7zip.apk is bigger than patch_signed.apk 329 byte, you should choose patch_signed.apk at these time!
Tinker patch done, total time cost: 8.806000s
Tinker patch done. you can go to file to find the output G:\Note\ChuangMei\app\build\outputs/tinkerPatch/debug -----------------------Tinker patch end------------------------- BUILD SUCCESSFULin 3m 16s

Copy the code
Packer test

Run the gradlew clean apkDebug command to run the test.

> Task :app:apkDebug ============================================================ PackerNg - https://github.com/mcxiaoke/packer-ng-plugin ============================================================ Variant: debug Input: G:\Note\ChuangMei\app\build\outputs\apk\app-debug.apk Output: G:\Note\ChuangMei\build\apks Channels: [Xiaomi Meizu] Generating: debug-v1.6.3-xiaomi. Apk Generating: debug-v1.6.3-meizu. Apk Outputs: G:\Note\ChuangMei\build\apksCopy the code
Task test

Gradlew task toFir

Start uploading to FIR HTTP://api.fir.im/apps
success_apk:{"is_completed":true}
success_icon:{"is_completed":true} Upload ends with value0
Copy the code

Some notes

Where gradle is placed

The default gradle reference is the current path. In this way, I do not need to configure the gradle reference path when I place it. Of course, if you place it in the same directory, it is OK. Just add the path of the reference when applying.

Reference position

Why some references are placed in the header and others in the middle depends on whether the referenced plug-in needs to read the configuration information of application. If it is Tinker, it must be placed in the middle, because it needs to obtain a lot of application information to generate patch packages. If packer packaging, it is not necessary, this need to pay special attention to, otherwise there will be a lot of puzzling errors.

The method call

In the same Gradle script, method calls are very simple, but when we have multiple Gradle scripts, how do we call each other’s methods? If we have two gradles, one is first. Gradle and the other is second. Gradle, I want to call the method in second. Just do the following configuration in first.gradle

ext{ 
    test= this.&test
 } 
	def  test(){  
       println("I've been called.")}Copy the code

Then configure it in second.gradle

// Call directly
   test()
// call through task
task CustomTask << {
    test()
}
Copy the code

##### About the code

In order to ensure that the modular separation of Gradle does not affect the construction of the project, I built tinker with my own real project. Because a simple Demo is difficult to simulate the build environment of a real project, I only provided gradle configuration files in the Demo, not tinker configuration, but tinker has been modular separation. Upload the code to the FIR using a Python script. You will need to configure the Python environment and install the Requests library. If you are interested, check out my article Python

The code download