Gradle 的卖家秀和买家秀

2,346 阅读5分钟

这里写图片描述

大部分人都经历过高中,不难发现高考650分的人和450分的人书单基本上是一样的,这是为什么呢?

这往往并不是因为他们接触了更多的信息,而是因为他们处理信息的方式与众不同。他们往往善于整理信息,并且获得“系统化知识体系”。

写在前面的话

今天翻了翻自己之前的博客,发现上一篇还是在2017-01-12发的。不知不觉3个月就过去了,之间好像再也没有很系统的总结过东西了。仔细想想一年也没几个3个月,还是有点方的。

3个月说长不长,但也足够发生很多事情。比如我投身到了直播(YOLO)行业,开始接手前人(PiasypromeG等等)的项目,之间曲折自不需多说。好在已经适应了现在的节奏,博客也会慢慢更新。

其实直播并没有想象中的那样复杂,概括的讲无非就是采集端(推流端)和观众端(拉流端)。然后再添加一些礼物系统、弹幕系统、聊天系统、人脸识别、游戏互动、连麦互动、美颜等功能使之丰满。

关于直播,我们暂且不进行深入讨论。今天我们来说一说Gradle相关的东西。可能好多人会有这样的感慨,为什么我看了那么多“Gradle从入门到精通”,仍然管理不好自己项目的构建呢?今天我们就来一起整理下Gradle相关的那些知识点,也许会有收获哦。

Gradle思维导图

为了方便梳理Gradle的知识点,我画了下面的思维导图,有不足的地方还望大家指出。其实这些点基本上每个都可以写一篇单独的博客来扩展,而且有很多已经总结的很好了,这里就不赘述了。

而关于我们为什么要用Gradle,Gradle能做什么这样的问题,弄清楚也是很有必要的。比较官方的说法是:Gradle使用易懂的DSL语法 ,将开发过程中需要的编译、构建、测试、打包以及部署工作,变得非常简单、而且方便重复使用。即Gradle可以从开发过程的各个环节来简化我们的开发。

这里写图片描述

Gradle实战

俗话说的好:Talk is cheap,show me the code。接下来我就抛砖引玉,分享下我们的Gradle是怎样配置的。

项目根目录下的build.gradle文件

// Top-level build file where you can add configuration options common to all sub-projects/modules.

apply from: 'buildsystem/dependencies.gradle'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        apply from: 'buildsystem/dependencies.gradle'

        classpath "com.android.tools.build:gradle:$gradleAndroidVersion"

        classpath 'com.github.promeg:android-multi-channel-plugin:0.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app下的build.gradle文件

import java.text.SimpleDateFormat

import static org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS
import static org.apache.tools.ant.taskdefs.condition.Os.isFamily

if (!testDevelopURC) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
apply plugin: 'android-multi-channel'

def keyConfigPath
if (isFamily(FAMILY_WINDOWS)) {
    keyConfigPath = System.getenv('USERPROFILE') + File.separator + ".ssh" + File.separator +
            "androidKeystore.properties"
} else {
    keyConfigPath = System.getenv('HOME') + "/.ssh/androidKeystore.properties"
}
Properties props = new Properties()
if (new File(keyConfigPath).exists()) {
    props.load(new FileInputStream(file(keyConfigPath)))
}

