【Gradle】Android工程Gradle构建-笔记

678 阅读7分钟

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

image.png

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变体版本

image.png

针对不同版本的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>

image.png

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作为渠道统计的标识

image.png

修改生产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名字后如下图所示

image.png

在运行时获取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";
  ...
}

生成的资源属性如下

image.png

8、构建速度优化

8.1、构建速度优化方案

  1. 及时更新Gradle版本
  2. 构建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..."')
   }
}
  1. 使用本地缓存,开启Gradle离线模式,避免每次构建时从maven仓库查询是否有新版本

image.png 4. 组件化开发

8.2、使用profile分析构建性能

使用Gradle profile工具分析构建过程,点击右侧大象图标,输入如下命令,其中offline为关闭网络,rerun-tasks为重新执行所有任务,--profile为生成分析文件

gradle :app:assembleOppoNormalProdRelease --offline --rerun-tasks --profile

image.png 执行完命令后会在项目的根目录下的Build/reports/目录下生成分析报告的一个html

image.png 在浏览器打开后能看到总耗时,各个task耗时等指标

image.png

image.png

Demo源码地址

本文为博主原创,转载请注明出处