Gradle For Android(9)--自定义构建

介绍

现在我们知道了Gradle如何工作,如何创建自己的Task以及Plugin,如何执行test任务,如何设置CI。这一章会包含一些小技巧,接下来会从以下Topic进行讨论:

  • Reducing the APK file size
  • Speeding up builds
  • Ignoring Lint
  • Advanced app deployment

Reducing the APK file size

APK文件的大小在最近几年都在疯长。有很多的原因,更多的Library,更多的Densities,App功能越来越强大。GooglePlay限制了APK大小50M,而一个更小的APK也就意味着用户会更快的下载和安装,并且减少内存空间的占用。

在这一节我们来看看如何通过Gradle构建配置来减少APK大小。

ProGuard

ProGuard除了可以shrink(压缩),也可以进行optimize(优化),obfuscate(混淆),在编译时期进行preverify(预验证)。它通过应用程序中的所有代码路径来查找未使用的代码并删除它。ProGuard也会重命名你的类和属性。这个过程会使得内存占用更小,更难逆向。

Android Plugin在buildType中有一个Boolean的属性名为minifyEnabled,可以设置成true启用Proguard:

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

当你设置了minifyEnabled为true后,proguardRelease任务就会执行,并且在构建过程中调用ProGuard。在启用了ProGuard之后,最好重新测试一下整个APP,有可能它仍然把你一些有用的代码都移除了,比如说JNI中调用的Java代码。为了解决这个问题,你可以定义ProGuard rules来把一些真正有用的代码保证不被移除。proguardFiles属性就是用来定义包含了ProGuard Rules的文件。例如,要Keep一个雷,你可以如下定义:

-keep public class <MyClass>

