【Gradle】Android(安卓)Gradle 高级自定义
Android Gradle 高级自定义
使用共享库
Android的包,如android.app,android.content,android.view,android.widget等,是默认包含在Android SDK库里的,所有应用都可以直接使用它们。还有一些库,如com.google.android.maps,android.test.runner等,这些库是独立的,并不会被系统自动链接,所以如果要使用的话,就需要单独进行生成使用,这类库我们称为共享库。
在AndroidManifest.xml中,我们可以指定要使用的库:
这样我们就声明了需要使用maps这个共享库。声明之后,在安装生成的apk包的时候,系统会根据我们的定义,帮助检测手机系统是否有我们需要的共享库。因为我们设置的android:required="true",如果手机系统不满足,将不能安装该应用。
在Android中,除了标准的SDK,还存在两种库:一种是add-ons库,它们位于add-ons目录下,这些库大部分是第三方厂商或者公司开发的,一般是为了开发者使用,但又不想暴露具体标准实现;第二类是optional可选库,它们位于platforms/androi-xx/optional目录下,一般是为了兼容旧版本的API,比如org.apache.http.legacy。
对于第一类add-ons附件库来说,Android Gradle会自动解析,帮我们添加到classpath里。第二类optional可选库就不会,需要自己将这个可选库添加到classpath中。Android Gradle提供了useLibrary方法,让我们把一个库添加到classpath中。
android{ useLibrary 'org.apache.http.legacy'}
以上的配置已经可以生成APK,并能安装运行。但最好也要在AndroidManifest文件中配置一下uses-library标签,以防出现问题。
批量修改生成的apk文件名
Android对象为我们提供了3个属性:applicationVariants(仅仅适用于Android应用Gradle插件),libraryVariants(仅仅适用于Android库Grdle插件),testVariants(以上两种Gradle插件都适用)。
以上3个属性返回的都是DomainObjectSet对象集合,访问它们都会触发创建所有的任务。
以下为批量修改apk名称的示例:
android { compileSdkVersion 28 defaultConfig { applicationId "com.wangyz.gradle" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" flavorDimensions "default" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } productFlavors { baidu { } huawei { } } applicationVariants.all { variant -> variant.outputs.all { output -> if (output.outputFile != null && output.outputFile.name.endsWith('.apk') && 'release'.equals(variant.buildType.name)) { def flavorName = variant.flavorName.startsWith("_") ? variant.flavorName.substring(1) : variant.flavorName def fileName = "channel_${flavorName}_${variant.versionName}.apk" outputFileName = fileName } } }}
动态生成版本信息
一般的版本由3部分组成:major.minor.patch,第一个是主版本号,第二个是副版本号,第三个是补丁号。
最原始的方式
android { compileSdkVersion 28 defaultConfig { applicationId "com.wangyz.channel" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }}
分模块的方式
可以把版本号的配置单独抽取出来,放在单独的文件里,供build引用。
新建一个vesion.gradle文件:
ext{ appVersionCode = 1 appVersionName = "1.0.0"}
在build.gradle中引用它:
apply from:'version.gradle'android { compileSdkVersion 28 defaultConfig { applicationId "com.wangyz.channel" minSdkVersion 21 targetSdkVersion 28 versionCode appVersionCode versionName appVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }}
先使用apply from加载version.gradle脚本文件,这样它里面定义的扩展属性就可以使用了。
从git的tag中读取
想获取当前的tag名称,在git下非常简单,使用以下命令即可:
git describe --abbrev=0 --tags
知道了命令,那么如何在Gradle中动态获取呢?这就需要exec。Gradle提供了执行shell命令非常简便的方法,即exec。它是一个Task任务。
def getAppVersionName(){ def stdout = new ByteArrayOutputStream() exec{ commandLine 'git','describe','--abbrev=0','--tags' standardOutput = stdout } return stdout.toString()}
以上定义了一个获取版本名称的方法,通过该方法获取了git tag的名称后,就可以把它作为应用的版本名称,只要把versionName配置成这个方法就好了。
android { compileSdkVersion 28 defaultConfig { applicationId "com.wangyz.channel" minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName getAppVersionName() testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" }}
从属性文件中动态获取和递增
1、在项目目录下新建一个version.properties的属性文件
2、把版本名称分为3部分major.minor.patch,版本号分为一部分number,然后在properties新增4个KV键值对
3、在build.gradle新建一个方法用于读取该属性文件
隐藏签名文件信息
定义一个文件,用来保存签名的相关信息,如sign.properties,这个文件加入.gitignore,不上传到git中。通过读取这个文件来获取配置信息。
android{ signingConfigs{ release{ def signPropertiesFile = 'sign.properties' storeFile file(readSignProperties(signPropertiesFile,'storeFile')) storePassword readSignProperties(signPropertiesFile,'storePassword') keyAlias readSignProperties(signPropertiesFile,'keyAlias') keyPassword readSignProperties(signPropertiesFile,'keyPassword') } }}buildTypes{ release{ signingConfig signingConfigs.release }}def readSignProperties(String filePath,String key){ File file = file(filePath) if(file.exists()) { def Properties properties = new Properties() properties.load(new FileInputStream(file)) return properties[key] } return "file not exist!"}
sign.properties内容如下:
storeFile=android.keystorestorePassword=androidkeyAlias=androidkeyPassword=android
动态配置AndroidManifest文件
Android Gradle 提供了非常便捷的方法让我们来替换AndroidManifest文件中的内容,它就是manifestPlaceholder,Manifest占位符。
android{ productFlavors{ google{ manifestPlaceholders = [APP_CHANNEL:"google"] } baidu{ manifestPlaceholders = [APP_CHANNEL:"baidu"] } }}
在AndroidManifest文件中使用
如果需要批量修改(假设需要将名称改为和渠道名一样),可以通过productFlavors迭代方法:
android{ productFlavors{ google{ } baidu{ } } productFlavors.all{ flavor-> manifestPlaceholder = [APP_CHANNEL:name] }}
自定义BuildConfig
Android Gradle 提供了buildConfigField(String type,String name,String value)让我们添加自己的常量到BuildConfig中。使用示例:
android{ productFlavors{ google{ buildConfigField 'String','URL','"http://www.google.com"' } baidu{ buildConfigField 'String','URL','"http://www.baidu.com"' } }}
需要注意,value这个参数,是单引号中间的部分,尤其对于String类型的值,里面的双引号不能省略。value是什么就写什么,原封不动地放在单引号里。
上面是渠道,productFlavor,其实不光渠道可以配置自定义字段,构建类型BuildType也可以配置。如:
android{ buildTypes{ debug{ buildConfigField 'String','NAME','"zhangsan"' } }}
动态添加自定义的资源
实现这一功能的方法是resValue方法。它在BuildType和ProductFlavor这两个对象中存在。它会生成一个资源,效果和在res/values文件中定义一个资源是等价的。
resValue方法有三个参数,第一个是type,也就是你要定义资源的类型,比如有string,id,bool等;第二个是name,也就是定义资源的名称,以便在工程中引用;第三个是value,就是定义资源的值。
android{ productFlavors{ google{ resValue 'string','tip','hello' } baidu{ resValue 'string','tip','hi' } }}
Java编译选项
Android对象提供了一个compileOptions方法,接受一个CompileOptions类型的闭包作为参数,来对Java编译选项进行配置:
android{ compileOptions{ encoding 'utf-8' sourceCompatibility JavaVersion.VERSION_1_6 targetCompatibility JavaVersion.VERSION_1_6 }}
CompileOptions是编译配置,它提供三个属性,分别是encoding,sourceCompatibility,targetCompatibility,通过对它们进行设置来配置Java相关的编译选项。
sourceCompatibility是配置Java源代码的编译级别
targetCompatibility是配置生成的Java字节码的版本
adb操作选项配置
在Android Gradle 中,为我们预留了对adb的一些选项的控制配置,它就是adbOptions{}闭包。
android{ adbOptions{ timeOutInMs 5*1000 installOptions '-r','-s' }}
DEX选项配置
Android Gradle 提供了dexOptions{}闭包,让我们可以对dx操作进行一些配置。
android{ dexOptions{ incremental true }}
突破65535方法限制
Java源文件被打包成一个DEX文件,这个文件就是优化过的、Dalvik虚拟机可执行的文件,Dalvik虚拟机在执行DEX文件时,使用了short类型来索引DEX文件中的方法,这就意味着单个DEX文件可以被定义的方法最多只有是65535,当定义的方法数量超过时,就会出错。
Android官方给出的解决方案:Multidex。对于Android5.0以后的版本,使用了ART的运行方式,可以天然支持App有多个DEX文件,ART在安装App的时候执行预编译,把多个DEX文件合并成一个oat文件执行。对于Android5.0之前的版本,Dalvik虚拟机限制每个App只能有一个class.dex,要使用它们,就得使用Android提供的Multidex库。
要在项目中使用Multidex,首先要修改Gradle build配置文件,启用Multidex,并同时配置Multidex需要的jar依赖。
android{ defaultConfig{ multiDexEnabled true }}dependencies{ implementation 'com.android.support:multidex:1.0.1'}
配置好之后,开启了Multidex,会让我们的方法多于65535个的时候生成多个DEX文件,名字为classes.dex,classes(...n)这样的形式。但是对于Android5.0以前的系统虚拟机,它只认识一个DEX,名字还是classes.dex,所以想达到程序可以正常运行的目的,也要让虚拟机把其它几个生成的classes加载进来。要做到这步,就必须在App程序启动的入口控制,这个入口就是Application。
Multidex提供现成的Application,名字是MultiDexApplication,如果我们没有自定义Applicaiton的话,直接使用MultiDexApplication即可,在Manifest清单文件中配置:
...
如果有自定义的Application,并且是直接继承自Application,那么只需要把继承改为我们的MultiDexApplication即可。
如果自定义的Application是继承自第三方提供的Application,就不能改继承了,这个时候可以重写attachBaseContext方法来实现:
@Overrideprotected void attachBaseContext(Context base){ super.attachBaseContext(base); MultiDex.install(this);}
虽然有了解决65535问题的方法,但还是要尽量避免我们工程中的方法超过65535.首先不能滥用第三方库,如果引用,最好也要自己精简。精简后,还要使用ProGuard减小DEX的大小。还有因为Dalvik linearAlloc的限制,尤其在2.2和2.3版本上,只有5MB,到Android 4.0的时候升级到8MB,所以低于4.0的系统上dexopt的时候可能会崩溃。
自动清理未使用的资源
Android Gradle 为我们提供了在构建打包时自动清理未使用的资源的方法,这个就是Resource Shrinking。
Resource Shringking要结合Code Shringking一起使用,即我们开发中经常使用的ProGuard,也就是我们要启用minifyEnabled,是为了减缩代码。
android{ buildTypes{ release{ minifyEnabled true shringkResources true ... } }}
自动清理未使用的资源这个功能虽然好用,但是有时候会误删有用的程序,因为我们在代码编写的时候,可能会使用反射去引用资源,尤其很多的第三方库会这么做,这个时候Android Gradle就区分不出来,可能会误认为这些资源不有被使用。针对这种情况,Android Gradle提供了keep方法来让我们配置哪些资源不被清理。
keep方法使用非常简单,我们要新建一个xml文件来配置,这个文件是res/raw/keep.xml,然后通过tools:keep属性来设置。这个tools:keep接受一个以逗号分隔的配置资源列表,并且支持星号*通配符。
<?xml version="1.0" encoding="utf-8">
keep.xml还有一个属性是tools:shrinkMode,用于配置自动清理资源的模式,默认是false,是安全的。
除了shrinkResources之外,Android Gradle 还提供了一个resConfigs,它属于ProductFlavor的一个方法,可以让我们配置哪些类型的资源才会被打包进APK中。
android{ defaultConfig{ resConfigs 'zh' }}
上面代码表示,只保留zh资源。
更多相关文章
- Android(安卓)拷贝Asset目录下文件或者文件夹
- android Java 提交数据到服务器的两种方式中四种方法
- Android(安卓)fragment生命周期解析
- NDK入门
- Android(安卓)平板开发关闭一个Fragment的方法
- Android如何判断设备为Pad?
- Android之 自定义属性 的使用
- Android开发之屏幕元素层次结构
- Android(安卓)开发艺术探索之二 -- IPC 机制