安卓项目代码风格指南与Android应用开发架构指南
方便大家查阅,把原文翻译了一下。点击查看作者介绍安卓MVP架构指南的翻译文章。1. 项目指南1.1 项目结构...
方便大家查阅,把原文翻译了一下。点击查看作者介绍安卓MVP架构指南的翻译文章。
1. 项目指南
1.1 项目结构
新项目应该遵循安卓Gradle项目结构,定义在这里 :安卓Gradle插件用户指南。项目ribot Boilerplate 是一个很好的参考。1.2 文件命名
1.2.1 类文件类名书写方式是 大写的驼峰命名.
继承于安卓组件的类,名称应该以继承的组件命名,比如:SignInActivity, SignInFragment, ImageUploaderService, ChangePasswordDialog.
1.2.2 资源文件
资源文件书写方式:小写_下划线。如:ic_launcher.png
1.2.2.1 Drawable 文件
drawable类型资源命名:
图标(icons)命名方式 (取自 安卓图标官方指南):
selector 状态命名方式:
1.2.2.2 布局文件
布局文件应该跟对应的安卓组件名称匹配,但是把最顶级的组件名称放在前面。比如,当我们创建一个用于 SignInActivity的布局,那布局文件名称应该是activity_sign_in.xml.
一个稍微不同的地方是当我们创建用于Adapter渲染的布局,如填充一个 ListView, 在这种情况下,布局文件命名应该是用 item开始。
注意有些地方这些规则没法应用,如,当我们创建一个布局文件,用于渲染另一个布局的一部分,这样的情况下,我们应该使用partial前缀。
1.2.2.3 菜单文件
跟布局文件相似,按钮文件应该跟对应的组件匹配。比如:当我们定义一个菜单,用于 UserActivity,那么文件名应该是 activity_user.xml
文件名不含有menu是个良好的习惯做法,因为这些文件已经位于 menu 目录了。
1.2.2.4 取值文件(Values files)
在values文件夹的资源文件应该是复数形式(plural),比如,strings.xml, styles.xml, colors.xml, dimens.xml, attrs.xml
2 代码指南
2.1 Java语言规范
2.1.1 不要忽略了异常你万不该这样做:
void setServerPort(String value) {虽然你可能觉得你的代码永远不会抛出这个异常或者这异常不重要,不需要处理它,像上面那样忽略异常会在你的代码中留下雷区,将来其他人会踩到。你必须在你的代码中以某种原则的方式处理每个异常。具体的处理视情况而定。._ - (安卓官方代码指南)
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) { }
}
指南中关于不要忽略异常的说明在 这里.
2.1.2 不要捕获一般的异常。
你不应该这样做:
try {查看不这样做的原因以及要怎么做: 猛戳安卓官方指南之不要捕获一般的异常
someComplicatedIOFunction(); // 可能抛出 IOException
someComplicatedParsingFunction(); // 可能抛出 ParsingException
someComplicatedSecurityFunction(); // 可能抛出 SecurityException
//啊哈,统一处理掉
} catch (Exception e) { //我将捕获所有的异常
handleError(); // 使用一个通用的处理手段!
}
2.1.3 不要用finalizer
我们不使用finalizer。它什么时候被调用是没有保证的,甚至它不被调用。大部分情况下,你可以使用良好的异常处理来满足需要在finalizer中做的工作。如果你确实需要它,定义一个 close() 方法(或类似的)然后在文档中明确的指出什么时候这个方法需要被调用。可以查看 InputStream 做为例子.这种情况下,在finalizer中打印一个简短的log信息是恰当的但不是必须的,因为我们不希望log泛滥 ._ - (猛戳安卓官方指南之不要用finalizer)
2.1.4 完全的import语句
反例: import foo.*;
正解: import foo.Bar;
点击 这里 查看更多说明
2.2 Java风格规范
2.2.1 全局变量定义及命名全局变量应该定义在文件的头部,遵循下面的命名规则。
- 私有的非静态全局变量以m开头
- 私有的静态全局变量以s开头
- 其他的全局变量以小写单词开头
- 常量使用全部大写,单词间用下划线间隔(ALL_CAPS_WITH_UNDERSCORES).
例子:译者注:这个规则取自Android Open Source Project代码贡献规范。在程序员的圈子里对于要不要前缀这个问题已经吵翻天了。我在使用greendao的时候使用前缀的话,自动生成的getter/setter会是getMxxx/setMxxx这样难看的方法。很多人也觉得这个命名规则没有意义。stackOverFlow 还有 这里 ,以及一本教你怎么写出整洁代码的书中如是说:
“我觉得如今这些前缀没有意义,尤其是在你的APP中!你的类和方法应该尽量的小,而且你应该使用代码高亮的编辑环境,使得成员变量易于分辨。再者,人们快速适应忽略了前缀或后缀来看名字的有意义部分。我们阅读代码越多,看到前后缀越少。渐渐地,前缀就变成了旧代码看不到的线索和标记。——代码整洁之道(Clean.Code).Robert.C.Martin”
public class MyClass {2.2.3把首字母缩写当做一个单词来看
public static final int SOME_CONSTANT = 42;
public int publicField;
private static MyClass sSingleton;
int mPackagePrivate;
private int mPrivate;
protected int mProtected;
}
2.2.4 使用空格缩进
使用四个空格做代码块缩进:
if (x == 1) {换行时使用8个空格缩进:
x++;
}
Instrument i =2.2.5 使用标准的大括号风格
someLongExpression(that, wouldNotFit, on, one, line);
大括号的开始跟代码同一行
class MyClass {条件语句使用大括号包住,除非条件体只有一行。
int func() {
if (something) {
// ...
} else if (somethingElse) {
// ...
} else {
// ...
}
}
}
如果条件及只有一行条件体,而且没被换行,大括号是不必的。
if (condition) body();2.2.6 注解
这是不好的:
if (condition)
body(); // 不好的,没有大括号包围!
2.2.6.1 注解实践规范
根据安卓代码风格指南,一些Java内置的注解标准的实践规范有:
- @Override: 无论一个方法是重载父类还是实现某个接口的都必须使用@Override 注解。比如,当你使用 @inheritdocs Javadoc 标签,然后 从一个类(不是一个接口)中导出,你也必须声明那个方法 @Overrides 父类的方法。
- @SuppressWarnings: @SuppressWarnings 注解应该仅当不可能消除一个警告的时候才使用。如果一个警告 通过这个 “不可能消除” 的测试, @SuppressWarnings 注解必不可少, 以便保证所有的警告在代码中反映实际的问题。
2.2.6.2 注解风格
类,方法及构造器
当注解被应用到一个类,方法或者构造函数时候,注解在代码注释之后,一行一个注解
/* 这是这个类的注解 */全局变量
@AnnotationA
@AnnotationB
public class MyAnnotatedClass { }
全局变量的注解应该在同一行列出。除非这一行超过了最大字数了。
@Nullable @Mock DataManager mDataManager;2.2.7 限制变量范围
变量的使用范围应该限制到最小(Effective Java 条目 29)。这样做,你可以为你的代码增加可读性和可维护性,减少可能的错误。每个变量应该在最内层的使用到的它代码块中定义
局部变量应该在第一次用到他们的时候定义。几乎每个局部变量都应该初始化。如果你没有足够的信息去初始化一个变量,你应该延迟声明这个变量,直到你有足够的初始化信息。._ - (安卓代码风格指南)
2.2.8 排序import语句
如果你使用IDE,如Android Studio,你不必担心这些规则,这些IDE已经遵循这些规则。如果你不用IDE,往下看这些规则:
import 安卓框架的
Import 来自第三方的(com, junit, net, org)
java 以及 javax
同一个项目的 imports
为了保持跟IDE的设置一致,这些imports应该是这样的:
首字母分组排序,大写的字母在小写的字母前面(如Z在a前面)。
每个主要分组 (android, com, junit, net, org, java, javax)间应该有一行空行。
更多信息查看这里
2.2.9 日志(Loging)指南
使用 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)
public class MyClass {VERBOSE 和 DEBUG 日志 必须在release版本中禁用。同时也建议禁用INFORMATION, WARNING 和 ERROR 日志,但是你可能觉得它们在release版本中定位问题很有用而保持启用。如果你决定让他们保持启用,你要保证这些日志信息不会泄漏email地址,用户id之类的隐私信息。
private static final String TAG = MyClass.class.getSimpleName();
public myMethod() {
Log.e(TAG, "My error message");
}
}
仅限debug版本显示Log的配置:
if (BuildConfig.DEBUG) Log.d(TAG, "The value of x is " + x);2.2.10 类成员排序
这虽然没有简单正确的方案,但是使用一个 逻辑的 及 一致的 顺序可以提高代码的可读性和可学习性。下面是推荐的顺序:
- 常量
- 全局变量
- 构造函数
- 重载的函数和回调 (public 或 private)
- Public 方法
- Private 方法
- 内部类或内部接口
public class MainActivity extends Activity {如果你的类继承一个安卓组件如Activity或Fragment,对重载的方法进行排序以便跟组件的生命周期匹配是一个最佳实践。比如,当你的Activity实现onCreate(), onDestroy(), onPause() 和 onResume(),正确的顺序是:
private String mTitle;
private TextView mTextViewTitle;
public void setTitle(String title) {
mTitle = title;
}
@Override
public void onCreate() {
...
}
private void setUpView() {
...
}
static class AnInnerClass {
}
}
public class MainActivity extends Activity {2.2.11 方法的参数顺序
//顺序跟Activity的生命周期匹配
@Override
public void onCreate() {}
@Override
public void onResume() {}
@Override
public void onPause() {}
@Override
public void onDestroy() {}
}
在安卓编码时候,定义一个方法拥有一个Context参数是非常常见的,如果你写一个这样的方法,Context应该是第一个参数。
相反的例子是回调接口,它应该是最后一个参数
例子:
// Context 总是在第一个位2.2.13 字符串常量,命名和取值
public User loadUser(Context context, int userId);
// 回调总是在最后一位
public void loadUserAsync(Context context, int userId, UserCallback callback);
很多安卓SDK的元素如SharedPreferences, Bundle, 或 Intent使用键值对实现,所以即使是一个小应用,使用一堆字符串常量也是很常见的。
当使用这些组件,你必须定义这些键是 static final 变量,并且它们应该像下面这样使用前缀
注意Fragment的参数——Fragment.getArguments()也是一个Bundle。然而,因为这是Bundle非常常见的用法,我们为它定义一个不同的前缀。
例子:
//注意这些变量的值应该和名称一致来避免问题2.2.14 Fragment和Activity的参数
static final String PREF_EMAIL = "PREF_EMAIL";
static final String BUNDLE_AGE = "BUNDLE_AGE";
static final String ARGUMENT_USER_ID = "ARGUMENT_USER_ID";
//Intent相关的使用完全包名作为值
static final String EXTRA_SURNAME = "com.myapp.extras.EXTRA_SURNAME";
static final String ACTION_OPEN_USER = "com.myapp.action.ACTION_OPEN_USER";
当数据通过Intent 或Bundle传递到一个Activity或 Fragment,这些不同值的键必须 遵循上面描述的规则。
当一个 Activity 或 Fragment 想要参数,它应该提供一个public static方法来简化对应的Intent 或 Fragment创建
在Activity中这个方法通常叫 getStartIntent():
public static Intent getStartIntent(Context context, User user) {对于Fragment它的名称是newInstance(),处理使用合适的参数创建Fragment。
Intent intent = new Intent(context, ThisActivity.class);
intent.putParcelableExtra(EXTRA_USER, user);
return intent;
}
public static UserFragment newInstance(User user) {注意1: 这些方法应该在类的 onCreate()前面
UserFragment fragment = new UserFragment;
Bundle args = new Bundle();
args.putParcelable(ARGUMENT_USER, user);
fragment.setArguments(args)
return fragment;
}
注意2: 如果我们提供上面说的方法,这些extras对应的键和参数应该是private 的,因为他们不需要暴露到类以外。
2.2.15 代码行长度限制
代码行不应该超过100 个字符。如果代码行超过这个限制长度,通常有两种方式来降低长度:
- 提取一个局部变量或方法(推荐方式).
- 把单行换行成多行。有两种例外可以让一行超过100字符:
- 这行不可分割,如长URL。
- package 和 import 语句.
没有精确的公式解释怎么换行,很多不同的方案是有效的。然而,这里有几个规则可以应用到常见情况。
在操作符前断开
当一行被操作符断开,断开处应该在操作符之前,例子如下:
int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne赋值符号(=)例外
+ theFinalOne;
在操作符前断开有一个例外,那就是赋值符号 =,应该在赋值符号后面断开。
int longName =方法链的情况
anotherVeryLongVariable + anEvenLongerOne -
thisRidiculousLongOne + theFinalOne;
当多个方法被链接在同一行的时候——如使用Builder时,每一个方法的调用应该在独立一行,在 .之前断开。
Picasso.with(context).长参数的情况
load("http://ribot.co.uk/images/sexyjoe.jpg").into(imageView);
Picasso.with(context)
.load("http://ribot.co.uk/images/sexyjoe.jpg")
.into(imageView);
当一个方法有很多参数或它的参数非常长,我们应该在每个逗号 ,后面断开
loadPicture(context, "http://ribot.co.uk/images/sexyjoe.jpg",2.2.16 RxJava 链式风格
mImageViewProfilePicture, clickListener, "Title of the picture");
loadPicture(context,
"http://ribot.co.uk/images/sexyjoe.jpg",
mImageViewProfilePicture,
clickListener,
"Title of the picture");
Rx链式操作符要求换行。每一个操作符必须在新的一行,断行应该在.之前
[code]public Observable syncLocations() {
return mDatabaseHelper.getAllLocations()
.concatMap(new Func1
关注 技术分析师
微信扫一扫关注公众号