getDefaultProguardFile('proguard-android.txt')````这个函数会获取proguard-android.txt文件作为默认的ProGuard配置文件。而该文件就在Android SDK的tools/proguard目录下。而proguard-rules.pro```文件会默认添加到新的Android Modules中,所以你可以在Modules中进行简单的Rule配置。

具体的ProGuard配置,可以参照官网压缩代码和资源

Shrinking resources

Gradle和Android Plugin在App打包的时候,会把没用的资源都删掉。如果你有一个旧的资源没有删除,那么它就会默认帮你删除掉。另外一个方面是当你引用了很多资源的Library,但是你只是用一小部分,你可以通过启用resource shrinking的方式来移除。这有两种方式来压缩资源,自动或者手动

Automatic shrinking

如果设置了shrinkResources属性为true的话,Android Build Tools将会自动的决定哪些资源是没用的,并且不把它们打包到APK中。

这个特性也有一个要求,就是你需要启用proGuard。正因为Resource Shrinking工作了,Android Build Tools不能指出哪些资源是无用的,直到这些代码引用的资源全部被移除。

在BuildType中自动配置资源Shrinking:

android {
       buildTypes {
          release {
               minifyEnabled = true
               shrinkResources = true
           }
        } 
}

如果你希望看到你的APK减少了多少,你可以执行shrinkReleaseResources这个任务。这个任务会打印出来包大小减少了多少:

:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB  to 354KB: Removed 18%

你可以通过添加--info标志位来获取移除的资源信息:

$ gradlew clean assembleRelease --info

当使用这个Flag的时候,Gradle会打印出在构建过程中的很多其他信息,包括最终没有打入APK包中的每一个资源。

而Automatic Resource Shrinking有一个问题,就是它可能会移除大量的资源。尤其是使用动态获取的资源,可能会被移除掉。为了避免这种情况,我们可以在res/raw/目录下创建一个keep.xml文件,用来保持资源:

<?xml version="1.0" encoding="utf-8"?>
   <resources xmlns:tools="http://schemas.android.com/tools"
              tools:keep="@layout/keep_me,@layout/also_used_*"/>

而这个keep.xml文件本身也不会被打入最终的包中。

Manual shrinking

减少资源的一种不极端的方案是减少多密度,多语言等文件。某些Library中包含了很多语言,例如Google Play Services。如果你的APP只想支持一个或者两个语言,而不想把所有的语言都打入最终的APK中。你可以使用resConfigs属性来配置你希望保留的资源,而剩下的都会被丢弃。

如果你希望保存English,Danish,Dutch的字符串,你可以使用resConfigs如下:

android {
       defaultConfig {
           resConfigs "en", "da", "nl"
       }
}

你同样也可以在density进行选择:

android {
       defaultConfig {
           resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
       }
}

它甚至可以组合Launguages和Density。实事上,每一种资源类型都可以通过这个属性进行配置。如果你不想配置ProGuard,或者你只想去掉不支持的语言和Density,那么使用resConfigs是一个不错的选择。

Speeding up builds

Gradle的构建速度会比Ant长一些,因为Gradle在构建的生命周期中有三个阶段,而当你每次执行Task的时候,它都会经过这三个阶段。这会使得整个过程都很容易进行配置,但是确实会比较慢。不过,我们也有一些方法能够提升Gradle构建速度。

Gradle properties

一种提升速度的方法就是修改默认的设置。我们之前提到过parallel构建,你可以通过设置parallel属性来提升构建速度。

首先在Top-Level创建一个gradle.properties文件。然后添加:

org.gradle.parallel=true

另外一种方式是启用Gradle Daemon。启用后,会在第一次启动构建的时候启动一个后台进程。当后续的构建启动时,都会使用这个后台进程,因此会节省一些启动的开销。这个进程会在你使用Gradle期间一直存在,而在空闲3个小时后关闭。使用Daemon在短时间内构建是非常有用的。你可以在gradle.properties中添加:

org.gradle.daemon=true

在Android Studio中,Gradle Daemon是默认启用的。我这也就意味着在IDE中第一次启动构建后,后续的构建都会比较快。如果你从命令行执行构建的话,Gradle Daemon则是关闭的,除非在Properties中启用。

为了提升编译本身的速度,你可以设置JVM的参数。在Gradle的属性中,名为jvmargs,可以用来为JVM启用设置内存分配的值。这两个参数也会对构建速度有直接的影响:XmsXmx

  • Xms:用来设置初始化使用的内存总值
    -Xmx:用来设置内存使用最大值
    可以在gradle.properties文件中添加:
org.gradle.jvmargs=-Xms256m -Xmx1024m

默认的Xmx是256M,而Xms没有设置。这两个选项都是基于你电脑的能力。

最后一个你可以配置的影响构建速度的属性是:org.gradle. configureondemand。如果具有多个模块的复杂项目,则此属性特别有用,因为它试图通过跳过正在执行任务不需要的模块来限制配置阶段花费的时间。如果设置了这个属性,那么Gradle就会在配置阶段前,查出哪个模块的配置有修改,而哪个没有修改。

如果只有一个App或者Library工程的话,那么就不会有用。如果你有很多Module,并且比较复杂的话,那么这个属性可以节省很多构建时间

Profiling

如果你想看到是那部分过程拖慢了构建速度,那么可以在Gradle Task的时候通过添加--profile标志位来获取一个Profile文件。当提供了这个标志位后,Gradle创建出了一个Profiling Report,可以从这个文件看到那部分的构建消耗了最多的时间。

这个Report会存在/build/reports/profile文件夹下,而类型为HTML。以下为一个执行完多Module的构建任务的Report:

Profile Report

这个Profile Report展示了每个阶段执行任务时所消耗的时间。在上面的Summary是每个Module在Configuration阶段所耗费的时间。而Dependency Resolution展示了每个模块解决依赖关系所耗费的时间。Task Execution阶段包含了执行阶段的时间。而这个里面包含了每一个任务所执行的时间,从高到低排序。

Jack and Jill

如果你想要使用一个实验工具,那么JackJill也可以提升构建速度。

Jack:Java Android Compile Kit,它是Android Build Toolchain中的一个新工具。她可以编译Java代码直接到Dex格式。它有它自己的.jack库格式,并且处理了打包和压缩。

Jill:Jack Intermediate Library Linker,它是一个可以把.aar和.jar文件转换成.jack库的工具。不建议在Production版本中使用这两个工具。

你可以把Build Tool版本提升到21.1.1以上,Gradle版本提升到1.0.0版本以上,然后在defaultConfig代码块中添加属性:

android {
       buildToolsRevision '22.0.1'
       defaultConfig {
         useJack = true
       }
}

你可以在一个buildType或者ProductFlavor中启用。这种方式,你可以继续使用常规的Build Toolchain,并且可以进行一个测试的构建。

android {
       productFlavors {
           regular {
               useJack = false
           }
           experimental {
               useJack = true
            } 
        }    
}

Ignoring Lint

当你执行一个Release构建的时候,Lint会检查你的代码。Lint是一个静态代码分析工具,可以标志出Java代码以及Layout的Bug。某些情况下,甚至会打断构建。如果你之前没用Lint,而现在想在Gradle中启用的话,Lint可能会报很多错误。至少能够让构建过程能够正常运转,你可能会让Gradle别处理Lint的错误。这只是一个临时方案,因为忽略Lint的错误可能会导致App Crash。

为了将Lint错误导致中断的问题避免,可以禁用掉abortOnError

android {
       lintOptions {
           abortOnError false
       }
}

临时禁用可以使Ant工程可以更快的升级到Gradle中。

Advanced app deployment

之前我们通过BuildTypeProduct Flavors来构建多个版本的APP。然而在很多情况下,我们可以通过其他方法来达到目的。

Split APK

Build Variants能够被分成很多块,每一块都有自己的代码,资源,Manifest文件。APK Split,从另一方面来说,只会影响App的打包。编译,压缩,混淆等等流程还是会共享。这种机制允许你可以基于Density或者ABI来分割APK。

你可以在android的配置项中通过定义一个splits代码块配置分割。为了配置density分割,就可以创建一个density代码块。如果希望按照ABI分割,则使用abi代码块。

如果你启用了density分割,Gradle会为了每个density创建一个单独的APK。如果不需要density的话,你可以手动的exclude其中的densities,来提升构建速度。例如:

android {
       splits {
           density {
               enable true
               exclude 'ldpi', 'mdpi'
               compatibleScreens 'normal', 'large', 'xlarge'
           }
        } 
}

如果你不只支持一些densities,你可以使用include来创建一个densities的白名单。为了使用include,首先需要使用reset()属性,该属性可以重置densities的列表为一个空的字符串。

compatibleScreens属性是可选的,并且会在manifest文件中注入一段代码。这个配置可以让一个App支持大屏幕,而不支持小屏幕。

使用ABI分割APK也是同样的,所有的属性都和density分割一样。在执行完density分割后的构建结果中:

   app-hdpi-release.apk
   app-universal-release.apk
   app-xhdpi-release.apk
   app-xxhdpi-release.apk
   app-xxxhdpi-release.apk

如果你希望把这些APK发布到Google Play上的话,你就需要确保每个APK都有不同的版本号。这也就意味着,分割必须有一个单独的版本号。而我们可以通过applicationVariants属性来完成:

ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]
import com.android.build.OutputFile

android.applicationVariants.all { variant ->
       // assign different version code for each output
       variant.outputs.each { output ->
           output.versionCodeOverride =  project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 +android.defaultConfig.versionCode
       } 
}

这一段代码会检查ABI,并且保证每一个Variant都有一个单独的版本号。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容