Приложение скомпилируется каждый раз, когда выполняется Gradle, принимая значительное время
При разработке с Eclipse, если я запускал/отлаживал приложение до и не изменял его исходный код, достаточно быстро запустить/отладить одно и то же приложение.
Однако, при использовании Android Studio и Gralde, каждый раз, когда я пытаюсь запустить/отлаживать приложение, сборка gradle всегда будет выполняться, добавив дополнительную задержку в 15-45 секунд до момента запуска приложения (и иногда до 70 секунд на 4-летнем ноутбуке HP i7).
Поэтому возникает вопрос: есть ли способ пропустить фазу сборки Android Studio gradle или хотя бы сократить время, затрачиваемое на запуск/отладку?
Примечание. Я уже настроил gradle.properties следующим образом:
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configureondemand=true
Изменить: Моя сборка gradle, вероятно, более сложна, чем большинство проектов, так как она имеет 7 разных вкусов (будет расширяться до ~ 20) и 3 типа сборки, а также содержать Groovy код изменить имя APK (вставить текущую дату) и автоматически вставлять задачи, чтобы увеличить код версии и имя версии в зависимости от текущего типа buildType. Здесь полный build.gradle(измененный, чтобы скрыть имена клиентов):
import java.text.SimpleDateFormat
apply plugin: 'com.android.application'
def appendVersionNameVersionCode(applicationVariants) {
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null) {
def PREFIX = "My_APP_"
if (outputFile.name.endsWith('.apk') && !outputFile.name.startsWith(PREFIX)) {
def names = variant.baseName.split("-");
def apkName = PREFIX+names[0]+"_";
if(names[1].equals(android.buildTypes.debugEx.name)) {
apkName += 'debugEx_'
} else {
apkName += new SimpleDateFormat("YYYYMMdd").format(new Date())
}
if(variant.name.toLowerCase().contains(android.buildTypes.release.name)) {
if (outputFile.name.contains('unsigned')) {
apkName += "-unsigned"
} else {
apkName += "_SIGNED"
}
}
if (!variant.outputs.zipAlign) {
apkName += "-unaligned"
}
apkName += ".apk"
println outputFile.name+" --> " + apkName
output.outputFile = new File(outputFile.parent, apkName)
}
}
}
}
}
def retrieveVersionCode(variantName) {
def manifestFile = file("src/$variantName/AndroidManifest.xml")
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def manifestText = manifestFile.getText()
def matcher = pattern.matcher(manifestText)
matcher.find()
return Integer.parseInt(matcher.group(1))
}
def retrieveVersionName(variantName) {
def manifestFile = file("src/$variantName/AndroidManifest.xml")
def pattern = Pattern.compile(Pattern.quote("versionName=\"") + "(.*?)"+ Pattern.quote("\""))
def manifestText = manifestFile.getText()
def matcher = pattern.matcher(manifestText)
matcher.find()
return matcher.group(1)
}
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
lintOptions {
abortOnError false
absolutePaths false
lintConfig file("lint.xml")
}
defaultConfig {
applicationId "com.app.sportcam"
minSdkVersion 8
targetSdkVersion 21
}
if(project.hasProperty("app.signing")
&& new File(project.property("app.signing")+'.gradle').exists()) {
apply from: project.property("app.signing")+'.gradle';
} else {
println 'Warning, signing credential not found: ' + project.property("app.signing")+'.gradle'
}
buildTypes {
all {
buildConfigField 'String', 'IP', '"192.168.1.1"'
buildConfigField 'String', 'RTSP_IP', '"rtsp://"+IP+"/"'
//debugging
buildConfigField 'boolean', 'DEBUG_DETAILED', 'false'
buildConfigField 'boolean', 'DEBUG_UI_STATE', 'false'
buildConfigField 'boolean', 'INTERNAL_DEBUG', 'false'
buildConfigField 'boolean', 'ENABLE_VIEWSERVER', 'false'
buildConfigField 'boolean', 'INJECT_PTP_PROPERTIES', 'false'
//functional
buildConfigField 'boolean', 'ENABLE_TIME_LIMIT', 'false'
buildConfigField 'boolean', 'HIDE_ACTIONBAR_ON_LANDSCAPE', 'false'
buildConfigField 'boolean', 'ENABLE_VIDEO_DOWNLOAD', 'true'
buildConfigField 'boolean', 'ENABLE_VIDEO_DOWNLOAD_PROGRESS', 'true'
buildConfigField 'boolean', 'ENABLE_VIDEO_DOWNLOAD_CANCEL', 'false'
buildConfigField 'boolean', 'SET_TIME', 'true'
buildConfigField 'boolean', 'SHOULD_SET_CAMERA_MODE_WHEN_TURNING_RECORDING_OFF', 'false'
buildConfigField 'boolean', 'SHOULD_SET_CAMERA_MODE_ON_CONNECTION', 'false'
appendVersionNameVersionCode(applicationVariants)
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
//for customers' testing
debug {
buildConfigField 'boolean', 'ENABLE_TIME_LIMIT', 'true'
}
//for internal testing
debugEx {
buildConfigField 'boolean', 'DEBUG_DETAILED', 'true'
buildConfigField 'boolean', 'INTERNAL_DEBUG', 'true'
buildConfigField 'boolean', 'ENABLE_VIEWSERVER', 'true'
buildConfigField 'boolean', 'INJECT_TEST_PROPERTIES', 'true'
debuggable true
signingConfig signingConfigs.debug
applicationIdSuffix ".debug"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
def time=Calendar.getInstance()
time.add(Calendar.MONTH, 3)
println 'Debug build expiry date='+time.getTime()
productFlavors {
// default BuildConfig variables
all {
buildConfigField 'long', 'TIME_LIMIT', time.getTimeInMillis()+'l'
buildConfigField 'boolean', 'ADD_ABOUT', 'true'
buildConfigField 'boolean', 'FORCE_DEVICE_CHECK', 'false'
buildConfigField 'boolean', 'SHOW_CUR_SELECTION_PREF', 'true'
buildConfigField 'boolean', 'SHOW_CUR_SELECTION_ONSCREEN', 'false'
buildConfigField 'boolean', 'NO_WIFI_SCREEN', 'true'
buildConfigField 'boolean', 'NO_STREAMING', 'false'
buildConfigField 'boolean', 'NO_GALLERY', 'false'
buildConfigField 'boolean', 'INIT_IN_START', 'true'
buildConfigField 'boolean', 'CUSTOM_FUNCTIONS', 'true'
buildConfigField 'boolean', 'ENABLE_TIMEOUT_CONTINUE', 'false'
buildConfigField 'boolean', 'TRANSPARENT_BOTTOM_BAR', 'false'
buildConfigField 'int', 'LOGO_TIMING', '1000'
}
default {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0xFF'
buildConfigField 'boolean', 'ADD_ABOUT', 'false'
applicationId = 'com.app.default'
def variantName='DEFAULT'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
Customer_1 {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0x0B'
buildConfigField 'boolean', 'FORCE_DEVICE_CHECK', 'true'
applicationId 'com.app.c1'
def variantName='c1'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
Customer_2 {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0xFF' //TODO not final
buildConfigField 'boolean', 'SHOW_CUR_SELECTION_ONSCREEN', 'true'
applicationId 'com.app.c2'
def variantName='c2'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
Customer_3 {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0x12'
buildConfigField 'int', 'LOGO_TIMING', '3000'
applicationId = 'com.app.c3'
def variantName='c3'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
Customer_4 {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0x02'
applicationId = 'com.app.c4'
def variantName='c4'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
Customer_5 {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0x04'
applicationId = 'com.app.c5'
def variantName='c5'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
Customer_6 {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0xFF'
applicationId = 'com.app.c6'
def variantName='c6'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
Customer_7 {
//mandatory
buildConfigField 'int', 'CUSTOMER_NAME_PREFIX', '0x14'
buildConfigField 'boolean', 'FORCE_DEVICE_CHECK', 'true'
applicationId = 'com.app.c7'
def variantName='c7'
versionCode retrieveVersionCode(variantName)
versionName retrieveVersionName(variantName)
}
}
sourceSets{
main {
res.srcDirs = ['src/main/res']
}
default {
res.srcDir 'src/_Strings_/Standard'
res.srcDir 'src/_Strings_/xx'
}
Customer_1 {
res.srcDir 'src/_Strings_/Standard'
res.srcDir 'src/_Strings_/xx'
}
Customer_2 {
res.srcDir 'src/_Strings_/Standard'
res.srcDir 'src/_Strings_/xx'
}
Customer_3 {
res.srcDir 'src/_Strings_/Standard'
res.srcDir 'src/_Strings_/xx'
res.srcDir 'src/_Strings_/yy'
}
Customer_4 {
res.srcDir 'src/_Strings_/Standard'
res.srcDir 'src/_Strings_/xx'
}
Customer_5 {
res.srcDir 'src/_Strings_/xx'
res.srcDir 'src/_Strings_/zz'
}
Customer_6 {
res.srcDir 'src/_Strings_/xx'
res.srcDir 'src/_Strings_/aa'
}
Customer_7 {
res.srcDir 'src/_Strings_/Standard'
res.srcDir 'src/_Strings_/xx'
}
}
}
import java.util.regex.Pattern
def variantNameRegex = Pattern.quote("generate") + "(.*?)"+ Pattern.quote("BuildConfig")
Pattern patternVariantName = Pattern.compile(variantNameRegex);
tasks.whenTaskAdded { task ->
//TODO disables lint
if (task.name.startsWith("lint")) {
println 'Disables lint task: '+task.name
task.enabled = false
}
def m = patternVariantName.matcher(task.name)
if (m.find()) {
def variantName = m.group(1)
def isRelease=false
if (variantName.endsWith('Debug')) {
variantName = variantName.substring(0, variantName.lastIndexOf('Debug'))
} else if (variantName.endsWith('Release')) {
variantName = variantName.substring(0, variantName.lastIndexOf('Release'))
isRelease=true;
} else {
return
}
def taskIncVerCode="increaseVersionCode$variantName"
if(!project.hasProperty(taskIncVerCode)) {
project.task(taskIncVerCode) << {
def manifestFile = file("src/$variantName/AndroidManifest.xml")
def pattern = Pattern.compile("versionCode=\"(\\d+)\"")
def manifestText = manifestFile.getText()
def matcher = pattern.matcher(manifestText)
matcher.find()
def versionCode = Integer.parseInt(matcher.group(1))
def manifestContent = matcher.replaceAll("versionCode=\"" + ++versionCode + "\"")
manifestFile.write(manifestContent)
}
}
task.dependsOn taskIncVerCode
if(isRelease) {
def taskIncVerName="increaseVersionName$variantName"
if(!project.hasProperty(taskIncVerName)) {
project.task(taskIncVerName) << {
def manifestFile = file("src/$variantName/AndroidManifest.xml")
def patternVersionNumber = Pattern.compile("versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\"")
def manifestText = manifestFile.getText()
def matcherVersionNumber = patternVersionNumber.matcher(manifestText)
matcherVersionNumber.find()
def majorVersion = Integer.parseInt(matcherVersionNumber.group(1))
def minorVersion = Integer.parseInt(matcherVersionNumber.group(2))
def pointVersion = Integer.parseInt(matcherVersionNumber.group(3))
def mNextVersionName = majorVersion + "." + minorVersion + "." + (pointVersion + 1)
def manifestContent = matcherVersionNumber.replaceAll("versionName=\"" + mNextVersionName + "\"")
manifestFile.write(manifestContent)
}
}
task.dependsOn taskIncVerName
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:support-v4:21.0.0'
compile files('libs/eventbus.jar')
compile files('libs/libGoogleAnalyticsServices.jar')
compile files('libs/trove-3.0.3.jar')
}
Здесь вывод консоли gradle, сгенерированный при выполнении Run дважды без изменений gralde/code:
Executing tasks: [:ptp_app_base:assembleCustomer_6DebugEx]
Parallel execution with configuration on demand is an incubating feature.
Debug build expiry date=Mon Mar 16 10:39:02 CST 2015
Disables lint task: lintVitalCustomer_1Release
Disables lint task: lintVitalCustomer_2Release
Disables lint task: lintVitalDefaultRelease
Disables lint task: lintVitalCustomer_3Release
Disables lint task: lintVitalCustomer_4Release
Disables lint task: lintVitalCustomer_5Release
Disables lint task: lintVitalCustomer_6Release
Disables lint task: lintVitalCustomer_7Release
Disables lint task: lint
Disables lint task: lintCustomer_1DebugEx
Disables lint task: lintCustomer_1Debug
Disables lint task: lintCustomer_1Release
Disables lint task: lintCustomer_2DebugEx
Disables lint task: lintCustomer_2Debug
Disables lint task: lintCustomer_2Release
Disables lint task: lintDefaultDebugEx
Disables lint task: lintDefaultDebug
Disables lint task: lintDefaultRelease
Disables lint task: lintCustomer_3DebugEx
Disables lint task: lintCustomer_3Debug
Disables lint task: lintCustomer_3Release
Disables lint task: lintCustomer_4DebugEx
Disables lint task: lintCustomer_4Debug
Disables lint task: lintCustomer_4Release
Disables lint task: lintCustomer_5DebugEx
Disables lint task: lintCustomer_5Debug
Disables lint task: lintCustomer_5Release
Disables lint task: lintCustomer_6DebugEx
Disables lint task: lintCustomer_6Debug
Disables lint task: lintCustomer_6Release
Disables lint task: lintCustomer_7DebugEx
Disables lint task: lintCustomer_7Debug
Disables lint task: lintCustomer_7Release
ptp_app_base-Customer_1-debugEx.apk --> MY_APP_Customer_1_debugEx_.apk
ptp_app_base-Customer_1-debug.apk --> MY_APP_Customer_1_20141216.apk
ptp_app_base-Customer_1-release.apk --> MY_APP_Customer_1_20141216_SIGNED.apk
ptp_app_base-Customer_2-debugEx.apk --> MY_APP_Customer_2_debugEx_.apk
ptp_app_base-Customer_2-debug.apk --> MY_APP_Customer_2_20141216.apk
ptp_app_base-Customer_2-release.apk --> MY_APP_Customer_2_20141216_SIGNED.apk
ptp_app_base-default-debugEx.apk --> MY_APP_default_debugEx_.apk
ptp_app_base-default-debug.apk --> MY_APP_default_20141216.apk
ptp_app_base-default-release.apk --> MY_APP_default_20141216_SIGNED.apk
ptp_app_base-Customer_3-debugEx.apk --> MY_APP_Customer_3_debugEx_.apk
ptp_app_base-Customer_3-debug.apk --> MY_APP_Customer_3_20141216.apk
ptp_app_base-Customer_3-release.apk --> MY_APP_Customer_3_20141216_SIGNED.apk
ptp_app_base-Customer_4-debugEx.apk --> MY_APP_Customer_4_debugEx_.apk
ptp_app_base-Customer_4-debug.apk --> MY_APP_Customer_4_20141216.apk
ptp_app_base-Customer_4-release.apk --> MY_APP_Customer_4_20141216_SIGNED.apk
ptp_app_base-i3-debugEx.apk --> MY_APP_i3_debugEx_.apk
ptp_app_base-i3-debug.apk --> MY_APP_i3_20141216.apk
ptp_app_base-i3-release.apk --> MY_APP_i3_20141216_SIGNED.apk
ptp_app_base-i5-debugEx.apk --> MY_APP_i5_debugEx_.apk
ptp_app_base-i5-debug.apk --> MY_APP_i5_20141216.apk
ptp_app_base-i5-release.apk --> MY_APP_i5_20141216_SIGNED.apk
ptp_app_base-Customer_7-debugEx.apk --> MY_APP_Customer_7_debugEx_.apk
ptp_app_base-Customer_7-debug.apk --> MY_APP_Customer_7_20141216.apk
ptp_app_base-Customer_7-release.apk --> MY_APP_Customer_7_20141216_SIGNED.apk
:ptp_app_base:preBuild
:ptp_app_base:compileCustomer_6DebugExNdk UP-TO-DATE
:ptp_app_base:preCustomer_6DebugExBuild
:ptp_app_base:checkCustomer_6DebugExManifest
:ptp_app_base:preCustomer_4DebugBuild
:ptp_app_base:preCustomer_4DebugExBuild
:ptp_app_base:preCustomer_4ReleaseBuild
:ptp_app_base:preCustomer_5DebugBuild
:ptp_app_base:preCustomer_5DebugExBuild
:ptp_app_base:preCustomer_5ReleaseBuild
:ptp_app_base:preCustomer_6DebugBuild
:ptp_app_base:preCustomer_6ReleaseBuild
:ptp_app_base:preDefaultDebugBuild
:ptp_app_base:preDefaultDebugExBuild
:ptp_app_base:preDefaultReleaseBuild
:ptp_app_base:preCustomer_3DebugBuild
:ptp_app_base:preCustomer_3DebugExBuild
:ptp_app_base:preCustomer_3ReleaseBuild
:ptp_app_base:preCustomer_7DebugBuild
:ptp_app_base:preCustomer_7DebugExBuild
:ptp_app_base:preCustomer_7ReleaseBuild
:ptp_app_base:preCustomer_1DebugBuild
:ptp_app_base:preCustomer_1DebugExBuild
:ptp_app_base:preCustomer_1ReleaseBuild
:ptp_app_base:preCustomer_2DebugBuild
:ptp_app_base:preCustomer_2DebugExBuild
:ptp_app_base:preCustomer_2ReleaseBuild
:ptp_app_base:prepareComAndroidSupportSupportV42100Library UP-TO-DATE
:ptp_app_base:prepareCustomer_6DebugExDependencies
:ptp_app_base:compileCustomer_6DebugExAidl UP-TO-DATE
:ptp_app_base:compileCustomer_6DebugExRenderscript UP-TO-DATE
:ptp_app_base:generateCustomer_6DebugExBuildConfig
:ptp_app_base:generateCustomer_6DebugExAssets UP-TO-DATE
:ptp_app_base:mergeCustomer_6DebugExAssets UP-TO-DATE
:ptp_app_base:generateCustomer_6DebugExResValues UP-TO-DATE
:ptp_app_base:generateCustomer_6DebugExResources UP-TO-DATE
:ptp_app_base:mergeCustomer_6DebugExResources UP-TO-DATE
:ptp_app_base:processCustomer_6DebugExManifest UP-TO-DATE
:ptp_app_base:processCustomer_6DebugExResources UP-TO-DATE
:ptp_app_base:generateCustomer_6DebugExSources
:ptp_app_base:compileCustomer_6DebugExJava
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
:ptp_app_base:preDexCustomer_6DebugEx UP-TO-DATE
:ptp_app_base:dexCustomer_6DebugEx
:ptp_app_base:processCustomer_6DebugExJavaRes UP-TO-DATE
:ptp_app_base:validateDebugSigning
:ptp_app_base:packageCustomer_6DebugEx
:ptp_app_base:zipalignCustomer_6DebugEx
:ptp_app_base:assembleCustomer_6DebugEx
BUILD SUCCESSFUL
Total time: 30.303 secs
Текущая сборка script, вероятно, не самая результативная, поэтому будут рассмотрены как советы о том, как пропустить пересоздание, так и ускорить перестройку.
Изменить 2: Я заметил, что большая часть времени сборки gradle расходуется на:
- скомпилировать [app] Java
- Dex [приложение]
- пакет [приложение]
Эти шаги появляются, несмотря на то, что с момента последней сборки ничего не изменилось.
Изменить 3: Оригинальное название: "Как пропустить gradle сборку при запуске/отладке с помощью Android Studio", измененный, чтобы лучше отражать симптом проблемы и средства защиты.
Ответы
Ответ 1
Причиной является глупость с моей стороны:
Я установил поле BuildConfig
как текущее время в миллисекундах, что привело к тому, что результирующий BuildConfig.java
был разным при каждом запуске Gradle, в результате чего выполнялись все фазы компиляции/дешировки/упаковки.
Edit:
Для меня проблема была вызвана выполнением script, аналогичной этому:
productFlavors {
all {
buildConfigField 'long', 'TIME_LIMIT', System.currentTimeMillis() + 'l'
}
...
}
Так как System.currentTimeMillis() будет отличаться каждый раз, это означает, что каждый раз, когда выполняется Gradle, он обнаруживает, что исходный код изменился, поэтому инициирует каскад действий. Он решается путем изменения параметра script на:
def time = Calendar.getInstance()
time.set(Calendar.HOUR, 1);
time.set(Calendar.MINUTE, 1);
time.set(Calendar.SECOND, 1);
time.set(Calendar.MILLISECOND, 1);
productFlavors {
// default BuildConfig variables
all {
buildConfigField 'long', 'TIME_LIMIT', time.getTimeInMillis() + 'l'
}
...
}
Приведенный выше script означает, что точно такое же время создается при запуске в тот же день. поэтому, если ничего не было изменено, предыдущий сгенерированный APK будет повторно использован без необходимости перекомпиляции.