本文翻译自 GitHub 上一篇个人认为比较好的 Android 项目规范文档: https://github.com/ribot/android-guidelines/blob/master/project_and_code_guidelines.md
每个项目都应该有编码规范,俗话说的好:”无规矩不成方圆”。没有编码规范的项目,恩,谁维护谁知道…反正我是深有体会。废话少说,本篇博文将会从几大模块介绍作为一个 Android 开发人员,应该遵循的。
文件命名
类命名
继承自某一组件的 java 类的命名方式因该以该组件为后缀,比如 SignInActivity, SignInFragment, ImageUploaderService, ChangePasswordDialog.
资源文件
Drawable文件
应遵守下面的规范:
| 种类 | 前缀 | 命名规范 |
|---|---|---|
| Action bar | ab_ |
ab_stacked.9.png |
| Button | btn_ |
btn_send_pressed.9.png |
| Dialog | dialog_ |
dialog_top.9.png |
| Divider | divider_ |
divider_horizontal.9.png |
| Icon | ic_ |
ic_star.png |
| Menu | menu_ |
menu_submenu_bg.9.png |
| Notification | notification_ |
notification_bg.9.png |
| Tabs | tab_ |
tab_pressed.9.png |
icons 相关命名(摘自Android iconography guidelines):
| 种类 | 前缀 | 命名规范 |
|---|---|---|
| Icons | ic_ |
ic_star.png |
| Launcher icons | ic_launcher_ |
ic_launcher_calendar.png |
| Menu icons and Action Bar icons | ic_menu_ |
ic_menu_archive.png |
| Status bar icons | ic_stat_notify_ |
ic_stat_notify_msg.png |
| Tab icons | ic_tab_ |
ic_tab_recent.png |
| Dialog icons | ic_dialog_ |
ic_dialog_info.png |
selector 相关:
| 种类 | 后缀 | 命名规范 |
|---|---|---|
| Normal | _normal |
btn_order_normal.9.png |
| Pressed | _pressed |
btn_order_pressed.9.png |
| Focused | _focused |
btn_order_focused.9.png |
| Disabled | _normal |
btn_order_normal.9.png |
| Selected | _selected |
btn_order_selected.9.png |
Layout 文件
布局文件应该与 Android 组件文件名匹配。比如说你为 SignInActivity 创建布局文件,布局文件应该是这样: activity_sign_in.xml
| 组件 | 文件名 | 布局文件命名方式 |
|---|---|---|
| Activity | UserProfileActivity | activity_user_profile.xml |
| Fragment | SignUpFragment | fragment_sign_up.xml |
| Dialog | ChangePasswordDialog | dialog_change_password.xml |
| AdapterView item | — | item_person.xml |
| Partial layout | — | partial_stats_bar.xml |
Note: 需要注意的是,当你为
adapter创建布局文件时,应该以item_为前缀;另一个就是包含在布局文件的子布局文件,应以partial_为前缀。
Menu 文件
Menu 文件与布局文件命名风格一样,比如说你准备为 UserActivity 创建 menu 文件,命名方式应是这样:activity_user.xml
你也许会疑惑,为何不以 menu_ 为前缀?大可不必这样做,因为这些文件都被包含在 menu 文件夹下了。
Values 文件
Values 文件的命名应该是以复数的形式来命名,例如 strings.xml, styles.xml, colors.xml, dimens.xml, attrs.xml。
代码规范
Java 语言规则
不要无视异常
比如下面这段代码:
1 | void setServerPort(String value) { |
也许你在想,依据当时的业务场景,这种异常根本不会存在,不处理也无可厚非。明天将会发生什么,谁也不知道,你的这种行为也只是为后人挖了个坑,总有一天,有人会掉进去。严谨的代码是必须处理这些异常的,至于具体如何处理,因情况而定。
更具体参见这里。
不要捕获父类的异常
你不应该写出下面的代码:
1 | try { |
至于为何要这么做,以及更详尽的信息,参见这里。
Don’t use finalizers
We don’t use finalizers. There are no guarantees as to when a finalizer will be called, or even that it will be called at all. In most cases, you can do what you need from a finalizer with good exception handling. If you absolutely need it, define a close() method (or the like) and document exactly when that method needs to be called. See InputStream for an example. In this case it is appropriate but not required to print a short log message from the finalizer, as long as it is not expected to flood the logs。-(Android code style guidelines)
完整包引入
你不应该这样做:import foo.*;你应该这样做:
import foo.Bar;
具体信息参见这里。
Java style rules
字段定义和命名
变量应被定义在文件的顶部,他们应该遵循下面列出的命名规则:
- 私有的,非静态的变量应该以 m 开头。
- 私有的,静态的变量以 s 开头。
- 其他类型的小写开头,驼峰式命名。
staticfinal类型的变量均已大写,下划线分割的方式。
如下:
1 | public class MyClass { |
驼峰式
| 正确 | 错误 |
|---|---|
| XmlHttpRequest | XMLHTTPRequest |
| getCustomerId | getCustomerID |
| String url | String URL |
| long id | long ID |
空格缩进
代码块内缩进使用 4个空格:
1 | if (x == 1) { |
行缩进使用 8个空格:
1 | Instrument i = |
Use standard brace style
Braces go on the same line as the code before them.1
2
3
4
5
6
7
8
9
10
11class MyClass {
int func() {
if (something) {
// ...
} else if (somethingElse) {
// ...
} else {
// ...
}
}
}
Braces around the statements are required unless the condition and the body fit on one line.
如果判断的代码只有一行,并且最长行数的代码还少的话,大括号就没有必要了:
1 | if (condition) body(); |
而不应该写成下面这样:
1 | if (condition) |
注解
Annotations practices
依据 Android code style guide,一些 Java 中的预定义注解的标准做法如下:
@Override: The @Override annotation must be used whenever a method overrides the declaration or implementation from a super-class. For example, if you use the @inheritdocs Javadoc tag, and derive from a class (not an interface), you must also annotate that the method @Overrides the parent class’s method.@SuppressWarnings: The @SuppressWarnings annotation should only be used under circumstances where it is impossible to eliminate a warning. If a warning passes this “impossible to eliminate” test, the @SuppressWarnings annotation must be used, so as to ensure that all warnings reflect actual problems in the code.
更多信息参见这里。
注解style
类, 方法和构造器:
当一个类,或者方法,或者构造器中运用到注解,则注解应该下注释的下面,且一行一注解的原则:
1 | /* This is the documentation block about the class */ |
字段上的注解:
对于字段上的注解应该和该字段处于同一行,除非达到最大行数:
1 | DataManager mDataManager; |
变量作用域
局部变量的作用域我们应保持到最小。如此,可以提升你代码的可读性和维护性,降低bug出现的可能性。
局部变量应被声明在第一次使用的地方。且每个变量都应初始化。
import语句排序
如果你正在使用的 IDE 是 android studio,你无须担心排序的问题,应该 android studio 已经自动帮你干完了.否则,你需要看看下面排序方式:
- 1.Android imports
- 2.Imports from third parties (com, junit, net, org)
- 3.java and javax
- 4.Same project imports
另:
- 每个分组中还需按字母表的排序, 且大写字母在小写字母的前面 (如: Z 在 a 之前).
- 分组和分组之间应该空一行 (android, com, junit, net, org, java, javax).
更多信息。
日志
使用 Log 类提供的方法能够让我们来发现问题所在:
- Log.v(String tag, String msg) (verbose)
- Log.d(String tag, String msg) (debug)
- Log.i(String tag, String msg) (information)
- Log.w(String tag, String msg) (warning)
- Log.e(String tag, String msg) (error)
通常情况下, 我们在类的顶部定义一个 static final 的 TAG 变量,该变量为所在类的类名,具体如下:
1 | public class MyClass { |
VERBOSE 和 DEBUG 日志需在 app 发行版本中禁用. 同样还建议你将 INFORMATION, WARNING 和 ERROR 日志禁用掉,当然这个看你自己,如果你觉得这些信息对于发行版本的 app 的 bug 有用处的话,你可以选择不禁用,这个时候你需要确保这些日志不会泄露用户的隐私信息(比如: email, 地址,用户名,密码等)。
仅在调试版本打印日志:
1 | if (BuildConfig.DEBUG) Log.d(TAG, "The value of x is " + x); |
类成员变量排序
使用正确的顺序将会显著提高你代码的可读性。这里推荐你使用下面的排序:
- 1.常量(Constants)
- 2.成员变量(Fields)
- 3.构造器(Constructors)
- 4.重写的方法和回调方法(public 或者 private)
- 5.Public 方法
- 6.Private 方法
- 7.内部类和接口
如下:
1 | public class MainActivity extends Activity { |
如果你写的类继承自 Activity 和 Fragment 这样的 android 组件,对于重写的方法,最好和组件的生命周期相匹配。比如,你写的 activity 实现了 onCreate(), onDestroy(), onPause(), 和 onResume(), 那么正确的排序是:
1 | public class MainActivity extends Activity { |
方法中参数的排序
在 Android 编程中,定义大部分的方法都可能需要一个 context 参数,如果正在定义这样一个方法,context 需要放在第一位。
而若存在回调接口,则需要放在最后一位,比如:
1 | // Context always goes first |
字符串
在 Android SDK 中的 SharedPreferences, Bundle, Intent 都大量使用了字符串常量,所以说,即使一个很小的 app,也有可能定义很多字符串常量。
如何你需要定义字符串常量,你需要将其定义为 static final,并加上下面的前缀:
| 所属 | 变量前缀 |
|---|---|
| SharedPreferences | PREF_ |
| Bundle | BUNDLE_ |
| Fragment Arguments | ARGUMENT_ |
| Intent Extra | EXTRA_ |
| Intent Action | ACTION_ |
需要注意的是 Fragment 中的arguments : Fragment.getArguments(),它同样是一个 Bundle.然而,因为它太常见了,我们需要为其声明一个不一样的前缀,如下:
1 | // Note the value of the field is the same as the name to avoid duplication issues |
Fragments和Activities中的参数
当我们通过 Intent 或者 Bundle 向 activity 传递参数时,对于不同参数的 key 应遵循上一节中的规则。
当 activity 或者 Fragment 需要传递参数,它需要提供一个 public static 方法以便接下来该干什么。
Activity中的此方法通常命名为 getStartIntent():
1 | public static Intent getStartIntent(Context context, User user) { |
Fragments 中的此方法通常命名为: newInstance():
1 | public static UserFragment newInstance(User user) { |
注意1:: 此方法要放置在
onCreate()方法之前。注意2:: 如果我们声明了上面的方法,那么参数的
key应该是private的。它们没有必要暴露给其他类。
代码行长度限制
每一行的代码不应超过 100 个字节。如果超过 100 个字节,这里有两种方法来减少代码行的长度:
- 提取局部变量或方法(优先考虑)
- 将当行转换为多行
但也有列外,下面这些情况,你可不必换行::
- 不太可能被分割的行, 比如: 很长的 url 地址.
- 包声明和包引入语句。
在哪里换行?
对于在哪里换行,其实,也没有一个特别合理的解决方案。但,下面几点还是能够给予我们参考的价值。
运算符时换行
1 | int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne |
=号后面
1 | int longName = |
.换行
比如说将下面的代码换行前:
1 | Picasso.with(context).load("http://ribot.co.uk/images/sexyjoe.jpg").into(imageView); |
换行后:
1 | Picasso.with(context) |
长参数换行
当一个方法拥有很长的参数时,我们应该换行:
1 | loadPicture(context, "http://ribot.co.uk/images/sexyjoe.jpg", mImageViewProfilePicture, clickListener, "Title of the picture"); |
换行后:
1 | loadPicture(context, |
RxJava chains styling
Rx chains of operators require line-wrapping. Every operator must go in a new line and the line should be broken before the .
1 | public Observable<Location> syncLocations() { |
XML 规范
使用头标签闭合
当一个 xml 节点中没有任何的内容,使用自闭合即可。
正确的做法:
1 | <TextView |
错误的做法:
1 | <!-- Don't do this! --> |
资源命名
resource 的 id 应该小写且下划线分割。
ID命名
| 节点 | 前缀 |
|---|---|
| TextView | text_ |
| ImageView | image_ |
| Button | button_ |
| Menu | menu_ |
ImageView范例:
1 | <!-- Don't do this! --> |
Menu范例:
1 | <!-- Don't do this! --> |
Strings
Strings 的命名应该以其所在的场景含义为前缀来命名。比如 registration_email_hint 或 registration_name_hint。如果一个 string,你找不到归类,你可以遵守的下面的规范:
| 前缀 | 描述 |
|---|---|
error_ |
错误信息 |
msg_ |
常规的信息 |
title_ |
标题,如 dialog 的标题 |
action_ |
动作,如保存或创建 |
Styles and Themes
根命名以大驼峰命名,其余则以小驼峰命名。
属性排序
- 1.View Id
- 2.Style
- 3.布局长宽
- 4.其他 layout 属性,按字母排序
- 5.剩下的属性, 按字母排序
测试规范
单元测试
测试类的命名应该以该类名加上 Test 为后缀来命名。比如,我们创建一个 DatabaseHelper 的测试类,类名应该为 DatabaseHelperTest.
测试方法因该加上 @Test 注解,并且以被测试的方法名开头,后跟一个前提或预期的行为(有时候并不需要,根据测试的场景)来命名。
- Template:
@Test void methodNamePreconditionExpectedBehaviour() - Example:
@Test void signInWithEmptyEmailFails()
有时候,一个类中包含了大量的方法,这个时候我们可以根据功能模块来分割这个类的测试类,比如说需要测试 DataManager, 这个类有大量的方法,我们就可以将其划分为 DataManagerSignInTest, DataManagerLoadUsersTest,等。
Espresso tests
Espresso 测试通常作用于 Activity,因此命名通常以被测试的 activity 的类名为开头,如: SignInActivityTest.
当你使用到 Espresso 的 API 时,通常以下面的方式换行测试:
1 | onView(withId(R.id.view)) |