Android 开发规范

628 阅读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
  • 尽量不使用缩写

常见类命名格式

描述 例如
Activity Activity 为后缀标识 主页面类 HomeActivity
Adapter Adapter 为后缀标识 新闻详情适配器 NewsDetailAdapter
解析类 Parser 为后缀标识 首页解析类 HomePosterParser
工具方法类 UtilsManager 为后缀标识 打印工具类:PrinterUtils
数据库类 DBHelper 后缀标识 新闻数据库:NewsDBHelper
Service Service 为后缀标识 时间服务 TimeService
BroadcastReceiver Receiver 为后缀标识 推送接收 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控件缩写表

名称 缩写
Button btn
CheckBox cb
EditText et
FrameLayout fl
GridView gv
ImageButton ib
ImageView iv
LinearLayout ll
ListView lv
ProgressBar pb
RadioButtion rb
RecyclerView rv
RelativeLayout rl
ScrollView sv
SeekBar sb
Spinner spn
TextView tv
ToggleButton tb
VideoView vv
WebView wv

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 字段,并且遵循以下指示作为前缀。

字段名前缀
SharedPreferences PREF_
Bundle BUNDLE_
Fragment Arguments ARGUMENT_
Intent Extra EXTRA_
Intent Action ACTION_

说明:虽然 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 需要修正,甚至代码是错误的,不能工作,需要修复的说明