目前Android开发主流的编译工具是Gradle,虽然后最快的编译工具并不是它,但它的优势就是它是亲儿子。它与Android Studio的关系非常密切,很多配置都是默认配置好的,开发起来极其方便。但是编译速度相对较慢。

了解Gradle的编译原理,学习如何优化Gradle对开发效率的提高尤为重要。

Gradle的优化,主要分为两大方面:

1、包大小优化;

2、编译速度优化。

Gradle目录(如已了解,跳过看优化)

首先,我们来了解一下Gradle的基础目录结构。

android 优化编译 安卓应用编译优化_android 优化编译

 setting.gradle:定义了需要加入编译的依赖module,Android Studio里面一般都是默认生成的。如:

include ':app', ':lib_iflytek', ':BlueMoonSDK'

外层build.gradle:全局的配置文件,一般配置maven下载地址,gradle的版本配置等。我们一般不会在这里面操作太多共有的东西,保证每个module的独立性。repositories就是代码仓库,Gradle 支持三种类型的仓库:Maven,Ivy和一些静态文件或者文件夹。如:

buildscript {
    repositories {
        jcenter()
        google()//3.0+版本默认加上的
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.3'
    }
}

allprojects {
    repositories {
        jcenter()
        google()
        maven {
            url "https://jitpack.io"//这里是配置maven的下载地址
        }
    }
}

 gradle-wrapper.properties:这里配置gradle的包版本,主要跟外层build.gradle的calsspath配置的版本对应。3.1.3对应的是4.4。wrapper 有版本区分,但是并不需要你手动去下载,当你运行脚本的时候,如果本地没有会自动下载对应版本文件。如:

distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

内层build.gradle:每个module单独的配置文件。如果这里的定义的选项和外层build.gradle定义的相同,后者会被覆盖。大部分关于Android的配置都是在这里完成。具体还是直接根据代码来分析吧。以app目录下的build.gradle为例。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion '23.0.3'

    signingConfigs {
        release {
            storeFile file('./Json20170206.keystore')
            storePassword "storePassword"
            keyAlias "keyAlias"
            keyPassword "keyPassword"
        }
    }

    defaultConfig {
        applicationId "cn.com.json.toall"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        /*个推*/
        manifestPlaceholders = [
                GETUI_APP_ID    : "GETUI_APP_ID",
        ]
        ndk {
            abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "mips", "mips64", "x86", "x86_64"
        }
        multiDexEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            minifyEnabled false
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile project(':SDK')
    compile 'com.android.support:appcompat-v7:23.4.0'
}

apply plugin:Android 程序的gradle插件,作为Android 的应用程序,这一步是必须的,因为plugin中提供了Android 编译、测试、打包等等的所有task。如果需要用到其他的编译文件,也是用这个声明,如:apply plugin: 'kotlin-android'。

android:编译文件中最大的代码块,关于android的所有特殊配置都在这里,包括正式和测试版本的区别配置。由第一句的plugin提供。下面我们来逐个分析:

compileSdkVersion,buildToolsVersion这两个配置对应的sdk版本,这里就不多说了。

signingConfigs:配置签名文件。可通过release和debug配置正式包和测试包不同的签名。下面需要用到。

defaultConfig:程序的默认配置。相同的属性,如果在AndroidMainfest.xml里面也定义了的话,会以这里的为主。、

  • multiDexEnabled:是否分包处理,这里配置为true的话,需要在application里面初始化。
  • manifestPlaceholders:配置参数,可在AndroidManifest.xml中使用。如果需要区分正式和测试配置,可以在下面的buildTypes中配置。

buildTypes:定义了编译类型,针对每个类型我们可以有不同的编译配置,不同的编译配置对应的有不同的编译命令。默认的有debug、release 的类型。配置参数manifestPlaceholders可以在这里区分配置。

dependencies:属于gradle 的依赖配置。定义当前项目需要依赖的其他库。

outputFile:通过variant重命名apk名字。这个属性在gradle3.0+版本做了改动,output.outputFile变成了只读属性,设置方式不一样具体看代码:

//Gradle3.0以下:
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def outputFile = output.outputFile
        if (outputFile != null && outputFile.name.endsWith('debug.apk')) {
        def fileName = "SFA_测试版.apk"
        output.outputFile = new File(outputFile.parent, fileName)
        }
    }
}

//Gradle3.0+:
applicationVariants.all { variant ->
    variant.outputs.all {
        if (outputFile != null && outputFile.name.endsWith('debug.apk')) {
        outputFileName = "SFA_测试版.apk"
        }
    }
}

 

包大小优化

