当前位置:   article > 正文

理解AndroidStudio中的build.gradle文件_android studio gradle 4.1 不混淆一个activity

android studio gradle 4.1 不混淆一个activity

对于才从eclipse转AndroidStudio的开发者来说,初次接触到build.gradle文件真是一头雾水,不知道这是几个意思,如下图

上图就是建立一个as工程生成的基本构建文件了,其中这些语句是什么意思,我们来慢慢看

Gradle 是基于Groovy语言来构建的,第一点就得了解什么是Groovy语言,以及它的语法

Groovy是一种动态语言,是从Java语言进化而来,可以说是Java的加强,它能运行于JVM上,具体的一些介绍可以自行谷歌了解,这里只是为了简单的看懂Groovy和基本使用。

我们先在我们熟悉的eclipse上来写一个Groovy项目

开发环境:

Eclipse Groovy开发插件 http://dist.springsource.org/release/GRECLIPSE/e4.4 后边的e4.x要自己对照自己的eclipse版本,否则可能出现无法安装的问题,我的是4.4。

第一个Groovy程序:

这里写图片描述

右键,新建一个Groovy工程,添加一个groovy资源包(只是为了区分),然后在src下先新建一个JavaTest.java类,勾选main方法,关在方法里写下一行代码打印一个字符串 Hello groovy!