android {
    def rootDep = rootProject.ext

    compileSdkVersion rootDep.androidCompileSdkVersion
    buildToolsVersion rootDep.androidBuildToolsVersion
    defaultConfig {
        minSdkVersion rootDep.androidMinSdkVersionn
        targetSdkVersion rootDep.androidTargetSdkVersion
        versionCode rootDep.releaseVersionCode
        versionName rootDep.releaseVersionName

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
        vectorDrawables.useSupportLibrary = true

        if (!testDevelopURC) {
            applicationId "tv.yoloyolo.renlei.gradlepractice"
        } else {
            consumerProguardFiles 'proguard-rules.pro', 'proguard-fresco.pro'
        }

        buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""

        manifestPlaceholders = [EASEMOB_APPKEY: "publish#publish"]
    }

    lintOptions {
        abortOnError false
    }

    packagingOptions {
        exclude 'META-INF/LICENSE.txt'
        exclude 'LICENSE.txt'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/services/javax.annotation.processing.Processor'
        exclude 'META-INF/rxjava.properties'
    }

    signingConfigs {
        release {
            storeFile file(props['keystore'])
            storePassword props['keystore.password']
            keyAlias "promegu"
            keyPassword props['key.password']
        }
    }

    dexOptions {
        maxProcessCount 8
        javaMaxHeapSize "6g"
    }

    testOptions.unitTests.all {
        testLogging {
            events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
            outputs.upToDateWhen { false }
            showStandardStreams = true
        }
        // configure the test JVM arguments
        jvmArgs '-noverify'
    }

    aaptOptions {
        cruncherEnabled false
    }

    publishNonDefault true

    productFlavors {
        dev {
            ndk {
                abiFilter "armeabi"
            }

            buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""

            manifestPlaceholders = [EASEMOB_APPKEY: "test#test"]
        }

        production {
            ndk {
                abiFilter "armeabi"
            }
            // inherit from default config
        }
    }

    buildTypes {
        debug {
            minifyEnabled false
            debuggable true
            ext.enableCrashlytics = false
            signingConfig signingConfigs.release
        }

        release {
            minifyEnabled false
            debuggable false
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

multiFlavors {
    prefix = "TEST_";
    def released = new SimpleDateFormat('yyyy-MM-dd').format(new Date())
    subfix = "_$released";
    defaultSigningConfig = android.signingConfigs.release
    channelConfig {
        production {
            childFlavors =
                    ["Tencent", "360", "Baidu", "Alibaba", "GFan", "Sogou",
                     "Lenovo", "XiaoMi", "Meizu", "OPPO", "Huawei", "GooglePlay"]
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

    compile 'com.android.support:appcompat-v7:25.3.1'

    testCompile 'junit:junit:4.12'

    testCompile 'org.robolectric:shadows-support-v4:' + rootProject.ext.robolectricVersion

    compile('com.github.promeg:android-multi-channel-plugin-lib:0.1') {
        exclude module: 'appcompat-v7'
    }
}

外部的dependencies.gradle文件

我在项目根目录创建了一个叫buildsystem的文件夹,dependencies.gradle文件放到了这个文件夹下。

allprojects {
    repositories {
        jcenter()
    }
}

ext {
    androidBuildToolsVersion = '25.0.2'
    androidCompileSdkVersion = 25
    androidMinSdkVersionn = 15
    androidTargetSdkVersion = 25
    androidSupportSdkVersion = '25.1.0'

    gradleAndroidVersion = '2.2.3'

    releaseVersionCode = 1
    releaseVersionName = "1.0"

    robolectricVersion = '3.0'
}

项目根目录下的gradle.properties文件

# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
#org.gradle.jvmargs=-Xmx1536m

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

org.gradle.jvmargs=-Xmx8192m -XX\:MaxPermSize\=3072m
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.parallel=true

testDevelopURC=false

应用到的知识点

基本上就是这样了,这几个文件的配置是从YOLO项目简化而来的,不过不影响研究。接下来我们看下里边用到的点。由于具体每个点的实现网上好多已经介绍的很全面了,这里我只说下大概的点,具体大家可以自行google。后边我也会附上一些链接供大家参考。

依赖外部配置

如果我们的配置文件过大或者有需要复用的配置,可以考虑抽离成一个单独的文件,引入方式如下:

apply from: 'buildsystem/dependencies.gradle'

全局配置

Stack Overflow:How to define common android properties for all modules using gradle

ext {
    androidBuildToolsVersion = '25.0.2'
    androidCompileSdkVersion = 25
    androidMinSdkVersionn = 15
    androidTargetSdkVersion = 25
    androidSupportSdkVersion = '25.1.0'

    gradleAndroidVersion = '2.2.3'

    releaseVersionCode = 1
    releaseVersionName = "1.0"

    robolectricVersion = '3.0'
}

占位符

如果我们在manifest中定义了占位符的话,可以在module的build.gradle中可以将其进行赋值。

<meta-data
            android:name="EASEMOB_APPKEY"
            android:value="${EASEMOB_APPKEY}" />

manifestPlaceholders = [EASEMOB_APPKEY: "publish#publish"]

ProductFlavors

对于ProductFlavors,我们可以在Build->Select Build Variant…选择我们开发状态下使用的默认配置。

productFlavors {
    dev {
        ndk {
            abiFilter "armeabi"
        }

        buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""

        manifestPlaceholders = [EASEMOB_APPKEY: "test#test"]
    }

    production {
        ndk {
            abiFilter "armeabi"
        }
        // inherit from default config
    }
}

BuildTypes

Gradle官方文档:BuildType,这篇讲的是有BuildType有哪些可以配置东西。

debug {
            minifyEnabled false
            debuggable true
            ext.enableCrashlytics = false
            signingConfig signingConfigs.release
        }

BuildConfig

自定义BUILDCONFIG

buildConfigField "String", "API_BASE_URL", "\"https://api.test.base\""

dependencies

包括依赖的几种类型,以及如果解决依赖冲突。

Gradle官方文档:依赖项管理基础知识

Android官方文档:Add Build Dependencies

排除传递依赖

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

    compile 'com.android.support:appcompat-v7:25.3.1'

    testCompile 'junit:junit:4.12'

    testCompile 'org.robolectric:shadows-support-v4:' + rootProject.ext.robolectricVersion

    compile('com.github.promeg:android-multi-channel-plugin-lib:0.1') {
        exclude module: 'appcompat-v7'
    }
}

签名

这里用到了一些Groovy中的语法,主要用来找到自己的androidKeystore.properties文件并从中读取配置。

Android官方文档:签署您的应用

def keyConfigPath
if (isFamily(FAMILY_WINDOWS)) {
    keyConfigPath = System.getenv('USERPROFILE') + File.separator + ".ssh" + File.separator +
            "androidKeystore.properties"
} else {
    keyConfigPath = System.getenv('HOME') + "/.ssh/androidKeystore.properties"
}
Properties props = new Properties()
if (new File(keyConfigPath).exists()) {
    props.load(new FileInputStream(file(keyConfigPath)))
}

signingConfigs {
        release {
            storeFile file(props['keystore'])
            storePassword props['keystore.password']
            keyAlias "promegu"
            keyPassword props['key.password']
        }
}

各种options配置

Gradle官方文档:Android Plugin DSL Reference

packagingOptions {
    exclude 'META-INF/LICENSE.txt'
    exclude 'LICENSE.txt'
    exclude 'META-INF/LICENSE'
    exclude 'META-INF/NOTICE'
    exclude 'META-INF/NOTICE.txt'
    exclude 'META-INF/services/javax.annotation.processing.Processor'
    exclude 'META-INF/rxjava.properties'
}
dexOptions {
        maxProcessCount 8
        javaMaxHeapSize "6g"
    }

    testOptions.unitTests.all {
        testLogging {
            events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
            outputs.upToDateWhen { false }
            showStandardStreams = true
        }
        // configure the test JVM arguments
        jvmArgs '-noverify'
    }

    aaptOptions {
        cruncherEnabled false
    }

多渠道打包

这个多渠道打包其实是之前同事自己写了一个Gradle插件。传送门

当然如果你熟悉Groovy语法,也可以自己编写。编写Gradle插件

multiFlavors {
    prefix = "TEST_";
    def released = new SimpleDateFormat('yyyy-MM-dd').format(new Date())
    subfix = "_$released";
    defaultSigningConfig = android.signingConfigs.release
    channelConfig {
        production {
            childFlavors =
                    ["Tencent", "360", "Baidu", "Alibaba", "GFan", "Sogou",
                     "Lenovo", "XiaoMi", "Meizu", "OPPO", "Huawei", "GooglePlay"]
        }
    }
}

加速编译

Android官方文档:优化你的编译速度

dexOptions {
        maxProcessCount 8
        javaMaxHeapSize "6g"
    }

org.gradle.jvmargs=-Xmx8192m -XX\:MaxPermSize\=3072m
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.parallel=true

Other

除了上边提到的那些点之外,还有一些总结的比较好的链接。其实国内好多博客都大同小异,都参考自官方文档,所以推荐直接看官方文档。

结语

可能上边说的内容比较多,不过大家可以按需查看。而且工欲善其事,必先利其器。对于我们来说,学好Gradle无疑可以对开发提供很大的帮助,节省更多的时间。

感谢官方文档详细的教程,也感谢喜欢分享的那些朋友,如果上边链接有侵权的话,请告诉我,我会删掉。