1、去除不必要资源:在编译的时候,我们可能会有很多资源并没有用到,此时就可以通过shrinkResources来优化我们的资源文件,除去那些不必要的资源。

注:这个属性必须跟代码混淆(代码压缩)一起使用,所以还需要做代码混淆,否则无法使用

buildTypes {
        debug {
            // (资源压缩)必须与代码压缩一起使用
            shrinkResources true
            //(代码压缩)代码混淆
            minifyEnabled true
        }

2、把图片转成webp格式:WebP是一种图片文件格式,它提供了像JPEG一样的有损压缩和像PNG一样的透明支持,但是同时它的压缩质量比JPEG或者PNG任何一个都更好,减小Image文件的大小,而不用在构建时做压缩,因此它能提高构建速度,尤其是你的APP使用了大量的图片资源。用Android Studio 可以很方便的转WebP格式,详情请看convert your images to WebP. 但是Android Studio自带的工具只能一张一张转,如果需要批量转的话,推荐一款很好的工具 WebPconv转化成webp的话,同时也可以减少png图片压缩带来的编译速度缓慢的问题。

 

3、去除无需兼容的ndk包:在我们项目中,有时候需要引入一些第三方或者自己开发的ndk包,但是有可能部分型号我们并不需要兼容,无需把所有的包都加载进来,这时候就可以在gradle里面配置需要用到的包:

defaultConfig {
        ndk {
            abiFilters "armeabi-v7a","arm64-v8a"//一般只需要这两个,视具体项目而定
        }
}

编译速度优化

1、开启并行编译:在项目根目录下面的gradle.properties中设置,开启并行编译,默认情况下Gradle处理多模块时,往往是挨个按顺序处理

//开启并行编译,默认情况下Gradle处理多模块时,往往是挨个按顺序处理
org.gradle.parallel=true

2、开启编译守护进程:该进程在第一次启动后回一直存在,当你进行二次编译的时候,可以重用该进程。同样是在gradle.properties中设置。

//不需要每次启动gradle进程(JVM实例),减少了初始化相关的工作
//daemon可以缓存项目结构,文件,task等,尽可能复用之前的编译成果,缩短编译过程
org.gradle.daemon=true

3、加大可编译内存:Dex-in-process 允许多个DEX 进程运行在一个单独的VM 中,这使得增量构建和清理构建变得更快。需要设置至少1536MB 的堆大小内存。如果在你的内层build.gradle 文件中为android.dexOptions.javaMaxHeapSize定义了一个值,那么你需要给Gradle的堆大小设置 的值为比javaMaxHeapSize多512MB,并且满足至少为1536MB。同样是在gradle.properties中设置。

//Gradle 后台程序的最大堆大小
org.gradle.jvmargs=-Xmx2048m

4、ZipAlign优化:在应用程序上运行zipalign,使得在运行时Android与应用程序间的交互更加有效率。让应用程序和整个系统运行得更快。在app下面的build.gradle文件中设置:

buildTypes {
        debug {
            zipAlignEnabled true
        }
        release{
            zipAlignEnabled true
        }
}

5、配置 dexOptions 和 开启 library pre-dexing(dex预处理):Dex-in-process:新发布的Android Studio 2.1增加了一个新的特性:Dex In Process,可以极大的加快重新编译的速度,同样也能提高Instant Run的性能。配置相应的DEX构建特性可以提高构建速度。

dexOptions {
        javaMaxHeapSize "2048m"
        // 忽略方法数限制的检查
        jumboMode true
        // 是否对依赖的库进行dex 预处理来使你的增量构建更快速,
        // 因为这个特性可能会使你的clean 构建变慢,
        // 因此在你的持续集成服务器上你可能想关闭这个特性
        preDexLibraries true
        //设置最大的线程数量使用当运行 dex-in-process时,默认值是4,如果不确定,还是用默认值吧
        maxProcessCount 8
    }

(1) preDexLibraaies : 声明是否对依赖的库进行dex 预处理来使你的增量构建更快速,因为这个特性可能会使你的clean 构建变慢,因此在你的持续集成服务器上你可能想关闭这个特性。

(2)javaMaxHeapSize: 为DEX 编译器 设置最大的堆大小,相对于设置这个属性,你应该增加 Gradle的 堆大小(这个堆大小dex-in-process可用的时候对DEX 编译器有效)这个值的设置需要调整第3点优化的值。

(3)maxProcessCount : 设置最大的线程数量使用当运行 dex-in-process时,默认值是4。

注:这里的参数值没有一个规定的值,需要调整数值来测试一下哪个更适合,不然会得到一个负面的影响。

 

6、构建一个变体:有许多配置是你在准备app的release 版本的时候需要,但是当你开发app的时候是不需要的,开启不必要的构建进程会使你的增量构建或者clean构建变得很慢,因此需要构建一个只保留开发时需要配置的变体。通过productFlavors构建,dev代表debug版本,prod代表release版本。

productFlavors {
        // 本地开发版本
        dev {
            // 避免编译不必要的资源
            resConfigs "en", "xxhdpi"
            // 禁止每次构建app都自动压缩图片
            aaptOptions {
                cruncherEnabled false
            }
            // 本地开发环境可以停止友盟统计等不需要的工具,这个是自定义的
            buildConfigField("boolean", "UM_FLAG", "false")
        }
        // jekins、正式环境,使用assembleProdxxx命令编译
        prod {
            buildConfigField("boolean", "UM_FLAG", "true")
        }
    }

这里解析一下:

resConfigs "en", "xxhdpi"

这句代码是指只在你的’dev’ flavor下指定一种语言和一个屏幕密度,只是用english string资源和xxhdpi屏幕密度资源。

aaptOptions {
                cruncherEnabled false
            }

这个是指 禁止使用 PNG crunching。我们资源中的图片可以全部转成web使用,但是如果不想转的话,仍然可以通过禁止每次构建app都自动压缩图片来提升构建速度。

buildConfigField("boolean", "UM_FLAG", "false")

这个只是设置一个变量来控制友盟等统计工具在开发版不开启,这个根据项目情况来自定义。

 

7、用静态的版本依赖:当你在build.gradle文件中声明依赖的时候,你应该避免在版本号结束的地方使用+号,比如:com.android.tools.build:gradle:2.+ 因为Gradle的检查更新,用动态的版本号会导致未知的版本更新、使解决版本的差异变得困难和更慢的构建。你应该使用静态或者硬编码版本号来代替。如:com.android.tools.build:gradle:2.2.2 。

 

8、分多module管理:抽取代码中相对独立的功能模块,创建新的module来开发,通过这种方式模块化你的代码将允许构建系统仅仅只编译那些有改动的模块,并将其构建结果缓存下来以被后面的构建使用。同时也可以提高开发效率,发布到maven上多APP公用。

9、构建分析:最后,我们可以通过不同的命令来分析我们的构建。生成和查看build profile,执行下面的步骤:

(1)用Android Studio打开项目,选择 View -> Tool Windows -> Terminal 打开命令行

(2)执行clean build 输入下面的命令,当你分析你的构建时,每次构建之间需要执行一个 clean build 操作,因为Gradle会跳过输入没有 改变的tasks,因此,第二个没有改变输入的构建通常会运行得更快因为tasks 没有重新运行,因此在构建之间运行一个cleantask 保证你分析了全部的构建进程。

//如果在Mac 或者 Linux上 用 ./gradlew
gradlew clean

(3)选择其中一个产品风味(product flavor) 执行debug 构建,比如:assembleDevDebug,assembleDebug等,如下:

gradlew --profile --recompile-scripts --offline --rerun-tasks assembleDevDebug

这里逐个分析一下上面的命令:

1、--profile:开启profiling

2、--recompile-scripts:强制脚本重新编译跳过cache

3、--offline:禁止 Gradle获取离线依赖,这是确保任何的延迟都是Gradle试图更新依赖而导致,不会误导你的分析数据。你应该先准备好构建一次工程确保Gradle 已经下载好并且缓存依赖。

4、--rerun-tasks:强制Gradle返回所有task 并且忽略任何task 优化。

(4)构建结束后,./build/reports/profile/ 目录下可以看到生成了一个分析报告:

         

android 优化编译 安卓应用编译优化_gradle_02

 (5)右键点击profile-xxxx.html,选择在浏览器中打开,你就会看到下面这张图,你可以观察报告中的每一个tab来了解你的构建,比如Tasks Execution 显示了每一个task执行的时间。

android 优化编译 安卓应用编译优化_android_03

有了分析报告,我们可以查看到每一个tab不同的分析,从而不断尝试,根据自己项目的实际情况来调整,是项目优化达到最佳。下面说明两点情况:

1、代码混淆,清除无用代码资源可以减少APK体积,但是同时也很耗时,对构建速度是有所影响的。

2、增加Gradle的堆大小,也有可能起到反作用。所以需要根据具体项目和环境调整。 

10、升级到gradle3.0+:gradle3.0+在编译速度上也有了很大的提高,特别是在增量更新方面。

最后

以上针对Gradle目录和apk大小优化以及构建速度提了几点优化建议,希望对大家有所帮助。

关于Gradle相关的只是还有很多,比如Groovy 语言,Task,keystore保护等,后面有时间会继续分析。