Android开发规范

Android开发规范

工欲善其事,必先利其器。为了有利于项目维护、增强代码可读性、提升 Code Review 效率以及规范团队安卓开发,特此整理了一篇Android基本开发规范,期望能帮助团队成长。

网络上关于Android开发规范的资料很多,这里只选取了个人认为比较重要的部分,更详细的文档我会在文末添加链接,有兴趣的可以进一步学习

1.Android Studio 规范

  • 尽量使用最新的稳定版的 IDE 进行开发;
  • 编码格式统一为 UTF-8
  • 编辑完.java.xml 等文件后一定要 格式化,格式化,格式化(如果团队有公共的样式包,那就遵循它,否则统一使用 AS 默认模板即可);
  • 删除多余的import,减少警告出现,可利用 AS 的Optimize Imports(Settings -> Keymap -> Optimize Imports)快捷键

2.命名规范

代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。正确的英文拼写和语法可以让阅读者易于理解,避免歧义。

注意:即使纯拼音命名方式也要避免采用。但 alibabataobaoyoukuhangzhou 等国际通用的名称,可视同英文。

2.1 包名

包名全部小写,连续的单词只是简单地连接起来,不使用下划线,采用反域名命名规则,全部使用小写字母。一级包名是顶级域名,通常为 comedugovnetorg 等,二级包名为公司名,三级包名根据应用进行命名,后面就是对包名的划分了,关于包名的划分,推荐采用 PBF(按功能分包 Package By Feature).

com
└── domain
    └── app
        ├── App.java 定义 Application 类
        ├── Config.java 定义配置数据(常量)
        ├── base 基础组件
        ├── custom_view 自定义视图
        ├── data 数据处理
        │   ├── DataManager.java 数据管理器,
        │   ├── local 来源于本地的数据,比如 SP,Database,File
        │   ├── model 定义 model(数据结构以及 getter/setter、compareTo、equals 等等,不含复杂操作)
        │   └── remote 来源于远端的数据
        ├── feature 功能
        │   ├── feature0 功能 0
        │   │   ├── feature0Activity.java
        │   │   ├── feature0Fragment.java
        │   │   ├── xxAdapter.java
        │   │   └── ... 其他 class
        │   └── ...其他功能
        ├── injection 依赖注入
        ├── util 工具类
        └── widget 小部件

2.2 类名

类名都以 UpperCamelCase(大驼峰) 风格编写。

例如:

Activity Activity 为后缀标识 欢迎页面类 WelcomeActivity
Adapter Adapter 为后缀标识 NewsAdapter
自定义的共享基础类 Base 开头 BaseActivity, BaseFragment

测试类的命名以它要测试的类的名称开始,以 Test 结束。例如:HashTestHashIntegrationTest

接口(interface):命名规则与类一样采用大驼峰命名法,多以 ableible结尾,如 interface Runnableinterface Accessible

2.3 方法名

方法名都以 lowerCamelCase 风格编写。

initXX() 初始化相关方法,使用 init 为前缀标识,如初始化布局 initView()
isXX(), checkXX() 方法返回值为 boolean 型的请使用 is/check 为前缀标识
handleXX(), processXX() 对数据进行处理的方法

2.4 常量名

常量名命名模式为 CONSTANT_CASE,全部字母大写,用下划线分隔单词。

2.5 非常量字段名

非常量字段名以 lowerCamelCase 风格的基础上改造为如下风格:基本结构为 scope{Type0}VariableName{Type1}type0VariableName{Type1}variableName{Type1}

说明:{} 中的内容为可选。

2.5.1 scope(范围)

非公有,非静态字段命名以 m 开头。

静态字段命名以 s 开头。

其他字段以小写字母开头。

public class MainActivity extends AppCompatActivity {
    public int publicField;
    private static String sString;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}
2.5.2 控件类型

考虑到 Android 众多的UI控件,为避免控件和普通成员变量混淆以及更好地表达意思,所有用来表示控件的成员变量统一加上控件缩写作为前缀。

例如:tvAvatarrvBooksflContainer

2.5.3 数据类型

对于表示集合或者数组的非常量字段名,我们可以添加后缀来增强字段的可读性,比如:

