Приложение скомпилируется каждый раз, когда выполняется 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 будет повторно использован без необходимости перекомпиляции.