Android 开发规范

176 阅读9分钟

1.AS规范

  1. 尽量使用最新的稳定的IDE进行开发;
  2. 编码格式统一为 UTF-8;
  3. 删除多余的 import,减少警告出现,可利用 AS 的 Optimize Imports(Settings -> Keymap -> Optimize Imports)快捷键;

2.命名规范

代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

2.1 包

2.1.1 包名

  1. 包名必须是全部小写,连续的单词只是简单地连接起来,不使用下划线,采用反域名命名规则。
  2. 一级包名为顶级域名,通常为 comedu
  3. 二级包名为公司名,如 denglin
  4. 三级包名为应用名,如 rubbish

2.1.2 包名划分

  1. 推荐采用PBF(按照功能分包Package By Feature)不建议使用PBL(按层分包 Package By Layer)
  2. 按照功能分包具体可以这样做:
com
└── domain
    └── app
        ├── App.java 定义 Application 类
        ├── Config.java 定义配置数据(常量)
        ├── Activity  
        │   ├── HomeActivity 
        │   ├── LoginActivity 
        │   ├── AboutActivity 
        ├── base 基础组件
        ├── custom_view 自定义视图
        ├── data 数据处理
        │   ├── DataManager.java 数据管理器
        |   ├── Aes.java Aes数据加密解密
        │   ├── local 来源于本地的数据,比如 SP,Database,File
        │   ├── model 定义 model(数据结构以及 getter/setter、compareTo、equals 等等,不含复杂操作)
        │   └── remote 来源于远端的数据
        ├── feature 功能
        │   ├── feature0 功能 0(搜索垃圾分类)
        │   │   ├── feature0Fragment.java
        │   │   ├── xxAdapter.java
        │   │   └── ... 其他 class
        |   ├── feature1 功能 1(获取垃圾搜索热点)
        │   │   ├── feature1Fragment.java
        │   │   ├── xxAdapter.java
        │   │   └── ... 其他 class
        |   ├── feature2 功能 2(app启动时同步最新垃圾分类数据)
        │   │   ├── feature1Fragment.java
        │   │   ├── xxAdapter.java
        │   │   └── ... 其他 class
        │   └──....其他功能
        ├── injection 依赖注入
        ├── util 工具类(例如网络请求,数据上传)
        └── widget 小部件

2.2 类

2.2.1 类名

  • 使用驼峰命名 例如: LoginActivity
  • 尽量不使用缩写

常见类命名格式

描述例如
ActivityActivity 为后缀标识主页面类 HomeActivity
AdapterAdapter 为后缀标识新闻详情适配器 NewsDetailAdapter
解析类Parser 为后缀标识首页解析类 HomePosterParser
工具方法类UtilsManager 为后缀标识打印工具类:PrinterUtils
数据库类DBHelper 后缀标识新闻数据库:NewsDBHelper
ServiceService 为后缀标识时间服务 TimeService
BroadcastReceiverReceiver 为后缀标识推送接收 JPushReceiver
ContentProvider以 Provider 为后缀标识ShareProvider
自定义的共享基础类Base 开头BaseActivity, BaseFragment

2.2.2 接口

  • 使用驼峰命名 多以able或者ible结尾 例如:interface Runnable
  • 如果项目采用MVP设计,所有 ModleViewPresenter 的接口都是以 I 为前缀,不加后缀,其他的接口采用上述命名规则。

2.3 方法名

  • 方法名以 lowerCamelCase 风格编写。
  • 方法名通常是动词或动词短语。

常见方法命名

方法说明
initXX()初始化相关方法,例:初始化布局 initView()
isXX() checkXX()方法返回boolean类型
getXXX()返回某个值的方法
setXXX()设置某个属性值
handleXXX() processXXX()对数据进行处理的方法
displayXXX() showXX()显示提示框或者消息提示
updateXX()更新数据
saveXXX()保存数据
clearXXX()清除数据
removeXXX()移除数据或者视图等,如:removeView()
drawXXX()绘制数据或者效果相关等

2.4 常量名

  • 常量名命名全部字母大写, 用下划线分割单词。 例如: CONSTANT_CAS
  • 常量名都是一个静态 final 字段,但不是所有静态 final 字段都是常量。
static final int VERSION = 1;
static final String VERSION_NAME "hello world";

2.5 非常量字段名

非常量字段名以 lowerCamelCase 风格的基础上改造为如下风格: 基本结构为 scopeVariableNameType

2.5.1 scope (范围)

  • 非公有,非静态字段命名为 m 开头。
  • 静态字段命名为 s 开头。
  • 其他字段以小写字母开头。