集合添加如下后缀:ListMapSet

数组添加如下后缀:Arr

例如:mIvAvatarListuserArrfirstNameSet

不明确数据类型的也可以使用复数形式来表示,如mBooks;

2.6 字符串常量的命名和值

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

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

SharedPreferences PREF_
Bundle BUNDLE_
Fragment Arguments ARGUMENT_
Intent Extra EXTRA_
Intent Action ACTION_
// 注意:字段的值与名称相同以避免重复问题
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. 代码规范

3.1 Activities 和 Fragments 的传参

ActivityFragment 传递数据通过 IntentBundle 时,不同值的键须遵循上一条所提及到的。

ActivityFragment 启动需要传递参数时,那么它需要提供一个 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;
}

3.2 基本组件规范

  1. Activity#onSaveInstanceState()方法不是 Activity 生命周期方法,它是用来在 Activity 被意外销毁时保存 UI 状态的,只能用于保存临时性数据,例如UI控件的属性等,不能跟数据的持久化存储混为一谈。持久化存储应该在 Activity#onPause()/onStop()中实行.
  2. 避免在 Service#onStartCommand()/onBind()方法中执行耗时操作,如果有需求,应改用 IntentService 或采用其他异步机制完成.
  3. 避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做.
  4. 避免使用隐式 Intent广播敏感信息,信息可能被其他注册了对应BroadcastReceiver 的 App 接收,如果广播仅限于应用内,则可以使用 LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和 Intent拦截的风险.
  5. 不要在 Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的销毁和停止,因为 onDestroy()执行的时机可能较晚。可根据实际需要,在 Activity#onPause()/onStop() 中结合 isFinishing()的判断来执行.
  6. Activity或者Fragment中动态注册BroadCastReceiver时,registerReceiver()unregisterReceiver()要成对(生命周期对应)出现.Activity 的生命周期不对应,可能出现多次 onResume 造成 receiver 注册多个,但最终只注销一个,其余 receiver 产生内存泄漏。

3.3 UI 与布局

  1. 在 Activity 中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非 Dialog/AlertDialog,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期.
  2. 禁止在设计布局时多次为子 View父 View 设置同样背景进而造成页面过度绘制,无需显示的布局及时隐藏.
  3. 在需要时刻刷新某一区域的组件时,建议通过以下方式避免引发全局 layout 刷新:
1) 设置固定的 View 大小的宽高,如倒计时组件等;
2) 调用 View 的 layout 方法修改位置,如弹幕组件等;
3) 通过修改 Canvas 位置并且调用 invalidate(int l, int t, int r, int b)等方式限定刷新区域;
4) 通过设置一个是否允许requestLayout的变量,然后重写控件的requestlayout、 onSizeChanged
方法,判断控件的大小没有改变的情况下,当进入 requestLayout 的时候,直接返回而不调用 super 
的 requestLayout 方法。
  1. 不能在 Activity 没有完全显示时显示 PopupWindowDialog,推荐在 Activity#onAttachedToWindow()/Activity#onWindowFocusChanged() 之后创建对话框.
  2. 尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错.
  3. 不能使用 ScrollView 包裹ListView/GridView/ExpandableListVIew;这样会把 ListView 的所有 Item 加载到内存中,会消耗巨大的内存和 cpu 资源,推荐使用 NestedScrollView.

3.4 进程、线程与消息通信

  1. 不要通过 Intent 在 Android 基础组件之间传递大数据(binder transaction 缓存为 1MB),可能导致 OOM,***TransactionTooLargeException***等.
  2. 新建线程时,定义能识别自己业务的线程名称,便于性能优化和问题排查.
  3. 新建线程时,必须通过线程池提供(AsyncTask,ThreadPoolExecutor,Rxjava,kotlin 协程,或其他形式自定义线程池,不能直接使用Executors),不允许在应用中自行显式创建线程.
  4. ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时线程能被释放.
  5. 禁止在多进程之间用 SharedPreferences 共享数据,虽然可以(MODE_MULTI_PROCESS),但官方已***不推荐***。

3.5 文件与数据库

  1. 不要硬编码文件路径,使用 Android 文件系统 API 访问
