这篇笔记整理自 CodePath Android Cliffnotes 里的 Organizing your Source Files, 再结合我自己的理解,聊聊一个 Android 项目的源码可以怎么收拾得干干净净。

主要围绕四件事:

  • 命名要有自我介绍的能力
  • 包结构要让人一眼看懂东西在哪儿
  • res 目录别变成垃圾场
  • 布局文件太多时,用一点小工具帮你收纳

先把名字起好:Java 与 Android 类的命名约定

代码整理的第一步,其实就是把名字起对。名字对了,很多问题不用解释。

Java 基本命名

在 Java 代码里,可以坚持这几条简单规则:

变量、方法、参数:用驼峰命名(camelCase

  int incomeTaxRate;
  int daysInMonth;

  void convertToEuroDollars() { ... }

  void depositAmount(double amount) { ... }

常量:全部大写,用下划线分隔单词

  public static final int DAYS_IN_WEEK = 7;

这些规则本身并不新鲜,但统一才是关键:团队里所有人都按这套来, 哪怕是第一次看你代码的人,也能靠直觉分辨“这是变量、这是常量、这是方法”。

Android 特有类的命名

Android 里有一批“角色”非常固定:ActivityFragmentAdapterService、数据库辅助类、网络层客户端等等。 文章建议是让类名把自己的“角色”写清楚。

例如:

  • CreateTodoItemActivity:一看就是一个界面。
  • TodoItemsAdapter:配合列表用的适配器。
  • TodoItemsDbHelper:操作 ToDo 数据库的 helper。
  • TodoClient:封装网络请求的客户端。
  • TodoItemFragment:某个局部 UI 的碎片。
  • FetchTodoService:在后台拉取 ToDo 数据的服务。

规范很简单:把业务名 + 角色名拼在一起

好处有两个:

  1. 在 IDE 左侧文件树里扫一眼,就能大致脑补出“这几个类在界面里是怎么配合的”。
  2. 搜索时非常方便,比如查 *Activity 就能获得所有页面入口。

其他类型的类(比如纯工具、业务对象),可以按自己团队的习惯来,核心思路还是那句:看到名字就应该猜到它是干嘛的、属于哪一层。

怎样划分包

类名起好了,下一步就是把它们装进合适的抽屉,也就是包结构。

原文给了两种主流方案:按类别分,或按功能分。两种都常见,各有适用场景。

按类别分包:结构简单,适合体量不大的项目

这种方式,就是把“同一类角色”的东西放在一起:

  • com.example.myapp.activities:放所有 Activity
  • com.example.myapp.fragments:放所有 Fragment
  • com.example.myapp.adapters:放所有自定义 Adapter
  • com.example.myapp.models:放所有数据模型
  • com.example.myapp.network:网络访问相关
  • com.example.myapp.utils:工具方法、辅助类
  • com.example.myapp.interfaces:接口定义

优点很直观:第一次进项目的人,能很快找到想看的那类文件。想看所有页面,就打开 activities 包;想看所有网络调用,就打开 network 包。

缺点也很明显:当项目长大之后,一个业务页面可能要同时修改 ActivityFragmentAdapterModel,这些文件散落在不同的包下,来回跳转比较频繁。

所以我更愿意把这种结构用在:Demo、小工具、教学项目,或者刚起步的产品。这时候代码量不大,“按类别收纳”足够了。

按功能(Feature)分包:项目一旦变复杂,就该上场

另一种做法是:以业务功能为中心组织包结构

大致思路是:

  • com.example.myapp.service:后台任务、同步服务等
  • com.example.myapp.ui:所有和界面相关的东西
  • com.example.myapp.ui.mainscreen:主列表页面相关类
  • com.example.myapp.ui.detailscreen:详情页面相关类
  • 以及其他功能模块,如 loginscreensettings

这样,一个功能模块需要的 ActivityFragmentAdapterViewModel(如果有)都聚在一起。

优点:

  1. 开发时心智负担小:我要改详情页,只要打开 detailscreen 包。
  2. 更适合多人协作:一个人负责一个功能包,冲突会少很多。
  3. 后期做“按模块拆库”时,也更容易抽取出完整的模块。

从 Java 开发的经验来看,按功能打包通常比按类别更适合中大型项目,原文也明确给了这样的建议。

一个折中做法是:项目刚开始按类别分包,等功能逐渐稳定、体量上来以后,慢慢往按功能分包演进,而不是一开始就大动干戈。

别把 res 目录搞成垃圾堆

写 Android 的人都知道,真正会爆炸的往往不是 Java 代码,而是 res 目录, 一堆 layoutdrawablevalues 混在一起,一两年之后,谁也不敢随便删。

先回顾一下最常用的几个资源目录,以及它们各自负责什么:

名称 路径 用途
布局 res/layout/ XML 布局文件
菜单 res/menu/ AppBar 等菜单定义
图像/形状 res/drawable/ PNG、矢量图、shape 等
颜色 res/values/colors.xml 颜色定义
尺寸 res/values/dimens.xml 间距、字号等尺寸
字符串 res/values/strings.xml 所有文案
样式 res/values/styles.xml 主题、样式

真正影响项目可维护性的,是两件小事:

不在布局里硬编码颜色和尺寸

例如,下面这种写法看上去省事,但后期会非常难维护:

<TextView
    android:text="Error"
    android:textColor="#ff0000"
    android:paddingLeft="8dp" />

改成下面这样,对自己和队友都友好得多:

<TextView
    android:text="@string/error_message"
    android:textColor="@color/error_red"
    android:paddingLeft="@dimen/item_padding_left" />

做法简单:

  • 所有颜色统一放到 colors.xml,用语义化命名,比如 error_redprimarydivider
  • 所有尺寸统一放到 dimens.xml,按用途命名,比如 list_item_paddingtoolbar_height
  • 文案全部进 strings.xml,哪怕现在还没有多语言需求,未来想加时也不会太痛苦。

用好 Android 的资源限定符

除了颜色和尺寸,Android 还提供了非常丰富的“配置限定符”:可以针对横竖屏、平板/手机、不同屏幕密度做不同的布局和图片资源,比如 layout-landdrawable-hdpi 等。

原文提醒我们,在代码里少写条件判断,多利用资源系统本身的能力

比如,不要在 Java 里写一堆 if (isTablet()) 去切换布局,而是准备两套 layout 文件,交给系统根据配置自动选择。

布局文件太多怎么办:用虚拟子目录“收纳”

随着界面增多,res/layout 下面可能会躺着上百个 XML 文件。 默认情况下,Android Studio 只会给你一个长长的扁平列表——找文件就像在回收站里翻东西。

一个比较实用的小技巧,是利用 Android Studio 的第三方插件,把布局文件“虚拟分组”。 原文推荐的是一款 folding 插件,可以在 IDE 里创建“虚拟文件夹”。

大概效果是这样的:

  • 原来 layout 目录里是一长串:

    chat_activity.xml
    chat_item_image.xml
    chat_item_text.xml
    home_fragment.xml
    home_toolbar.xml
    ...
    
  • 装上插件之后,可以在布局文件上右键,选择类似 Group 的菜单项,把文件按前缀或功能归到一起,变成:

    layout
    ├── chat
    │   ├── chat_activity.xml
    │   ├── chat_item_image.xml
    │   └── chat_item_text.xml
    ├── home
    │   ├── home_fragment.xml
    │   └── home_toolbar.xml
    └── ...
    

需要特别说明两点(这也是原文反复强调的):

这些“子目录”只是 IDE 里的分组,不会真的在磁盘上创建新文件夹。
插件也不会帮你移动任何文件,它只是改变显示方式。

所以它非常安全:只要不顺眼,关掉插件一切恢复原样。

在一个旧项目里渐进式整理布局时,这个小工具会带来非常明显的心情改善。