1、统一版本库管理
1.1、统一版本号管理
创建一个gradle文件统一管理 不同module下的第三方库和其他属性的配置参数 如下,在项目根目录创建config.gradle
ext {
COMPILE_SDK = 30
APPLICATION_ID = "com.chenyangqi.gradle"
MIN_SDK = 19
TARGET_SDK = 30
VERSION_CODE = 1
VERSION_NAME = "1.0.0"
JVM_TARGET = '1.8'
MULTIDEX = 'androidx.multidex:multidex:2.0.1'
CORE_KTX = 'androidx.core:core-ktx:1.3.2'
APPCOMPAT = 'androidx.appcompat:appcompat:1.2.0'
ANDROID_MATERIAL = 'com.google.android.material:material:1.3.0'
CONSTRAINTLAYOUT = 'androidx.constraintlayout:constraintlayout:2.1.0'
TEST_JUNIT = 'junit:junit:4.+'
ANDROID_EXT_JUNIT = 'androidx.test.ext:junit:1.1.2'
ANDROID_TEST_ESPRESSO = 'androidx.test.espresso:espresso-core:3.3.0'
}
然后在项目的gradle中引用config.gradle
apply from: project.file('config.gradle')
1.2、local.perporties使用场景
一些不便对外展示的敏感参数可以定义在local.properties中,如一些付费库的key,maven仓库的用户名和密码等
sdk.dir=/Users/mutou/Library/Android/sdk
username=chenyangqi
password=123456
编译时通过如下方式获取local.properties中定义的属性
Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())
def username = properties.getProperty('username')
def password = properties.getProperty('password')
在运行时获得Local.properties属性要借助BuildConfig
def getUsername() {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
return properties.getProperty("username");
}
android {
defaultConfig {
buildConfigField "String", "USERNAME", "\""+getUsername()+"\""
}
}
1.3、版本冲突处理
出现依赖冲突时可通过gradle的dependencies查看依赖树,定位冲突位置 ,比如我要查看的Build Variants为oppoNormalProdRelease,命令如下
./gradlew :app:dependencies --configuration oppoNormalProdReleaseCompileClasspath
依赖树如下
mutou@chenyangqi GradleDemo % ./gradlew :app:dependencies --configuration oppoNormalProdReleaseCompileClasspath
...
oppoNormalProdReleaseCompileClasspath - Compile classpath for compilation 'oppoNormalProdRelease' (target (androidJvm)).
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21
| +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.21
| | +--- org.jetbrains:annotations:13.0
| | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.21
| \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.21
| \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.21 (*)
+--- androidx.multidex:multidex:2.0.1
+--- androidx.core:core-ktx:1.3.2
| +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.71 -> 1.5.21 (*)
| +--- androidx.annotation:annotation:1.1.0
| \--- androidx.core:core:1.3.2
| +--- androidx.annotation:annotation:1.1.0
| +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.1.0
| | +--- androidx.lifecycle:lifecycle-common:2.1.0
| | | \--- androidx.annotation:annotation:1.1.0
| | +--- androidx.arch.core:core-common:2.1.0
| | | \--- androidx.annotation:annotation:1.1.0
| | \--- androidx.annotation:annotation:1.1.0
| \--- androidx.versionedparcelable:versionedparcelable:1.1.0
| +--- androidx.annotation:annotation:1.1.0
| \--- androidx.collection:collection:1.0.0 -> 1.1.0
| \--- androidx.annotation:annotation:1.1.0
+--- androidx.appcompat:appcompat:1.2.0
| +--- androidx.annotation:annotation:1.1.0
| +--- androidx.core:core:1.3.0 -> 1.3.2 (*)
| +--- androidx.cursoradapter:cursoradapter:1.0.0
| | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| +--- androidx.fragment:fragment:1.1.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
| | +--- androidx.collection:collection:1.1.0 (*)
| | +--- androidx.viewpager:viewpager:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | +--- androidx.core:core:1.0.0 -> 1.3.2 (*)
| | | \--- androidx.customview:customview:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | \--- androidx.core:core:1.0.0 -> 1.3.2 (*)
| | +--- androidx.loader:loader:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | +--- androidx.core:core:1.0.0 -> 1.3.2 (*)
| | | +--- androidx.lifecycle:lifecycle-livedata:2.0.0
| | | | +--- androidx.arch.core:core-runtime:2.0.0
| | | | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | | | \--- androidx.arch.core:core-common:2.0.0 -> 2.1.0 (*)
| | | | +--- androidx.lifecycle:lifecycle-livedata-core:2.0.0
| | | | | +--- androidx.lifecycle:lifecycle-common:2.0.0 -> 2.1.0 (*)
| | | | | +--- androidx.arch.core:core-common:2.0.0 -> 2.1.0 (*)
| | | | | \--- androidx.arch.core:core-runtime:2.0.0 (*)
| | | | \--- androidx.arch.core:core-common:2.0.0 -> 2.1.0 (*)
| | | \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0 -> 2.1.0
| | | \--- androidx.annotation:annotation:1.1.0
| | +--- androidx.activity:activity:1.0.0
| | | +--- androidx.annotation:annotation:1.1.0
| | | +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
| | | +--- androidx.lifecycle:lifecycle-runtime:2.1.0 (*)
| | | +--- androidx.lifecycle:lifecycle-viewmodel:2.1.0 (*)
| | | \--- androidx.savedstate:savedstate:1.0.0
| | | +--- androidx.annotation:annotation:1.1.0
| | | +--- androidx.arch.core:core-common:2.0.1 -> 2.1.0 (*)
| | | \--- androidx.lifecycle:lifecycle-common:2.0.0 -> 2.1.0 (*)
| | \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0 -> 2.1.0 (*)
| +--- androidx.appcompat:appcompat-resources:1.2.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.core:core:1.0.1 -> 1.3.2 (*)
| | +--- androidx.vectordrawable:vectordrawable:1.1.0
| | | +--- androidx.annotation:annotation:1.1.0
| | | +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
| | | \--- androidx.collection:collection:1.1.0 (*)
| | \--- androidx.vectordrawable:vectordrawable-animated:1.1.0
| | +--- androidx.vectordrawable:vectordrawable:1.1.0 (*)
| | +--- androidx.interpolator:interpolator:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | \--- androidx.collection:collection:1.1.0 (*)
| \--- androidx.drawerlayout:drawerlayout:1.0.0
| +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| +--- androidx.core:core:1.0.0 -> 1.3.2 (*)
| \--- androidx.customview:customview:1.0.0 (*)
+--- com.google.android.material:material:1.3.0
| +--- androidx.annotation:annotation:1.0.1 -> 1.1.0
| +--- androidx.appcompat:appcompat:1.1.0 -> 1.2.0 (*)
| +--- androidx.cardview:cardview:1.0.0
| | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| +--- androidx.coordinatorlayout:coordinatorlayout:1.1.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
| | +--- androidx.customview:customview:1.0.0 (*)
| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
| +--- androidx.constraintlayout:constraintlayout:2.0.1 -> 2.1.0
| +--- androidx.core:core:1.2.0 -> 1.3.2 (*)
| +--- androidx.dynamicanimation:dynamicanimation:1.0.0
| | +--- androidx.core:core:1.0.0 -> 1.3.2 (*)
| | +--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
| | \--- androidx.legacy:legacy-support-core-utils:1.0.0
| | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | +--- androidx.core:core:1.0.0 -> 1.3.2 (*)
| | +--- androidx.documentfile:documentfile:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | +--- androidx.loader:loader:1.0.0 (*)
| | +--- androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
| | | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | \--- androidx.print:print:1.0.0
| | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| +--- androidx.annotation:annotation-experimental:1.0.0
| +--- androidx.fragment:fragment:1.0.0 -> 1.1.0 (*)
| +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.1.0 (*)
| +--- androidx.recyclerview:recyclerview:1.0.0 -> 1.1.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
| | +--- androidx.customview:customview:1.0.0 (*)
| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
| +--- androidx.transition:transition:1.2.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.core:core:1.0.1 -> 1.3.2 (*)
| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
| +--- androidx.vectordrawable:vectordrawable:1.1.0 (*)
| \--- androidx.viewpager2:viewpager2:1.0.0
| +--- androidx.annotation:annotation:1.1.0
| +--- androidx.fragment:fragment:1.1.0 (*)
| +--- androidx.recyclerview:recyclerview:1.1.0 (*)
| +--- androidx.core:core:1.1.0 -> 1.3.2 (*)
| \--- androidx.collection:collection:1.1.0 (*)
...
BUILD SUCCESSFUL in 1s
1.3.1、去除冲突依赖
当确定最终只保留的版本时,过滤掉其他版本,如下只保留ore:0.9.5.0
api("com.afollestad.material-dialogs:core:0.9.5.0") {
exclude group: 'com.android.support'
}
1.3.2、CompileOnly只编译不打包
当我们开发SDK时如果需要依赖第三方库,使用CompileOnly引用第三方库,而让使用SDK的开发者去决定选择哪个版本的第三方库,避免他人调用你的SDK时出现版本冲突
1.3.3、通过gradle脚本检测依赖库版本
待实现...
2、MultiDex分包
2.1、65535产生原因
默认情况下,Android工程代码编译后的.class文件会打包进一个dex中,dex中每一个方法会使用C++中的unsigned short类型的字段标记,unsigned short取值范围为0~65535,所以一旦方法数超过上限就会造成65536
2.2、分包
通过分包解决65535问题,根据minSdk版本不同分两种情况
2.2.1、minSdk>=21时分包
在app module下的build.gradle中设置multiDexEnabled=true即可
android {
compileSdk 30
defaultConfig {
applicationId "com.chenyangqi.gradle"
minSdk 21
targetSdk 30
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
...
}
2.2.2、minSdk<21时分包
minSdk小于21时除了设置multidexEnabled=true还要引用androidx.multidex:multidex:2.0.1这个Google提供的分包库
android {
compileSdk 30
defaultConfig {
applicationId "com.chenyangqi.gradle"
minSdk 19
targetSdk 30
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
...
}
dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
...
}
然后再在application中继承分包库中的MultiDexApplication,又分三种情况
没有自定义Application时,直接在清单文件中注册name=MultiDexApplication
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.chenyangqi.gradle">
<application
android:name="androidx.multidex.MultiDexApplication"
...
</application>
</manifest>
有自定义Application时直接继承
class MyApplication:MultiDexApplication() {
}
当自定义Application已继承其他父类时重写attachBaseContext方法进行初始化
class MyApplication:OtherApplication() {
override fun onCreate() {
super.onCreate()
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
3、代码混淆
3.1、开启混淆
buildTypes {
debug {
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
设置minifyEnabled=true和shrinkResources=true启用压缩、混淆和优化功能, proguard-android-optimize.txt为存放在SDK中Android默认的混淆规则,存放路径~/SDK/tools/proguard/proguard-android-optimize.txt,proguard-rules.pro为项目中自己定义的混淆规则
3.2、常用混淆规则
关键字 | 描述 |
---|---|
keep | 保留类和类中的成员,防止被混淆或者移除 |
keepnames | 保留类和类中的成员,防止被混淆,但是当成员没有被引用时会被移除 |
keepclassmembers | 只保留类中的成员,防止他们被混淆或者移除 |
keepclassmembersnames | 只保留类中的成员,防止他们被混淆或者移除,但是当类中的成员没有被引用时还是会被移除 |
keepclasseswithmembers | 保留类和类中的成员,前提是指明的类中必须含有该成员,没有的话还是会被混淆 |
keepclasseswithmembersnames | 保留类和类中的成员,前提是指明的类中必须含有该成员,没有的话还是会被混淆。需要注意的是没有被引用的成员会被移除 |
关键字 | 描述 |
---|---|
<filed> | 匹配类中的所有字段 |
<method> | 匹配类中的所有方法 |
<init> | 匹配类中的所有构造函数 |
* | 匹配任意长度字符,但不含包名分隔符(.)。比如说我们的完整类名是com.example.test.MyActivity,使用com.*,或者com.exmaple.*都是无法匹配的,因为*无法匹配包名中的分隔符,正确的匹配方式是com.exmaple.*.*,或者com.exmaple.test.*,这些都是可以的。但如果你不写任何其它内容,只有一个*,那就表示匹配所有的东西。 |
** | 匹配任意长度字符,并且包含包名分隔符(.)。比如proguard-android.txt中使用的-dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包。 |
*** | 匹配任意参数类型。比如void set*(***)就能匹配任意传入的参数类型,*** get*()就能匹配任意返回值的类型。 |
… | 匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)这些方法。 |
3.3、Android常用混淆模板
#-------------------------------------------基本不用动区域--------------------------------------------
#---------------------------------基本指令区----------------------------------
-optimizationpasses 5
-dontskipnonpubliclibraryclassmembers
-printmapping proguardMapping.txt
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
#----------------------------------------------------------------------------
#---------------------------------默认保留区---------------------------------
#继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends androidx.multidex.MultiDexApplication
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep class android.support.** {*;}
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keep class **.R$* {
*;
}
-keepclassmembers class * {
void *(*Event);
}
#枚举
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#// natvie 方法不混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#---------------------------------webview------------------------------------
-keepclassmembers class android.webkit.WebView {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String);
}
#----------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------------
#---------------------------------实体类---------------------------------
#修改成你对应的包名
-keep class com.chenyangqi.gradle.proguard.bean.** { *; }
#---------------------------------第三方包-------------------------------
#---------------------------------反射相关的类和方法-----------------------
#---------------------------------与js互相调用的类------------------------
#---------------------------------自定义View的类------------------------
4、APK签名
如下,对release包进行签名
android {
...
signingConfigs {
release {
keyAlias 'chenyangqi'
keyPassword '123456'
storeFile file('../app_key.jks')
storePassword '123456'
}
}
buildTypes {
debug {
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
...
}
有些情况下生产jks时会出现如下警告提示,按照提示迁移jks格式即可,命令如下
keytool -importkeystore -srckeystore ./app_key.jks -destkeystore ./app_key.jks -deststoretype pkcs12
5、APK构建变体
在一些情况下需要在一个android工程需要输出不同的APK,比如为了照顾到低端机型运行流畅度,推出极速版APP,例如今日头条极速版,抖音极速版等,或者是免费版和VIP付费版APP等,所以需要构建APK变体
5.1 flavorDimensions和productFlavors
使用flavorDimensions定义要输出的APK的维度,比如这个栗子中的version和environment,version表示APP极速版和普通版这个维度,environment表示APP是生产环境还是系统集成测试环境,然后再通过productFlavors定义极速版和普通版的dimension为version,sit环境和prod环境的dimension为environment,两种维度排列组合后可以生成 极速版sit环境.apk、极速版prod环境.apk、普通版sit环境.apk、普通版prod环境.apk
不要使用中文,这里只是举栗子便于理解
android {
...
// 设置apk划分维度
// version用来表示APP版本,比如极速版,普通版,免费版付费版等
// environment用来表示运行环境,比如系统集成测试环境sit,和生产环境prod等
flavorDimensions "version", "environment"
productFlavors {
极速版 {
dimension "version"
}
普通版 {
dimension "version"
}
sit环境 {
dimension "environment"
}
prod环境 {
dimension "environment"
}
}
...
}
在左下角Build Variants面板选择你需要编译的apk变体版本
针对不同版本的APK可以定制不同的特性,比如applicationId,applicationName,logo等,也可以通创建BuildConfig的常亮值进行一些版本定制,如下例举了常用熟悉设置
android {
flavorDimensions "version", "environment"
productFlavors {
speed {
dimension "version"
versionNameSuffix ".speed"
applicationIdSuffix ".speed"
//设置混淆文件
//consumerProguardFiles 'consumer-rules.pro'
//配置差异化的logo和appName
manifestPlaceholders = [
logo : "@mipmap/ic_launcher_speed",
appName: "GradleDemo极速版"
]
//定义字段在BuildConfig中
buildConfigField('boolean',"isSpeed",'true')
}
normal {
dimension "version"
//配置差异化的logo和appName
manifestPlaceholders = [
logo : "@mipmap/ic_launcher",
appName: "GradleDemo"
]
buildConfigField('boolean',"isSpeed",'false')
}
sit {
dimension "environment"
versionNameSuffix ".sit"
applicationIdSuffix ".sit"
applicationIdSuffix ".sit"
buildConfigField('String','baseUrl','"https://sit..."')
}
prod {
dimension "environment"
buildConfigField('String','baseUrl','"https://prod..."')
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
通过manifestPlaceholders定义了logo和appName,然后在manifests设置如下,就可以修改各组的logo和APP名字
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="${logo}"
android:label="${appName}"
android:roundIcon="${logo}"
android:supportsRtl="true"
android:theme="@style/Theme.GradleDemo">
...
</application>
5.2、定义BilddConfig常量
buildConfigField('String','baseUrl','"https://sit..."')
通过buildConfigField方法,可以作为代码逻辑中区分不同版本业务区分的依据,如上定义了测试环境的BaseUrl,对应生成的BuildConfig如下
public final class BuildConfig {
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String APPLICATION_ID = "com.chenyangqi.gradle.speed.sit";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "speedSit";
public static final String FLAVOR_version = "speed";
public static final String FLAVOR_environment = "sit";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0.speed.sit";
// Field from product flavor: sit
public static final String baseUrl = "https://sit...";
// Field from product flavor: speed
public static final boolean isSpeed = true;
}
以上例举了常用的apk变体属性修改操作,更多的属性参考官方文档
6、多渠道打包
多渠道打包跟上面讲到的APK构建变体其实差不多,都是为了满足业务需求构建不同版本的apk,比如为了满足运营统计需要统计不同手机厂商渠道APP用户数量,我们一般会针对不同的引用市场发布不同的APK包,以oppo vivo 小米应用市场为例,首先使用meta-dta标签在manifest定义渠道标,然后增加一个flavorDimensions维度为channel如下
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="${logo}"
android:label="${appName}"
android:roundIcon="${logo}"
android:supportsRtl="true"
android:theme="@style/Theme.GradleDemo">
<meta-data
android:name="MTA_CHANNEL"
android:value="${MTA_CHANNEL_VALUE}" />
...
</application>
flavorDimensions "channel", "version", "environment"
productFlavors {
oppo {
dimension "channel"
manifestPlaceholders = [MTA_CHANNEL_VALUE: "oppo"]
}
vivo {
dimension "channel"
manifestPlaceholders = [MTA_CHANNEL_VALUE: "vivo"]
}
xiaomi {
dimension "channel"
manifestPlaceholders = [MTA_CHANNEL_VALUE: "xiaomi"]
}
...
}
执行./gradlew assembleRelease 命令就能够打包出对呀的渠道包,并生成对应的MTA_CHANNEL作为渠道统计的标识
修改生产APK名字,其中variant.productFlavors表示flavorDimensions定义的几种维度
applicationVariants.all { variant ->
variant.outputs.each { output ->
if (variant.buildType.name == 'release') {
def fileName = "GradleDemo_${variant.productFlavors[0].name}_" +
"${variant.productFlavors[1].name}_" +
"${variant.productFlavors[2].name}_" +
"${defaultConfig.versionName}" +
"_release.apk"
output.outputFileName = fileName
}
}
}
修改apk名字后如下图所示
在运行时获取meta-data方法如下
val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
val channelName = appInfo.metaData.getString("MTA_CHANNEL")
7、BuildConfig使用
BuildConfig可以在编译器生成常量字段和资源文件字段,如下在buildTypes下定义了一个BuildConfig.BUILD_TIME常量和资源resource变量title
buildTypes {
debug {
...
buildConfigField("String", "BUILD_TIME", ""${System.currentTimeMillis()}"")
resValue("String", "title", "debug首页标题")
}
release {
...
buildConfigField("String", "BUILD_TIME", ""${System.currentTimeMillis()}"")
resValue("String", "title", "release首页标题")
}
}
生成的BuildConfig如下
public final class BuildConfig {
...
public static final String VERSION_NAME = "1.0.0";
...
}
生成的资源属性如下
8、构建速度优化
8.1、构建速度优化方案
- 及时更新Gradle版本
- 构建minSDK>21的开发期变体APK,minSDK<21时需要做更多的版本适配和分包会相对耗时 如下,在sit系统集成测试包下设置minSDK=26
productFlavors {
...
sit {
dimension "environment"
versionNameSuffix ".sit"
applicationIdSuffix ".sit"
applicationIdSuffix ".sit"
buildConfigField('String', 'baseUrl', '"https://sit..."')
minSdkVersion 26
}
prod {
dimension "environment"
buildConfigField('String', 'baseUrl', '"https://prod..."')
}
}
- 使用本地缓存,开启Gradle离线模式,避免每次构建时从maven仓库查询是否有新版本
4. 组件化开发
8.2、使用profile分析构建性能
使用Gradle profile工具分析构建过程,点击右侧大象图标,输入如下命令,其中offline为关闭网络,rerun-tasks为重新执行所有任务,--profile为生成分析文件
gradle :app:assembleOppoNormalProdRelease --offline --rerun-tasks --profile
执行完命令后会在项目的根目录下的Build/reports/目录下生成分析报告的一个html
在浏览器打开后能看到总耗时,各个task耗时等指标