“替你”总结的Gradle配置

96
BeWinner
2016.07.28 00:06* 字数 2146

阅读说明

参考链接: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: ")

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

分享集
Web note ad 1