Android 代码规范指北

974 阅读13分钟
原文链接: yedaxia.github.io

统一的代码风格在多人协作开发中的作用是不言而喻的,通过参考一些比较优秀的实践,这里大量参考了阿里巴巴 JAVA 开发手册,再结合了个人的思考,制定了这么一套规范,由于个人的认识是非常有限的,本规范也肯定存在很多不合理和需要补充的东西,在这里恳请大家根据自己的实践和工作中,提出一些中肯的建议和修改意见。

意义

  1. 高度有秩序的代码具有天然的美感,写代码的人心情会好很多。
  2. 减低开发人员流动带来的风险。
  3. 对于新人能更好地理解和参与到开发中。
  4. 更好的可维护性。
  5. 随之带来更好的产品质量和开发效率提升。

命名规约

  • 【强制】所有编程相关命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
  • 【强制】所有编程相关的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
  • 【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。

类的命名

  • 【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
  • 【强制】相关组件类应该以组件名作为后缀以便识别。
组件 命名规则 命名举例
Activity ×××Activity MainActivity
Fragment ×××Fragment HomeFragment
Dialog ×××Dialog AlertDialog
Service ×××Service DownloadService
BroadcastReceiver ×××Receiver LoginReceiver
ContentProvider ×××Provider UserProvider
Adapter 名字+类型+Adapter ArticleListAdapter, ImageGridAdapter
AsyncTask ×××Task LoginTask
Handler 名字+所在线程+Handler HomeUIHandler, CompressWorkHandler
ViewHolder VH + 名字 VHArticle
  • 【推荐】实用工具类命名成 **Utils**Helper
  • 【推荐】EventBus 发布的事件名命名成以 Event 结尾,比如 LoginEvent
  • 【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。
  • 【推荐】接口一般以大写 I 开头,回调的接口一般为 Listener 或者 Callback 结尾。

方法命名

  • 【强制】POJO 类中的任何布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。
    ==反例==:定义为基本数据类型 boolean isSuccess; 的属性,它的方法也是 isSuccess() ,部分Json 框架在反向解析的时候,“以为”对应的属性名称是 success,导致属性获取不到,进而抛出异常。
  • 【强制】EventBus 回调的方法必须和相关 Event 类一致,比如LoginEvent类的方法签名为: public final void onLoginEvent(LoginEvent e);
  • 【推荐】客户端逻辑大部分是基于事件驱动的,应该以on来开头,比如登录按钮的点击可以是:onLoginClick()

变量命名

  • 【强制】常量命名全部大写,单词间用下划线隔开。
  • 【强制】局部变量和一般类变量以小写字母开头。
  • 【推荐】static 类变量名称以s开头,final类变量用f开头。
  • 【推荐】组件相关的可以用【全部首字母+名字】的命名,比如 id 是 R.id.tv_login 的控件名称是:tvLogin

资源文件命名

除了 attr 和 style 资源遵循驼峰命名之外,其他资源的命名统一用小写字母+下划线的风格。

  • 【推荐】布局文件命名
布局类型 命名规则 例子
Activity activity_××× HomeActivity 对应 activity_home
Dialog dlg_××× LoginDialog 对应 dlg_login
Fragment frag_××× HomeFragment 对应 frag_home
页面标题 title_××× title_main
列表Footer footer_××× footer_article
列表Header header_××× header_article
列表的Item item_××× item_article
可重用嵌入布局 include_ ××× include_navagator
分割线 line_+颜色+大小 line_gray_1px
  • 【推荐】id命名:截取相关组件首个字母作为开头,然后以下划线作为分割。比如【TextView:tv_name】。
  • 【推荐】图片命名:【类别+名称+状态(如果有)】,根据图片的用途可以分类为图标「ic」,背景「bg」,前景「fg」(比较少用到),图片「img」(比如引导图,开机页面),如果有多个状态用同一张图,那么首选声明为 【××_selected】 。
状态 示例
正常状态 ic_login_primary
checked ic_login_checked
pressed ic_login_pressed
selected ic_login_selected
disable ic_login_disable
  • 【推荐】drawable命名
  1. xml定义图形:参考图片命名规则,在以上基础上添加辨识,比如: bg_login_shape, bg_login_vector, bg_login_shape_primary。
  2. 多个状态drawable: 命名为 【××_selector】,比如 bg_login_selector。
  • 【推荐】颜色命名
  1. 单色:命名规则为【类型+名字+状态(如果有)】,我把用到颜色的元素类型做了个归类,分别是字体「text」,背景「bg」,线「line」,还是一些常规色(比如白色,透明等),正例:text_black_primary, bg_black_selected, line_orange。
  2. 字体selector颜色:【名字 + text_selector】 ,比如 tab_item_text_selector。
  • 【推荐】尺寸命名:通用性比较强和需要适配的在 dimens.xml 中设置,一般的可以在代码中直接写。
  1. 字体大小: 【font_××】,××和设计稿保持一致的换算。
  2. 间距:组件外用【××_margin】,组件内用【××_padding】, 例子: app_left_margin。
  3. 控件大小:【宽度:××_width】,【高度:××_height】,其他【××_size】,例子: login_button_height。
  • 【推荐】其他:文字不得直接写到 java 代码和 layout 代码里面,应该抽离到相关的 strings 资源文件中;menu 和动画资源命名不需要加前缀或者后缀,能表达意图即可,因为 R.menu 或者 R.anim 已经携带了相关的信息。

