正确的姿势来优化你的build.gradle

引言

eclipse 已经成功了过去式.人人 Android Studio 时代已经到来.面对 gradle .很多人都诚惶诚恐.甚至很多时候github clone下来的项目都不会编译.gradle versiongradle plugin versionsdk versionsupport version 的概念都没有分清楚.导致因为跟原作者的一些配置和本地拥有的环境不匹配而无法编译成功.刚好公司项目到了一個阶段性手尾.趁着有一点时间.特地记录一下.和大家一齐分享交流.

下面统一用我写的一個RecyclerView的开源项目 RvHelper 来作为sample代码:RvHelper-github

统一管理所有依赖

在项目中新建一個buildsystem文件夹.并且新建一個dependencies.gradle文件.文件夹名称和文件名字随意.自己使用的时候映射即可.如图:

然后再进行文件的编写

ext.versions = [
        code       : 1,
        name       : '1.0.0',

        minSdk     : 15,
        targetSdk  : 23,
        compileSdk : 23,
        buildTools : '23.0.3',

        // Library versions
        junit      : '4.12',
        supportLibs: '23.4.0',
        multidex   : '1.0.1',
]
ext.libraries = [
        junit                : "junit:junit:$versions.junit",
        supportV4            : "com.android.support:support-v4:$versions.supportLibs",
        supportAppCompat     : "com.android.support:appcompat-v7:$versions.supportLibs",
        supportDesign        : "com.android.support:design:$versions.supportLibs",
        supportRecyclerView  : "com.android.support:recyclerview-v7:$versions.supportLibs",
        supportCardView      : "com.android.support:cardview-v7:$versions.supportLibs",
        supportAnnotations   : "com.android.support:support-annotations:$versions.supportLibs",

        //拆dex
        multidex             : "com.android.support:multidex:1.0.1",
]

上面只是提供了最简单的一些依赖的配置.这样做的好处就是.如果存在多个module.同时都依赖相同的 minSdk & targetSdk & compileSdk & buildTools & supportLibs 就变得非常的方便.比较好管理.

gradle version说明

首先.对于项目来讲.gradle version会在如下的文件里面进行声明.

可以看到我目前使用的版本是 gradle2.10.当然.其实本身下载Android Studio的时候自带也有一個默认的Gradle.如图:

不过如果你想自己想尝鲜的话.也可以到gradle官网进行下载.

下载好之后配置一下环境变量即可.
配置好之后也可以在项目中指定gradle文件夹.如图:

gradle plugin version说明

很多时候.gradle plugin版本和本地的as自带的版本不对应.然后刚好又被墙了.没有自备梯子.就导致无法编译.所以在open project之前对应自己本地as的版本来修改好是很重要的一件事.
首先.打开项目根目录的 build.gradle 如图:

类似的.现在我的as版本是 2.1.2.那么.我的gradle plugin version就应该对应就是

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.2'
    }
} 

如果我的as版本是2.2pv3.那么将会是

classpath 'com.android.tools.build:gradle:2.2-alpha3'

当然其实本身如果你刚好在用as2.2pv3的话.其实你打开项目的时候.他就会机智的弹出让你升级的选择框了.

module中使用统一配置

首先需要在根目录的 build.gradle 中声明一下引入我们自己编写的配置单.

然后在对应自己项目的Android-Lib-Moduel或App-Module就可以直接这么使用.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile libraries.junit
    compile libraries.supportAppCompat
    compile libraries.supportRecyclerView
}

这样就可以相对优雅的处理好各种依赖的版本一致性问题.

优雅的管理so库

很多时候第三方sdk都会提供多个版本的so库.而因为可能我们同时都会使用不同的sdk.然后有一些刚好没提供上.而我们想要正常使用.理应取所有so库的交集目录.

如:

armeabi
    sdk1.so
    sdk2.so
armeabi-v7a
    sdk1.so

这种情况下.我们只能使用armeabi的目录.而删除掉 armeabi-v7a.因为如果你刚好apk安装到了架构是 armeabi-v7a 的手机上.他就会解压apk中的 armeabi-v7a 目录到/data/data/xxApp/lib. 而显然sdk2.so是不存在的.这个时候如果你代码有使用到sdk2.so的话.必然会出现找不到so库的问题.从而导致crash.

当然.我们可以直接只删除到剩下 armeabi文件夹.但是这种方式就有点麻烦.因为如果刚好sdk2.so我们发现业务不需要了.可以删了.而sdk1.so我们是有多个平台编译的so库了.为了更加好的效果.我也不在于apk大一点.这种情况下.又要把 armeabi-v7a 的东西copy回来.这样也太麻烦了.所以.我们通过gradle来实现.

首先.编辑项目根目录下的 gradle.properties文件.
添加一行

#...
android.useDeprecatedNdk=true

然后.在app-module的 build.gradle

android {
    //...
    defaultConfig {
        //xx
        ndk {
            //设置支持的SO库架构
            abiFilters 'armeabi'//, 'armeabi-v7a', 'arm64-v8a'//, 'x86', 'x86_64'
        }
    }        
}

直接随便根据自己需求来配置需要打包到apk的so库类型.

自定义生成的apk名称

