Gradle技巧和遇到的问题

Gradle 是什么

是构建工具,不是语言
它用了 Groovy 这个语言,创造了一种 DSL,但它本身不是语⾔

packagingOptions

packagingOptions常见的设置项有exclude、pickFirst、doNotStrip、merge。

1. exclude,过滤掉某些文件或者目录不添加到APK中,作用于APK,不能过滤aar和jar中的内容。

1
2
3
4
packagingOptions {
exclude 'META-INF/**'
exclude 'lib/arm64-v8a/libmediaplayer.so'
}

2. pickFirst,匹配到多个相同文件,只提取第一个。只作用于APK,不能过滤aar和jar中的文件。

1
2
3
4
packagingOptions {
pickFirst "lib/armeabi-v7a/libaaa.so"
pickFirst "lib/armeabi-v7a/libbbb.so"
}

3. doNotStrip,可以设置某些动态库不被优化压缩。

1
2
3
4
packagingOptions{
doNotStrip "*/armeabi/*.so"
doNotStrip "*/armeabi-v7a/*.so"
}

4. merge,将匹配的文件都添加到APK中,和pickFirst有些相反,会合并所有文件。

1
2
3
4
packagingOptions {
merge '**/LICENSE.txt'
merge '**/NOTICE.txt'
}

compile, implementation 和 api

implementation:不会传递依赖
compile / api:会传递依赖;
从 Gradle 3.0 版本开始,compile 废弃

二级依赖 0 级项目
当依赖被传递时,二级依赖的改动会导致 0 级项目重新编译;
当依赖不传递时,二级依赖的改动不会导致 0 级项目重新编译

gralde.wrapper 所有项目的gradle的下载地址,就不用把gradle上传上去了
我们可以把 compile 替换为 provided 关键字 provided 的意思是只在编译时会用到相应的 jar 包,打包成 apk 后 jar 包并不会在 apk 中存在

compileOnly

父组件实现 implementation,子组件 compileOnly,这样组件可以编译期有,运行时没有(可以用到父组件的)

只要确保有一个 module 中该依赖能参与到打包即可。

编译时仅需要其 API,但具体实现由别的 module 1实现
所以 compileOnly 经常用于解决依赖冲突等问题,一般第三方库中,比较常用的依赖,如 support、gson、Eventbus 等等。

Gradle 常用命令

Mac 配置 adb 环境、gradlew:command not found
./gradlew build –stacktrace > logs.txt 2>logErrors.txt
输出错误日志

gradlew assembleDebug
gradlew tinkerPatchDebug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 查看构建版本
./gradlew -v
# 清除build文件夹
./gradlew clean
# 检查依赖并编译打包
./gradlew build
# 编译并安装debug包
./gradlew installDebug
# 编译并打印日志
./gradlew build --info
# 译并输出性能报告,性能报告一般在 构建工程根目录 build/reports/profile
./gradlew build --profile
# 调试模式构建并打印堆栈日志
./gradlew build --info --debug --stacktrace
# 强制更新最新依赖,清除构建并构建
./gradlew clean build --refresh-dependencies

# 编译并打Debug包
./gradlew assembleDebug
# 这个是简写 assembleDebug
./gradlew aD
# 编译并打Release的包
./gradlew assembleRelease
# 这个是简写 assembleRelease
./gradlew aR

./gradlew app:dependencies 去除重复依赖库优化

混淆恢复1
android-sdk/tools/proguard/bin/proguardgui.bat

减少apk体积

  • Android Studio 3.0 推出了新 Dex 编译器 D8 与新混淆工具 R8,目前 D8 已经正式 Release,大约可以减少 3% 的 Dex 体积。
    Android Studio 使用 R8(它使用 ProGuard 规则文件)来压缩代码,
    排查 R8 问题:
    https://developer.android.google.cn/studio/build/shrink-code?hl=zh-cn#shrink-code

  • MultiDex.install(this);分多个dex包

  • 使用andresguard,路径变成了r/d/a,还有Android 编译过程中,下面这些格式的文件会指定不压缩;在 AndResGuard 中,我们支持针对 resources.arsc、PNG、JPG 以及 GIF 等文件的强制压缩。

  • 移出无用的资源

