Android Gradle的理解以及升级Gradle插件3.0遇到的坑

3,484 阅读8分钟
原文链接: www.jianshu.com

以下内容均为个人理解,如果有描述不正确的地方,欢迎指正 转载请注明原文链接

Gradle简介

开始填坑之前,先简单聊聊gradle,Gradle其实就是一个构建项目的工具,也就是把那一个个文件、文件夹按照一定的规则关联起来,形成一个项目的工具,它其实不仅仅是用在AndroidStudio上。

我们在AS中用到的Gradle其实应该被叫做 Android Gradle Plugin,也就是安卓项目上的gradle插件;
Gradle插件会有版本号,每个版本号又对应有一个或一些 Gradle发行版本(一般是限定一个最低版本),也就是我们常见的类似gradle-3.1-all.zip这种东西;

如果这两个版本对应不上了,那你的工程构建的时候就会报错。
对应关系如下(参考自 developer.android.google.cn):

插件版本 Gradle版本
1.0.0 - 1.1.3 2.2.1 - 2.3
1.2.0 - 1.3.1 2.2.1 - 2.9
1.5.0 2.2.1 - 2.13
2.0.0 - 2.1.2 2.10 - 2.13
2.1.3 - 2.2.3 2.14.1+
2.3.0+ 3.3+
3.0.0+ 4.1+

Android Studio 3.0 之后自动将插件版本升级到3.0.0,所以我们也需要对应地把Gradle升级到4.1才行

另外, Android Gradle Plugin又会跟 Android SDK BuildTool有关联,因为它还承接着AndroidStudio里的编译相关的功能,这也是我们要在项目的 local.properties 文件里写明Android SDK路径、在build.gradle 里注明 buildToolsVersion 的原因。

所以 Android Gradle Plugin 本质上就是 一个AS的插件,它一边调用 Gradle本身的代码和批处理工具来构建项目,一边调用Android SDK的编译、打包功能,从而让我们能够顺畅地在AS上进行开发。

