配置 Android 项目-重要的小事情

说明:如果你发现文章中有翻译不恰当的地方,欢迎指出,我会马上改正。

本文是配置 Android 项目系列的一部分:

  1. Little Things That Matter
  2. Version Name & Code
  3. Static Code Analyses Tools
  4. Continuous Integration

系列翻译:

  1. 重要的小事情
  2. 版本名和版本号
  3. 静态代码分析工具
  4. 持续集成

我们在这篇文章中讨论的一切都可以在 template 项目中找到

gitignore

当你在 Android Studio 中创建一个新的 Android 项目时,它就已经生成了 gitignore 文件,但是通常并不包含所有必要的规则。

为了快速生成和下载 gitignore 文件,我推荐你使用 gitignore.io 网站。只要输入必要的关键词,例如 Android,Intellij,然后点击生成按钮。

查看 template 项目的 gitignore 文件。

tools folder

如果你有一些第三方脚本、规则集或者其他和你项目相关的文件,不要放在根目录下,会造成混乱。(特别是哪些使用项目视图,而不是 Android 视图)

尝试创建一个文件夹(例如:tools),并把这些文件放入这个文件夹。

通常我会放入自定义的 gradle 脚本文件、混淆 (proguard) 规则和静态代码分析工具,例如:pmdfindbugslint

查看 template 项目的 tools 文件夹

flavors

Flavors 用于创建不同设置的构建。在大多数情况下,我会设置两种风格,它们的不同在于:

  • applicationId
  • versionCode / versionName
  • server endpoints
  • google services keys
1
2
3
4
5
6
7
8
9
10
11
12
13
productFlavors {
dev {
signingConfig signingConfigs.debug
versionCode gitVersionCodeTime
versionName gitVersionName
}

prod {
signingConfig signingConfigs.release
versionCode gitVersionCode
versionName gitVersionName
}
}

查看 template 项目的 productFlavors

keystore

密钥库是一个二进制文件,包含一个或多个私钥用于签名你的应用程序。

当你在 IDE 中运行或者调试项目,Android Studio 会通过 Android SDK 工具生成一个调试证书自动的签名你的 APK。

使用本地调试密钥库时有几个问题:

  • 365天期满
  • 通过多台电脑安装应用需要先卸载
  • 谷歌的服务需要密钥库 SHA-1 指纹

这就是为什么我通常生成调试密钥库提交到版本控制系统。

1
2
3
4
5
6
7
8
9
10
11
signingConfigs {
debug {
keyAlias 'androiddebugkey'
keyPassword 'android'
storePassword 'android'
storeFile file('../keystore/debug.keystore')
}
release {
...
}
}

查看 template 项目的 signingConfigs

proguard

Android 上的混淆器用于三件事:

  • 缩小未使用的代码,帮助你免于64K限制 (64k limit)
  • 优化代码和 APK
  • 混淆代码,使你的 APK 难逆向工程

问题是,混淆和代码优化显著增加编译时间,使调试更难。

这就是为什么最好是针对发布和调试构建使用不同的混淆器规则:

  • rules-proguard.pro
  • rules-proguard-debug.pro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
"$project.rootDir/tools/rules-proguard.pro"
signingConfig signingConfigs.release
}
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
"$project.rootDir/tools/rules-proguard-debug.pro"
signingConfig signingConfigs.debug
}
}

对于调试构建,混淆器规则必须具备以下内容,强制忽略警告,跳过代码混淆与优化:

1
2
3
4
# Add project specific ProGuard rules here.
-dontobfuscate
-dontoptimize
-ignorewarnings

对于发布版本设置混淆器规则是很难的,因为几乎每一个库都会有它自己特定的规则。幸好有开源库 android-proguard-snippets,包含所有主要库的混淆规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Add project specific ProGuard rules here.

# Remove logs
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}

# Proguard configurations for common Android libraries:
# https://github.com/krschultz/android-proguard-snippets

查看 template 项目的 rules-proguard.prorules-proguard-debug.pro

strict mode

Android 的严格模式 (StrictMode) 帮助你检测不同种类的问题:

  • 可以关闭的对象没有关闭
  • 在主线程执行文件读取和网络请求
  • 暴露 uri

每当检测到这样的问题,它可以显示适当的日志或让应用程序崩溃,这取决于你的配置。

我建议你在调试版本中打开它,并且使用 detectAll 方法检测各种问题。

1
2
3
4
5
6
7
8
9
10
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
}

以下是示例 log,当你忘记关闭 SQLiteCursor :

1
2
3
4
5
6
7
8
9
10
11
12
StrictMode: 
A resource was acquired at attached stack trace but never released.
See java.io.Closeable for information on avoiding resource leaks.
java.lang.Throwable: Explicit termination method 'close' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:184)
at android.database.CursorWindow.<init>(CursorWindow.java:111)
at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:139)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:197)
at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:237)
at com.dd.template.MainActivity.onCreate(MainActivity.java:124)

查看 template 项目的 StrictMode