android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
  1. 当使用外部存储时,必须检查外部存储的可用性
// 读/写检查
public boolean isExternalStorageWritable() {
 String state = Environment.getExternalStorageState();
 if (Environment.MEDIA_MOUNTED.equals(state)) {
 return true;
 }
 return false;
}
// 只读检查
public boolean isExternalStorageReadable() {
 String state = Environment.getExternalStorageState();
 if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
 return true;
 }
 return false;
}
  1. SharedPreference 中只能存储简单数据类型(int、boolean、String 等),复杂数据类型建议使用文件、数据库等其他方式存储.
  2. SharedPreference 提交数据时,尽量使用 Editor#apply() ,而非 Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用 Editor#commit(),commit 会直接读写磁盘,频繁操作性能不好.
  3. 数据库 Cursor 必须确保使用完后关闭,以免内存泄漏.

3.6 BitmapDrawable与动画

  1. ListViewViewPagerRecyclerViewGirdView 等组件中使用图片时,应做好图片的缓存,避免始终持有图片导致内存溢出,也避免重复创建图片,引起性能问题。建议 使 用 FrescoGlide等图片库.
  2. 在 #onPause()或 #onStop()回调中,关闭当前正在执行的的动画.
  3. 使用 RGB_565 代替 RGB_888,在不怎么降低视觉效果的前提下,减少内存占用.(Glide).
  4. 尽量减少 BitmapBitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、渐变色(GradientDrawable)、StateSelectorStateListDrawable)等与 Shape 结合的形式构建绘图.
  5. 谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个 gif 图片的大小.

4. 资源文件规范

资源文件命名为全部小写,采用下划线命名法。

4.1 动画资源文件(anim/ 和 animator/)

安卓主要包含属性动画和视图动画,其视图动画包括补间动画和逐帧动画。属性动画文件需要放在 res/animator/ 目录下,视图动画文件需放在 res/anim/ 目录下。

命名规则:{模块名_}逻辑名称

说明:{} 中的内容为可选,逻辑名称 可由多个单词加下划线组成。

例如:refresh_progress.xmlmarket_cart_add.xmlmarket_cart_remove.xml

如果是普通的补间动画或者属性动画,可采用:动画类型_方向 的命名方式。

fade_in 淡入
fade_out 淡出
slide_in_from_top 从头部滑动进入

4.2 图片资源文件(drawable/ 和 mipmap/)

res/drawable/ 目录下放的是位图文件(.png.9.png.jpg.gif)或编译为可绘制对象资源子类型的 XML 文件,而 res/mipmap/ 目录下放的是不同密度的启动图标,所以 res/mipmap/ 只用于存放启动图标,其余图片资源文件都应该放到 res/drawable/ 目录下。

命名规则:类型{_模块名}_逻辑名称类型{_模块名}_颜色

说明:{} 中的内容为可选;类型 可以是可绘制对象资源类型,也可以是控件类型;最后可加后缀 _small 表示小图,_big 表示大图。

btn_main_about.png 主页关于按键 类型_模块名_逻辑名称
btn_red.png 红色按键 类型_颜色
divider_white.png 白色分割线 类型_颜色

如果有多种形态,如按钮选择器:sel_btn_xx.xml,采用如下命名:

sel_btn_xx 作用在 btn_xx 上的 selector
btn_xx_normal 默认状态效果
btn_xx_pressed state_pressed 点击效果

4.3 布局资源文件(layout/)

命名规则:类型_模块名类型{_模块名}_逻辑名称

说明:{} 中的内容为可选。

activity_main.xml 主窗体 类型_模块名
fragment_music.xml 音乐片段 类型_模块名
dialog_loading.xml 加载对话框 类型_逻辑名称

4.4 菜单资源文件(menu/)

菜单相关的资源文件应放在该目录下。

命名规则:{模块名_}逻辑名称

说明:{} 中的内容为可选。

例如:main_drawer.xmlnavigation.xml

4.5 values 资源文件(values/)

values/ 资源文件下的文件都以 s 结尾,如 attrs.xmlcolors.xmldimens.xml,起作用的不是文件名称,而是 标签下的各种标签,

比如