Android开发规范

Android编码规范

因为要进行混合开发,RN所需环境是JDK1.8以上,所以统一使用JDK1.8以上。
IDE统一采用AndroidStudio最新版

1 源文件基础

1.1 文件名

源文件以功能模块名称来命名,大小写敏感。

1.2 文件编码:UTF-8

源文件编码格式为UTF-8。

特殊字符:统一采用符号表来获取。

2 命名规范

注意:

  • 使用完整的英文描述命名
  • 避免命名超长
  • 避免相似的命名
  • 慎用缩写,如果用到缩写,参考下一跳
  • 按照缩写规则使用缩写
2.1 包命名规范

采用反域名命名规则,包名全部小写,连续的单词只是简单地连接起来,不使用下划线,一级包名为com,二级包名为xxx(可以是公司域名或者个人命名),三级包名根据应用进行命名,四级包名为模块名或层级名。

1
2
com.akathink.yinhua
com.akathink.yinhua.ui
2.2 类命名规范

采用大驼峰式命名法,尽量避免缩写,除非该缩写是众所周知的,比如HTML,URL;如果类名称包含单词缩写,则单词缩写的每个字母均应大写。

1
class BaseActivity | class BaseListAdapter | class HomeLogic
2.3 接口命名规范

采用大驼峰命名法,多以ableible结尾。

1
interface Runnabe | interface Accessible
2.4 变量命名

非静态成员变量Google的m命名法。

1
private String mUserName;

静态成员变量Google的s命名法。

1
private String sUserName;

临时变量、参数均以Java命名方法。

1
private String userName;
2.5 常量命名

常量使用全大写字母加下划线的方式命名,避免代码中出现魔法数字和硬编码。

1
2
public static final String TAG = "tag";
public static final int MOUDLE_TREAT = 0;
2.6 控件命名

类中控件名称必须与xml布局文件中id保持一致。

在布局文件中Button的id为android:id = "@+id/pay_btn"

类中的控件命名为private Button mPayBtn

2.7方法命名

采用小驼峰命名法,动词或名词。

1
run();| onCrete()
2.8 layout命名

全部小写,采用下划线命名法,使用模块名_功能名。

1
login_activity.xml |
2.9 id命名

全部小写,采用下划线命名法,能通过id名直接理解当前组件要实现的功能。

命名模式:模块名称view缩写 逻辑名称

1
@+id/login_name_edit 登录界面用户名输入框
2.10 资源命名

全部小写,采用下划线命名法,使用模块名_用途来命名。

1
login_btn_normal.9.png
2.11 动画文件命名

全部小写,采用下划线命名法,加前缀区分。

1
2
fade_in //淡入
fade_out//淡出

3 类声明

3.1 类成员顺序

类成员顺序对易学性有很大的影响,不用的类对成员的排序可能是不同的。

但每个类应该以某种逻辑去排序。

类成员排列常用规则:

  • 按照发生的先后
  • 常量按照使用先后
  • UI控件成员变量按照layout文件中的先后
  • 普通成员变量按使用先后
  • 方法基本按调用的先后在各自区块中排列
  • 相关功能模块作为小区块放在一起
3.2 区块划分

建议使用注释将文件分为明显的区块,划分如下

  • 常量声明区
  • UI控件成员变量声明区
  • 普通成员变量声明区
  • 内部接口声明区
  • 初始化相关方法区
  • 事件响应方法区
  • 普通逻辑方法区
  • 重载的逻辑方法区
  • 发起异步任务方法区
  • 异步任务回掉方法区
  • 声明周期回掉方法区(除去onCreate()方法)
  • 内部类声明区
3.3 重载:永不分离

当一个类有多个构造函数,或是多个同名方法,这些函数/方法应该按顺序出现。

1
2
3
4
5
6
7
8
9
public class PeoPle {
void PeoPle(String name) {
System.out.print("name is " + name);
}
void PeoPle(String name, int age) {
System.out.print("name is " + name + "; age is " + age);
}
}

##

4 注释

4.1 文件头注释 使用/* /注释

每个文件头都应注释,包含创建时间,作者,主要功能等。

1
2
3
4
5
6
7
8
9
10
11
/** *
* Created by akathink on 2016/10/12.
* 登陆界面
*/
public class LoginActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_activity);
}
}
4.2 方法头注释 使用/* /注释

每一个成员(包含自定义成员方法,覆盖方法,属性方法)的方法头都必须做方法头注释。应该包含实现功能(用途),参数,返回值和抛出异常的列表等。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 登陆接口
* @param context context
* @param request 回调
* @param loginName 用户名
* @param passWord 密码
* return null
*/
public static void Login(Context context, JsonRequest request, String loginName, String passWord) {
request.addParams("loginName", loginName);
request.addParams("passWord", passWord);
BaseNetApi.baseRequest(context, "RMIS.API/api/MCommon/Login", request);
}
4.3 块注释 使用//注释

在实现一段阶段性功能代码前做快注释。