升级Android Gradle Plugin到3.0.1的踩坑之旅

  1. 把工程目录下的build.gradle中,将gradle插件版本升级到3.0.1

     dependencies {
         classpath 'com.android.tools.build:gradle:3.0.1' //这里从2.2.2改到了3.0.1
     }
    
  2. 点击同步gradle,报错。
    提示gradle-wrapper版本过低,gradle-wrapper的作用,就是设置你的项目工程要对应用那个Gradle发行版本来执行构建,
    3.0.1的插件版本必须对应4.1以上的gradle版本,因此需要在../gradle/wrapper/gradle-wrapper.properties文件中把版本号改为4.1

     distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip  //这里从 3.1 改到了 4.1
    

    然后同步gradle。
    通常情况下你会发现速度特别慢,因为没有翻墙,默认是从国外服务器下载gradle;
    这时候就可以强关AS,直接在网上找资源,把gradle-4.1-all.zip这个包,放在

    C:\Users\你的用户名.gradle\wrapper\dists\gradle-4.1-all\bzyivzo6n839fup2jbap0tjew

    目录中(最后那个乱码文件夹名字每台机器上不一样),注意不需要手动解压,然后重启AS自动同步gradle即可;

  3. 重启自动同步之后,又报错。
    提示有一些依赖库无法正常引用,需要添加google maven 仓库的依赖
    在工程目录下的build.gradle 文件加上maven依赖即可,

     repositories {
         mavenCentral()
         jcenter()
    
         //加上下面这段
         maven {
             url 'https://maven.google.com/'
             name 'Google'
         }
     }
    
  4. 再同步gradle,又报错。
    这次是提示没有26.0.2版本的 SDK buildTool ,然后直接在AS报错弹框里点击下载就可以了。现在SDK可以不用翻墙直接下载,速度还蛮快的。下载确认解压之后再次同步gradle。

  5. 同步gradle,此时又会报错:

     Error:The specified Android SDK Build Tools version (25.0.0) is ignored, 
     as it is below the minimum supported version (26.0.2) for Android Gradle Plugin 3.0.1.
     Android SDK Build Tools 26.0.2 will be used.
     To suppress this warning, remove "buildToolsVersion '25.0.0'" from your build.gradle file, 
     as each version of the Android Gradle Plugin now has a default version of the build tools.
    

    这个提示,说是需要去掉各个module的build.gradle中的 buildToolsVersion 的设置,因为3.0.1以上的gradle插件会自动用一个默认的BuildTool版本,不需要像以前一样,在每个build.gradle里写明buildToolsVersion了。
    所以我们把提示到的各个build.gradle中的这行删掉,再重新同步一下gradle,就不会报这个错了。

  6. 再次同步gradle,继续报错:

    Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html
    

    需要在app/build.gradle加上默认的dimension;大概就是为了保证各个渠道包要保持某些属性的一致;总之按照官网上的说法,只要给每个渠道都设置一个“flavorDimensions”就可以了,代码如下(app/build.gradle)

         flavorDimensions "default"//这个名字貌似随便取,也可以有多个,总之一定要有..
         productFlavors {
             market {
                 dimension "default"
             }
             //        other{
             //          dimension "default"  //如果有其他的渠道,也要做类似的声明
             //        }
         }
    
  7. 继续同步,继续报错...
    这次的错误提示比较接地气了,说是build/intermediates/xxxx.xml 里的某个值没有找到,这个简单,build目录下的都是编译期生成的文件,clean下再来一发;或者直接rebuild项目(rebuild = clean + build)

  8. rebuild项目,仍然报错

     Error:Execution failed for task ':framework:third:xxxModule:javaPreCompileDebug'.
     Annotation processors must be explicitly declared now.  
     The following dependencies on the compile classpath are found to contain annotation processor.  
     Please add them to the annotationProcessor configuration.
    xxxxxx.jar (com.xxxx:xxxxx:1.0.5)
     Alternatively, set android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true to continue with previous behavior.  
     Note that this option is deprecated and will be removed in the future.
     See https://developer.android.com/r/tools/annotation-processor-error-message.html for more details.
    

    一时半会儿不知道怎么搞,看看这个开发者文档链接的说明吧,
    developer.android.com这个域名不翻墙是进不去的,不过现在已经有了国内的域名,把域名替换成developer.android.google.cn,后面保持不变就可以访问了;其他类似的官方文档地址也一样可以用这个方法去访问。

    看了下官方文档,大意是在说,工程里的某个module依赖了某个jar包,然后jar里面又用到了注解,在新的gradle版本里,需要写新的groovy代码来对每个引用注解的地方单独配置。在以往的版本中,gradle会默认给每个module都依赖一个annotationProcess,导致很多多余的对annotationProcess的依赖,老版本中的默认方法,会在将来版本中被删除。

    按照提示,解决方案大致有两个:
    要么我们需要在依赖于注解的module中,加上“annotationProcessor”这个配置;
    要么我们可以设置android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true,但是要注意这玩法以后会被删除

    按照官方推荐的第一种方法:应该是在报错的build.gradle中修改:

     dependencies {
         compile xxxxx
         ...
         //加上类似这些对于注解处理器的的依赖
         annotationProcessor 'com.xxxx.xxxxx-1.0.0'
     }
    

    但是我加上了并没有什么作用,原因待查.. 为了节约时间,还是先用includeCompileClasspath=true的办法凑合下吧,以后真的被删除了再说...

    直接在app/build.gradle(准确的说是每个涉及到注解依赖的module的build.gradle)上加一行

     defaultConfig {
         minSdkVersion rootProject.ext.android.minSdkVersion
         targetSdkVersion rootProject.ext.android.targetSdkVersion
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
         javaCompileOptions {
             annotationProcessorOptions {
                 includeCompileClasspath = true    //加上这行即可
             }
         }
     }
    

    然后再次同步gradle

  9. 同步gradle,不出意外的又报错了。 一堆的style属性未找到的问题,跟第六步里的现象貌似是一样的

     Error:(713, 5) error: style attribute '@android:attr/windowExitAnimation' not found.
     Error:(713, 5) error: style attribute '@android:attr/windowExitAnimation' not found.
     Error:(713, 5) error: style attribute '@android:attr/windowExitAnimation' not found.       
    

    这才发现其实clean是没有用的,真正的问题原因在一堆错误的最后几行

     Error:java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
     Error:java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for 
     com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details
    

    AAPT2 , 貌似就是aapt的2.0版本?
    aapt.exe 是 Android SDK里的一个工具,详情出门左转自己查去..
    直接说解决方案:
    在Project/gradle.properties中添加 android.enableAapt2=false
    再次同步...

  10. 嗯,没错,又报错了。

    Error:(247, 1) Execution failed for task ':app:processMarketDebugManifest'.
    Manifest Tasks does not support the manifestOutputFile property any more, please use the manifestOutputDirectory instead.
    For more information, please check https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html
    

    这个错跟7号有点类似,又是新版本gradle插件不支持某些方法啦,又要换用新的写法才行啦...然后最后给你贴一个文档地址自己看去。。
    大概就是说:现在不支持manifestOutputFile这个方法,要用processManifest.manifestOutputDirectory()来替换

    也就是你app/build.gradle 打包的这一段代码要重新写一下;一般我们都会在build.gradle中编写这样的代码,来实现对Manifest文件的修改、以及自定义apk的输出文件名等。

     android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.processManifest.doLast {
                ...
                def manifestFile = output.processManifest.manifestOutputFile;   //这里被废弃导致报错
                def apkFileName = "_myapp_${android.defaultConfig.versionCode}_${formatedDate}.apk";
                manifestFile.write(updatedContent, 'UTF-8')
                ...
            }
        }
    }
    
  11. 改了之后还是报错,还是这里,连续各种错

    比如

        Error:(250, 1) Execution failed for task ':app:processMarketDebugManifest'.
        No signature of method: java.lang.String.getText() is applicable for argument types: (java.lang.String) values: [UTF-8]
        Possible solutions: getAt(java.lang.String), getAt(groovy.lang.IntRange), getAt(groovy.lang.Range), getAt(int), getAt(java.util.Collection), getAt(groovy.lang.EmptyRange)
    

    比如

        Error:(251, 1) Execution failed for task ':app:processMarketDebugManifest'.
        No signature of method: java.lang.String.write() is applicable for argument types: (java.lang.String, java.lang.String) values: [H:\GitWorkSpace\DouyutvClient\app\build\intermediates\manifests\full\market\debug/AndroidManifest.xml, ...]
        Possible solutions: wait(), trim(), size(), size(), toSet(), wait(long)
    

    比如

        Error:(258, 1) Execution failed for task ':app:processMarketDebugManifest'.
        Cannot set the value of read-only property 'outputFile' for ApkVariantOutputImpl_Decorated{apkData=Main{type=MAIN, fullName=marketDebug, filters=[]}} of type com.android.build.gradle.internal.api.ApkVariantOutputImpl.
    

    反正就是各种各样的groovy语法报错,然而并不懂groovy语法,现学现卖改一改
    我们工程里是既有动态修改manifest文件的需求,也有自定义apk名字的功能,
    包括自动修改apk名称的代码也有报错,也要改,最终改成了这样

    android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.processManifest.doLast {
                def umengKey = "11111111"
                //取得manifest路径
                String manifestPath = "$manifestOutputDirectory/AndroidManifest.xml"
                //取得manifest的文本内容
                def manifestContent = file(manifestPath).getText('UTF-8')
                //替换UMENG_APPKEY的文本
                manifestContent = manifestContent.replaceAll("\\{\\{UMENG_APPKEY\\}\\}", umengKey)
                //重新把内容写进.xml文件里
                file(manifestPath).write(manifestContent, 'UTF-8')
            }
        }
        variant.outputs.all {
            //定义apk名字
            def formatedDate = new Date().format("yyyyMMddHHmm")
            def apkFileName = "_myapp_${android.defaultConfig.versionCode}_${formatedDate}.apk";
            outputFileName = apkFileName
        }
    }
    
  12. 至此再次同步gradle,总算是没有再报错了,AS上可以正常运行代码了。
    我们的全部修改都只涉及到gradle的配置代码,所以不会对项目里的业务逻辑产生任何影响。
    这时候再打个包,验证下我们的最后一段打包相关的gradle脚本是否正常运行.打包成功了,也就全部OK了。

参考文献

谷歌爸爸的文档:
developer.android.google.cn/studio/buil…

最后

附一张图

折腾一下午之后的我.gif