Android开发规范
Android开发规范
一、命名规范
JAVA类和变量命名
1.命名使用英文单词拼接,驼峰命名法,不可使用拼音
2.类名采用首字母大写的驼峰命名法。但以下情形例外:DO / BO / DTO / VO等。例如:
class MarcoPolo{}
class UserDO{}
3.方法名、参数名、成员变量、局部变量采用首字母小写的驼峰命名法。例如:
localValue;inputUserId;
4.常量名全部大写,单词间用下划线隔开。例如:
MAX_STOCK_COUNT
5.布尔变量前不要加is。例如set,不要用isSet.
如果用isSet,安卓生成的get/set方法,会生成和变量名一样的方法
private boolean isSet;
public boolean isSet() {
return isSet;
}
public void setSet(boolean set) {
isSet = set;
}
private boolean set;
public boolean isSet() {
return set;
}
public void setSet(boolean set) {
this.set = set;
}
6.命名不要使用缩写,尽量使用完整的单词组合
资源文件命名
1.资源文件需带模块前缀
2.layout文件命名
- Activity的layout,以activity_module开头
- Fragment的layout,以fragment_module 开头
- Dialog的layout, 以dialog_module 开头
- include的layout, 以include_module 开头
- ListView的行layout, 以list_item_module 开头
- RecyclerView的行layout, 以recycle_item_module 开头
- GridView的行layout, 以grid_item_module 开头
3.图片资源命名
- drawable 资源名称以小写单词+下划线的方式命名
- 建议只使用一套,放在drawable-xxhdpi。如确实因为图片大小引起视觉走查问题,再放多套图。
- 采用规则如下:模块名_业务功能描述_控件描述_控件状态限定词
- 如:login_login_btn_pressed, login_name_btn_normal
4.color资源要写入colors.xml文件
- 命名规则 模块名_使用描述,后年不用加_color 。
- 前面使用颜色定义,后面每个场景使用定义的颜色,这样方便多主题配置
- 如果数目太多,可以新建文件module_colors.xml
例如:
<color name="main_color">#33b5e5e5</color>
<color name="main_case_list_bg">@color/main_color </color>
5.string资源要写入strings.xml,命名规则 模块名_逻辑名称。(如果数目太多,可以新建文件module_strings.xml)
例如:
<string name="main_homepage_notice_tips">首页通知</string>
6.控件命名,原则上以驼峰法命名,View 组件的资源 id 需要以 View 的缩写作为 前缀。常用缩写表如下:
控件 | 缩写 | 控件 | 缩写 |
---|---|---|---|
LinearLayout | ll | RelativeLayout | rl |
ConstraintLayout | cl | ListView | lv |
ScollView | sv | TextView | tv |
Button | btn | ImageView | iv |
CheckBox | cb | RadioButton | rb |
EditText | et | ||
DatePicker | dp | ProgressBar | pb |
TextView示例:
<EditText
android:id="@+id/et_vin"
android:layout_width="200dp"
android:layout_height="wrap_content"/>
7.java文件的控件名称,使用驼峰命名法,etVin.
二、代码格式规范
1.大括号的使用约定。
如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则:
- 左大括号前不换行。
- 左大括号后换行。
- 右大括号前换行。
- 右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行。
例如:
public void start { }
public void end(int max) {
if(max > 10){
testA();
} else {
testB();
}
}
2.在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用 单行的编码方式
正例:
if (condition) {
statements;
}
反例:if (condition) statements;
3.空格和缩进,使用AndroidStudio自带的格式化,快捷键 alt+command+L
三、注释规范
1.类、类方法的注释必须使用 Javadoc 规范,使用/**xxx**/格式,不得使用 // xxx方式。好处如下:
- 在IDE编辑窗口中,Javadoc方式会提示相关注释,生成Javadoc可以正确输出相应注释;
- 在IDE中工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。
/**
* 评估单位选择
* Created by admin on 18/6/21.
*/
public class OuterGarageSearchActivity extends BaseNormalActivity {
...
/**
* 设置搜索文本
*/
public void setText() {
this.setQuery(Html.fromHtml(getText()), false);
}
...
}
2.所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。
说明: 对子类的实现要求,或者调用注意事项,请一并说明。
/**
* 返回当前Activity的布局文件ID,每个Activity子类都必须实现该方法
* @return layout
*/
abstract protected int getLayoutResourceId();
3.所有的类都必须添加创建者和创建日期。
/**
* 评估单位选择
* Created by admin on 18/6/21.
*/
public class OuterGarageSearchActivity extends BaseNormalActivity {
4.方法内部注释
- 方法内部单行注释,在被注释语句上方另起一行,使用//注释。
- 方法内部多行注释,使用/* */注释,注意与代码对齐。
protected void downData(final int position) {
//无网络判断
if (!NetworkUtil.isNetworkAvailable(this)) {
/**
* 修改界面,提示无网络对话框
*/
updateAllDownloadUI();
MessageDialog.init().setTitle("无网络连接")
.setMsg("当前未能连接到网络!").show(getSupportFragmentManager());
return;
}
...
}
5.所有的枚举类型字段必须要有注释,说明每个数据项的用途。
android 中不建议使用美剧enums,使用注解@IntDef,@StringDef代替,同样要做好注释
//1-银行卡 2-驾驶证 3-身份证 4-行驶证
public static final String BankCard = "1";
public static final String DrivingLicense = "2";
public static final String IdCard = "3";
public static final String VehicleLicense = "4";
@Documented // 表示开启Doc文档
@StringDef({IdCard, BankCard, DrivingLicense, VehicleLicense})
@Target({
ElementType.PARAMETER,
ElementType.FIELD,
ElementType.METHOD,
}) //表示注解作用范围,参数注解,成员注解,方法注解
@Retention(RetentionPolicy.SOURCE) //表示注解所存活的时间,在运行时,而不会存在 .class 文件中
public @interface OcrType {}
private @OcrType String ocrType;
public void setOcrType(@OcrType String ocrType) {
this.ocrType = ocrType;
}
四、Android开发
Application
1.不要在 Android 的 Application 对象中缓存数据。基础组件之间的数据共享,请使用 Intent 等机制,也可使用 SharedPreferences 等数据持久化机制。
2.在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程初始化。特别是后台进程减少不必要的业务初始化。
public class MyApplication extends Application {
@Override
public void onCreate() {
//在所有进程中初始化
....
//仅在主进程中初始化
if (mainProcess) {
...
}
//仅在后台进程中初始化
if (bgProcess) {
...
}
}
}
Activity
1.不能在 Activity 没有完全显示时显示 PopupWindow 和 Dialog。
2.在 Activity 中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非 Dialog/AlertDialog,这样便于随 Activity 生命周期管理对话框/弹出浮层的生命周期。
3.使用 Adapter 的时候,如果你使用了 ViewHolder 做缓存,在 getView()的 方法中无论这项 convertView 的每个子控件是否需要设置属性(比如某个 TextView 设置的文本可能为 null,某个按钮的背景色为透明,某控件的颜色为透明等),都需 要为其显式设置属性(Textview 的文本为空也需要设置 setText(""),背景透明也需要 设置),否则在滑动的过程中,因为 adapter item 复用的原因,会出现内容的显示错乱。
4.在需要时刻刷新某一区域的组件时,建议通过以下方式避免引发全局 layout 刷新:
- 设置固定的 view 大小的高宽,如倒计时组件等;
- 调用 view 的 layout 方式修改位置,如弹幕组件等;
- 通过修改 canvas 位置并且调用 invalidate(int l, int t, int r, int b)等方式限定刷新区域
- 通过设置一个是否允许 requestLayout 的变量,然后重写控件的 requestlayout、
5.在 Activity.onPause()或 Activity.onStop()回调中,关闭当前 activity 正在执行的的动画。
在动画或者其他异步任务结束时,应该考虑回调时刻的环境是否还支持业 务处理。例如 Activity 的 onStop()函数已经执行,且在该函数中主动释放了资源, 此时回调中如果不做判断就会空指针崩溃。
6.尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放。图片数量较少的 AnimationDrawable 还是可以接受的。但是如果你的帧动画中如果包含过多帧图片,一次性加载所有帧图片所导致的内存消耗会使低端机发生 OOM 异常。
Service
1.避免在 Service#onStartCommand()/onBind()方法中执行耗时操作
如果确 实有需求,应改用 IntentService 或采用其他异步机制完成。
Service 组件一般运行主线程,应当避免耗时操作,
如果有耗时操作应该在 Worker线程执行。
可以使用 IntentService 执行后台任务。
Broadcast
1.避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作, 应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做。
2.对于只用于应用内的广播,优先使用 LocalBroadcastManager 来进行注册 和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。
对于使用 Context#sendBroadcast()等方法发送全局广播的代码进行提示。
如果该广播仅用于应用内,则可以使用LocalBroadcastManager
避免广播泄漏以及广播被拦截等安全问题,同时相对全局广播本地广播的更高效。
布局文件
1.不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因为这 样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图面。
ScrollView 中嵌套 List 或 RecyclerView 的做法官方明确禁止。
除了开发过程中遇到的各种视觉和交互问题,这种做法对性能也有较大损耗。
ListView 等 UI 组件自身有垂直滚动功能,也没有必要在嵌套一层 ScrollView。
目前为了较好的 UI 体验,更贴近 Material Design 的设计,推荐使用 NestedScrollView。
2.布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout 嵌套,改用 RelativeLayout,可以有效降低嵌套数。
Android 应用页面上任何一个 View 都需要经过 measure、layout、draw 三个步骤
才能被正确的渲染。从 xml layout 的顶部节点开始进行 measure,每个子节点都需 要向自己的父节点提供自己的尺寸来决定展示的位置,在此过程中可能还会重新
measure(由此可能导致 measure 的时间消耗为原来的 2-3 倍)。节点所处位置越
深,套嵌带来的 measure 越多,计算就会越费时。这就是为什么扁平的 View 结构
会性能更好。
3.禁止在设计布局时多次设置子 view 和父 view 中为同样的背景造成页面过 度绘制,推荐将不需要显示的布局进行及时隐藏。
线程
1.新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor 或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。
说明: 池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解 决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。另外创建匿名线程不便于后续的资源使用分析,对性能分析等会造成困扰。
2.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方 式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM;
- CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
3.新建线程时,定义能识别自己业务的线程名称,便于性能优化和问题排查
4.ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时 线程能被释放。
5.谨慎使用 Android 的多进程,多进程虽然能够降低主进程的内存压力,但会 遇到如下问题:
- 不能实现完全退出所有 Activity 的功能;
- 首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白屏还是黑屏和新 Activity 的主题有关
- 应用内多进程时,Application 实例化多次,需要考虑各个模块是否都需要在所有进程中初始化;
- 多进程间通过 SharedPreferences 共享数据时不稳定。
文件存储
1.任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。
说明:
Android 应用提供内部和外部存储,分别用于存放应用自身数据以及应用产生的用 户数据。可以通过相关 API 接口获取对应的目录,进行文件操作。
正例:
android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
2.当使用外部存储时,必须检查外部存储的可用性。
正例
// 读/写检查
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.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
3.SharedPreference 中只能存储简单数据类型(int、boolean、String 等), 复杂数据类型建议使用文件、数据库等其他方式存储。
4.SharedPreference 提交数据时,尽量使用 Editor#apply(),而非 Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使 用 Editor#commit()。
说明:
SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入 磁盘,commit 方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,apply 会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。
5.执行 SQL 语句时,应使用 SQLiteDatabase#insert()、update()、delete(), 不要使用 SQLiteDatabase#execSQL(),以免 SQL 注入风险。
正例:
public int updateUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put("content", content);
String[] args = {String.valueOf(userId)};
return db.update(TUserPhoto, cv, "userId=?", args);
}
6.如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受 信任的外部数据直接拼接在原始 SQL 语句中,可使用一个用于将 ? 作为可替换参 数的选择子句以及一个单独的选择参数数组,会避免 SQL 注入。
正例:
// 使用一个可替换参数
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
反例:
// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;
图片
1.在 ListView,ViewPager,RecyclerView,GirdView 等组件中使用图片时, 应做好图片的缓存,避免始终持有图片导致内存泄露,也避免重复创建图片,引起性能问题 。
建 议 使 用 Fresco ( https://github.com/facebook/fresco )、 Glide (https://github.com/bumptech/glide)等图片库。
2.使用 ARGB_565 代替 ARGB_888,在不怎么降低视觉效果的前提下,减少 内存占用。
说明:
android.graphics.Bitmap.Config 类中关于图片颜色的存储方式定义:
- ALPHA_8 代表 8 位 Alpha 位图;
- ARGB_4444 代表 16 位 ARGB 位图;
- ARGB_8888 代表 32 位 ARGB 位图;
- RGB_565 代表 8 位 RGB 位图。
位图位数越高,存储的颜色信息越多,图像也就越逼真。大多数场景使用的是
ARGB_8888 和 RGB_565,RGB_565 能够在保证图片质量的情况下大大减少内存
的开销,是解决 oom 的一种方法。
但是一定要注意 RGB_565 是没有透明度的,如果图片本身需要保留透明度,那么就不能使用 RGB_565
其他
1.循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
说明:下例中,反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象, 然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}