1. Settings file
Gradle defines a settings file, its name is settings.gradle , placed in the root project directory, settings.gradle is generally used to configure sub- modules . It should be noted that a sub- module is only in settings.gradle be included in the build if it is configured .
Create a new project, the project name is AndroidConfig
Check settings.gradle , you can see that the project name and app Module are configured by default
= rootProject.name "AndroidConfig"
the include ': App'
copy the code
include calls the methods of the Settings interface
void include (String...str) ;
copy the code
In addition to the root directory in the same directory and also can Module into any directory, for example, the Core Module into ARouter directory
rootProject.name = "AndroidConfig"
include ':app'
the include ': Core'
Project ( ': Core' ) = .projectDir new new File (RootDir, 'ARouter/Core' )
copying the code
The effect is as follows
2. unified version management
After n iterations, projects tend to become larger and larger, and modularity is essential. Therefore, multiple modules may depend on multiple versions of the same library, which may easily cause unnecessary troubles and require unified version management Up.
Gradle allows adding custom attributes, which are implemented through ext , for example, ext.age = 18. When adding multiple custom attributes, they are implemented through ext code blocks.
Based on the project AndroidConfig , create a config.gradle file in the project root directory for unified version management and include the following content
ext {
android = [
"compileSdkVersion" : 30 ,
"minSdkVersion" : 21 ,
"targetSdkVersion" : 28 ,
"versionCode" : 1 ,
"versionName" : "1.0" ,
"appcompat" : "1.2.0" ,
"glide" : "4.12.0 " ,
]
dependencies = [
" appcompat " : "androidx.appcompat:appcompat:${android[" appcompat "]}" ,
"glide" : "com.github.bumptech.glide:glide:${android[" glide "]}" ,
"glide -compiler" : "com.github.bumptech.glide:compiler:${android[" glide "]}"
]
}
Copy code
Then apply the script in the project root directory build.gradle , so that all Modules can refer to the custom properties inside
the Apply from: "config.gradle"
Copy the code
Use these custom attributes in the sub- Moduel , for example , compile the following code in build.gradle under app Module
android {
compileSdkVersion rootProject.ext.android[ "compileSdkVersion" ]
defaultConfig {
minSdkVersion rootProject.ext.android[ "minSdkVersion" ]
targetSdkVersion rootProject.ext.android[ "targetSdkVersion" ]
versionCode rootProject.ext.android[ "versionCode" ]
versionName rootProject.ext.android[ "versionName" ]
}
}
dependencies {
implementation fileTree( include: [ '*.jar' ], dir: 'libs' )
//Android Library
implementation rootProject.ext.dependencies[ " appcompat " ]
//Glide
implementation rootProject.ext.dependencies[ "glide" ]
annotationProcessor rootProject.ext.dependencies[ "glide-compiler" ]
}
Copy code
When you need to change the version of the dependent library, you can modify it directly in config.gradle , which is very easy to manage
3. The version number of the release package is incremented by 1 each time you hit the release package
Based on the project AndroidConfig , create a version.properties file in the project root directory to record the version number and include the following content
= VERSION_CODE . 1
duplicated code
In app Module under build.gradle incorporated following code
def versionCodeAutoIncrement() {
def file = file( '../version.properties' )
if (file.canRead()) {
def properties = new Properties()
properties.load( new FileInputStream(file))
def versionCode = properties[ 'VERSION_CODE' ].toInteger()
def taskNames = gradle.startParameter.taskNames
//Increment automatically only when you hit the release package
if (taskNames.contains( ":app: assembleRelease" )) {
versionProps[ 'VERSION_CODE' ] = (++versionCode).toString()
versionProps.store(versionFile.newWriter(), null )
}
return versionCode
} else {
throw new GradleException( "Could not find version.properties!" )
}
}
android {
defaultConfig {
applicationId "com.example.androidconfig"
minSdkVersion 21
targetSdkVersion 30
versionCode versionCodeAutoIncrement()
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
Copy code
When packaging , the version number in the version.properties file is read first , and then the version number is automatically +1, and rewritten into the version.properties file.
4. modify the apk/aar file name generated by the package
The Android object provides 3 attributes, as shown in the table. Variant is literally translated as a variant. A variant is a cross product of a build type and a product variant. For example, ApplicationVariant can represent a release package, or it can represent a debug package, and so on.
It should be noted that accessing these 3 attributes will trigger the creation of all tasks, and there is no need to reconfigure. By using this feature, by accessing them, modifying the apk/aar file name, it will automatically trigger the creation of all tasks, thus modifying the apk/aar file The name will take effect, achieving the purpose of modifying the apk/aar file name.
Variants | Description |
---|---|
applicationVariants | Only applicable to Android apps Gradle plugin |
libraryVariants | Only applicable to Android library Gradle plugin |
testVariants | Both of the above Gradle plugins are applicable |
In order to understand what types of these three variants are, you need to view the source code of the Android plug-in. By default, the source code is not visible. You need to add a dependency in the build.gradle of the app Module , click sync , and wait for the source code to download carry out
dependencies {
compileOnly "com.android.tools.build:gradle:4.2.1"
}
Copy code
Start to view the source code of these 3 attributes
//applicationVariants
public abstract class AbstractAppExtension public constructor (dslServices: com.android.build.gradle. internal .services.DslServices, globalScope: com.android.build.gradle. internal .scope.GlobalScope, buildOutputs: org.gradle.api. NamedDomainObjectContainer<com.android.build.gradle.api.BaseVariantOutput>, sourceSetManager: com.android.build.gradle. internal .dependency.SourceSetManager, extraModelInfo: com.android.build.gradle. internal .ExtraModelInfo, isBaseModule: kotlin. Boolean ): com.android.build.gradle.TestedExtension {
public final valapplicationVariants: org.gradle.api.DomainObjectSet<com.android.build.gradle.api.ApplicationVariant> /* compiled code */
}
//libraryVariants
public open class LibraryExtension public constructor (dslServices: com.android.build.gradle. internal .services.DslServices, globalScope: com.android.build.gradle. internal .scope.GlobalScope, buildOutputs: org.gradle.api. NamedDomainObjectContainer<com.android.build.gradle.api.BaseVariantOutput>, sourceSetManager: com.android.build.gradle. internal .dependency.SourceSetManager, extraModelInfo: com.android.build.gradle. internal .ExtraModelInfo, publicExtensionImpl: com.android .build.gradle. internal .dsl.LibraryExtensionImpl): com.android.build.gradle.TestedExtension, com.android.build.gradle. internal.dsl.InternalLibraryExtension {
public final val/* compiled code */libraryVariants: org.gradle.api. internal .DefaultDomainObjectSet<com.android.build.gradle.api.LibraryVariant>
}
//testVariants
public abstract class TestedExtension public constructor (dslServices: com.android.build.gradle. internal .services.DslServices, globalScope: com.android.build.gradle. internal .scope.GlobalScope, buildOutputs: org.gradle.api. NamedDomainObjectContainer<com.android.build.gradle.api.BaseVariantOutput>, sourceSetManager: com.android.build.gradle. internal .dependency.SourceSetManager, extraModelInfo: com.android.build.gradle. internal .ExtraModelInfo, isBaseModule: kotlin. Boolean ): com.android.build.gradle.BaseExtension, com.android.build.gradle.TestedAndroidConfig, com.android.build.api.dsl.TestedExtension {
public open val testVariants: org.gradle.api.DomainObjectSet<com.android.build.gradle.api.TestVariant> /* compiled code */
}
Copy code
Found by viewing the source code, these three types of attributes are set by all traverse method thereof, and these variant has a outputs set, each variant having at least one Output , need to traverse outputs
1. Modify the apk file name generated by packaging
android {
applicationVariants.all {variant ->
variant.outputs.all {output ->
if (output.outputFile != null && output.outputFile.name.endsWith( ".apk" ) && variant.buildType.name == "release" ) {
output.outputFileName = "xxx-v${variant.flavorName}-release.apk"
}
}
}
}
Copy code
2. Modify the aar file name generated by the package
android {
libraryVariants.all {variant ->
variant.outputs.all {output ->
if (output.outputFile != null && output.outputFile.name.endsWith( ".aar" ) && variant.buildType.name == "release" ) {
output.outputFileName = "xxx-v${variant.flavorName}-release.aar"
}
}
}
}
Copy code
5. break through the 65535 method limit
Before Android 5.0 , when the number of methods included in an application and its referenced library exceeds 65536 , a build error will be encountered
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option. Copy code
The reason for the error is that Dalvik restricts each APK to only one classes.dex bytecode file, and the total number of methods referenced in a single DEX file is limited to 65536, including Android framework methods, library methods, and methods in its own code.
Taking Android 5.0 as the boundary, let s see how to deal with breaking the 65535 method limit
1. Before Android 5.0
In the app Module of build.gradle document incorporated the following
android {
defaultConfig {
multiDexEnabled true
}
}
dependencies {
implementation "androidx.multidex:multidex:2.0.1"
}
Copy code
Need to customize Application to complete MultiDex initialization
public class MyApplication extends Application {
@Override
protected void attachBaseContext (Context base) {
super .attachBaseContext(base);
MultiDex.install( this );
}
}
Copy code
2. After Android 5.0
There is no need to do any processing, because after Android 5.0 uses ART 's runtime, it supports loading multiple DEX files from APK files. ART performs pre-compilation during application installation, scans the classesN.dex file, and compiles it into a single .oat file. For Android devices to execute.
6. custom BuildConfig
BuildConfig is generated by the Android Gradle build script after compilation. The declarations are all constants and can be used directly globally. For example, BuildConfig.DEBUG , new project, open view, usually long like this
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean( "true" );
public static final String APPLICATION_ID = "com.example.androidconfig" ;
public static final String BUILD_TYPE = "debug" ;
public static final int VERSION_CODE = 1 ;
public static final String VERSION_NAME = "1.0" ;
}
Copy code
You can also add some constants to BuildConfig through customization, so that the global can be used, for this Android Gradle provides a function to achieve
public Open Fun buildConfigField (type: Kotlin . String , name: Kotlin . String , value: Kotlin . String ) :. Kotlin Unit { /* Compiled code */ }
copy the code
The first parameter type is the type of the field to be generated, the second parameter name is the constant name of the field to be generated, and the third parameter value is the constant value of the field to be generated.
At this time, a demand came, requiring a page to load Baidu web pages under the debug package and Sina web pages under the release package. Ever since, compile the code in build.gradle of app Module
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt' ), 'proguard-rules.pro'
buildConfigField "String" , "URL" , '"https://www.baidu.com"'
}
debug {
buildConfigField "String" , "URL" , '"https://www.sina.com.cn/"'
}
}
}
Copy code
After writing, you need to rebuild Project to generate BuildConfig file
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean( "true" );
public static final String APPLICATION_ID = "com.example.androidcustomconfig" ;
public static final String BUILD_TYPE = "debug" ;
public static final int VERSION_CODE = 1 ;
public static final String VERSION_NAME = "1.0" ;
//Field from build type: debug
public static final String URL = "https://www.sina.com.cn/" ;
}
Copy code
Then on that page, write the address directly as BuildConfig.URL , and different URLs will be automatically replaced when different packages are opened
7. dynamically add custom resources
Regarding string resources, we generally use xml definitions in the res/values folder , but in fact, they can also be defined in Gradle . Similar to buildConfigField , Android Gradle also provides a function resValue to achieve. Ever since, compile the code in build.gradle of app Module
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt' ), 'proguard-rules.pro'
resValue "string" , "name" , "Android Config"
}
debug {
resValue "string" , "name" , "Android Config(debug)"
}
}
}
Copy code
After writing, you need to rebuild the Project to generate it. The Gradle- defined string is generated in the file build\generated\res\resValues\debug\values\gradleResValues.xml
<?xml version="1.0" encoding="utf-8"?>
< resources >
<!-- Automatically generated file. DO NOT MODIFY -->
<!-- Value from build type: debug -->
< string name = "appName" translatable = "false" > Android Config (debug) </string >
</resources >
copy code
Then you can use it directly like the string defined in xml in the res/values folder
8. DEX options
After the Java source code is compiled into Class bytecode, when it is packaged into Apk, it is optimized by the dx command into a DEX file executable by the Android virtual machine , just to run faster, but sometimes it will prompt an error of insufficient memory. You can deal with this problem by modifying the following dexOptions
android {
dexOptions {
//Whether to start the dx incremental mode, there are more restrictions and may not take effect. Use
incremental false
with caution //When calling the dx command, the maximum heap memory
allocated javaMaxHeapSize "4g"
//Whether to pre-execute the dex libraries library project, greatly after opening Increase the speed of incremental build, but it will also affect the speed of clean build, generally false
preDexLibraries = false
//The number of threads used when the dx command is running, an appropriate number can improve the efficiency of dx
threadCount 2
}
}
Copy code
9. layout subcontracting
When there are too many layouts, if they are all placed under res/layout , it is not easy to manage. At this time, subcontracting is required, similar to code subcontracting
android {
sourceSets {
main {
res.srcDirs = [
'src/main/res' ,
'src/main/res/reLayout/welcome' ,
'src/main/res/reLayout/home'
]
}
}
}
Copy code
As shown
10. inject build variables into the list
During the build process, some content in the AndroidManifest file needs to be dynamically modified . For example , the appId of a certain SDK needs to be declared in the AndroidManifest . However, in some cases, for example, the appId of the test package and the production package need to be different, and they are counted separately.
Android Gradle provides a function manifestPlaceholders to achieve, you can insert placeholders as attribute values into the manifest file. First define the BUGLY_APPID attribute name in the AndroidManifest file
< manifest xmlns:android = "http://schemas.android.com/apk/res/android"
package = "com.example.androidconfig" >
< application >
< meta-data
android:name = "BUGLY_APPID"
android:value = "${APP_ID}"/>
</application >
</manifest >
copy code
In app Module of build.gradle configuration attribute value
android {
buildTypes {
release {
manifestPlaceholders = [ APP_ID: "12345" ]
}
debug {
manifestPlaceholders = [ APP_ID: "67890" ]
}
}
}
Copy code
At this point, when playing different packages, you can insert different APP_IDs
Eleven, configuration build variants
Each build variant in buildTypes represents a different application version that can be built, and can also be customized according to different needs. For example, if you want to build two versions of the application, one is a free version with limited content, and the other contains more The paid version of the content.
The build.gradle configuration of app Module is as follows
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt' ), 'proguard-rules.pro'
}
free {
applicationIdSuffix ".free"
}
vip {
applicationIdSuffix ".vip"
}
}
}
Copy code
applicationIdSuffix means to add a suffix after the default applicationId . For example , the applicationId configured in the default defaultConfig is com.example.androidconfig , then the package name for constructing free is com.example.androidconfig.free , so that it can be installed on the phone at the same time.
In this case, it is recommended to write the package name in the manifest file
< Intent-filter >
< Action Android: name = "{$ .TRANSMOGRIFY applicationId}"/>
</Intent-filter >
copy the code
After the above configuration is completed, when you need to package, you can see that there are several more build variants
12. Configure product variants
Product variants with similar type of build, support and product variants defaultConfig the same attributes as defaultConfig itself is ProductFlavor class, all variants must specify a variant dimensions, namely a product variant group, otherwise it will error
Error:All flavors must now belong to a named flavor dimension. The flavor 'flavor_name' is not assigned to a flavor dimension.
app Module build.gradle version free vip
android {
flavorDimensions "version"
productFlavors {
free {
dimension "version"
applicationIdSuffix ".free"
versionNameSuffix "-free"
}
vip {
dimension "version"
applicationIdSuffix ".vip"
versionNameSuffix "-vip"
}
}
}
APP APP APP
android {
//
signingConfigs {
//
release {
storeFile file('xxx.jks') //
storePassword 'xxx' //
keyAlias 'xxx' //
keyPassword 'xxx' //
}
//debug $HOME/.android/debug.keystore
//debug
debug {
storeFile file('xxx.jks')
storePassword 'xxx'
keyAlias 'xxx'
keyPassword 'xxx'
}
}
buildTypes {
release {
//release release
signingConfig signingConfigs.release
}
debug{
//debug debug
signingConfig signingConfigs.debug
}
}
}
android {
signingConfigs {
def theStoreFile = System.getenv("STORE_FILE")
def theStorePassword = System.getenv("STORE_PASSWORD")
def theKeyAlias = System.getenv("KEY_ALIAS")
def theKeyPassword = System.getenv("KEY_PASSWORD")
//Android debug
//debug
if (!theStoreFile || !theStorePassword || !theKeyAlias || !theKeyPassword){
theStoreFile = "debug.keystore"
theStorePassword = "android"
theKeyAlias = "androiddebugkey"
theKeyPassword = "android"
}
release {
storeFile file(theStoreFile)
storePassword theStorePassword
keyAlias theKeyAlias
keyPassword theKeyPassword
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
apk Android Gradle
public open var isShrinkResources: kotlin.Boolean
apk app Module
android {
buildTypes {
release {
//
shrinkResources true
//
minifyEnabled true
}
}
}
Android Gradle
resources XML tools:keep tools:discard
res/raw/keep.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:discard="@layout/unused2"/>
Android android.view android.content com.google.android.maps
AndroidManifest
<manifest>
<application>
<uses-library
android:name="com.google.android.maps"
android:required="true"/>
</application>
</manifest>
apk android:required="true"
optional platforms/android-xx/optional API org.apache.http.legacy HttpCLient API 23 Android SDK HttpClient
app Module
android {
useLibrary "org.apache.http.legacy"
}
AndroidManifest uses-library