《Gradle 权威指南》读书笔记——第九章 Android Gradle 高级自定义

此章教隐藏证书
批量修改生成的apk文件名
突破65535方法限制

使用共享库
Android的包(比如android.app android.content android.view android.widge等)是默认就包含在Android SDK中的,系统会帮我们自动链接它们;
但有些库是需要我们去AndroidManifest.xml中配置后才能使用(如com.google.android.maps android.test.runner)等,需要单独去生成,这些库被称为共享库

//声明需要使用共享库后,在安装时需要手机系统没有该共享库,那么该应用不能被安装

三种共享库:

  • 标准的AndroidSDK,

  • add-ons库:
    位于add-ons目录下,一般是第三方公司开发的,为了让开发者们使用但又不想暴露具体实现;
    AndroidGradle会自动解析,添加到classpath中,
    add-ons文件夹该目录中存放 Android 的扩展库,比如 Google Maps,但若未选择安装 Google API,则该目录为空。

  • optional库
    位于platforms/android-xx/optional目录下,一般是为了兼容旧版本.(如org.apache.http.legacy是httpClient库,api23后sdk移除了该库,如需要则必须使用可选库)
    不会自动解析并添加到classpath中,所以需要我们手动解析

    //仅仅是为了保证编译通过
          //最好在AndroidManifest.xml中也要配置
          //PackageManager().getSystemSharedLibraryNames();
          android{
              useLibrary 'org.apache.http.legacy'
          }
    

批量修改生成的apk文件名称
Andoird工程相对Java工程来说,要复杂的多,因为它有很多相同的任务,这些任务的名称是通过BuildTypes和ProductFlavors动态创建和生成的(通过project.tasks无法获取任务,因为还无生成).

为了解决这个问题,Android对象提供了三个属性,这三个属性都是DomainObjectSet对象集合

1.applicationVariants 仅适用于Android应用插件

2.libraryVariants 仅适用于Android库Gradle插件

3.testVariants 以上两种都适用

注意这三种集合都会触发创建所有的任务,这以为着访问这些集合后不需要重新配置就会产生

public DomainObjectSet getApplicationVariants(){
    return applicationVariantList;
}

实现修改apk文件的需求

android{
    ...
    useLibrary 'org.apache.http.legacy'
    buildTypes{
        realeas{
        }
    }
    productFlavors{
        google{
        }
    }
    applicationVariants.all{
        variant->
        variant.outputs.each{
            output->
            if(output.outputFile!=null && output.outputFile.name.endsWith('.apk')
                && 'release'.equals(variant.buildType.name)){
                    println "variant:${variant.name}___output:${output.name}"
                    def file = new File(output.outputFile.parent,"my_${variant.name}.apk")
                    output.outputFile=file
                }
        }
    }
}


applicationVariants是一个DomainObjectCollection集合,通过all()遍历,遍历的每个variant是一个生成的产物,
生成数量为 productFlavor * buildType 个.
applicationVariant具有一个outputs作为它的输出,outputs是一个List集合

动态生成版本信息
在build中配置,但是不方便修改,一般格式 major.minor(.patch)

分模块设置版本信息

//version.gradle
ext{
    appVersion=1
    appVersionName="1.0.0"
}
​
//build.gradle
apply from:'version.gradle'
android{
    ...
    defaultConfig{
        ...
        versionCode appVersion
        appVersionName appVersionName
    }
}

从Git的tag中获取

//git 中获取tag的命令
git describe --abbrev=0 --tags

在Gradle中执行Shell命令

//推荐
ExecResult exec(Closure closure);
ExecResult exec(Action action);
//闭包委托给ExecSpec
public interface ExecSpec extends BaseExecSpec {
    void setCommandLine(Object... args);
    void setCommandLine(Iterable args);
    ExecSpec commandLine(Object... args);
    ExecSpec commandLine(Iterable args);
    ExecSpec args(Object... args);
    ExecSpec args(Iterable args);
    ExecSpec setArgs(Iterable args);
    List getArgs();
}

//定义一个方法
def getAppversion(){
        def os = new ByteArrayOutputStream()
    exec{
        //貌似亲测不行,找不到名称,但其他命令可以
//        commandLine 'git','describe','--abbrev=0','--tags'
//        commandLine 'git','status'
        standardOutput=os
    }
    return "mytask:"+os.toString()
}
​
//使用该方法
android{
    defaultConfig{
        versionName getAppversion()
    }
}
​
​