public class JavaTest {  
    public static void main(String[] args) {  
        System.out.println("Hello groovy!");  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5

上面是一个很简单的Java类,运行之后将会在控制台输出Hello groovy!

然后同理,我们在groovy资源包下新建一个GroovyTest.groovy 选择新建-other-Grooovy Class

    class GroovyTest {  
        static main(args) {  
            println "Hello groovy!"  
        }  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5

右键使用Groovy运行后,也会在控制台上打印出 Hello groovy!

相比于JavaTest.java我们发现,GroovyTest.groovy。似乎和JavaTest.java长得很像,只不过是将JavaTest.java去掉了一些内容,如class的修饰符public ,打印的System.out ,参数args的类型String[]。下面我们对GroovyTest.groovy做一些改动:

    class GroovyTest {  
        static main(args) {  
            System.out.println "Hello groovy!"  
        }  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5

运行之后发现一样能输入Hello groovy!,那我们再给args加上String[]的类型能

class GroovyTest {  
    static main(String[] args) {  
        System.out.println.println "Hello groovy!"  
    }  
}
  • 1
  • 2
  • 3
  • 4
  • 5

发现一样能运行,并且同样输入Hello groovy! ,有趣了,是不是说我把java里面的代码都拷过来也能运行呢,这时变成这样:

public class GroovyTest {  
    public static void main(String[] args) {  
        System.out.println("Hello groovy!");  
    }  
} 
  • 1
  • 2
  • 3
  • 4
  • 5

和你想的结果一样,他也准确无语的输出了Hello groovy!,原来和我们看到的一些说明是正确的,groovy和java代码是一样的,它只不过是对java代码进行简化了。

不是说简化吗,假如我把GroovyTest类的代码简化成这样:

println "Hello groovy!"
  • 1

运行后,发现居然可以输出来Hello groovy!

下面是groovy的一些说明:

Groovy类和java类一样,完全可以用标准java bean的语法定义一个Groovy类。但作为另一种语言,可以使用更Groovy的方式定义类,这样的好处是,可以少写一半以上的javabean代码。
(1)不需public修饰符
如前面所言,Groovy的默认访问修饰符就是public,如果Groovy类成员需要public修饰,则根本不用写它。
(2)不需要类型说明
同样前面也说过,Groovy也不关心变量和方法参数的具体类型。
(3)不需要getter/setter方法
在很多ide(如eclipse)早就可以为程序员自动产生getter/setter方法了,在Groovy中,不需要getter/setter方法–所有类成员(如果是默认的public)根本不用通过getter/setter方法引用它们(当然,如果一定要通过getter/setter方法访问成员属性,Groovy也提供了它们)。
(4)不需要构造函数
不再需要程序员声明任何构造函数,因为实际上只需要两个构造函数(1个不带参数的默认构造函数,1个只带一个map参数的构造函数–由于是map类型,通过这个参数可以构造对象时任意初始化它的成员变量)。
(5)不需要return
Groovy中,方法不需要return来返回值。
(6)不需要()
Groovy中方法调用可以省略()(构造函数除外)。

我们可以这样写:

定义一个字符串

def aa="Hello"  
println aa
  • 1
  • 2

定义一个整形

def num=10  
println num
  • 1
  • 2

定义一个整形数组集合

def colum=[2,3,4,5]  
println colum[0]
  • 1
  • 2

因为groovy已经重载了<<所以我们可以用<<来向集合加入一个值

    def strArry=["jin","mu","shui"]  
    strArry.add("huo")  
    strArry << "tu"  
    println strArry  
  • 1
  • 2
  • 3
  • 4

输出
[jin, mu, shui, huo, tu]

定义一个Map类型

 def map=[key:"name",value:"mjk"]  
    println map["key"]  
    println map["value"]  
  • 1
  • 2
  • 3

输出:
name
mjk

只要看到是 key:value都是map类型,因为key会自动转换成string,所以你可以写成def map=[“key”:”name”,”value”:”mjk”]也可以写成def map=[key:”name”,value:”mjk”]

如果想知道当前这个变量是一个什么类型可以用 .class来打印

    def str="I am string"  
    println str.class  
  • 1
  • 2

输出
class java.lang.String

循环的使用:

    def arry=["a","b","c","d"]  
    for(a in arry){  
        println a  
    }  
  • 1
  • 2
  • 3
  • 4

定义一个方法,接收两个参数,输出它们的和

    def num=10  
    def method(a,b){  
        println a+b  
    }  
    method num,9  
  • 1
  • 2
  • 3
  • 4
  • 5

当然使用method(num,9)是一样的,只是去掉了括号,参数之间用逗号隔开

在定义一个map类型的时候,一定要加[],如果不加会报错,而在方法调用时,可以不加[],如果方法调用的map参数要加[],则必须在外部加()

如:

 //打印 map值  
    def printMap(map){  
        println map["key"]  
    }  
    printMap key:"name"  
  • 1
  • 2
  • 3
  • 4
  • 5

输出:name

定义一个方法,打印接收到的值

    def printStr(str){  
        println str  
    }  
    printStr "Hello groovy!"  
  • 1
  • 2
  • 3
  • 4

定义闭包:

闭包是Groovy里面一个很重要的特性,这里要着重讲,

先看一个groovy对集合本身的实现了的一个包含闭包的方法each

 def acoll = ["妈妈", "爸爸", "我"]     
    acoll.each{  
     println it  
    }  
  • 1
  • 2
  • 3
  • 4

输出:

妈妈
爸爸

acoll是一个字符串的集合,这个集合里面有一个方法叫each,接收一个参数,这个参数是一个闭包,因为闭包也是一种类型,所以它也能像变量一样当参数传递给方法使用。我们这里简单的理解,闭包是一个用花括号{}括起来的代码块,这个代码块在被触发的时候调用。

上面each中的it是一个关键字,它保存了返回到这个闭包中的一个值。下面我们来用自己的方法实现这个each

定义一个方法,这个方法接收一个集合参数跟一个闭包,当方法被调用时,遍历集合里面的值,并把值传回给闭包

    def myEach(arry,block){  
        for(i in arry){  
            block(i)  
        }  
    }  
    def arr=["爸爸","妈妈","我"]//字符串集合  
    //调用myEach方法  
    myEach(arr,{  
        println it  
    });  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

输出:

爸爸
妈妈

原来流程是这样,当我们调用myEach方法时,要传入一个集合arr,和闭包。当方法,myEach在遍历集合的时候,每得到一个值 就会回调一下这个闭包并把值传给它,从这里看是不是觉得,这个闭包既像一个变量参数,又像一个方法呢。

定义一个闭包并调用它:

因为闭包是一种类型,所以在定义的时候要用=来给它赋值,这是跟方法有本质的区别

    def block={  
        println "调用block"  
    }  
    block()  
    输出:调用block  
    定义一个闭包并传入一个参数  
    def block={  
        println "调用block"+it  
    }  
    block("--并传入参数")  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

输出
调用block--并传入参数

要是我们不想用it这个关键字,也是可以定义自己的关键字的,只是我们要使用->来告诉它,是用我们自己定义的变量来接收值,而不用it

    def block={param ->  
        println "调用block"+param  
    }  
    block("--并传入参数")  
    //两个参数  
    def block={param1,param2->  
        println "调用block"+param1+param2  
    }  
    block("--参数1","参数2")  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

下面我们定义一个block,这个block是用来打印接收到的值,目的是去遍历给定的集合

    def block={data->  
        println "value="+data  
    }  
    //定义一个方法,这个方法接收一个集合参数跟一个闭包,当方法被调用时,遍历集合里面的值,并把值传回给闭包  
    def myEach(arry,block){  
        for(i in arry){  
            block(i)  
        }  
    }  
    def arr=["爸爸","妈妈","我"]//字符串集合  
    //调用myEach方法  
    myEach(arr,block)  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

输出:

value=爸爸
value=妈妈
value=我

我们知道方法的括号是可以去掉的,如果上面调用方法时我们去掉括号就变成了:

原来的是:myEach(arr,block)

myEach arr,block

而如果只是接收一个闭包的方法就可以写成:

myEach block

如果block是匿名的,那就成为:

myEach {

}
  • 1
  • 2
  • 3

所以我们可以知道,集合的each方法是怎么实现遍历的,当我们的这个集合已经知道了里面的值,调用each时,只需要传入一个闭包参数,就可以得到每一个值。

一般来说,如果有多个参数,而最后一个为闭包的时候,调用方法的时候习惯写成:

myEach(param1,param2,param3){

}
  • 1
  • 2
  • 3

实例:

定义一个方法,这个方法接收一个集合参数跟一个闭包,当方法被调用时,遍历集合里面的值,并把值传回给闭包

 def myEach(arry,block){  
        for(i in arry){  
            block(i)  
        }  
    }  
    def arr=["爸爸","妈妈","我"]//字符串集合  
    //调用myEach方法,并传入一个集合跟一个匿名的闭包  
    myEach (arr){data->  
        println "value="+data  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

到此差不多把groovy里面的一些基本的语法说完了。下面讲一下gradle

build.gradle文件

关于Android Studio构建文件 build.gradle 的相关配置,重点学习几个方面的内容:

  1. applicationId 和 package 属性值的关系
  2. 怎么配置安全的自定义签名
  3. 两种构建类型的区别
  4. 为什么要定制产品的偏好配置?
  5. 怎么才能加快DEX文件的生成速度
  6. 为什么要将一个apk拆分成多个?
  7. 关于引入依赖包你不知道的秘密

通过以下学习,你会对 build.gradle 文件有一个全新的认识
代码如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToulsVersion "25.0.2"
    /**
     * 一、默认产品偏好配置
     */
    defaultConfig {
        ...
    }
    /**
     * 二、自定义签名配置
     */
    signingConfigs {
        config {
          ...
        }
    }
    /**
     * 三、构建类型,分为release和debug两种
     */
    buildTypes {
        release {
          ...
        }
        debug {
          ...
        }
    }
    /**
     * 四、自定义产品偏好配置,可以定义多个偏好产品
     */
    productFlavors {
        demo {
            applicationId "cn.teahcourse.demo"
            versionName "1.0-demo"
            signingConfig signingConfigs.config
        }
        personal{
          ...
        }
        enterprise{
          ...
        }
    }
    /**
     *五、DEX文件构建属性配置(加快构建速度)
     */
    dexOptions {
        ...
    }
    /**
     * 六、将一个apk拆分成多个相关配置(拆分依据:屏幕密度、系统架构)
     */
    splits {
        density {
           ...
        }
        abi {
           ...
        }
    }
}
/**
 * 七、引入依赖包的秘密
 */
dependencies {
   ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

1. applicationId 和 package 属性值的关系

Android Studio开发工具创建module的时候,默认在 build.gradle 文件生成一个 applicationId ,对应的属性值是填写的package name,如下图:

这时候的 applicationId 和 package 属性值一样,刚开始接触Android Studio的时候,applicationId 表示真正的包名,而 package 不再被认为是包名,因为应用程序被打包成apk文件的时候,原先在manifest声明的 package 被 applicationId 代替,也就是说如果你的 build.gradle 文件添加了 applicationId 属性值,无论两者是否一样,打包的apk文件的 package 属性值等于 applicationId 。

如果不信,先来做个实验,将 applicationId 改为 cn.teachcourse.demo ,将 package 改为 cn.teachcourse ,然后将module打包成apk文件,使用反编译工具apktoul.exe,如下图

最后,打开 AndroidManifest.xml 文件,如下图:

结果证明: cn.teachcourse 被 cn.teachcourse.demo 代替

正是因为打包的apk文件的 package 的属性值被 applicationId 代替,也刚好说明为什么应用程序安装到手机后,手机上显示的是 applicationId ,而不是显示 package ,同时如果想在应用程序中接入第三方的API,填写的包名也必须是 applicationId ,常见的例子有:
1.接入微信的支付功能
2.接入微信的分享功能
3.集成百度地图的定位功能

那么,AndroidManifest.xml的 package 到底有什么用呢?尽管, package 在打包成apk的时候被 applicationId 代替,但是在构建的时候 package 有两方面的作用:

第一个作用:在 package 指定的目录下,生成对应的 R.java 文件,上面的例子,构建的时候,生成 R 文件的目录,如下图:

app\build\generated\source\r\demo\debug\cn\teachcourse\R.java

第二个作用:在 package 指定的目录下,引用对应的 activity 、 server 组件,如下图:

<!-- 定义Activity -->
<activity android:name=".MainActivity"/>

<!-- 添加service组件 -->
<service android:name=".service.music.MusicService" />
  • 1
  • 2
  • 3
  • 4
  • 5

在上面反编译的AndroidManifest.xml文件中,查看对应的组件目录,如下图:

也就是说,manifest指定的组件不管使用相对路径还是绝对路径,打包成apk文件后,都变成绝对路径,结构是:package.组件

需要特别注意的问题有:

第一个问题:代码中使用 getPackageName() 或 getPackageManager() 对应的方法,返回的是 applicationId 属性值

第二问题:使用 WebView 基本存放于 res/raw 内的文件时,如果 applicationId 不等于 package ,提示 ClassNotFoundException 异常(可能是官方的bug),测试后找到两个解决的办法

尝试将 res/raw/note.html 文件移动到 assets 文件夹下,更换资源文件加载路径

mWebView.loadUrl("file:///android_asset/note.html");
  • 1

保持 applicationId 属性值和 package 属性值一致

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="cn.teahcourse.demo">
     ...
    </manifest>
  • 1
  • 2
  • 3
  • 4
  • 5

2. 配置安全的自定义签名

自定义签名指的是使用开发者导出的密钥库对apk文件进行签名,关于怎么生成自己的密钥库,不懂的同学,可以后面看一下这篇文章《 Android Studio运行时自带签名配置过程详解 》,文章介绍了怎么配置Android Studio的运行时签名,这样做的目的:在接入一些需要自定义签名的API时,方便直接调试。

这里,介绍的是安全的自定义签名,即怎么才让别人看不到我们在 build.gradle 写入的密码(包括别名密码、密钥库密码),关于签名文件的重要性,在这里就不说了。

2.1 配置安全的自定义签名(1),步骤:
  • 在项目的根目录下创建一个名称为 keystore.properties 的文件。此文件应当包含您的签署信息,如下所示:
storePassword=myStorePassword
keyPassword=mykeyPassword
keyAlias=myKeyAlias
storeFile=myStoreFileLocation
  • 1
  • 2
  • 3
  • 4

这里需要注意:keystore.properties中 storeFile 签名文件是相对module目录的路径,即将密钥库文件保存在module根目录下

  • 在模块的 build.gradle 文件中,于 android {} 块的前面添加用于加载 keystore.properties 文件的代码。
...
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
 ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注:您可以选择将 keystore.properties 文件存储在其他位置(例如,存储在模块文件夹中而不是项目的根文件夹中,或者如果您使用连续集成工具,也可以存储在构建服务器上)。在这种情况下,您应当修改上面的代码,以便使用实际 keystore.properties 文件的位置正确初始化 keystorePropertiesFile。

  • 可以使用语法 keystoreProperties[‘属性名称’] 引用存储在 keystoreProperties 中的属性。修改模块 build.gradle 文件的 signingConfigs 块,以便使用此语法引用存储在 keystoreProperties 中的签署信息。
android {
 signingConfigs {
     config {
         keyAlias keystoreProperties['keyAlias']
         keyPassword keystoreProperties['keyPassword']
         storeFile file(keystoreProperties['storeFile'])
         storePassword keystoreProperties['storePassword']
     }
 }
 ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 最后,我们就可以按照《Android Studio运行时自带签名配置过程详解》介绍的方式,将 signingConfigs 块作用于release版本或debug版本
2.2 配置安全的自定义签名(2),步骤:

第二种安全的自定义签名的方式是:
将别名、别名密码、密钥密码以键值对的形式保存到当前电脑的环境变量中,然后通过变量名读取变量值

android {
 signingConfigs {
     config {
         keyAlias System.getenv("KEYALIAS")
         keyPassword System.getenv("KEYPWD")
         storeFile file('release.jks')
         storePassword System.getenv("KSTOREPWD")
     }
 }
 ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

KEYALIAS指的是环境变量的变量名, System.getenv(“KEYALIAS”) 的读取变量名对应的变量值,如下图:

KEYPWD,按照上图的方式添加,如下图:
这里写图片描述

KSTOREPWD以同样的方式,如下图:
这里写图片描述

需要特别注意的是:第二种自定义签名的方式,需要先检查Android Studio是否已配置了 gradle 工具的环境变量,打开Android Studio的terminal窗口,输入: gradle build ,如下图

这里写图片描述

如果,你的terminal窗口提示gradle不是内部命令,操作上述步骤之前,你得添加 gradle 工具的环境变量,Android Studio的 gradle 工具默认存放路径:

C:\Program Files\Android\Android Studio\gradle\gradle-3.2

配置 gradle 的环境变量,如下图:

这里写图片描述
这里写图片描述

三. 两种构建类型的区别

每一个APP至少包含 debug 和 release 两种构建类型, debug 定义APP的调试版本, debug 模式的几个特点:

  • 支持断点调试和log信息打印, debuggable 属性值为 true
  • 使用系统默认的密钥库签署apk文件
  • 没有对apk文件进行代码和资源文件的优化(包括文件压缩、冗余文件删除)
  • 没有对代码进行混淆

release 定义APP的发布版本,创建项目module中的 build.gradle 文件,代码如下:

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

minifyEnable 定义是否压缩代码, false 表示不压缩; proguardFiles 定义混淆代码的默认混淆规则, proguard-android.txt 表示系统自带的混淆规则, proguard-rules.pro 位于当前module根目录下,用于定义开发者自己的混淆规则。

release 模式需要注意的几个特点:

  • 不支持断点调试, debuggable 默认为false
  • 没有压缩类文件代码, minifyEnabled ,默认为false
  • 没有压缩资源文件, shrinkResources ,默认为false
  • 没有指定自定义签名文件,默认使用系统的密钥库签署apk

开发者在发布应用程序时,需要对 release 模式下的属性配置进行修改,优化apk文件,删除无用的代码和资源文件,混淆类文件和资源名称,自定义签名密钥库,代码如下:

release {
    shrinkResources true
    minifyEnabled true
    useProguard true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    signingConfig signingConfigs.config
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

总结: debug 和 release 模式,最大的区别默认属性配置不一样,两种模式支持的属性配置还包括,如下图:

debug
这里写图片描述

release
这里写图片描述

记不住代码的同学,可以选中Build Types定义的模式,在可选项中改变对应属性配置,Android Studio运行时签名的实质将 debug 模式下的 Signing Config 设置为自定义密钥库文件,但是TeachCourse随着不断深入学习后发现,其实 debug 模式下配置 Signing Config 是 多此一举 ,而只要在 release 模式下配置 Signing Config 就够了,Android Studio的可以方便为我们生成两种模式下对应的apk文件,在Android Studio的左下角Build Variant中切换,如下图:

这里写图片描述

下面介绍了 产品偏好配置 后,回头再看看它们两者之间的关系。

四. 为什么要定制产品的偏好配置?

什么是产品的偏好配置呢?比如说,TeachCourse想要开发一个应用程序,包含个人版本 personal 和企业版本 enterprise ,这两个版本之间在功能上有所区别,企业版自然比个人版功能要多一些,很明显就是要就一个Android项目打包成两个产品发布,它们之间的要求如下所示:

personal:版本号为1,最低SDK版本定义为11,最高SDK定义为24,版本名称后缀定义为-personal,applicationId后缀定义为-per,签名文件为自定义密钥库,代码如下:

personal {
    versionCode 1
    minSdkVersion 11
    targetSdkVersion 24
    versionNameSuffix '-personal'
    applicationIdSuffix '-per'
    signingConfig signingConfigs.config
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

enterprise:版本号为1000,最低SDK版本定义为11,最高SDK定义为24,版本名称后缀定义为-profession,applicationId后缀定义为-pro,签名文件为自定义密钥库代码如下:

enterprise {
    versionCode 1000
    minSdkVersion 11
    targetSdkVersion 24
    versionNameSuffix '-profession'
    applicationIdSuffix 'full'
    signingConfig signingConfigs.config
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

同时,定义第三个产品偏好配置为demo,用于上传GitHub,提供下载,代码如下:

demo {
    applicationId "cn.teahcourse.demo"
    versionName "1.0-demo"
    signingConfig signingConfigs.config
}
  • 1
  • 2
  • 3
  • 4
  • 5

一个Android项目,配置三个偏好的产品,即使修改了项目代码,也可以快速编译并打包三个apk文件,在Android Studio的左下角Build Variant中切换,如下图:

这里写图片描述

看上面的图片,你是不是发现了什么,突然间,三个偏好配置的产品,出现了 6 个变体,一个产品包含 debug 和 release 两个版本,构建类型和偏好产品之间的关系是:一个偏好产品,肯定包含一个debug版本和一个release版本,可以生成变体的总数为flavors2*,选中需要调试的版本或选中需要发布的版本,Android Studio自动重新构建Android项目,就可以针对指定的产品进行调试或打包,非常的方便吧!偏好产品相关配置,如下图:

这里写图片描述

defaultConfig 也属于其中一种偏好产品,在我们没有定义自己的偏好产品时,我们构建和编译的就是默认的 defaultConfig 这个产品,也就只包含 debug 和 release 两个变体。

五. 怎么才能加快DEX文件的生成速度?

你有没有遇到Android Studio在每次构建的时候,都感觉花好长时间,TeachCourse就不止一次和同事抱怨说,Android Studio的编译速度还不如Eclipse快,蜗牛的速度真受不了呀?那该怎么办呢?

Android Studio提供 dexOption 区块以便于我们配置 DEX 构建属性,加快DEX文件的生成速度,代码如下:

dexOptions {
    preDexLibraries true
    maxProcessCount 8
    javaMaxHeapSize "2048m"
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • preDexLibraries 声明是否预先编译依赖库,从而加快构建速度,实质是通过延时清除已生成的依赖库的构建文件,从而提高构建速度,根据使用情况合理配置。
  • maxProcessCount 设置进程运行过程中可以使用的最大线程数。默认值为4。
  • javaMaxHeapSize 设置DEX编译器的最大堆大小,堆或者栈都是用于存放暂时不用的垃圾,当内存不足时,垃圾回收机制会清除过时的缓存,堆大小决定垃圾清除的频率,影响着构建的速度

六. 为什么要将一个apk拆分成多个?

根据以往的经验,一个apk文件可以支持不同屏幕密度和不同ABIs的手机设备,是因为我们进行了屏幕适配,做法:将市场主流的屏幕密度和ABIs集成到一个apk,造成的影响,如果你的应用程序本身就比较大,集成了不同屏幕密度和支持不同ABIs的代码,打包出来的apk文件变得更大,考虑到流量成本和用户体验,减少apk文件的大小其中一种方式将一个apk文件拆分成多个。

Gradle能够单独指定只包含一种屏幕密度或一种ABI代码和资源文件的apk,在 build.gradle 文件中使用到 splits 区块, splits 区块内同时提供了按屏幕密度拆分的 density 区块和按abi拆分的 abi 区块,在一个 build.gradle 文件中可以同时指定两者或两者中的其中一者,下面分别介绍:

6.1 按屏幕密度拆分
android {
  ...
  splits {

    density {
      enable true
      exclude "xxxhdpi"
      reset() 
      include "ldpi", "xxhdpi"
      compatibleScreens 'small', 'normal', 'large', 'xlarge'
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面是一个按屏幕密度拆分的一个例子,各个标签的含义是:

  • enable ,是否基于定义的屏幕密度拆分成多个apk文件,默认为false
  • exclude ,指定忽略拆分的屏幕密度列表,想要拆分成更多类型的apk文件,该关键字包含的屏幕密度列表应就可能少
  • reset() ,重置默认拆分的屏幕密度依据,然后使用 include 标签定义拆分的屏幕密度依据
  • include ,结合 reset 一起使用,定义拆分的屏幕密度依据
  • compatibleScreens ,指定兼容的屏幕尺寸列表,区别于屏幕密度,该标签将会在清单文件 manifest 中通过 注入到每一个apk文件中,即apk文件只能安装到 指定尺寸的手机上

按照上面在 build.gradle 配置完成后,点击 Build APK 后,将在apk文件夹内生成多个apk文件,如下图:

这里写图片描述

为了验证是否在清单文件中注入指定屏幕尺寸,反编译其中一个apk文件,如下图:

这里写图片描述

6.2 按abi拆分
android {
  ...
  splits {

    abi {

      enable true
      reset()
      include "x86", "armeabi-v7a", "mips"
      universalApk false
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面是一个按abi拆分的一个例子,除了 universal 标签不一样外,其他标签是一样的,使用方法一样, include 标签定义拆分的abi依据,关于abi介绍,参考下面连接:

https://developer.android.google.cn/ndk/guides/abis.html

同样,点击 Build APK 后,将在apk文件夹内生成多个apk文件,如下图:

这里写图片描述

仔细观察生成的apk文件,会发现下面两个规律:

  • 第一个规律:apk总数=abi数量+density数量xabi数量
  • 第二个规律:apk filename=modulename-screendensityABI-buildvariant.apk

七. 关于引入依赖包你不知道的秘密

dependencies 区块引入的jar包的名称长,基本无法记住,每一节又表示什么含义?Android Studio引入依赖项有几种方式?让我先看下面的这个例子:

dependencies {
    compile project(":mylibrary")
    compile files('libs/zxing.jar')
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile group: 'com.android.support', name: 'appcompat-v7', version: '25.1.0'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看到Android Studio引入依赖项的方式分为上述四种,按顺序依次称为:
1、 模块依赖项
2、 本地二进制依赖项
3、 本地二进制依赖项
4、 远程二进制依赖项
5、 远程二进制依赖项

  • compile project(‘:mylibrary’) 行声明了一个名为 mylibrary 的本地 Android 库模块作为依赖项,并要求构建系统在构建应用时编译并包含该本地模块。
  • compile files(‘libs/zxing.jar’) 和 compile fileTree(dir: ‘libs’, include: [‘*.jar’]) 都称为本地依赖项,告诉构建系统在编译类路径和最终的应用软件包中包含 app/libs/ 目录内的指定或全部 JAR 文件。如果您有模块需要本地二进制依赖项,请将这些依赖项的 JAR 文件复制到项目内部的 /libs 中。
  • compile ‘com.android.support:appcompat-v7:25.1.0’ 和 compile group: ‘com.android.support’, name: ‘appcompat-v7’, version: ‘25.1.0’ 都称为远程二进制依赖项,通过指定其 JCenter 坐标,针对 Android 支持库的 25.1.0 版本声明了一个依赖项。默认情况下,Android Studio 会将项目配置为使用顶级构建文件中的 JCenter 存储区。当您将项目与构建配置文件同步时,Gradle 会自动从 JCenter 中抽取依赖项。或者,您也可以通过使用 SDK 管理器下载和安装特定的依赖项。

第五种可以清楚看出每一节表示的含义,在Android Studio引入远程二进制依赖项,通常的做法是在Library Dependency窗口中搜索,搜索到最新版本的依赖项,如下图:

这里写图片描述

似乎无法搜索到低版本的依赖项,如果想要引入低版本的,那该怎么办呢?如果先前不了解远程二进制依赖项的含义,可能想不到修改 version 的办法,现在就变得很简单了。

总结:

本篇文章在阅读Android Studio用户指南多篇相关文档后完成的,想要更详细深入学习 gradle 指令的同学,可以继续研读Gradle官网文档,部分内容在TeachCourse开发的项目没有对应的需求,暂时也没有用到,是否使用更多应该根据项目实际情况而定,但可以作为用户开发的例子,先分享和收藏,以备不时之需。

以上文章参考资料:

http://blog.csdn.net/huanchengdao/article/details/51472628
http://www.tuicool.com/articles/ZJJjqmj
https://developer.android.google.cn/studio/build/application-id.html
https://developer.android.google.cn/studio/build/optimize-your-build.html
https://developer.android.google.cn/studio/build/dependencies.html
https://developer.android.google.cn/studio/build/build-variants.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/293481
推荐阅读
相关标签
  

闽ICP备14008679号