Android-项目流程之基础篇(三)

本篇文章内容集中讨论项目规范、文件结构组织等宏观内容,目的是尽可能地以最少的努力去实现结构清晰的项目结构。

在公司写项目时编码规范要跟着公司规定走,个人做项目却不代表能完全自由发挥。因为个人随意制定的开发规范难以保证一致性,而且容易培养编程的坏习惯,另外在开源社区交流时也会造成误解和障碍。 总的来说,比较明智的做法是挑选网络上比较流行的大公司的编码规范方案,然后一直使用它。一般常见的 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
    • entities
    • network
    • local
  • 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
    • entities
    • network
    • local
  • di
    • 自定义 Qualifier
    • 自定义 Scopes
    • Application Component
    • Application Module
    • 其他统用 Component 和 Module
  • module1
    • contract:模块功能规划
    • 相关 activity
    • 相关 fragment
    • 相关 Dagger2 Module 和 Component
    • 相关 presenter
  • module2
  • common
    • utils
    • widgets
    • http
    • database

一般每一个功能模块会对应地有一个 presenter,也对应有一个 Dagger2 Module 和 Component 来管理依赖。

大中型项目功能较多,会涉及很多功能模块,common 包下的通用型文件也会明显增加。这个时候,就不应该再在一个 Android 应用模块中管理代码,要结合实际文件数量和代码耦合情况把各个包拆解为 Android Library,甚至有可能每个包还要再次拆解成多个 lib。最后,每个模块下是一个功能模块,或者是多个高耦合或关系密切的模块。 个人项目较少出现这样的情况,实际上完成这样的项目也非常耗费时间,不如直接参考公司的管理办法来管理项目文件。

Android Studio 默认的资源文件组织格式比较简洁,实际项目中不应该以默认的方式存放资源文件,要根据实际情况建立多个版本、情景下的资源文件,比如 drawable-xxhdpi、values-v21 等。当然,这样的目录最好不要手动添加,而是使用插件或着官方工具自动添加,比如:

add-drawable.PNG

add-resoureces.PNG

一般小型项目的依赖库直接写在 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 文件中,当然签名内容就不应该纳入版本管理了。