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