本篇文章内容集中讨论项目规范、文件结构组织等宏观内容,目的是尽可能地以最少的努力去实现结构清晰的项目结构。
在公司写项目时编码规范要跟着公司规定走,个人做项目却不代表能完全自由发挥。因为个人随意制定的开发规范难以保证一致性,而且容易培养编程的坏习惯,另外在开源社区交流时也会造成误解和障碍。
总的来说,比较明智的做法是挑选网络上比较流行的大公司的编码规范方案,然后一直使用它。一般常见的 Java 编码规范有:
上面的链接都指向了 Github 项目,可以在其中找到 Itellij 的 Code Style 设置文件。把相应的 xml 文件下载到本地之后,在 Android Studio 的 File -> Settings -> Code Style 选项中可以直接导入配置选项,然后就不用去记忆各种繁琐的规条了。另外,还可以搜索安装 CheckStyle 插件来进一步检查代码风格是否符合规范。
当然,更简单的方法是下载每个规范的专有的 Intellij 插件,然后进行开发即可。毕竟插件可以提供更丰富的功能,也省去了导入、定制的麻烦——尤其 CheckStyle 插件并没有比较完备的统一的 Android 规范方案,自己写又太费事了。
我个人比较偏爱 google-java-format 插件,安装好之后在 File -> Settings -> Other settings -> google-java-format Settings 中可以选择 Android Open Source Project(AOSP) Style。这个规范是更适合 Android 开发,文字指导可以参见 面向贡献者的代码样式指南。
而阿里的插件功能更丰富,中文提示更方便,不过它更偏向于传统大公司的 Java 项目,并不是完全适合个人 Android 开发,是否选用就看个人洗好了。
最后,还可以通过 Android Lint 工具改进项目编码质量,具体的教程可以参考 Android Developer 的官方教程 使用 Lint 改进您的代码。
一般来说,项目的文件结果不是固定的,通常是因项目的规模变化而变化。
如果是类似本文的仅有一两个小功能的示例项目,一般分层或分类管理源码文件,可以用如下方式组织:
- base
- 自定义 Application
- BaseActivity
- BaseFragment
- BaseView
- BasePresenter
- configs
- API 配置文件
- 数据库配置文件
- SharedPreferences 配置文件
- model
- presenter(可选)
- di(特指 Dagger2,可选)
- 自定义 Qualifier
- 自定义 Scopes
- Application Component
- Application Module
- ui:UI 层相关 Module
- model:Model 层相关 Module
- ui
- activities
- fragments
- adapters
- common:跨项目通用的代码
- utils
- widgets
- http:http 封装
- database:数据库封装
因为,简单的项目 ui 包下的文件不会过多,这样的方式足够使用。甚至因为功能简单,很多包未必会建立,实际的项目中的目录结构总是很简单的。
还要注意这其中的命名规范:
- 表示抽象概念(某一层)的包,如 ui、model、presenter 等,一般是单数
- 表示各种同类文件分类收集的包,如 utils、configs 等,一般是复数
这种项目的功能明显增多,分层或分类的方法可能造成单个包内的文件数量激增(一般是 ui),应该按照功能模块来组织管理,比如:
- base
- 自定义 Application
- BaseActivity
- BaseFragment
- BaseView
- BasePresenter
- configs
- API 配置文件
- 数据库配置文件
- SharedPreferences 配置文件
- model
- di
- 自定义 Qualifier
- 自定义 Scopes
- Application Component
- Application Module
- 其他统用 Component 和 Module
- module1
- contract:模块功能规划
- 相关 activity
- 相关 fragment
- 相关 Dagger2 Module 和 Component
- 相关 presenter
- module2
- common
一般每一个功能模块会对应地有一个 presenter,也对应有一个 Dagger2 Module 和 Component 来管理依赖。
大中型项目功能较多,会涉及很多功能模块,common 包下的通用型文件也会明显增加。这个时候,就不应该再在一个 Android 应用模块中管理代码,要结合实际文件数量和代码耦合情况把各个包拆解为 Android Library,甚至有可能每个包还要再次拆解成多个 lib。最后,每个模块下是一个功能模块,或者是多个高耦合或关系密切的模块。
个人项目较少出现这样的情况,实际上完成这样的项目也非常耗费时间,不如直接参考公司的管理办法来管理项目文件。
Android Studio 默认的资源文件组织格式比较简洁,实际项目中不应该以默认的方式存放资源文件,要根据实际情况建立多个版本、情景下的资源文件,比如 drawable-xxhdpi、values-v21 等。当然,这样的目录最好不要手动添加,而是使用插件或着官方工具自动添加,比如:
一般小型项目的依赖库直接写在 app 模块的 build.gradle 文件中即可,最多在每组依赖语句前加入注释以方便管理。这样的好处有:
- Android Studio 会自动探查依赖库的版本更新
- 而且可以使用 Project Structure 去查询依赖,只要输入关键字即可
但是,如果项目依赖过多,尤其有多个 lib 需要进行依赖管理时,应该在 project 根目录下建立 dependencies.gradle 文件来统一管理项目依赖库版本,其示例内容如下:
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
| project.ext {
android = [
compileSdkVersion: 25,
buildToolsVersion: "25.0.2",
minSdkVersion : 16,
targetSdkVersion : 25,
versionCode : 1,
versionName : "1.0"
]
supportVersion = "25.3.1"
rxBindingVersion = "2.0.0"
butterKnifeVersion = "8.5.1"
daggerVersion = "2.10"
rxLifecycleVersion = "2.0.1"
retrofit2Version = "2.2.0"
espressoVersion = "2.2.2"
mockitoVersion = "2.7.22"
supportDependencies = [
"support-v4" : "com.android.support:support-v4:${supportVersion}",
"appcompat-v7" : "com.android.support:appcompat-v7:${supportVersion}",
"design" : "com.android.support:design:${supportVersion}",
"recyclerview" : "com.android.support:recyclerview-v7:${supportVersion}",
"cardview" : "com.android.support:cardview-v7:${supportVersion}",
"constraint-layout": "com.android.support.constraint:constraint-layout:1.0.2",
]
// dependencies injection
diDependencies = [
//dagger
"dagger" : "com.google.dagger🗡${daggerVersion}",
"dagger-compiler" : "com.google.dagger:dagger-compiler:${daggerVersion}",
//butter knife
"butterknife" : "com.jakewharton:butterknife:${butterKnifeVersion}",
"butterknife-compiler": "com.jakewharton:butterknife-compiler:${butterKnifeVersion}",
]
databaseDependencies = [
"sqlbrite": "com.squareup.sqlbrite:sqlbrite:1.1.1",
"debug-db": "com.amitshekhar.android:debug-db:1.0.0"
]
rxDependencies = [
"rxjava" : "io.reactivex.rxjava2:rxjava:2.0.8",
"rxandroid" : "io.reactivex.rxjava2:rxandroid:2.0.1",
"rxbinding" : "com.jakewharton.rxbinding2:rxbinding:${rxBindingVersion}",
"rxbinding-support-v4" : "com.jakewharton.rxbinding2:rxbinding-support-v4:${rxBindingVersion}",
"rxbinding-appcompat-v7" : "com.jakewharton.rxbinding2:rxbinding-appcompat-v7:${rxBindingVersion}",
"rxbinding-design" : "com.jakewharton.rxbinding2:rxbinding-design:${rxBindingVersion}",
"rxbinding-recyclerview-v7": "com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:${rxBindingVersion}",
"rxlifecycle" : "com.trello.rxlifecycle2:rxlifecycle:${rxLifecycleVersion}",
"rxlifecycle-android" : "com.trello.rxlifecycle2:rxlifecycle-android:${rxLifecycleVersion}",
"rxjava2-interop" : "com.github.akarnokd:rxjava2-interop:0.9.5"
]
networkDependencies = [
"okHttp" : "com.squareup.okhttp3:okhttp:3.7.0",
"retrofit2" : "com.squareup.retrofit2:retrofit:${retrofit2Version}",
"retrofit2-rxjava2": "com.squareup.retrofit2:adapter-rxjava2:${retrofit2Version}",
"retrofit2-gson" : "com.squareup.retrofit2:converter-gson:${retrofit2Version}",
]
testDependencies = [
//resolve conflicts
"jsr305" : "com.google.code.findbugs:jsr305:3.0.1",
//unit test
"junit4" : "junit:junit:4.12",
//mockito
"mockito-all" : "org.mockito:mockito-all:2.0.2-beta",
"mockito-core" : "org.mockito:mockito-core:${mockitoVersion}",
"mockito-android": "org.mockito:mockito-android:${mockitoVersion}",
"mockito-inline" : "org.mockito:mockito-inline:${mockitoVersion}",
//hamcrest
"hamcrest-all" : "org.hamcrest:hamcrest-all:1.3",
//instrumental test
"espresso-core" : "com.android.support.test.espresso:espresso-core:${espressoVersion}",
]
}
|
然后在需要的地方可以用如下方式添加依赖:
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
| dependencies {
def supportDependencies = rootProject.ext.supportDependencies
def testDependencies = rootProject.ext.testDependencies
def diDependencies = rootProject.ext.diDependencies
def databaseDependencies = rootProject.ext.databaseDependencies
def rxDependencies = rootProject.ext.rxDependencies
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation supportDependencies['appcompat-v7']
implementation supportDependencies['support-v4']
implementation supportDependencies.design
implementation supportDependencies.recyclerview
implementation supportDependencies.cardview
implementation diDependencies.dagger
annotationProcessor diDependencies['dagger-compiler']
implementation diDependencies.butterknife
annotationProcessor diDependencies['butterknife-compiler']
implementation rxDependencies.rxjava
implementation rxDependencies.rxandroid
implementation rxDependencies['rxjava2-interop']
implementation databaseDependencies.sqlbrite
implementation databaseDependencies['debug-db']
testImplementation testDependencies.junit4
testImplementation testDependencies['mockito-all']
testImplementation testDependencies['hamcrest-all']
androidTestImplementation(testDependencies['espresso-core'], {
exclude group: 'com.android.support', module: 'support-annotations'
})
}
|
另外,发布应用要用到的签名内容也应该以这样的方式去管理,而不是直接把签名密码等敏感信息写入到模块的 build.gradle 文件中,当然签名内容就不应该纳入版本管理了。