此章教隐藏证书
批量修改生成的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 super ExecSpec> 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 ,分辨率问题。