task mytask {
    def os = new ByteArrayOutputStream()
    exec{
        //貌似亲测不行,找不到名称
//        commandLine 'git','describe','--abbrev=0','--tags'
//        commandLine 'git','status'
        standardOutput=os
    }
    println "mytask:"+os.toString()
}
​

隐藏签名文件信息
保存到服务器中,以环境变量的方式读取
首先,你得有一个专门打包发版的服务器
并配置对应的环境变量

android{
    ...
    signingConfigs{
        def appStoreFile=System.getenv("STORE_FILE")
        def appStorePassword=System.getenv("STORE_PASSWORT")
        def appKeyAlias=System.getenv("KEY_ALIAS")
        def appKeyPassword=System.getenv("KEY_PASSWORD")
​
        //当不能从当前环境变量中获取时则使用Debug签名
        //从AndroidSdk(${Home}/.android/)中复制Debug签名到工程目录中
        if(!appStoreFile||!appStorePassword||!appKeyAlias||!appKeyPassword){
            appStoreFile="debug.keystore"
            appStorePassword="android"
            appKeyAlias="androiddebugkey"
            appKeyPassword="android"
        }
        release{
            storeFile file(appStoreFile)
            storePassword appStorePassword
            keyAlias appkeyAlias
            keyPassword appKeyPassword
        }
    }
    buildTypes{
        release{
            signingConfig signConfigs.release
            zipAlignEnabled true
        }
    }
}

动态配置AndroidManifest.xml
在构建过程中动态的修改配置文件

,如 友盟第三方分析统计的时候会要求我们

//AndroidManifest.xml

但配置文件只有一个.

为了解决这个问题,AndroidGradle提供了非常便捷的manifestPlaceholder Manifest占位符.

ManifestPlaceholder是ProductFlavor的一个属性:Map,所以我们可以同时配置多个占位符

android{
    ...
    productFlavor{
        google{
            manifestPlaceholder.put("UMENG_CHANNEL","google")
        }
        baidu{
            manifestPlaceholder.put("UMENG_CHANNEL","baidu")
        }
    }
    //也可以一次性修改
    productFlavor.all{
        flavor->
        manifestPlaceholder.put("UMENG_CHANNEL",name)
    }
}

//在配置文件中是,未验证,但应该不需要在配置文件中写这行,${UMENG_CHANNEL}就是占位符,到时 baidu ,google会替代${UMENG_CHANNEL}内容

自定义BuildConfig
BuildConfig是由AndroidGradle编译自动生成的

public final class buildConfig{
    //是否是debug模式
    public static final boolean DEBUG=Boolean.parseBoolean("true")
    //包名
    public static final String APPLICATION_ID="org.flysnow.app.projectName"
    //构建类型
    public static final String BUILD_TYPE="debug"
    //产品风格
    public static final String FLAVOR="baidu"
    //版本号和版本名称
    public static final int VERSION_CODE=1
    public static final String VERSION_NAME="xx.1.0"
}
​

自定义BuildConfig

android{
    ...
    productFlavors{
        google{
            //注意'""'中的""不能省略,否则生成的类型是String WEB_URL=http://www.google.com
            buildConfigField 'String','WEB_URL','"http://www.google.com"'
        }
        baidu{
            buildConfigField 'String','WEB_URL','"http://www.baidu.com"'
        }
    }
    //因为BuildType也是一种productFlavor,所以... debug ,release版本跟衍生版本都可以用  buildConfigField!
    buildType{
        debug{
            buildConfigField 'String','NAME','"value"'
        }
    }
}

动态添加自定义的资源
仅针对res/values资源
它们不光可以在res/values.xml中定义,还可以在AndroidGradle中定义. 也可以在 BuildType 使用

//product.Flavor.resValue源码
//由注释可知它会生成一个资源,其效果和在res/values文件中定义是一样的
    public void resValue(
            @NonNull String type,
            @NonNull String name,
            @NonNull String value) {
        ClassField alreadyPresent = getResValues().get(name);
        if (alreadyPresent != null) {
            logger.info("BuildType({}): resValue '{}' value is being replaced: {} -> {}",
                    getName(), name, alreadyPresent.getValue(), value);
        }
        addResValue(new ClassFieldImpl(type, name, value));
    }
​
​
//demo
android {
    ...
    buildTypes {
        debug {
            zipAlignEnabled true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt')
            , 'proguard-rules.pro'
            //string id bool dimen integer color
            resValue 'string','BaseUrl','http://www.baidu.com'
        }
    }
}
//会在build/generated/res/resValues/baidu/debug/values/generated.xml

    http://www.baidu.com
  

Java编译选项
在AndroidGradle中对Java源文件的编码 源文件使用的JDK版本进行修改

