Gradle (four) Android Config

Gradle (four) Android Config

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.

VariantsDescription
applicationVariantsOnly applicable to Android apps Gradle plugin
libraryVariantsOnly applicable to Android library Gradle plugin
testVariantsBoth 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