1
2
3
4
5
6
7
// 获取历史用户列表
userList = AppContext.getInstance().getUserList();
if(userList != null && userList.userInfoList != null && userList.userInfoList.size() != 0) {
currentUserConfig = userList.userInfoList.get(0);
} else {
currentUserConfig = new MLoginHistory();
}
4.4 变量注释 使用//注释

所有成员变量和大多数局部变量在声明是做功能注释。

1
2
public String userName; // 用户名
public String userPwd; // 密码
4.5 语句注释 使用//注释

在关键语句的右侧做单条语句注释。

1
2
initView();//初始化控件
login();//登录操作

一个项目的注释风格要统一,并且要多使用注释。

5 异常

5.1 不要忽略异常
1
2
3
4
5
void setServerPort(Strng value){
try{
serverPort = Integer.parseInt(value);
}catch(NumberFormatException e){}
}

有时代码永远不会碰到这种出错情况,或者处理异常并不重要,但必须在代码中以某种规矩来处理所有的异常。根据情况不同,处理方式不同。

可接受的替代方案包括:

  • 向方法的调用者抛出异常。
1
2
3
void setServerPort(String value) throws NumberFormatException{
serverPort = Integer.parseInt(value);
}
  • 根据抽象级别抛出新的异常。
1
2
3
4
5
6
7
void setServerPort(Strng value) throws ConfigurationException{
try{
serverPort = Integer.parseInt(value);
}catch(NumberFormatException e){
throw new ConfigurationException("port" + value + "is not valid.");
}
}
  • 默默的处理错误并在catch{}语句块中替换为合适的值。
1
2
3
4
5
6
7
void setServerPort(Strng value) throws ConfigurationException{
try{
serverPort = Integer.parseInt(value);
}catch(NumberFormatException e){
serverPort = 80;// default port for server
}
}
  • 捕获异常并抛出一个新的RuntimeException。这种做法比较危险:只有确信发生该错误时最合适的做法就是崩溃,才会这么做。
1
2
3
4
5
6
7
void setServerPort(Strng value) throws ConfigurationException{
try{
serverPort = Integer.parseInt(value);
}catch(NumberFormatException e){
throw new RuntimeException("port " + value " is invalid, ", e);
}
}
  • 如果确信忽略异常比较合适,就忽略,但必须把忽略的原因写出来。
1
2
3
4
5
6
7
8
void setServerPort(Strng value) throws ConfigurationException{
try{
serverPort = Integer.parseInt(value);
}catch(NumberFormatException e){
// Method is documented to just ignore invalid user input.
// serverPort will just be unchanged.
}
}
5.2 不要捕获顶级的exception

绝大部分情况下,捕获顶级的Exception或Throwable都是不合适的,Throwable更不合适,因为它还包含了Error异常。这种捕获非常危险。这意味着本来不必考虑的Exception(包括类似ClassCastException的RuntimeException)被卷入到应用程序级的错误处理中来。这会让代码运行的错误变得模糊不清。例:

1
2
3
4
5
6
7
try{
someComplicatedIOFunction(); // may throw IOException
someComplicatedParsingFunction(); // may throw ParsingException
someComplicatedSecurityFunction(); // may throw SecurityException
}catch(Exception e){
handleError();
}

比捕获顶级Exception更好的方案:

  • 分开捕获每一种异常,一条try语句后面跟随多个catch语句
  • 使用多个try块。
  • 再次抛出异常。
5.3 import

使用完全限定的import,尽量不使用顶级导包、不适用通配符。

import语句不换行。

例:当需要使用foo包红的bar类时,存在两种可能:

1
import foo.*;//可能会减少import语句,不建议使用

或者:

1
import foo.bar;//代码的可读性更好,便于维护

6 Log

记录日志会对性能产生显著的负面影响。如果日志不厚简练的话,很快会丧失可用性。日志包含5中不同的级别。

使用通用的LogUtil类取代原生Log类。

6.1 ERROR

该级别日志应在致命错误发生时使用。也就是说,错误的后果能被用户看到,但是不明确删除部分数据、卸装程序、清除数据区或重新刷机(或更糟糕)就无法恢复。该级别总是记录日志。需要记录ERROR级别日志的事件一般都应该向统计信息收集(statistics-gathering )服务器报告。

6.2 WARNING

该级别日志应该用于哪些重大的、意外的事件。也就是说,错误的后果能被用户看到,但是不采取明确的动作可能就无法无损恢复,从等待或重启应用开始,直至重新下载新版程序或重启设备。该级别总是记录日志。需记录WARNING级别日志的事件也可以考虑向统计信息收集服务器报告。

6.3 INFORMATIVE

该级别日志应该用于记录大部分人都会感兴趣的事件。也就是说,如果检测到事件的影响面可能很广,但不一定是错误。应该只有那些拥有本区域内最高级别身份认证的模块才能记录这些日志(为了避免级别不足的模块重复记录日志)。该级别总是记录日志。

6.4 DEBUG

该级别日志应该用于进一步记录有关调查、调试意外现象的设备事件。应该只记录那些有关控件运行所必需的信息。如果debug日志占用了太多的日志空间,那就应该使用详细级别日志(verbose)才更为合适。

6.5 VERBOSE

该级别日志应用于所有其余的事件。

7 代码规约

