Summary: Android Xiaobai's Growth Path_Knowledge System Summary【Continuously updated...】
Table of contents
problem background
The company's project uses Android Studio and Gradle to compile. Every time the code is modified (even if it is a line modification), it takes three to four minutes or even longer to compile and run again. It lasted more than ten minutes during the first compilation, which greatly affected the development efficiency. As the saying goes, if a worker wants to do a good job, he must first sharpen his tools. This is a wave of optimization for compilation speed, let's start step by step!
Gradle build process
First understand the Gradle construction process, which is divided into three stages as a whole:
- Initialization phase: Gradle supports single-project and multi-project construction. In the initialization phase,
setting.gradle
Gradle reads the modules that need to participate in the construction and creates a Project instance for each module. - Configuration phase: configure the project module and the scripts it needs to execute, that is,
build.gradle
other files - Execution phase: start executing the configured script task
After understanding these processes in general, we can start to optimize them from these processes
Relevant instructions before optimization
Information about the computer currently used as verification:
- Computer name: MacBook Pro
- System: Mac
- Memory: 8GB 1867MHz DDR3
- Processor: Dual Core Intel Core i5 2.7GHz
- AS version: 4.1.1
Three angles to verify compilation speed:
- rebuild all projects, all compile
- Add a method to trigger java recompilation
- Modify an xml to trigger resource recompilation
Compare data acquisition methods: rebuild three times to get the lowest value, modify method or xml try five times to get the lowest value
Related instructions:
- Because the computer sometimes freezes or other reasons affect the compilation, it will take a long time to compile a certain time, so the average value cannot be used as a reference
- Compilation is generally faster than once, because Android studio comes with a cache
- The development stage itself will not change the configuration all the time, so taking the minimum value can basically simulate daily use
- Because the main project module is relatively large, the code of the main project module is used for verification. If the component code is modified, it will generally take less time
- The scheme used for each data statistics inherits all the previous optimization schemes
- Modify method and xml with apply changes
Optimization
From the overall construction process, we can know that we need to optimize from three aspects as a whole:
- Initialization speed optimization
- Configuration speed optimization
- Execution speed optimization
Among them, the execution process accounts for the largest proportion, so the focus is on execution speed optimization
Initialization speed optimization
Generally, the initialization process has fewer tasks and is already fast, but some processing can still be done to achieve the best state:
- When the degree of componentization is high, some components do not need to be introduced in the process of developing a specific function. At this time, the
setting.gradle
component modules that do not need to be introduced can be removed in , which can reduce the initialization time setting.gradle
Try not to write too much code before the include
Configuration speed optimization
The configuration phase is mainly to build.gradle
analyze each, so you can pay attention to the following points:
- Import modules on demand, reducing
build.gradle
parsing build.gradle
Do as little time-consuming operations as possible, such as reading the system time and dynamically configuring the name composition of the apk- It is not necessary to perform tasks in the development stage. You can write judgments to avoid the configuration of these tasks, such as some bytecode stubs, performance monitoring and the like
Execution speed optimization
There are a large number of tasks that need to be performed at this stage, so there are many optimization points
Configure Gradle
Enable parallel compilation
After it is turned on, multiple tasks will be executed in parallel, greatly reducing compilation time, just gradle.properties
add in:
org.gradle.parallel=true
increase compile memory
Since everyone's computer configuration is different, the specific setting of how much memory needs to be reasonably configured according to individual circumstances. Generally, there gradle.properties
are already relevant configurations in the computer, and the configuration can be modified, for example
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
build.gradle
At the same time , modify it in the main project module :
dexOptions {
javaMaxHeapSize "4g"
}
It is worth noting that javaMaxHeapSize
the value of , needs to be org.gradle.jvmargs
less than the set value 512m
, and the org.gradle.jvmargs
higher the value of , the better. According to the verification, it is best to configure it as 1/3 of the system memory, and at most it should not exceed 1/2. It is shown in some documents that javaMaxHeapSize
configuration is no longer required in higher versions javaMaxHeapSize
, only configuration is required org.gradle.jvmargs
. After consulting a lot of information, it is not clear, so it is configured for the time being.
Enable on-demand builds
Modules that have not changed are no longer compiled, which is very suitable for already componentized projects. gradle.properties
Add:
org.gradle.configureondemand=true
Enable build caching
Directly use the previously generated cache and no longer build it. It will be displayed after the task at build time FROM CACHE
. gradle.properties
Add:
org.gradle.caching=true
Enable incremental annotation compilation
Support incremental compilation of annotations, and will not re-trigger compilation (need to be removed in higher versions of gradle), gradle.properties
add in:
android.enableSeparateAnnotationProcessing=true
Data comparison (parallel compilation is enabled before optimization, so the following time does not include optimization of parallel compilation):
rebuild | Modification method | modify xml | |
---|---|---|---|
Before configuration optimization | 4m46s | 46s | 22s |
After configuration optimization | 2m39s | 42s | 20s |
income | 44% reduction | 8% reduction | 9% reduction |
Configure the AS
Turn on offline mode
After the offline mode is turned on, it will not detect whether there is an update to the dependency at the beginning, nor will it download the updated dependency. The first build cannot be enabled, otherwise the build cannot be completed. Subsequent builds can be enabled, and in some cases it will be greatly improved. Improve compilation speed, strongly recommended for development phase. Click the button of the icon in the picture below to start the offline mode, some versions are displayed as an icon similar to wifi, click again to cancel the offline mode:
Change AS memory size
Click on the menu item of AS Help
and select Change Memory Settings
the option. As shown in the picture:
The pop-up box as shown in the figure below pops up, Maxinum Heap Size
modify to an appropriate value, and the specific modification value is selected according to the memory configuration of your own computer
Data comparison:
rebuild | Modification method | modify xml | |
---|---|---|---|
Before the AS configuration is modified | 2m39s | 42s | 20s |
After the AS configuration is modified | 2m16s | 37s | 16s |
income | 14% reduction | 11% reduction | 20% reduction |
Update the latest Gradle version
Since gradle generally further optimizes the build speed in new versions, keeping the latest gradle version can obtain the best build experience. The update method is as follows:
-
First
gradle-wrapper.properties
configure the gradle version in:distributionUrl=https\:``//services.gradle.org/distributions/gradle-6.7.1-all.zip
-
build.gradle
Then update the gradle plugin version in the root directory :classpath 'com.android.tools.build:gradle:4.1.1'
Possible problems and solutions after updating to 6.x or higher:
-
Exception reported:
FAILURE: Build failed with an exception. * What went wrong: A problem occurred configuring project ':live'. > Failed to notify project evaluation listener. > org.gradle.api.tasks.TaskInputs.property(Ljava/lang/String;Ljava/lang/Object;)Lorg/gradle/api/tasks/TaskInputs; > Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. * Try: Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Exception is: org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':live'. at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:75) ...... at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) Cause 2: groovy.lang.MissingPropertyException: Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. at org.gradle.internal.metaobject.AbstractDynamicObject.getMissingProperty(AbstractDynamicObject.java:85) ...... at java.lang.Thread.run(Thread.java:748) * Get more help at https://help.gradle.org
This is caused by the current greenDao version being too low. Just update the greenDao version and modify the version under the root directory
build.gradle
:classpath 'org.greenrobot:greendao-gradle-plugin:3.3.0'
-
Exception reported:
FAILURE: Build failed with an exception. * Where: Build file '/Users/uxin/AndroidStudioProjects/Pika/UXLiveOverseas/live/build.gradle' line: 253 * What went wrong: A problem occurred configuring project ':live'. > Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. * Try: Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Exception is: org.gradle.api.ProjectConfigurationException: A problem occurred configuring project ':live'. at org.gradle.configuration.project.LifecycleProjectEvaluator.wrapException(LifecycleProjectEvaluator.java:75) ...... at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) Caused by: groovy.lang.MissingPropertyException: Could not get unknown property 'additionalParameters' for task ':live:dexBuilderDebug' of type com.android.build.gradle.internal.tasks.DexArchiveBuilderTask. at org.gradle.internal.metaobject.AbstractDynamicObject.getMissingProperty(AbstractDynamicObject.java:85) ...... at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) * Get more help at https://help.gradle.org
In
sdk 21
the past, third-party dependencies were generally used tomultidex
enable dex blocks, andsdk 21
later, the official comes with itmultidex
, so the third-party needs to be removedmultidex
:-
In the main project module
build.gradle
, delete the multidex dependencies and custom tasks://implementation 'androidx.multidex:multidex:2.0.0' // afterEvaluate { // tasks.matching { // it.name.startsWith('dex') // }.each { dx -> // if (dx.additionalParameters == null) { // dx.additionalParameters = ['--multi-dex'] // } else { // dx.additionalParameters += '--multi-dex' // } // } // }
-
multidex
Initialization deleted in custom Application class ://import androidx.multidex.MultiDex; //MultiDex.install(this);
-
-
Exception reported:
FAILURE: Build failed with an exception. * Where: Build file '/Users/xxx/Projects/xxx/xxx/xxx/build.gradle' line: 1 * What went wrong: A problem occurred evaluating project ':live'. > Failed to apply plugin 'com.android.internal.application'. > The option 'android.enableSeparateAnnotationProcessing' is deprecated. The current default is 'false'. It was removed in version 4.0 of the Android Gradle plugin. This feature was removed in AGP 4.0 * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org
This is the newly added annotation incremental compilation field has been removed in the new version of gradle, so it should be removed and
gradle.properties
deleted in:#android.enableSeparateAnnotationProcessing=true
-
Exception reported:
private static final String LIBRARY_VERSION = ". Version: " + BuildConfig.VERSION_NAME; ^ 符号: 变量 VERSION_NAME 位置: 类 BuildConfig
Since there is not much difference between
versionName
andversionCode
, in order to prevent concept confusion, the official removed itVERSION_NAME
, so if we still need to use it in our project, it can be configured with the custombuildConfig
oneVERSION_NAME
in the module that reported the error :build.gradle
defaultConfig { minSdkVersion MIN_SDK_VERSION as int targetSdkVersion TARGET_SDK_VERSION as int versionCode 2 versionName "1.0.1" buildConfigField 'String', 'VERSION_NAME', "\"" + versionName + "\"" }
-
Exception reported:
/Users/xxx/Projects/xxx/xxx/xxx/xxx/src/main/java/com/xxx/base/utils/Utils.java:86: 错误: 找不到符号 intent.putExtra(PAKAGENAME, BuildConfig.APPLICATION_ID); ^ 符号: 变量 APPLICATION_ID 位置: 类 BuildConfig
In the higher version of Gradle, in order to prevent confusion between the id and the id of the appication itself when used in the library
BuildConfig.APPLICATION_ID
, it needs to be changed to a new field:BuildConfig.LIBRARY_PACKAGE_NAME
-
Exception reported:
/Users/xxx/Projects/xxx/xxx/xxx/xxx/src/main/java/com/xxx/base/view/ShareScreenShotDialog.java:205: 错误: 找不到符号 shareInfo.setWeiboCopyWriter(String.format(mContext.getString(R.string.novel_share_intro_wb_empty), ^ 符号: 变量 novel_share_intro_wb_empty 位置: 类 string
The higher version of Gradle does not allow the default language configuration in the language configuration to be empty, so you need to
default string
add the part of the string that reported the error in -
Exception reported:
Execution failed for task ':live:transformClassesWithAjxForRelease'. > Cannot cast object 'com.android.build.gradle.internal.pipeline.TransformTask$2$1@6fe77eee' with class 'com.android.build.gradle.internal.pipeline.TransformTask$2$1' to class 'com.android.build.gradle.internal.pipeline.TransformTask'
This is because
aspectjx
the version is too low, just update the version and modify it in the root directorybuild.gradle
:classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
Data comparison:
rebuild | Modification method | modify xml | |
---|---|---|---|
Before Gradle update | 2m16s | 37s | 16s |
After Gradle update | 1m15s | 33s | 12s |
income | 44% reduction | 10% reduction | 25% reduction |
Convert Module source code to aar
As the business volume increases, the introduction of modules will also increase, and each module will take a certain amount of time to compile. Even if the new version of gradle does not compile unmodified and cached modules, it still needs to be cached. certain time. After converting the module into aar, it is no longer necessary to compile or cache every time, which can reduce part of the time
Module to aar optimization steps are as follows:
-
Build->Make module xxx
Generate aar for each modulebuild/output/aar
in -
Create a new module, name it randomly, copy the aar generated by other modules to the libs of the new module, and copy the aar packages under the libs of other modules to the libs of the new module (because the aar package will not contain its own dependencies Other aar packages), you can also not create a new module, directly put it under the libs of the main project
-
Delete other files and folders under the src of the new module, leaving only the main folder and
AndroidManifest.xml
files -
Delete
AndroidManifest.xm
the application in -
In the new module,
build.gradle
copy all the aar dependencies referenced by other modules, and rely on the aar made by several other modules at the same time -
Modify in the root directory
build.gradle
:flatDir { dirs 'libs',project(':aar的module').file('libs') }
The benefits of using a new module to store aar:
- Separated from the main project module, there is no need to copy aar to the libs of the main project, and there is no need to write dependencies in the dependencies of the main project module
- You can
setting.gradle
directly judge whether to choose aar compilation or source code compilation in - When a component is updated, you can compile it and directly replace the aar file, and update it immediately
extension:
You can make a global variable to control the use of component source code or aar, the operation steps are as follows:
-
setting.gradle
Added global switch in#是否修改组件代码 isModifyInComponent=false
-
Modify in build.gradle of the main project module:
def modifyInComponent = isModifyInComponent.toBoolean() flatDir { if (modifyInComponent) { dirs 'libs', project(':源码的module').file('libs'), }else { dirs 'libs',project(':aar的module').file('libs') } } if (modifyInComponent) { implementation project('源码的module') } else { implementation project('aar的module') }
-
Modify in
setting.gradle
:include ':app if (isModifyInComponent.toBoolean()) { include ':源码的module' } else { include ':aar的module' }
The best way is to build a private server maven warehouse to store aar, and the direct dependency is completed, which is more convenient and easy to manage. I will write the corresponding article later here
Data comparison:
rebuild | Modification method | modify xml | |
---|---|---|---|
Component source code | 1m15s | 33s | 12s |
Component aar | 50s | 28 p | 11s |
income | 33% reduction | 15% reduction | 8% reduction |
Customized tasks
During the build process, some tasks are performed to optimize the app. These tasks do not need to be executed during the development process, they only need to be executed during official packaging. These tasks can therefore be temporarily turned off to reduce execution time. You can introduce a global variable as a switch, and then make judgments where these task plugins are introduced, and execute them as needed.
The operation steps are as follows:
-
gradle.properties
Define a switch in#开启快速编译模式,快速编译舍弃了一些配置,可以较快编译执行app,适合开发调试阶段 isFastBuildMode=false
-
Make judgments in the main project module
build.gradle
, such as the following:def fastBuildMode = isFastBuildMode.toBoolean() if (fastBuildMode) { repositories { flatDir { dirs 'libs', project(':aar的module').file('libs') } } } else { apply plugin: 'org.greenrobot.greendao' apply plugin: 'walle' apply plugin: 'com.didiglobal.booster' apply plugin: 'android-aspectjx' repositories { flatDir { dirs 'libs', project(':源码的module').file('libs') } } greendao { schemaVersion 2 targetGenDir 'src/main/java' } walle { ... } aspectjx { exclude 'com.alipay', 'com.tencent', 'com.squareup.leakcanary' } } if (fastBuildMode) { ndk { abiFilters 'armeabi-v7a' } resConfigs "cn", "xhdpi" } else { ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' } }
Note: Turning on the fast compilation switch will close some tasks, so it may cause some unpredictable problems. If an exception occurs during the debugging process, you can turn off the fast compilation switch and try again. Remember to turn off the fast compilation switch when packaging the apk for release.
Data comparison:
rebuild | Modification method | modify xml | |
---|---|---|---|
turn off fast compilation | 50s | 28 p | 11s |
Enable fast compilation | 38s | 13s | 11s |
income | 24% reduction | 53% reduction | flat |
Maven proxy
As mentioned earlier, you can create a private server maven to store the generated aar. In fact, you can also use a private server maven to proxy the dependencies that need to be downloaded, put them in the internal network warehouse, and read them directly from the internal network when needed, without going to the remote maven. Warehouse reading, which is very helpful for some projects that use maven warehouses on the external network. If you don’t want to build them yourself, you can also use Alibaba Cloud’s mirrored maven warehouse. There are some commonly used warehouse mirrors in it. The construction method will be updated later article to illustrate
Use a remote shared build cache
The way to enable the cache was mentioned earlier, but that is only for the local cache. When compiling for the first time, the cache is empty, and it still takes a lot of time to compile. But in the company's development process, there are usually colleagues' machines or CI builds that have been compiled and built. Can we use their cache in some way? This solves the problem of too long compilation time for the first time. There are more solutions than difficulties. If you are interested in sharing the build cache, you can check this article: Gradle uses remote build cache
Summarize
Relatively safe and simple optimization scheme:
- Enable parallel compilation, on-demand build, build cache
- Enable annotation incremental compilation (below Gradle plugin 4.0)
- Turn on offline mode
- Use apply changes
- Modify JVM size
The optimization scheme that needs to be adapted:
- Upgrade gradle and its plugins
- Use the official muitidex
An extremely fast solution that is only suitable for debugging and developing:
- Module
- Customize execution tasks on demand
Further optimization:
-
Customize and write optimization plug-ins to improve cache hit rate, etc.
-
Private server maven mirror proxy
-
CI shared build cache
Total revenue for Mac systems:
rebuild | Modification method | modify xml | |
---|---|---|---|
before optimization | 4m46s | 46s | 22s |
Optimized | 38s | 13s | 11s |
income | 86% reduction | 46% reduction | 50% reduction |
Windows system verification
Configuration:
- Processor: i7-10510U 2.3GHz
- Memory: 8GB
- SSD
Total revenue:
rebuild | Modification method | modify xml | |
---|---|---|---|
before optimization | 10m30s | 2m33s | 12s |
Optimized | 1m9s | 12s | 9s |
income | 89% reduction | 92% reduction | 25% reduction |
The data may not be completely accurate, but the compilation speed is visible to the naked eye. After optimization, the compilation time can be greatly reduced. It’s not good to drink coffee at this time. If the funds are sufficient, update the computer configuration again, and the speed will take off directly. Less overtime depends on this optimization!