1
2
3
4
5
6
7
8
9
10
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
}
}
}

  1. minifyEnabled 会对代码进行混淆和压缩,shrinkResources 会对比R文件对无用资源进行删除
  2. minifyEnabled 设置为true时shrinkResources 的设置才会生效

常用注解

添加 @Nullable 和 @NonNull 注释,以检查给定变量、参数或返回值的 null 性
public abstract void setTitle(@StringRes int resId)
其他资源类型的注释(例如 @DrawableRes、@DimenRes、@ColorRes 和 @InterpolatorRes
@MainThread@UiThread@WorkerThread
 @IntRange、@FloatRange 和 @Size 
 @RequiresPermission

id可以不同,装两个类似应用

1
2
3
4
5
6
7
8
9
10
android {       
defaultConfig {
applicationId "com.example.myapp"
}
productFlavors {
free { applicationIdSuffix ".free" }
pro { applicationIdSuffix ".pro" }
}
}

配置动态版本代码 :不用自己手改了

优化构建速度
https://developer.android.google.cn/studio/build/optimize-your-build?hl=zh-cn

Gradle 自动化构建

BuildTypes、Flavors、BuildVariants
BuildTypes:构建类型,AndroidStudio的Gradle组件默认提供给了“debug”“release”两个默认配置,此处用于配置是否需要混淆、是否可调试等
productFlavors:产品渠道,默认不提供任何默认配置,在实际发布中,根据不同渠道,我们可能需要用不同的包名,服务器地址、收费或付费等
BuildVariants:每个buildtype和flavor组成一个buildvariant

Build->Generate Signed APK,补充好签名信息,可以看到Flavors多了两个我们添加的渠道,这时候选择一个然后开始构建,看效果吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
buildTypes {
release {
debuggable true
minifyEnabled true //启用Proguard
shrinkResources true //是否清理无用资源,依赖于minifyEnabled
zipAlignEnabled true //是否启用zipAlign压缩
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
debuggable true
minifyEnabled false //不启用Proguard
shrinkResources false //是否清理无用资源,依赖于minifyEnabled
zipAlignEnabled false //是否启用zipAlign压缩
signingConfig signingConfigs.debug
}
}

技巧:可以创建个debug、debug文件夹,写个工具类,不同包BuildTypes就会执行不同的逻辑,会和默认的合并。比如,debug的设置图标蓝色,其他红色

升级3.0引起的问题(butterknife)

1
2
3
4
5
Error:Cannot choose between the following configurations of project :XXX:
- debugApiElements
- debugRuntimeElements
- releaseApiElements
- releaseRuntimeElements

解决办法:
1、project的build.gradle文件,删除“apt”配置:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //删除
2、module的build.gradle文件,删除“android-apt”引用:
apply plugin: 'android-apt' //删除
3、module的build.gradle文件,修改“dependencies”中的“apt”方式 为“annotationProcessor”方式:
apt "引用项" //老方式,删除 annotationProcessor "引用项" //新方式

Android项目目录

(我觉的挺好,就拿别人的放到这里来了
70d88bd0-fd3f-491a-b998-922b636717b1

gralde缓存目录

Windows用户 C:\Users(用户)\username.gradle\caches\modules-2\files-2.1
linux/mac用户 ~/.gradle/caches/modules-2/files-2.1

全局变量的使用

在多个module的情况下,不同module的build.gradle文件中有部分配置项类似,或者依赖的类库,有部分是相同的,在维护上不是很方便,这个时候就可以考虑统一配置。在项目根目录的build.gradle文件中添加以下代码和android{}同级

1
2
3
4
5
6
ext {
//全局变量控制,可在module中的build.gradle文件通过rootProject.ext.xxx开头来使用
compileSdkVersion = 24
buildToolsVersion = '24.0.3'
supportVersion = '24.2.1'
}

配置打包用的签名

主要有接过分享或者授权登录功能的都应该知道,像微信或者微博的分享和授权登录提供sdk,只有在指定的签名下才能生效,而我们平时开发都习惯使用默认的androidkeystore打包签名,这个时候想要测试分享或者登录功能就需要手动去打包指定keystore的签名。非常影响开发效率,这个时候可以通过配置gradle,根据release或者是debug打包指定的签名。
项目根目录新建一个签名用到的密码管理文件signing.properties

1
2
3
4
signing.alias=dou361            #release
signing.password=dou361 #release
signing.jjdxm_alias=dou361 #debug
signing.jjdxm_password=dou361 #debug

在主程序build.gradle的apply plugin: ‘com.android.application’下面添加

1
2
Properties props = new Properties()
props.load(new FileInputStream(file(rootProject.file("signing.properties"))))

在android{}节点里面添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
signingConfigs {
release {
keyAlias props['signing.alias']
keyPassword props['signing.password']
storeFile file(rootProject.file("debug.keystore"))
storePassword props['signing.password']
}

debug {
keyAlias props['signing.jjdxm_alias']
keyPassword props['signing.jjdxm_password']
storeFile file(rootProject.file("debug.keystore"))
storePassword props['signing.jjdxm_password']
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

运行不同的接口环境的apk

项目可以通过定义多个不同的productFlavors来实现应用的不同定制版本,每一个Flavor与buildTypes配合产出对应的一种输出类型的apk文件,新建的项目初始化只有一个默认的Flavor:defaultConfig

1
2
3
4
5
6
7
8
9
10
productFlavors {
//接口正式环境还是测试环境
env_public {
buildConfigField "boolean", "isTestEnv", "false"
}

env_test {
buildConfigField "boolean", "isTestEnv", "true"
}
}

跟buildTypes结合就有四种Build Variants(构建变种)。可以不修改代码直接运行相应的apk
8b9f9a3f-7382-4781-a829-cf0294777d38

会自动运行到BuildConfig里,可以判断不同的值去加载不同的接口环境

1
2
3
4
5
6
7
8
    /**
* 是否测试环境
*/
public static boolean isTest() {
return BuildConfig.isTestEnv;
}

ServiceInfoManager.getInstance().setEnv(IqbConfig.isTest() ? ServiceInfoManager.Environment.TestEnv : ServiceInfoManager.Environment.PublicEnv);

为什么 BuildConfig.DEBUG 始终为 false

  • BuildConfig.java 是编译时自动生成的,并且每个 Module 都会生成一份,以该 Module 的 packageName 为 BuildConfig.java 的 packageName。所以如果你的应用有多个 Module 就会有多个 BuildConfig.java 生成,编译时被依赖的 Module 默认会提供 Release 版给其他 Module 或工程使用,这就导致该 BuildConfig.DEBUG 会始终为 false。
  • AndroidManifest.xml 中 application 节点的 android:debuggable 值是不同的。Debug 包值为 true,Release 包值为 false,这是编译自动修改的。可以通过 ApplicationInfo 的这个属性去判断是否是 Debug 版本。

http://www.trinea.cn/android/android-whether-debug-mode-why-buildconfig-debug-always-false/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AppUtils {

private static Boolean isDebug = null;

public static boolean isDebug() {
return isDebug == null ? false : isDebug.booleanValue();
}
public static void syncIsDebug(Context context) {
if (isDebug == null) {
isDebug = context.getApplicationInfo() != null &&
(context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
}
}
//在自己的 Application 内调用进行初始化
AppUtils.syncIsDebug(getApplicationContext());

找不到so新建jniLibs,将so放进来

1
2
3
4
5
6

sourceSets {
main {
jniLibs.srcDir 'jniLibs'
}
}

配置gradle离线工作

在gradle中引用第三方模块时采用maven方式依赖,每次打开Android Studio或者点击sync按钮时,都会去maven中央仓库去取第三方的库文件,一般是jar或者aar文件。如果下载完可以配置gradle离线工作 ,勾选gradle会使得速度更快,但同时存在一个问题,如果需要从网上加载第三方库,会无法下载,所以酌情使用。所以需要没有的第三方模块记得把这个关了。
external libraries下的库在C:\Users\用户名.gradle\caches\modules-2\files-2.1下,或者右击某个库下面的某个类,点击file path就可以查

在 debug 模式下产生 release 版本

有时候调试SDK必须要用release版本,例如地图、登录,但是每次打包混淆太麻烦,希望能在IDE中直接跑出release版本的应用,简单来说就是在debug模式下产生release版本的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//配置keystore签名
signingConfigs {
release {
storeFile file("KeyStore")
storePassword "98765432"
keyAlias "lyly"
keyPassword "98765432"
}
}

appbuildTypes {
debug {
signingConfig signingConfigs.release
}
release {
signingConfig signingConfigs.release
}
}

这样编译出来的debug版本直接用的是正式签名。

依赖包重复的问题

遇到这样的一个错误:com.android.dex.DexException: Multiple dex files define XXXX,一般情况下,是我们项目中引用了重复的库或者jar包引起的,我们找到去掉即可解决
294fb60f-e597-4a1e-b8e6-cf4dc8572c56

com.loonggg.saoyisao.lib:1.1.0 这个依赖里引用了第三方zxing。com.timmy.qrcode.lib:1.4.1这个依赖里也引用了zxing这个库,在com.timmy.qrcode.lib:1.4.1的依赖里添加语句 exclude group: ‘com.google.zxing’,意思是编译的时候将group为com.google.zxing的所有library都去除在外,这样com.timmy.qrcode.lib:1.4.1就会自动去引用com.loonggg.saoyisao.lib:1.1.0项目里的zxing依赖了。这样问题就解决了。

过滤日志

^(?!.* (你要过滤掉的tag)).* $
^(?!.* (UserConnection|BroadcastConnection)).*
logcat

判断不同机型 Rom

根据Build.BRAND 字段判断不同机型Rom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Rom {

private Rom() {
//no instance
}

/**
* 是否是Oppo
*/
public static final boolean IS_OPPO;
/**
* 是否是Vivo
*/
public static final boolean IS_VIVO;
/**
* 是否是华为,注意不包括华为荣耀
*/
public static final boolean IS_HUAWEI;
/**
* 是否是华为荣耀
*/
public static final boolean IS_HUAWEI_HONOR;
/**
* 是否是三星
*/
public static final boolean IS_SAMSUNG;
/**
* 是否是努比亚
*/
public static final boolean IS_NUBIA;

static {

final String brand = Build.BRAND.toUpperCase();

IS_OPPO = brand.equalsIgnoreCase("OPPO");
IS_VIVO = brand.equalsIgnoreCase("VIVO");
IS_HUAWEI = brand.equalsIgnoreCase("HUAWEI");

IS_HUAWEI_HONOR = brand.contains("HONOR");
IS_SAMSUNG = brand.contains("SAMSUNG");

IS_NUBIA = brand.contains("NUBIA");
}

}

isRunAlone

通过在组件工程下的gradle.properties文件中设置一个isRunAlone的变量来区分不同的场景,在组件的build.gradle开头这样写:

1
2
3
4
5
if(isRunAlone.toBoolean()){    
apply plugin: 'com.android.application'
}else{
apply plugin: 'com.android.library'
}

Gradle技巧和遇到的问题
http://peiniwan.github.io/2024/04/b43718b5322d.html
作者
六月的雨
发布于
2024年4月6日
许可协议