【问题标题】:App is compiled every time Gradle runs, taking significant time每次运行 Gradle 时都会编译应用程序,需要大量时间
【发布时间】:2015-11-24 21:28:45
【问题描述】:

在使用 Eclipse 进行开发时,如果我之前运行/调试过一个应用并且没有更改其源代码,那么再次运行/调试同一个应用会相当快。

但是,对于 Android Studio 和 Gralde,每次我尝试运行/调试应用程序时,gradle build 总是会运行,从而在应用程序启动时额外增加 15~45 秒的延迟(有时甚至会延迟) 4 岁的 HP i7 笔记本电脑上 70 秒)。

因此,问题是:有没有办法跳过 Android Studio 的 gradle 构建阶段,或者至少减少运行/调试所需的时间?


注意:我已经配置了gradle.properties如下:

org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configureondemand=true

编辑:我的 gradle 构建可能比大多数项目更复杂,因为它有 7 种不同的风格(将扩展到 ~20 种)和 3 种构建类型,并且还包含用于更改 APK 名称的 Groovy 代码(插入当前日期),并根据当前 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 生成,无需任何 gradde/代码修改:

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

当前的构建脚本可能不是性能最高的,因此有关如何跳过重建或加快重建的提示将不胜感激。


编辑 2: 我注意到大部分 gradle 构建时间都花在了:

  1. 编译[app]Java
  2. dex[应用]
  3. 包[应用]

尽管自上次构建以来没有任何变化,但这些步骤似乎仍在运行。


编辑 3: 原标题为“如何在使用 Android Studio 运行/调试时跳过 Gradle 构建”,改为更好地反映问题的症状和补救措施。

【问题讨论】:

  • 目前,每次运行应用都会重新运行 Gradle 是一个已知问题。但是,它应该为所有操作显示为 UP TO DATE 并且不应该构建任何东西,并且它不应该必须在您的设备上重新安装 APK。如果是这样,那么有趣的事情正在发生。 Gradle 的重新运行应该相当快(低个位数的秒数),因为它不应该做任何事情; 15-45秒是相当不正常的。你可以包括 Gradle 控制台输出吗?并包含您的构建脚本文件?
  • 您使用的是什么构建配置?你能发个 SS 吗
  • @ScottBarta 看来我的 gradle 构建会经历整个编译、dex、打包过程,关于如何跳过它的任何想法?谢谢
  • 可能你最好的跳过它是通过命令行手动安装和运行应用程序,并使用调试器附加到正在运行的进程。至于为什么您的构建需要这么长时间,您的所有自定义脚本都会干扰分析以查看哪些步骤是最新的。如果我不得不猜测,我会说您的 APK 重命名可能会搞砸。该代码在构建的评估阶段运行,似乎有意想不到的副作用。如果您可以将此作为构建后的任务来代替,它可能会更好。
  • 我不清楚为什么当你尝试运行一种口味时它会构建所有口味——也许是另一种无意的副作用。我建议使用 --debug 标志从命令行进行一些构建,以查看其执行阶段的输出。这将使您更深入地了解它为何运行特定任务。

标签: android android-studio android-gradle-plugin


【解决方案1】:

原因是我的愚蠢:

我将 BuildConfig 字段设置为当前时间(以毫秒为单位),这导致每次运行 Gradle 时生成的 BuildConfig.java 都不同,从而导致整个编译/索引/打包阶段运行。

编辑:

对我来说,问题是由执行类似这样的脚本引起的:

productFlavors {
    all {
        buildConfigField 'long', 'TIME_LIMIT', System.currentTimeMillis() + 'l'
    }
    ...
}

由于 System.currentTimeMillis() 每次都会不同,这意味着每次运行 Gradle 都会发现源代码发生了变化,从而触发了一系列动作。通过将脚本更改为:

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'
    }
    ...
}

上面的脚本意味着在同一天运行时产生完全相同的时间。因此,如果没有更改任何其他内容,则将重复使用先前生成的 APK,而无需重新编译。

【讨论】:

  • 你能给出你添加的文本吗
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-07
  • 1970-01-01
  • 1970-01-01
  • 2021-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多