as中默认只是生成app-debug.apk.这样看起来太单调了.看起来一点都不nice.所以我们都喜欢自定义打包生成的apk的名称能够自定义.很简单.只需要在app-module的build.gradle中配置

android {
    //...
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.outputFile = new File(
                    output.outputFile.parent + "/${variant.buildType.name}",
                    "dreamliner-rvhelperSample-${variant.buildType.name}-V${variant.versionName}.apk".toLowerCase())
        }
    }
}

效果如下:

配置正式签名

很多时候我们接入微信/高德地图.都需要进行正式签名才可以正常校验成功和使用.as本身也提供了配置签名的可视化操作.
如图:

当然.我们更喜欢的是自己写配置清单.
首先在app-module下创建自己的签名和对应的密码.别名.别名密码等配置.

jks的创建就不详细描述了.自行google.主要讲的是配置 private.properties文件.
补充相关内容:

RELEASE_STORE_FILE=sample.jks   //签名的名称
RELEASE_STORE_PASSWORD=123456  //签名的密码
RELEASE_KEY_ALIAS=sample       //别名名称
RELEASE_KEY_PASSWORD=123456       //别名的密码

然后在使用的app-module的build.gradle下再进行配置.

android{
    //...
    signingConfigs {
        release {
            def filePrivateProperties = file("private.properties")
            if (filePrivateProperties.exists()) {
                Properties propsPrivate = new Properties()
                propsPrivate.load(new FileInputStream(filePrivateProperties))
                storeFile file(propsPrivate['RELEASE_STORE_FILE'])
                storePassword propsPrivate['RELEASE_STORE_PASSWORD']
                keyAlias propsPrivate['RELEASE_KEY_ALIAS']
                keyPassword propsPrivate['RELEASE_KEY_PASSWORD']
            }
        }
    }
}

然后直接在buildType中指定一下使用什么签名即可.

android{
    buildTypes {
        debug {
            minifyEnabled false
            signingConfig signingConfigs.release  //指定使用上面配置好的签名内容
        }

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

        preview {
            debuggable true // save debug mes
            minifyEnabled true
            shrinkResources true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

常用的差异化构建

比如我现在喜欢在debug的时候.我的app_name是rvHelper-debug.而到正式的时候就用rvHelper.这样自己使用的时候也能一眼就看出来在使用的是测试/正式的包.

android{
    buildTypes {
        debug {
            resValue 'string', 'APP_NAME', 'rvHelper-debug'
            resValue 'string', 'isDebug', 'true'
        }

        release {
            resValue 'string', 'APP_NAME', 'rvHelper'
            resValue 'string', 'isDebug', 'false'
        }

        preview {
            resValue 'string', 'APP_NAME', 'rvHelper-preview'
            resValue 'string', 'isDebug', 'true'
        }
    }
}

然后直接在 string.xml 中进行配置

<string name="app_name">@string/APP_NAME</string>

而在清单文件上面当然使用 @string/app_name.这样一配置下来.就可以实现你想要的差异构建的效果.
同理.我想根据debug/release来初始化的时候做不同的逻辑.比如在Application初始化的时候.

public class AppContext extends Application {

    private static AppContext mInstance;

    private boolean isDebug;

    public static AppContext getInstance() {
        return mInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        initDebug();
        initNet();
        //如初始化net一样.把相关的图片加载框架/Log/其他有debug开关的一些依赖.都可以进行差异化构建
    }

    public boolean isDebug() {
        return isDebug;
    }

    private void initDebug() {
        isDebug = Boolean.parseBoolean(getString(R.string.isDebug));
    }

    private void initNet() {
        if (isDebug()) {
            //使用本地服务器/测试服务器的地址
        } else {
            //使用生成服的服务器地址
        }
    }
}

这样就可以很好的进行差异化构建[和多渠道打包同一个概念]

FAQ

开启multidex的常见错误

在我之前的博客中有简单介绍过如何配置multidex.但是很多时候编译的时候就会因为javaMaxHeapSize不足导致无法编译成功.这个时候只需要加入一下配置即可.

android {
    //...
    dexOptions {
        preDexLibraries = false
        javaMaxHeapSize "4g"
    }

}

exclude重复的jar版本等信息

很多时候我们用不同的jar包.里面都会有版本信息/日志等等文件会一齐进行打包.如果刚好同名.就会导致打包的时候出错.这个时候只需要根据报错提供的名称来进行exclude即可.如下:

android {
    //...
    packagingOptions {
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/services/javax.annotation.processing.Processor'
    }

}

依赖aar

首先copy *.aar文件到对应的module的libs目录.然后在对应的module的build.gradle文件中进行配置.

allprojects {
    repositories {
        jcenter()
        flatDir {
            dirs 'libs'
        }
    }
}

dependencies {
    compile(name: 'sampleAar', ext: 'aar')
}

总结

暂时把我平时使用的配置风格都大概过了一下.虽然都只是简单的介绍.但是基本都还算是完整.能够满足项目需求.如果有其他的疑问.欢迎留言.大家进行沟通交流.同时如果我上文中提及到的点有所疏漏.还有各位兄弟们提点一二.

坚持原创技术分享,您的支持将鼓励我继续创作!