代码风格

  • 【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果
    是非空代码块则:
  1. 左大括号前不换行。
  2. 左大括号后换行。
  3. 右大括号前换行。
  4. 右大括号后还有 else 等代码则不换行;表示终止右大括号后必须换行。
  • 【强制】 左括号和后一个字符之间不出现空格;同样,右括号和前一个字符之间也不出现空格; 代码块缩进 4 个空格,如果使用 tab 缩进,请设置成 1 个 tab 为 4 个空格。
正例:
public static void main(String args[]) {
    // 缩进 4 个空格
    String say = "hello";
    // 运算符的左右必须有一个空格
    int flag = 0;
    // 关键词 if 与括号之间必须有一个空格,括号内 f 与左括号,1 与右括号不需要空格
    if (flag == 0) {
        System.out.println(say);
    }
    
    // 左大括号前加空格且不换行;左大括号后换行
    if (flag == 1) {
        System.out.println("world");
    // 右大括号前换行,右大括号后有 else,不用换行
    } else {
        System.out.println("ok");
    // 右大括号做为结束,必须换行
    }
}
  • 【强制】任何运算符左右必须加一个空格。

说明:运算符包括赋值运算符 =、逻辑运算符 &&、加减乘除符号、三目运行符等。

  • 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
  • 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时,遵循如下原则:
  1. 换行时相对上一行缩进 4 个空格。
  2. 运算符与下文一起换行。
  3. 方法调用的点符号与下文一起换行。
  4. 在多个参数超长,逗号后进行换行。
  5. 在括号前不要换行,见反例。
正例:
StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,换行缩进 4 个空格,并且方法前的点符号一起换行
sb.append("zi").append("xin")…
    .append("huang");
反例:
StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,不要在括号前换行
sb.append("zi").append("xin")…append
    ("huang");
//参数很多的方法调用也超过 120 个字符,逗号后才是换行处
method(args1, args2, args3, ...
    , argsX);
  • 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。

正例:下例中实参的 “a” ,后边必须要有一个空格。method("a", "b", "c");

  • 【强制】在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:if (condition) statements;

  • 【推荐】推荐尽量少用 else, if-else 的方式可以改写成:

if(condition){
     …
    return obj;
} 
说明:如果使用要 `if-else if-else` 方式表达逻辑,【强制】请勿超过 3 层,超过请使用状态设计模式。
  • 【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外),遍历长度的提前获取;避免使用 foreach,迭代器,尽量采用下标的形式
正例:
(for i=0 ;i < size; ++i)
反例:
for(int i : items)

编码规约

OOP 规约

  • 【强制】过时的类或者方法不要使用,覆盖的方法要用Override声明。这样当写错覆盖方法时可以得到编译错误提示。
  • 【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
  • 【强制】单例或者实用类构造方法应该声明为 private。
  • 【强制】如果用到 FastJson 来解析数据,必须在相关 getter 和 setter 方法上添加注解。
  • 【推荐】声明为 public 的方法要对参数进行校验,private 的可以不用。
  • 【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
  • 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。
  • 【推荐】循环体内,字符串的联接方式,使用 StringBuilder 的 append 方法进行扩展。
  • 【推荐】类成员与方法访问控制从严:
  1. 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
  2. 工具类不允许有 public 或 default 构造方法。
  3. 类非 static 成员变量并且与子类共享,必须是 protected。
  4. 类非 static 成员变量并且仅在本类使用,必须是 private。
  5. 类 static 成员变量如果仅在本类使用,必须是 private。
  6. 若是 static 成员变量,必须考虑是否为 final。
  7. 类成员方法只供类内部调用,必须是 private。
  8. 类成员方法只对继承类公开,那么限制为 protected。

常量定义

  • 【强制】不允许出现任何魔法值(即未经定义的常量)直接出现在代码中。
  • 【强制】long 或者 Long 初始赋值时,必须使用大写的 L,不能是小写的 l ,小写容易跟数字 1 混淆,造成误解。
  • 【推荐】不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如:缓存
    相关的常量放在类:CacheConsts 下;系统配置相关的常量放在类:ConfigConsts 下。
  • 【推荐】避免使用枚举类型,用@IntDef进行替代。