例如:

public class MyClass {
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}

使用 1 个字符前缀来表示作用范围,1 个字符的前缀必须小写,前缀后面是由表意性强的一个单词或多个单词组成的名字,而且每个单词的首写字母大写,其它字母小写,这样保证了对变量名能够进行正确的断句。

2.5.2 控件类型

为了避免控件和普通成员变量混淆以及更好地表达意思,所有用来表达控件的成员变量统一加上控件缩写作为前缀

UI控件缩写表

名称缩写
Buttonbtn
CheckBoxcb
EditTextet
FrameLayoutfl
GridViewgv
ImageButtonib
ImageViewiv
LinearLayoutll
ListViewlv
ProgressBarpb
RadioButtionrb
RecyclerViewrv
RelativeLayoutrl
ScrollViewsv
SeekBarsb
Spinnerspn
TextViewtv
ToggleButtontb
VideoViewvv
WebViewwv

2.6 变量名

变量名中可能会出现量词,我们需要创建统一的量词,他们更容易理解,也更容易搜索。 例如: mFirstBookmPreBookcurBook

量词列表量词后缀说明
First一组变量中的第一个
Last一组变量中的最后一个
Next一组变量中的下一个
Pre一组变量中的上一个
Cur一组变量中的当前变量

3. 代码规范

3.1使用标准大括号样式

左大括号不单独占一行,与其前面的代码位于同一行:

class MyClass {
    int func() {
        if (something) {
            // ...
        } else if (somethingElse) {
            // ...
        } else {
            // ...
        }
    }
}

需要在条件语句周围添加大括号。例外情况整个条件语句适合放在同一行,那么可以将其全部放在一行上。例如,我们接受一下样式:

if (condition) {
    body();
}

也可以接受一下样式:

if (condition) body();

但不接受一下样式:

if (condition)
    body(); 

3.2 类成员的顺序

推荐使用如下的排序顺序:

  1. 常量
  2. 字段
  3. 构造函数
  4. 重写函数和回调
  5. 公有函数
  6. 私有函数
  7. 内部类或接口 例如:
public class MainActivity extends Activity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private String mTitle;
    private TextView mTextViewTitle;

    @Override
    public void onCreate() {
        ...
    }

    public void setTitle(String title) {
    	mTitle = title;
    }

    private void setUpView() {
        ...
    }

    static class AnInnerClass {

    }
}

如果类继承于Android组件例如:Activity,那么把重写函数按照他们的生命周期进行排序是一个非常好的习惯。例如:

public class MainActivity extends Activity {
    //Order matches Activity lifecycle
    @Override
    public void onCreate() {}

    @Override
    public void onResume() {}

    @Override
    public void onPause() {}

    @Override
    public void onDestroy() {}
}

3.3 函数参数的排序

在Android开发过程中, Context 在函数参数中是再常见不过的了,我们最好把 Context 作为函数的第一个参数。

相反,我们回调接口应该作为其最后一个参数。

例如:

// Context always goes first
public User loadUser(Context context, int userId);

// Callbacks always go last
public void loadUserAsync(Context context, int userId, UserCallback callback);

3.4 字符串常量的命名和值

Android SDK中的很多类都用到了键值对函数,比如 SharedPreferencesBundleIntent ,所以,即便是一个小应用,我们最终也不得不编写大量的字符串常量。

当我们用到这些类时,必须将他们的将定义为 static final 字段,并且遵循以下指示作为前缀。

字段名前缀
SharedPreferencesPREF_
BundleBUNDLE_
Fragment ArgumentsARGUMENT_
Intent ExtraEXTRA_
Intent ActionACTION_

说明:虽然 Fragment.getArguments() 得到的也是 Bundle ,但因为这是 Bundle 的常用用法,所以特意为此定义一个不同的前缀。

例如:

// 注意:字段的值与名称相同以避免重复问题
static final String PREF_EMAIL = "PREF_EMAIL";
static final String BUNDLE_AGE = "BUNDLE_AGE";
static final String ARGUMENT_USER_ID = "ARGUMENT_USER_ID";

// 与意图相关的项使用完整的包名作为值的前缀
static final String EXTRA_SURNAME = "com.myapp.extras.EXTRA_SURNAME";
static final String ACTION_OPEN_USER = "com.myapp.action.ACTION_OPEN_USER";

3.5 Activities 和 Fragments 的传参

当 Activity 或 Fragment 传递数据通过 Intent 或 Bundle 时,不同值的键须遵循上一条所提及到的。

当 Activity 或 Fragment 启动需要传递参数时,那么它需要提供一个 public static 的函数来帮助启动或创建它。