7.1 方法

一个方法尽量不要超过50行。如果方法太长,需要进行方法拆分。

保证每个方法只做一件事,不要使用try catch处理业务逻辑。

7.2 参数和返回值

一个方法的参数尽可能不超过4个;

如果方法返回一个错误码,请使用异常;

尽可能不要使用null,替代为异常,或者使用空变量。

7.3 数字

代码中不允许出现单独的数字、字符,如果需要使用,将他们按照含义封装成静态变量。

7.4 控制语句

判断中如有常量,则应该将常量置于判断式的右侧。

1
if( isAdmin() == true)...
7.5 变量赋值

避免在一个语句中给多个变量赋相同的值。

不要将赋值运算符用爱容易于相等关系运算符混肴的地方。

不要使用内嵌赋值运算符视图提高运行时的效率。

1
2
3
4
5
//错误
d = (a = b + c) + r;
//应该写成
a = b + c;
d = a + r;
7.6 大括号

大括号与if,else,for,do,while语句一起使用,即使只有一条语句或者空,都应把大括号加上。

对于非空快和块状结构,大括号遵循Kernighan和Ritchie风格Egyptian brackets:

  • 左大括号前不换行
  • 左大括号后换行
  • 右大括号前换行
  • 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号后面是else或逗号,则不换行。
1
2
3
4
5
6
7
8
9
class loginOnClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
if((int) view.getTag() == currentPos){
return;
}
login();
}
}

一个空的块状结构离什么都不包含,大括号可以简洁写出{},不需要换行。

1
void doNothing(){};
7.7 块缩进:2个空格

开始一个新的块,缩进增加两个空格,块结束时,缩进返回先前的缩进级别。

7.8 枚举类

枚举常量间用逗号隔开,所有适用于其他类的格式也适用于枚举类。

1
2
3
4
5
6
private enum Suit{
CLUBS,
HEARTS,
SPADES,
DIAMONDS
}
7.9 变量声明

每次只声明一个变量,不要使用组合声明。

需要时才声明,并尽快进行初始化。

限制变量的作用范围。

7.10 switch语句

switch块中的内容缩进为2个空格,每个switch标签后新起一行。

在switch块内,每个语句组通过break,continue,return或抛出异常来终止。

default的情况要写出来,即使什么代码也不包含。

7.11 其他要求
  • 代码中尽量不要出现中文。注释除外。代码中通过string.xml引用来来显示中文。
  • 控件声明放在activity级别。
  • 在一个View.OnClickListener中处理所有的点击事件逻辑,
  • 界面之间传值尽量使用intent方式,少用全局变量。
  • 不建议在布局文件中添加点击事件。
  • 数据类型转换一定要检验。
  • 使用常量代替枚举。
  • 实体不要在不同模块间共享,但可以在统一模块下的不同页面共享。

8 封装

  1. 自定义UI组件替代原生组件

    原因:有的时候,我们可能更换字体或者Image的加载方式,若是使用自定义组件的话,则需要将用到这些组件的地方都得做相应的处理,若是采用自定义组件的话,则只需更改一处,易于维护。

    若是自定义UI组件,则需要以UI开头,例如UITextView,目的是为了避免在引包的时候,不会引成谷歌官方的包。

  2. 凡是使用的第三方组件均需做一定的封装,不要直接使用第三方组件。

缩写规则

  • view缩写
控件 布局文件中缩写 代码中缩写
LinearLayout xxx_layout xxxLLayout
RelativeLayout xxx_layout xxxRLayout
TextView xxx_tv xxxTv
EditText xxx_edt xxxEdt
Button xxx_btn xxxBtn
ImageView xxx_iv xxxIv
RadioButton xxx_rbtn xxxRbtn
ListView xxx_lv xxxLv
ProgressBar xxx_pbar xxxPbar
  • 包包含
包名 此包中包含
com.xx.yinhua.activity 页面用到的Activity类 (activities层级名用户界面层)
com.xx.yinhua.base 页面中每个Activity类共享的可以写成一个BaseActivity类 (所有Activity类均需继承自该类)
com.xx.yinhua.adapter 页面用到的Adapter类 (适配器的类)
com.xx.yinhua.tools 公共工具方法类(tools模块名)
com.xx.yinhua.bean//或者.unity 元素类
com.xx.yinhua.db 数据库操作类
com.xx.yinhua.view//或者.ui 自定义的View类等
com.xx.yinhua.service Service服务
com.xx.yinhua.broadcast Broadcast服务
  • 类缩写描述
描述 例如
activity 类 Activity为后缀标识 登录页面LoginActivity
Adapter类 Adapter为后缀标识 Adapte 为后缀标识
解析类 Hlr为后缀标识 首页解析类HomePosterHlr
公共方法类 Tools或Manager为后缀标识 日志工具类LogTools
数据库类 以DBHelper后缀标识 用户数据库UserDBHelper
Service类 以Service为后缀标识 时间服务TimeService
Broadcast类 Broadcast类 时间通知TimeBroadcast
ContentProvider 以Provider为后缀标识
直接写的共享基础类 以Base开头 BaseActivity,BaseFragment