异常处理

  • 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

    反例:
    void setServerPort(String value) {
        try {
            serverPort = Integer.parseInt(value);
        } catch (NumberFormatException e) { }
    }
  • 【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。

  • 【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
  • 【强制】不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
  • 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。==说明==:如果预期抛的是绣球,实际接到的是铅球,就会产生意外情况。
  • 【推荐】不要偷懒直接捕获顶级异常,这样会把Runtime的异常也囊括进来,要明确每种异常出现的场景被给出相应的处理。

    反例:
    try {
        someComplicatedIOFunction();        // may throw IOException
        someComplicatedParsingFunction();   // may throw ParsingException
        someComplicatedSecurityFunction();  // may throw SecurityException
        // phew, made it all the way
    } catch (Exception e) {                 // I'll just catch all exceptions
        handleError();                      // with one generic handler!
    }
  • 【推荐】除非有充分的理由,不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如:IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。

代码注释

  • 【强制】类、类属性、类方法的注释必须使用 javadoc 规范,使用/*内容/格式,不得使用//xxx 方式。
  • 【强制】所有的抽象方法(包括接口中的方法)必须要用 javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
  • 【强制】所有的类都必须添加创建者信息。
  • 【强制】对那些临时性的、短期的、够棒但不完美的代码,请使用TODO注释。

    // TODO: Change this to use a flag instead of a constant.
  • 【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/ /注释,注意与代码对齐。

    void method(){
        // 打个招呼
        sayHello();
        
        /*
          这里保持移动,
          并不断对步数加1
         */
        keepMove();
    }
  • 【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一
    个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。

Android 实践

  • 【强制】避免对同个 View 进行多次查找,应该缓存起来,比如列表中使用的 ViewHolder 模式。
反例:
view.findViewById(R.id.iv_toolbar_right).setOnClickListener(this);
view.findViewById(R.id.iv_toolbar_right).setEnabled(true);
  • 【强制】不要使用 System.out.println() ,printf() 打印日志。
  • 【强制】调试日志要有个开关,在正式发布的时候不能出现应用日志打印。
  • 【强制】读文件,数据库,网络请求等一些耗时操作必须在异步线程中执行。
  • 【强制】不能粗暴的把 Activity, Service 对象或者一些比较重的对象直接声明为 static 变量,如果必须这么做,请注意它们的生命周期变化。
  • 【强制】每引入一个第三方库,要检查是否加入相关的混淆规则。
  • 【强制】自定义属性动画 (ObjectAnimator) 的 getter 和 setter 方法时要注意防止混淆。
  • 【强制】在 (Activity,Fragment) 组件退出时要保证相关资源得到释放,比如 EventBus 订阅的事件,注册的广播,轮播任务等。
  • 【强制】对于Api Level大于等于23的设备要进行运行时权限检查。
  • 【推荐】在使用 Thread,Handler,AsyncTask,Timer,耗时的匿名回调类,内部类时,考虑声明成静态的内部类,并且弱引用或者软引用来处理相关的大对象,并且要注意生命周期的变化,避免出现内存泄漏问题。
  • 【推荐】利用 TextView 的drawableLeft等来减少 ImageView 的使用。
  • 【推荐】通过使用 RelativeLayout 或者 ConstraintLayout 来减少布局的层次。
  • 【推荐】在 findViewById 尽可能选取一个比较近的父节点来减少查找时间。
  • 【推荐】Bundle 中的常量 Key 值统一在一个文件中,而不是直接在 Activity 或者 Fragment 中,比如 BundleKeys 文件中。
  • 【推荐】可滑动列表复用的 ViewHolder 对象中,对于一些事件对象应该尽可能地复用。
  • 【推荐】对隐式 Intent 的运行时调用 resolveActivity 进行检查保护。
  • 【推荐】使用 NotificationCompat 兼容包来处理消息通知。
  • 【推荐】延迟创建没有用到的对象,比如 Adapter 的列表对象,应该在请求数据取到之后再去创建,而不是提前创建好。
  • 【推荐】使用 ArrayMap,Sparse×× 系列对象来减少内存消耗和避免AutoBoxing。
  • 【推荐】更新列表局部数据避免粗暴地调用 Adapter.notifyDataSetChanged,应该只更新变化的部分。
  • 【推荐】用 @Nullable, @NonNull, @IdRes, @MainThread 等Support Annotations注解来增强代码的编译期检查和可读性。
  • 【参考】如果不用getColor(int id, Theme theme)等相关方法,至少使用 ContextCompat 来获取相关 Color 或者图片资源。
  • 【参考】尽可能通过 Fragment 来实现视图层,Activity 只是负责嵌入和管理。
  • 【参考】通过 Picasso,Glide 等开源组件去加载和缓存你的图片。

资料参考

  1. 阿里巴巴 JAVA 开发手册
  2. Android Code Style Rules
  3. 常见的八种导致 APP 内存泄漏的问题
  4. Android开发最佳实践
  5. Android Support Annotations