这方面,AS 已帮你写好了相关的 Live Templates,启动相关 Activity 的只需要在其内部输入 starter 即可生成它的启动器,如下所示:

public static void start(Context context, User user) {
      Intent starter = new Intent(context, MainActivity.class);
      starter.putParcelableExtra(EXTRA_USER, user);
      context.startActivity(starter);
}

同理,启动相关 Fragment 在其内部输入 newInstance 即可,如下所示:

public static MainFragment newInstance(User user) {
      Bundle args = new Bundle();
      args.putParcelable(ARGUMENT_USER, user);
      MainFragment fragment = new MainFragment();
      fragment.setArguments(args);
      return fragment;
}

4. 行长限制

4.1 换行策略

  1. 除赋值操作之外,我们把换行符放在操作符之前,例如:
int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne
        + theFinalOne;
  1. 赋值操作符的换行我们放在其后,例如:
int longName =
        anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne + theFinalOne;

4.2 函数链的换行

当同一行中调用多个函数时,对每个函数的调用都应该在行的一行中,我们把换行符插入在 . 之前。

例如:

Picasso.with(context).load("https://blankj.com/images/avatar.jpg").into(ivAvatar);

应该使用如下规则:

Picasso.with(context)
        .load("https://blankj.com/images/avatar.jpg")
        .into(ivAvatar);

4.3 多参数的换行

当一个方法有很多参数或者参数很长的时候,我们应该在每个 , 后面进行换行。 比如:

loadPicture(context, "https://blankj.com/images/avatar.jpg", ivAvatar, "Avatar of the user", clickListener);

应该使用如下规则:

loadPicture(context,
        "https://blankj.com/images/avatar.jpg",
        ivAvatar,
        "Avatar of the user",
        clickListener);

4.4 RxJava 链式换行

RxJava 的每个操作符都需要换新行,并且把换行符插入在 . 之前。

例如:

public Observable<Location> syncLocations() {
    return mDatabaseHelper.getAllLocations()
            .concatMap(new Func1<Location, Observable<? extends Location>>() {
                @Override
                 public Observable<? extends Location> call(Location location) {
                     return mRetrofitService.getLocation(location.id);
                 }
            })
            .retry(new Func2<Integer, Throwable, Boolean>() {
                 @Override
                 public Boolean call(Integer numRetries, Throwable throwable) {
                     return throwable instanceof RetrofitError;
                 }
            });
}

5.资源文件规范

6.注释规范

6.1类注释

每个类完成后应该有作者姓名和联系方式的注解,对自己的代码负责。 例如:

/**
 * author : Blankj
 * time   : 2019/07/13
 * desc   : xxxx 描述
 * version: 1.0
 */
public class WelcomeActivity {
    ...
}

具体可以在AS在那个自己配置,进入 Settings -> Editor -> File and Code Templates -> Includes -> File Header,输入

/**
 * author : ${USER}
 * time   : ${YEAR}/${MONTH}/${DAY}
 * desc   : 
 * version: 1.0
 */

这样可在每次新建类的时候自动加上该头注释。

6.2方法注释

每一个成员方法(包括自定义成员方法、覆盖方法、属性方法)的方法头都必须做方法头注释,在方法前一行输入 /** + 回车 或者设置 Fix doc comment(Settings -> Keymap -> Fix doc comment)快捷键,AS 便会帮你生成模板,我们只需要补全参数即可,如下所示。

/**
 * bitmap 转 byteArr
 *
 * @param bitmap bitmap 对象
 * @param format 格式
 * @return 字节数组
 */
public static byte[] bitmap2Bytes(Bitmap bitmap, CompressFormat format) {
    if (bitmap == null) return null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bitmap.compress(format, 100, baos);
    return baos.toByteArray();
}

6.3块注释

块注释与其周围的代码在同一缩进级别。它们可以是 / ... / 风格,也可以是 // ... 风格(// 后最好带一个空格)。对于多行的 / ... / 注释,后续行必须从 ***** 开始, 并且与前一行的 ***** 对齐。以下示例注释都是 OK 的。

/*
 * This is
 * okay.
 */

// And so
// is this.

/* Or you can
* even do this. */

6.4其他注释

AS 已帮你集成了一些注释模板,我们只需要直接使用即可,在代码中输入 todo、fixme 等这些注释模板,回车后便会出现如下注释。

// TODO: 17/3/14 需要实现,但目前还未实现的功能的说明
// FIXME: 17/3/14 需要修正,甚至代码是错误的,不能工作,需要修复的说明