“替你”总结的Gradle配置

作者: BeWinner | 来源:发表于2016-07-28 00:06 被阅读8786次

    阅读说明

    参考链接:https://developer.android.com/studio/build/index.html
    本片文章的内容全部参考自上面的链接,其中有些内容是直接翻译的,有些内容是结合自己的经验总结的,可能有理解错误的地方,非常希望大家能指正出来,在交流中进步。

    Gradle 编译过程

    编译流程图

    上图展示了一个典型的 App 编译过程,主要分为以几步:

    1. 编译器将源代码(包括依赖库)转化为 DEX 文件,编译资源文件(res 以及 assets 文件下的资源)。
    2. APK Packager 整合所有的 DEX 文件和编译过的资源文件,并且对 APK 进行签名。
    3. 签名文件必须使用 Debug 版或者 Release 版,使用 Debug Keystore 生成的 app 被用来测试和分析,使用 Release Keystore 生成的 app 可以进行发布供其他用户使用。
    4. 在生成最终的 APK 之前,APK Packager 会使用 zipalign 工具优化整个 app ,以便 app 在使用的过程中更加节省内存。

    自定义编译配置

    Android Studio 的 gradle 插件方便我们在以下几个方面配置我们的编译选项:

    Build Types - 编译类型


    编译类型,包括我们最熟悉的 release 和 debug 两种类型,我们可以根据这两种类型定义出更多的类型。配置对应的 build.gradle 文件在 moudle 下,需要添加新的或者修改 Build Type ,只需要在 android{ ... }里面操作。一个示例如下:

    android {
        ...
        defaultConfig {...}
        buildTypes {
            release {
                //开启混淆
                minifyEnabled true
                //混淆规则文件
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
    
            debug {
                //apk的后缀
                applicationIdSuffix ".debug"
            }
            
            //debug的一个扩展
            jnidebug {
                // 复制debug的属性和签名配置
                initWith debug
                applicationIdSuffix ".jnidebug"
                //开启Jni调试
                jniDebuggable true
            }
        }
    }
    

    其中 initWith 可以方便我们继承其他的配置,只需要添加需要的部分。

    Product Flavors - 构建不同版本


    配置 apk 的版本信息,可以为每一个版本指定不同的 applicationId 和版本名称。关于 applicationId ,可以把它也理解为包名,不过和 Manifest 文件中的包名作用不同,它是用来给应用商店和设备区分不同的 app ,而 Manifest 中的 pakage 属性用来在源代码中引用 R 类和其他类。即同一份代码 applicationId 可以让它变成不同的 app 。示例配置如下:

    android {
        ...
        defaultConfig {...}
        buildTypes {...}
        productFlavors {
            demo {
                applicationId "com.example.myapp.demo"
                versionName "1.0-demo"
            }
            full {
                applicationId "com.example.myapp.full"
                versionName "1.0-full"
            }
        }
    }
    

    通过上面的配置之后,如果 buildTypes 里面配置了两个编译类型,假如是 debug 和 release ,将会产生四个 apk 文件,每一种 buildType 都会和每种 flavor 进行组合拼接,进而产生不同的变种版本(Build Variant),上面对应的四个不同的变种版本分别是:demoDebug、demoRelease、fullDebug、fullRelease。

    Mutiple Manifest Files - 合并多个清单文件


    配置多个 Manifest 文件。经常会在项目中依赖其他项目,这个时候就会有多个 Manifest 文件,那在编译的时候该如何处理呢?这个时候需要进行合并,而且还必须有一套相应的合并规则解决和避免合并冲突。对于不同的 Manifest 文件中同一个属性的不同值,在合并的时候还需要优先级来进行判断,用高优先级的去覆盖低优先级的。关于优先级定义如下:

    • 最高优先级:buildType 的设置
    • 次高优先级:productFlavor 的设置
    • 中等优先级:在 src/main 目录下的 Manifest 文件
    • 最低优先级:各种依赖和第三方库的设置

    合并规则:概括来说是这样:

    1. 合并之前,先将每个 module 里面的 buildType 内容写到 Manifest 里面去,比如你在 buildType 里面的 minSdkVersion 和targetSdkVersion 以及 versionCode 和 VersionName 等等(此时合并后的 Manifest 文件可以在 app/intermediates/manifests/* 目录下查看)。
    2. 对于同一个属性,当高优先级和低优先级都为非默认值时,如果可以匹配,那直接合并,不能匹配,就会产生冲突(这种是针对两个不同的 module 来说),下面会专门给出例子。
    3. 不管高优先级还是低优先级,如果其中一个没有设置该属性或者设置为默认的属性值,而另外一个设置了非默认的属性值,则合并的结果就是非默认的属性值,在项目编译后,可以查看 Manifest 的合并记录,该文件目录为:app/intermediates/outputs/logs/manifest*.txt。

    示例:现在给出一些例子说明上述规则,我的主 module 名为 app ,新建一个依赖的 module 叫 uisdk ,现在分别给出两个 module 的 build.gradle 文件:
    app/build.gradle

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 24
        buildToolsVersion "24.0.0"
    
        defaultConfig {
            applicationId "com.example.rth.study"
            minSdkVersion 15
            targetSdkVersion 24
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        productFlavors {
            demo {
                minSdkVersion 7
                applicationId "com.rth.app"
            }
        }
    }
    
    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:24.0.0'
        compile project(':uisdk')
    }
    

    uisdk/build.gradle

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion 24
        buildToolsVersion "24.0.0"
    
        defaultConfig {
            minSdkVersion 8
            targetSdkVersion 24
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:24.0.0'
    }
    

    在 app/build.gradle 里面,defaultConfig 的 minSdkVersion 为15,但我在变种版本(productFlavors 里的 demo)里设置的 minSdkVersion 为7,最终 app 的 Manifest 的 minSdkVersion 就为7,再看 uisdk 里面的 build.gradle ,minSdkVersion 为8,就是说 app 这个 module 和 uisdk 这个 module library 在同一个属性上使用了不同的非默认值,而且 library 的 Manifest 属于最低优先级,它设置的值又比优先级比它高的值还要高,就会出错,出错信息的描述也很清晰:

    Error:Execution failed for task ':app:processDemoDebugManifest'.
    Manifest merger failed : uses-sdk:minSdkVersion 7 cannot be smaller than version 8 declared in library 
    ...
    Suggestion: use tools:overrideLibrary="com.example.uisdk" to force usage
    

    根据错误信息,我们有两种方式解决这个问题:

    1. 把 app 里面的值调高,或者把 uisdk 里面的值调低。
    2. 就像上面建议的那样,使用 overrideLibrary 这个标签。该标签的作用在名字上已经体现出来了,就是直接覆盖 library 里面的设置,现在我们在 app/src/main/Manifest 里面加上这么一句:
    <uses-sdk tools:overrideLibrary="com.example.uisdk"/>
    

    就能编译通过了,这适用于比较特殊的情况,就是在依赖库里可能要适用一些新特性,这些特性在 app 的 minSdkVersion 下不能使用,而且 app 的 minSdkVersion 已经不能更改了。

    标记选择器(Marker Selectors) :选择器的功能可以让一些属性在某些 libary 里面无效,比如就拿上面的例子来说,我想让 uisdk 只处理 ui 上的东西,不想让他具有网络访问的功能,那么我可以这么设置:

    <uses-permission android:name="android.permission.INTERNET"
        tools:node="remove"
        tools:selector="com.example.uisdk"
        />
    

    其中 tools:node 标签表示删除该权限,tools:selector 标签选择在哪个依赖库里执行 tools:node 表示的动作。

    可以看出这些配置还是挺灵活的。

    Configure dependencies - 配置依赖


    这个应该是最熟悉的了,项目中经常要依赖第三方库,一个典型了例子如下:

    
    android {...}
    ...
    dependencies {
        //将本地 module library 编译到项目中
        compile project(":mylibrary")
        //编译远程依赖
        compile 'com.android.support:appcompat-v7:23.4.0'
        //编译本地 jar 包
        compile fileTree(dir: 'libs', include: ['*.jar'])
    }
    

    上面主要用到的方式是 compile ,gradle 支持6种编译方式:

    • compile:对所有 buildType 以及 flavors 进行编译并打包到 apk 。
    • provided:和 compile 相似,但只在编译时使用,几只参与编译,不打包到最终 apk 。
    • apk:只会打包到 apk 中,不参与编译,所以不能在项目代码中使用相应库中的方法。
    • test compile:相比于 compile ,仅仅针对单元测试的代码编译打包。
    • debug compile:仅针对 debug 模式编译打包。
    • release compile:仅针对 release 模式编译打包。

    另外在进行 sdk 开发时,一般为了减小 sdk 体积,一些依赖库会用 provided 的方式,同时需要注意的是,对于远程依赖,compile 和 provided 的效果一样,都不会打包到 jar 包或者 arr 包中,但对于本地的 jar 包或者 arr 包的依赖,compile 和 provided 就有区别了。

    Configure Sigining - 配置签名


    在用 gradle 配置 release 版本的签名信息时,需要下面三个步骤:

    1. 生成一个 keystore ,一个二进制文件保存一些私钥,这个必须好好保存。
    2. 生成一个私钥,用于开发者或者公司与这个 app 建立对应关系。
    3. 将生成的信息配置到 moudle 层的 build.gradle 里。
      示例如下:
    android {
        ...
        defaultConfig {...}
        signingConfigs {
            release {
                storeFile file("myreleasekey.keystore")
                storePassword "password"
                keyAlias "MyReleaseKey"
                keyPassword "password"
            }
        }
        buildTypes {
            release {
                ...
                signingConfig signingConfigs.release
            }
        }
    }
    

    上面的配置中直接显示了一些敏感信息,比如各种密码,一种更加安全的方式是通过环境变量的方式获取:

    storePassword System.getenv("KSTOREWD");
    keyPassword System.getenv("KEYPWD");
    

    或者如果使用命令行的方式编译,还可以让用户在命令行输入密码:

    storePassword System.console().readLine("\nKeystore password: ")
    keyPassword System.console().readLine("\nKey password: ")
    

    暂时就总结到这么多了,再次说明,如果发现理解错的地方欢迎指正!!!

    相关文章

      网友评论

      • Robots:您好, 请问怎么解决两个module中资源的使用问题, Amodule 和Bmodule的包名一致, 使用compile方式构建的时候会出来: A library uses the same package as this project这个错误, 参考了一些网上的办法修改为provided方式构建, 结果在Rebuild的时候会报如下错误,信息如下: No resource found that matches the given name: attr 'XXX'; 原因是我在Amodule中使用了Bmodule中的资源文件(attr.xml中的自定义属性), A和Bmodule是完全一样的包名,比如都是com.dkl.android.setting, 请问怎么解决这个问题 :blush: ,求分享 :heart_eyes:
        BeWinner:@Robots 我的意思是如果A项目和B项目都叫mian,那不应该是一个项目么,如果一个项目依赖另外一个,最好不要让包名重复,还有看看setting.gradle是怎么写的,module.gradle是compile project:":main"的话,那么setting.builde肯定要有include ':main',不过如你所说,两个项目都叫main的话,那setting.gradle该怎么写呢,难道是include ':main','main'?,可以先检查下setting.gradle,还有两个项目必须报名一样吗?是的话应该就是一个module,如果非要两个module,那么是不是能考虑将其中一个module改包名?
        Robots:compile project(':main') 这是A项目中的buil.gradle, main是B项目的名字,为什么是两个名字,这个我也不太清楚, 在Eclipse中两个项目的包名一致不会出现这种问题,导入AS中就有这个问题了
        BeWinner:@Robots 能否贴下你现在gradle的配置,我不太理解既然是两个modue了,为什么包名会一样?如果你要使用b的资源,那肯定a要对b有依赖,不知道现在具体你是怎么依赖的。
      • 小池laucherish:总结的挺详细的,学习了。
      • 捡淑:mark
      • bhfo:compile 和 provided 这两个区别,还是不明白,能再详细的讲解一下吗?
        storePassword System.console().readLine("\nKeystore password: ")这条是不是跟gradle版本有关的。在2.14上报错。
        BeWinner: @bhfo 不客气,有时间我把这部分更新一下。
        bhfo:@BeWinner 谢谢
        BeWinner:@bhfo compile不仅在编译的时候需要,而且还会把依赖打包到apk,而provided只需要在编译的时候需要依赖库,但不会把依赖库的代码打包到apk;报错是Studio的问题,你可以用命令行编译:`./gradlew assembleRelease --no-daemon`,另外这个问题可以给你一些参考链接:http://stackoverflow.com/questions/19487576/gradle-build-null-console-object 和 http://www.jianshu.com/p/714ea34f739a
      • Aspirinrin:全面,易懂,谢谢!
        BeWinner: @Aspirinrin 不客气
        BeWinner: @Aspirinrin 不客气!😁
      • 誓词倾城:很棒!有个问题,tools:overrideLibrary 是不是就是强制覆盖其他module了?如果这样的话我觉得我会果断选择修改我app 里面的minsdk。不能歧视外来物种 :stuck_out_tongue_winking_eye:
        建议:在使用讲解的时候能用列表的形式讲解,因为一大段话看的有些乱 :blush:
        期待你的下一篇
        誓词倾城:@BeWinner :+1:
        BeWinner:@誓词倾城 是的,在我目前的例子里面是直接覆盖了,另外谢谢你的宝贵建议,有两个讲解的部分(一个是合并规则、一个是解决冲突示例)都修改为了列表的方式。
      • 三季人:清楚明白,受益匪浅:relaxed:
        BeWinner: @三季人 谢谢,会继续努力!

      本文标题:“替你”总结的Gradle配置

      本文链接:https://www.haomeiwen.com/subject/obfajttx.html