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都有一个单独的版本号。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏玄魂工作室

Hacker基础之Linux篇:基础Linux命令十六

今天我们来学习几个小知识,不一定是Linux的命令,都是用于查看Linux的系统信息的

17930
来自专栏FreeBuf

新手指南:DVWA-1.9全级别教程(完结篇,附实例)之XSS

* 本文原创作者:lonehand,转载请注明来自FreeBuf.COM 目前,最新的DVWA已经更新到1.9版本(http://www.dvwa.co.uk...

1.6K50
来自专栏技术博文

Linux下ps命令详解

linux上进程有5种状态:  1. 运行(正在运行或在运行队列中等待)  2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)  3. 不可中断(...

42940
来自专栏程序猿DD

Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】

前言 在上一篇《服务容错保护(hystrix断路器)》的介绍中,我们提到断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。而这些请求情况的...

23470
来自专栏程序猿DD

Spring Boot中使用LDAP来统一管理用户信息

很多时候,我们在构建系统的时候都会自己创建用户管理体系,这对于开发人员来说并不是什么难事,但是当我们需要维护多个不同系统并且相同用户跨系统使用的情况下,如果每个...

95260
来自专栏aoho求索

详解Hystrix资源隔离

在货船中,为了防止漏水和火灾的扩散,一般会将货仓进行分割,避免了一个货仓出事导致整艘船沉没的悲剧。同样的,在Hystrix中,也采用了这样的舱壁模式,将系统中的...

42450
来自专栏Java帮帮-微信公众号-技术文章全总结

02.WebService_使用三要素

02.WebService_使用三要素 一、Java中WebService规范 JAVA 中共有三种WebService 规范,分别是JAX-WS、J...

38460
来自专栏IT笔记

SpringBoot开发案例之微信小程序文件上传

最近在做一个口语测评的小程序服务端,小程序涉及到了音频文件的上传,按理说应该统一封装一个第三方上传接口服务提供给前段调用,但是开发没有那么多道理,暂且为了省事就...

78270
来自专栏Flutter&Dart

DartVM服务器开发(第十九天)--jaguar_reflect使用Controller

上面有一个ReflectedController(UserController()).routes,就是把UserController里面的接口反射出来,添加到...

15930
来自专栏IT笔记

SpringBoot开发案例之整合ActiveMQ实现秒杀队列

在实际生产环境中中,通常生产者和消费者会是两个独立的应用,这样才能通过消息队列实现了服务解耦和广播。因为此项目仅是一个案例,为了方便期间,生产和消费定义在了同一...

2.5K40

扫码关注云+社区

领取腾讯云代金券