android{
    ...
    compileOptions{
        encoding='utf-8'
        sourceCompatibility=JavaVersion.VERSION_1_6   //配置Java源代码的编译级别  可用的值 1."1.6" ,2.1.6, 3. JavaVersion.Version_1_6 4."Version_1_6"
        targetCompatibility=JavaVersion.VERSION_1_6   // 配置生成的 Java字节码的版本 
    }
}
​

Adb操作选项配置
adb,Android Debug Bridge,用于连接电脑和设备的进行一些调试操作.
在Shell中我们可以通过输入adb来查看其功能和使用说明.
在Gradle中我们也可以有一些配置

android{
    ...
    adbOptions{
        //超时则抛出CommandRejectException
        timeOutInMs 5*1000
        //详情见下图
        setInstallOptions '-r','-s'
    }
}
​

setInstallOptions

-l:锁定该应用程序
-r:替换已经存在的程序,也就是强制安装
-t:允许测试包
-s:把应用安装到sd卡上
-d:允许进行降级安装
-g:给该应用授权所有运行时的权限

DEX选项配置
Android中的源码被编译成class文件后,在打包成apk文件时又被dx命令优化成Android虚拟机可执行的dex文件.
对于这些dex文件的生成和处理,AndroidGradle会自动调用android SDK的dx命令.

但是有时候也会出现内存不足的异常(java.lang.OutOfMemoryError),因为该命令其实就是一个脚本(dx.jar),由Java程序执行的.
由错误信息可知,默认分配的是G8(1024MB)
我们也可以通过 -j 参数配置

dexOptions{
    //是否开启增量模式,增量模式速度会更快,但可能会出现很多问题,一般不开启 慎用
    incremental true
    //分配dx命令的堆栈内存
    javaMaxHeapSize '1024mb'
    //65536后能构建成功
    jumboMode true
    //配置是否预执行dex Library库工程,开启后会大大加快增量构建的速度,不过clean构建的速度
    //默认true,但有时需要关闭这个选项(如mutil dex)
    preDexLibraries false
    //dx命令时的线程数量,适当的线程数量可以提高dx 的效率
    threadCount 2
}

//源码
public interface DexOptions {
    boolean getPreDexLibraries();
    boolean getJumboMode();
    boolean getDexInProcess();
    boolean getKeepRuntimeAnnotatedClasses();
    String getJavaMaxHeapSize();
    Integer getThreadCount();
    Integer getMaxProcessCount();
    List getAdditionalParameters();
}

解决64K异常
随着业务越来越复杂,特别是集成第三方jar包
因为Dalvik虚拟机使用了short类型做作为dex文件中方法的索引,也就意味着单个dex文件只能拥有65536个方法

首先使用的Android Build Tools和Android Support Repository到21.1
其次在Gradle中开启

//没超过只会有一个dex文件
//开启后会生成class.dex .. calssn.dex
android{
    defaultConfig{
        ...
        multiDexEnabled true
    }
}

之后得这样设置

//但在5.0前只认一个dex,所以需要在入口中配置
//没有自定义applcation时

自动清理未使用的资源的Gradle配置
使用Android Lint检测没有使用的资源手动删除
Resource Shrinking
在构建时,会检测所有资源,看看是否被引用(不管是不是第三方),没有被引用的资源则不会被打包的apk中.
一般Resource Shrinking要配合混淆使用,混淆时会清理无用代码,这样无用代码引用的资源也会被移除

android{
  ...
  buildTypes{
        release{
        //通过日志输出可以看到哪些文件被清理了
            minifyEnabled true
            shrinkResource true
        }
    }
}
​

但有时候通过反射引用资源文件的时候,使用到的资源文件也会被删除,所以我们需要保存某些资源文件

//res/raw/keep.xml,该文件不会被打包进apk


//keep.xml还有一个属性是tools:shrinkMode,用于配置清理模式
默认safe是安全的,可以识别getResource().getIdentifier("unused","drawable",getPackageName())
如果改成strict则会被删除

resConfigs中配置,resConfigs 属于 ProductFlavor 的一个方法
使用GoogleMaps时因为国际化的问题,我们可能并不需要其中的某些文件,我们只需要其中一些语言就行了
resConfigs是ProductFlavor的一个方法,它的参数就是我们常用的资源限定符

android{
  ...
  defaultConfig{
        ...
        //打包时仅保留中文
        resConfig 'zh'
        //一次配置多个
        resConfigs{
​
        }
    }
}
​

resConfig 参数不止语言,还有Api Level ,分辨率问题。

你可能感兴趣的:(Android,Gradle,AS)