背景
项目的代码时间时间很长,经过太多人手,代码的规范性堪忧,目前存在较多的比较自由的「代码规范」,这非常不利于项目的维护,代码可读性也不够高。 分析现有项目的代码的情况,输出的『定制化规范』文档,用于提高代码的可读性和可维护性。
收益
对于个人:帮助团队写「正确」的代码,提升编程能力。
团队内部:统一项目的编码风格,降低维护『非自己模块』的成本
对外部门:交付更加稳定的产品,并降低后期的维护难度
开发工具配置规范
工欲善其事,必先利其器。
-
推荐使用最新的稳定版本的 Android Studio 进行开发
-
编码格式必须统一为UTF-8
-
编辑完 .java、.kt、.xml 等文件后必须格式化(需要在设置好以下几点的前提下,Reformat Code 的必要性,一定需要保证 IDE 配置一致为前提,尽可能贴切于 Android Studio 默认。强烈建议对于比较长的老代码局部格式化,不全局格式化)
每行字符数不得超过100字符(IDE默认) 设置 Editor -> Code Style
Java代码格式规则(默认设置)
- 全部设置为单类导入
XML代码格式规则(默认设置)
- 格式化快捷键(默认设置)
-
以上几处设置完毕,其他采用 Android Studio 默认方式,再进行 Reformat Code 快捷键即可。
- 可以安装Alibaba Java开发规范插件,进行代码规范的检查
-
地址:Alibaba Java Coding Guidelines - IntelliJ IDEs Plugin | Marketplace
代码格式
-
【强制】提交经过格式化的代码
-
【强制】单行代码不要超过IDE右边界,单行字符数限制不超过100个(默认设置) 如果超出行长,我们通常有两种方法来缩减行长。 1.提取一个局部变量或方法(最好) 2.使用换行符将一行换成多行(参考『换行策略』)
-
【强制】方法、参数、变量命名使用小骆驼拼写法(lowerCamelCase)
-
【强制】方法之间需要空1行
-
【强制】杜绝完全不规范的缩写,避免望文不知义。比如:condition“缩写”命名成 condi,英文拼音混合
-
【强制】如果使用到了设计模式,建议在类名中体现出具体模式。
-
【强制】方法调用的点符号与下文一起换行,在多个参数超长,在逗号后换行
换行策略(操作符"之前"换行,而逗号"之后"换行。)
这没有一个准确的解决方案来决定如何换行,通常不同的解决方案都是有效的,但是有一些规则可以应用于常见的情况。
操作符的换行
赋值操作符的换行我们放在其后,例如
int longName =
anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne + theFinalOne;
除赋值操作符之外,我们把『换行符』放在『操作符』之前,例如:
int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne
+ theFinalOne;
函数链的换行
当同一行中调用多个函数时(比如使用构建器时),对每个函数的调用应该在新的一行中,我们把换行符插入在 . 之前。
例如:
Picasso.with(context).load("https://blankj.com/images/avatar.jpg").into(ivAvatar);
Picasso.with(context)
.load("https://blankj.com/images/avatar.jpg")
.into(ivAvatar);
多参数的换行
当一个方法有很多参数或者参数很长的时候,我们应该在每个 , 后面进行换行。
例如:
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);
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;
}
});
}
- 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用『单数形式』,但是类名如果有复数含义,类名可以使用『复数形式』。 正例: 应用工具类包名为com.alibaba.open.util、类名为MessageUtils(此规则参考 spring 的框架结构)
- 【参考】枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
如果类继承于 Android 组件(例如 Activity 或 Fragment),那么把重写函数按照他们的生命周期进行排序是一个非常好的习惯,例如,Activity 实现了 onCreate()、onDestroy()、onPause()、onResume(),它的正确排序如下所示:
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() {}
}
方法的内部实现
-
【强制】尽量使用lambda代替匿名内部类
-
【极好】方法的参数和返回值,添加@NonNull,@Nullable注解 (包含其他androidx.anotation)
-
【强制】无用的代码直接删除(检测:各自模块无用代码直接删除)
-
【强制】不要任意添加try-catch,如果添加try-catch需要明确异常
-
【强制】不要在方法内部修改参数的值(a方法调用,会影响后面的b方法)
-
【推荐】单个方法实现不超过屏幕(80行,缩短代码行数,做好拆分)
-
【推荐】方法内部不需要空行
-
【推荐】不同业务View设置不同事件监听(例如OnClickListener)
-
【推荐】if-else不要写反向逻辑
-
【推荐】表达异常的分支时,少用 if-else 方式,这种方式可以改写成:if (condition) { return ; }
-
【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 正例: "test".equals(object); 反例: object.equals("test");
-
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式。
-
【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
-
【强制】在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免使用 单行的形式:if (condition) statements;
-
【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
Android代码
Android基本组件
-
【强制】Activity间通过隐式Intent的跳转,在发出Intent之前必须通过resolveActivity 检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。
-
【强制】Activity和Fragment的参数要使用@Autowired(原因:减少模板代码,支持ARouter跳转)
-
【强制】避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作, 应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做。
-
【强制】如果广播仅限于应用内,则可以使用LocalBroadcastManager.sendBroadcast()实现,避免敏感信息外泄和Intent拦截的风险。
-
【推荐】优先考虑EventBus等框架代替Broadcast(如果操作轻量级,不涉及Intent和Context的情况下)
-
【推荐】当前Activity的onPause方法执行结束后才会执行下一个Activity的onCreate 方法,所以在onPause方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率。
-
【推荐】使用 Toast 时,建议定义一个全局的Toast对象,这样可以避免连续显示Toast 时不能取消上一次Toast消息的情况(如果你有连续弹出Toast的情况,避免使用Toast.makeText)。
-
【强制】Activity或者Fragment中动态注册BroadCastReceiver时registerReceiver()和unregisterReceiver()要成对出现。否则可能导致已经注册的receiver没有在合适的时机注销,导致内存泄漏,占用内存空间,加重SystemService负担。
-
【强制】同一方法禁止多次调用sp的apply(一些SPUtils只封装单次的提交,连续调用这样的方法就有这个问题)
-
【强制】在Activity中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非 Dialog/AlertDialog,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期。
-
【推荐】不能在 Activity 没有完全显示时显示 PopupWindow 和 Dialog
进程、线程与消息通信
-
【强制】新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor 或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。(需要线程池)
-
【强制】线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor 的方 式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
-
【推荐】ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时 线程能被释放。
文件与数据库
-
【强制】任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。
-
【强制】当使用外部存储时,必须检查外部存储的可用性。
-
【强制】应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用 FileProvider。
-
【推荐】SharedPreference中只能存储简单数据类型(int、boolean、String等), 复杂数据类型建议使用文件、数据库等其他方式存储。
-
【推荐】SharedPreference 提交数据时,尽量使用 Editor#apply(),而非 Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用Editor#commit()。
-
【强制】数据库 Cursor 必须确保使用完后关闭,以免内存泄漏。
-
【强制】多线程操作写入数据库时,需要使用事务,以免出现同步问题。
-
【推荐】大数据写入数据库时,请使用事务或其他能够提高 I/O 效率的机制,保证执行速度。
-
【强制】执行 SQL 语句时,应使用 SQLiteDatabase#insert()、update()、delete(), 不要使用 SQLiteDatabase#execSQL(),以免 SQL 注入风险。
Android资源文件
布局
-
【推荐】推荐使用ConstraintLayout(必要时可以有内部嵌套),布局中不得使用ViewGroup多重嵌套(ConstraintLayout需要普及讲解)
-
【强制】禁止在非ui线程进行 view 相关操作。
-
【强制】禁止在设计布局时多次设置子 view和父view中为同样的背景造成页面过度绘制,推荐将不需要显示的布局进行及时隐藏。
-
【推荐】灵活使用布局,推荐 Merge、ViewStub 来优化布局,尽可能多的减少 UI 布局层级。
-
【极好】布局文件可以不用运行,仿真预览(使用tools)
Bitmap、Drawable 与动画
-
【强制】加载大图片或者一次性加载多张图片,应该在异步线程中进行。图片的加载,涉及到I 操作,以及CPU密集操作,很可能引起卡顿。
-
【强制】png 图片使用 tinypng 或者类似工具压缩处理,减少包体积。
-
【强制】在 Activity.onPause()或 Activity.onStop()回调中,关闭当前 activity 正在执 行的的动画。
-
【推荐】在动画或者其他异步任务结束时,应该考虑回调时刻的环境是否还支持业务处理。例如 Activity的onStop()函数已经执行,且在该函数中主动释放了资源,此时回调中如果不做判断就会空指针崩溃。
-
【推荐】尽量减少Bitmap(BitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、渐变色(GradientDrawable)、StateSelector(StateListDrawable)等与Shape结合的形式构建绘图。
Android 资源文件命名与使用
-
【强制】资源文件需带模块前缀(公用放在base)
-
【强制】动画资源文件(anim/和animator/)
Android主要包含属性动画和视图动画。属性动画文件需要放在res/animator/目录下,视图动画文件需放在res/anim/目录下。命名规则:{模块名_}{逻辑名称}_动画类型_方向,{}中的内容为可选。
逻辑名称可由多个单词加下划线组成。例如:refresh_progress.xml、market_cart_add.xml、market_cart_remove.xml
动画类型和方向的命名方式举例:
名称 | 说明 |
fade_in | 淡入 |
fade_out | 淡出 |
push_down_in | 从下方推入 |
push_down_out | 从下方推出 |
push_left | 推向左方 |
slide_in_from_top | 从头部滑动进入 |
zoom_enter | 变形进入 |
slide_in | 滑动进入 |
shrink_to_middle | 中间缩小 |
-
【强制】颜色资源文件(color/)
color/是专门用于存放颜色相关资源的文件夹。命名规则:类型{_模块名}_逻辑名称。
-
【强制】图片资源文件(drawable/ 和 mipmap/)
res/drawable/ 目录下放的是位图文件(.png、.9.png、.jpg、.gif)或编译为可绘制对象资源子类型的 XML 文件,而 res/mipmap/ 目录下放的是不同密度的启动图标,所以 res/mipmap/ 只用于存放启动图标,其余图片资源文件都应该放到 res/drawable/ 目录下。
命名规则:类型{_页面名称}{_逻辑名称}{_颜色}
说明:{}中的内容为可选;类型 可以是可绘制对象资源类型。 例如:
名称 | 说明 |
bg_input.png | 输入框背景 类型_逻辑名称 |
bg_main.png | 主页背景 类型_逻辑名称 |
bg_main_head.png | 主页头部背景 类型_页面名称_逻辑名称 |
btn_back.png | 返回按键 类型_逻辑名称 |
btn_main_about.png | 主页关于按键 类型_页面名称_逻辑名称 |
btn_red.png | 红色按键 类型_颜色 |
btn_red_big.png | 红色大按键 类型_颜色 |
def_search_cell.png | 搜索页面默认单元图片 类型_页面名称_逻辑名称 |
divider_list_line.png | 列表分割线 类型_逻辑名称 |
divider_maket_white.png | 商城白色分割线 类型_页面名称_颜色 |
divider_white.png | 白色分割线 类型_颜色 |
ic_avatar_small.png | 小头像图标 类型_逻辑名称 |
ic_edit.png | 编辑图标 类型_逻辑名称 |
ic_more_help.png | 更多帮助图标 类型_逻辑名称 |
sel_search_ok.xml | 搜索界面确认选择器 类型_页面名称_逻辑名称 |
shape_music_ring.xml | 音乐界面环形形状 类型_页面名称_逻辑名称 |
如果有多种形态,如按钮选择器:sel_btn_xx.xml,采用如下命名:
名称 | 说明 |
sel_btn_xx | 作用在 btn_xx 上的 selector |
btn_xx_normal | 默认状态效果 |
btn_xx_pressedstate_pressed | 点击效果 |
btn_xx_focusedstate_focused | 聚焦效果 |
btn_xx_disabledstate_enabled | 不可用效果 |
btn_xx_checkedstate_checked | 选中效果 |
btn_xx_selectedstate_selected | 选中效果 |
btn_xx_hoveredstate_hovered | 悬停效果 |
btn_xx_checkablestate_checkable | 可选效果 |
btn_xx_activatedstate_activated | 激活效果 |
btn_xx_window_focusedstate_window_focused | 窗口聚焦效果 |
注意:使用 Android Studio 的插件 SelectorChapek 可以快速生成 selector,前提是命名要规范。
-
【强制】 布局资源文件(layout/)
命名规则:模块名_类型_
类型 | 名称 | 说明 |
Activity | passport_activity_main.xml | 窗体页面 模块名_类型_页面名称 |
adapter 的列表项 | my_item_main_song.xml | 主页歌曲列表项 模块名_类型_页面名称_逻辑名称 |
Dialog | resume_dialog_loading.xml | 加载对话框 模块名_类型_逻辑名称 |
Fragment | resume_fragment_music.xml | 音乐片段 模块名_类型_逻辑名称 |
PopupWindow | position_popup_info.xml | 信息弹窗(PopupWindow) 模块名_类型_逻辑名称 |
-
【强制】视图layout布局的命名:view_float_live
-
【强制】嵌套layout布局的命名:layout_title_bar(一般有复用)
-
【强制】布局资源 id 命名
命名规则:类型小写_{模块名_}_逻辑名,例如:btn_main_search、btn_back。
-
【推荐】colors.xml
的name命名使用下划线命名法,在你 colors.xml 文件中应该只是映射颜色的名称一个argb值,而没有其它的。不要使用它为不同的按钮来定argb值。
例如,不要像下面这样做:
<resources>
<color name="button_foreground">#FFFFFF</color>
<color name="button_background">#2A91BD</color>
<color name="comment_background_inactive">#5F5F5F</color>
<color name="comment_background_active">#939393</color>
<color name="comment_foreground">#FFFFFF</color>
<color name="comment_foreground_important">#FF9D2F</color>
...
<color name="comment_shadow">#323232</color>
使用这种格式,会非常容易重复定义argb值,而且如果应用要改变基色的话会非常困难。同时,这些定义是跟一些环境关联起来的,如 button或者comment,应该放到一个按钮风格中,而不是在 colors.xml 文件中。
应该这样做
<resources>
<!-- grayscale -->
<color name="white" >#ffffff</color>
<color name="gray_light">#dbdbdb</color>
<color name="gray" >#939393</color>
<color name="gray_dark" >#5f5f5f</color>
<color name="black" >#323232</color>
<!-- basic colors -->
<color name="green">#27d34d</color>
<color name="blue">#2a91db</color>
<color name="orange">#ff9d2f</color>
<color name="red">#ff432f</color>
</resources>
向应用设计者那里要这个调色板,名称不需要跟 "green"、"blue" 等等相同。"brand_primary"、"brand_secondary"、"brand_negative" 这样的名字也是完全可以接受的。像这样规范的颜色很容易修改或重构,会使应用一共使用了多少种不同的颜色变得非常清晰。通常一个具有审美价值的 UI 来说,减少使用颜色的种类是非常重要的。
-
【强制】styles.xml
style的name命名使用大驼峰命名法,几乎每个项目都需要适当的使用 styles.xml 文件,因为对于一个视图来说,有一个重复的外观是很常见的,将所有的外观细节属性(colors、padding、font)放在 styles.xml 文件中。在应用中对于大多数文本内容,最起码你应该有一个通用的styles.xml 文件,例如:
<resources>
<style name="ContentText">
<item name="android:textSize">@dimen/font_normal</item>
<item name="android:textColor">@color/basic_black</item>
</style>
</resources>
应用到 TextView 中
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/price"
style="@style/ContentText"/>
【推荐】style 资源采用小写单词+下划线方式命名,写入 module_styles.xml 文件中, 采用以下规则:父style名称.当前style名称,如ParentTheme.ThisActivityTheme。
常见的视图控件缩写表
UI控件 | 缩写 |
LinearLayout | ll_ |
RelativeLayout | rl_ |
FrameLayout | fl_ |
Button | btn_ |
ImageButton | ibtn_ |
TextView | tv_ |
ImageView | iv_ |
RecyclerView | rv_ |
ConstraintLayout | cl_ |
ScollView | NestedScollView | sv_ |
CheckBox | cb_ |
RadioButton | rb_ |
RadioGroup | rg_ |
EditText | et_ |
View | v_ |
ViewPager | ViewPager2 | vp_ |
自定义View | i_ |
ViewStub | vs_ |
include | in_ |