Android开发规范参考Java版

Android开发规范参考(Java版)

一、命名风格

1.类名使用 UpperCamelCase 风格

不要中英文混合(通用拼音除外,如Beijing等),以每个单词首字母大写。

2.方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格

驼峰命名法,第一个单词首字母要小写,其他单词首字母大写。

①为区分成员变量和局部变量,可以在书写成员变量时,第一个字母用m(member),其他单词遵循驼峰命名法,如mName。注意在写实体类时则不能用m表示member,因为实体类一般都是纯变量和setter、getter。

②布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。

反例:定义为基本数据类型 Boolean isDeleted;的属性,它的方法也是 isDeleted(),RPC框架在反向解析的时候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

3.包名命名

包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

正例:应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils

4.数组定义

类型与中括号紧挨相连来定义数组。

正例:定义整形数组 int[] arrayDemo;

反例:在 main 参数中,使用 String args[]来定义。

5.接口

接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。

二、常量定义

1.常量命名全部大写,单词间用下划线隔开

使用public/private static final定义,力求语义表达完整清楚,不要嫌名字长。

正例:MAX_STOCK_COUNT

反例:MAX_COUNT

2.不要使用接口作为常量定义类

3.不要使用一个常量类维护所有常量

按常量功能进行归类,分开维护。

说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。

正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。

三、代码格式

1.大括号使用

左大括号前不换行,左大括号后换行,右大括号前换行,右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。

如果是大括号内为空,则简洁地写成{}即可,不需要换行。

2.空格使用

①(强制)任何二目、三目运算符的左右两边都需要加一个空格;

②(强制)if/for/while/switch/do 等保留字与括号之间都必须加空格;

③(强制)注释的双斜线与注释内容之间有且仅有一个空格;

④(强制)方法参数在定义和传入时,多个参数逗号后边必须加空格;

⑤(可选)没有必要增加若干空格来使某一行的字符与上一行对应位置的字符对齐;

3.编写注释

良好的注释可以提高团队开发效率。

①类、方法、成员变量的注释

/**
 * 类、方法、成员变量
 */

对于类注释,应该完善作者、时间、类的作用等信息

②代码中的注释,使用双斜线

四、OOP 规约

1.避免通过一个类的对象引用访问此类的静态变量或静态方法

无谓增加编译器解析成本,直接用类名来访问即可。

2.相同参数类型,相同业务含义,才可以使用 Java 的可变参数

避免使用 Object参数

说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)

正例:public User getUsers(String type, Integer… ids) {…}

3.构造方法里面禁止加入任何业务逻辑

如果有初始化逻辑,请放在 init 方法中,尤其是不能在构造方法里调用成员方法,会发生不可预期的结果(对象都还没生成,怎么可以使用这个对象来做事情呢?)。

4.避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则

说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。

正例:一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取:

private boolean checkParam(DTO dto) {...}

五、集合处理

1.关于 hashCode 和 equals 的处理

①只要重写 equals,就必须重写 hashCode;

②因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的

对象必须重写这两个方法;

③如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。

2.不要在 foreach 循环里进行元素的 remove/add 操作

remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁

正例:

Iterator<String> iterator = list.iterator(); 
while (iterator.hasNext()) { 
	String item = iterator.next(); 
	if ("1".equals(item)) { 
		iterator.remove(); 
	} 
}

反例:

List<String> list = new ArrayList<String>(); 
list.add("1"); 
list.add("2"); 
for (String item : list) { 
	if ("1".equals(item)) { 
		list.remove(item); 
	} 
}

3.高度注意 Map 类集合 K/V 能不能存储 null 值的情况

集合类 Key Value Super 说明
Hashtable 不允许为 null 不允许为 null Dictionary 线程安全
ConcurrentHashMap 不允许为 null 不允许为 null AbstractMap 锁分段技术(JDK8:CAS)
TreeMap 不允许为 null 允许为 null AbstractMap 线程不安全
HashMap 允许为 null 允许为 null AbstractMap 线程不安全

反例: 由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上,存储 null 值时会抛出 NPE 异常。如果实在记不住,区分不了,用的时候查表确认一下。

六、并发处理

1.获取单例对象需要保证线程安全,其中的方法也要保证线程安全

2.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

正例:

public class TimerTaskThread extends Thread {
    
     
	public TimerTaskThread() {
    
     
		super.setName("TimerTaskThread");
		...
	}
}

3.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

4.SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量

如果定义为static,必须加锁,或者使用 DateUtils 工具类。

正例:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
    
     
	@Override 
	protected DateFormat initialValue() {
    
     
		return new SimpleDateFormat("yyyy-MM-dd"); 
	} 
};

如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat

5.避免 Random 实例被多线程使用

虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。

6.volatile 解决多线程内存不可见问题

对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:

AtomicInteger count = new AtomicInteger(); 
count.addAndGet(1); 

如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。

七、控制语句

1.在一个 switch 块内,每个 case 要么通过 break/return 等来终止

在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使空代码。

2.在 if/else/for/while/do 语句中必须使用大括号

即使只有一行代码,避免采用单行的编码方式,反例:if (condition) statements;

3.不要在条件判断中执行其它复杂的语句

将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

4.循环体中的语句要考量性能

以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。

5.谨慎注释掉代码

说明:代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。

6.特殊注释标记,请注明标记人与标记时间

经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。

① 待办事宜(TODO):( 标记人,标记时间,[预计处理时间])

表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。

② 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])

在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。

八、异常处理

1.finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch

说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。

2.避免不必要的try-catch

正例:if (obj != null) {...}

反例:try { obj.method() } catch (NullPointerException e) {…}

3.方法的返回值可以为 null

不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。

九、安卓项目相关

1.大型项目不应该按属性分包

反例:所有的Activity类都放在activity包下

正例:按功能模块分包,如登录模块、用户资料模块、购物车模块、发现模块等

2.布局文件命名方式

①Activity 的 layout 以 activity_module 开头;

②Fragment 的 layout 以 fragment_module 开头;

③Dialog 的 layout 以 dialog_module 开头;

④include 的 layout 以 include_module 开头;

⑤ListView/RecyclerView 的行 layout 以 item_module 开头。

3.大分辨率图片(单维度超过 1000)大分辨率图片建议统一放在 xxhdpi 目录下管理,否则将导致占用内存成倍数增加

说明:为了支持多种屏幕尺寸和密度,Android 为多种屏幕提供不同的资源目录进行适配。为不同屏幕密度提供不同的位图可绘制对象,可用于密度特定资源的配置限定符(在下面详述) 包括 ldpi(低)、mdpi(中)、 hdpi(高)、xhdpi(超高)、xxhdpi (超超高)和 xxxhdpi(超超超高)。

根据当前的设备屏幕尺寸和密度,将会寻找最匹配的资源,如果将高分辨率图片放入低密度目录,将会造成低端机加载过大图片资源,又可能造成 OOM,同时也是资源浪费,没有必要在低端机使用大图。

正例:将 144*144 的应用图标 PNG 文件放在 mipmap-xxhdpi 目录

反例:将 144*144 的应用图标 PNG 文件放在 mipmap-mhdpi 目录

4.Activity#onSaveInstanceState()

onSaveInstanceState()方法不是 Activity 生命周期方法,也不保证一定会被调用。它是用来在 Activity 被意外销毁时保存 UI 状态的,只能用于保存临 时性数据,例如 UI 控件的属性等,不能跟数据的持久化存储混为一谈。持久化存储应该在 Activity#onPause()/onStop()中实行。

注意:当前Activity的onPause方法执行结束后才会执行下一个Activity的onCreate方法,所以在 onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率。

5.Activity 间通过隐式 Intent 的跳转,在发出 Intent 之前必须通过 resolveActivity检查

避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。

public void viewUrl(String url, String mimeType) {
    
    
	Intent intent = new Intent(Intent.ACTION_VIEW);
	intent.setDataAndType(Uri.parse(url), mimeType);
	if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_
ONLY) != null) {
    
    
        try {
    
    
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
    
    
            if (Config.LOGD) {
    
    
                ...
            }
        }
    }
}

反例:

Intent intent = new Intent();
intent.setAction("com.great.activity_intent.Intent_Demo1_Result3");

6.避免在 Service#onStartCommand()/onBind()方法中执行耗时操作

如果确实有需求,应改用 IntentService 或采用其他异步机制完成

同样,避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做。BroadcastReceiver#onReceive()方法耗时超过 10 秒钟,可能会被系统杀死。

7.避免使用隐式 Intent 广播敏感信息

信息可能被其他注册了对应BroadcastReceiver 的 App 接收。对于只用于应用内的广播,优先使用LocalBroadcastManager 来进行注册和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。

8.Activity或者 Fragment 中动态注册BroadCastReceiver 时,registerReceiver()和 unregisterReceiver()要成对出现

如果 registerReceiver()和 unregisterReceiver()不成对出现,则可能导致已经注册的receiver 没有在合适的时机注销,导致内存泄漏,占用内存空间,加重 SystemService负担。

部分华为的机型会对 receiver 进行资源管控,单个应用注册过多 receiver 会触发管控模块抛出异常,应用直接崩溃。

9.文本大小使用单位 sp,view 大小使用单位 dp

对于 Textview,如果在文字大小确定的情况下推荐使用 wrap_content 布局避免出现文字显示不全的适配问题。

10.灵活使用布局,减少布局嵌套层数

推荐 Merge、ViewStub 来优化布局,尽可能多的减少 UI布局层级,推荐使用 FrameLayout,LinearLayout、RelativeLayout 次之。

注意:不要在非 UI 线程中初始化 ViewStub,否则会返回 null。

11.尽量不要使用 AnimationDrawable

它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。

说明:Android 的帧动画可以使用 AnimationDrawable 实现,但是如果你的帧动画中如果包含过多帧图片,一次性加载所有帧图片所导致的内存消耗会使低端机发生 OOM异常。帧动画所使用的图片要注意降低内存消耗,当图片比较大时,容易出现 OOM。

12.不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew

因为这样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图面。

说明:ScrollView 中嵌套 List 或 RecyclerView 的做法官方明确禁止。除了开发过程中遇到的各种视觉和交互问题,这种做法对性能也有较大损耗。ListView 等 UI 组件自身有垂直滚动功能,也没有必要在嵌套一层ScrollView。目前为了较好的 UI 体验,更贴近 Material Design 的设计,推荐使用 NestedScrollView。

13.在Adapter里为控件设置样式/内容时,必须考虑if和else两种情况

如选中和未选中的样式,如果仅仅写了if的样式,在滚动时可能会造成错乱

14.不要通过 Intent 在 Android 基础组件之间传递大数据

binder transaction缓存为 1MB,可能导致 OOM

15.注意多进程的情况

在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程初始化。特别是后台进程减少不必要的业务初始化。

public class MyApplication extends Application {
    
    
	@Override
	public void onCreate() {
    
    
		//在所有进程中初始化
		....
		//仅在主进程中初始化
		if (mainProcess) {
    
    
			...
		}
		//仅在后台进程中初始化
		if (bgProcess) {
    
    
			...
		}
	}
}

16.新建线程

必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。

17.禁止在多进程之间用 SharedPreferences 共享数据

虽然可以(MODE_MULTI_PROCESS),但官方已不推荐。

18.SharedPreference 提交数据时,尽量使用 Editor#apply() ,而非Editor#commit()

一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用 Editor#commit()。

说明:SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入磁盘,commit 方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,apply 会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。

19.谨防内存泄露和句柄溢出

数据库 Cursor 必须确保使用完后关闭,以免内存泄漏。除了写文件,其他如网络连接、新建线程都会占用句柄,当句柄数量到达最大打开文件数量(一般为1024)时,应用会闪退。

20.加载大图片或者一次性加载多张图片,应该在异步线程中进行

图片的加载,涉及到 IO 操作,以及 CPU 密集操作,很可能引起卡顿。

21.启动模式singletTop并不能绝对阻止多次启动同一个Activity

可以有效防止手抖启动多次同个Activity的情况,但如果是for循环或者并发启动的情况,就不好使了,如果确实要保证只启动一个实例,使用singleInstance。一般来说,不会在for循环里启动同一个Activity,所以一般情况下做好手抖点击的情况即可。

22.打release包开启混淆注意事项

一定要仔细检查相关开源库的混淆规则,保证自身APP的bean不被混淆,否则JSON解析可能会出错

23.使用ViewBinding时要谨慎

如果已经在抽象的父类Activity执行了setContentView方法,则子类绝对不能在重写onCreate方法的时候再次setContentView,否则会提示找不到相关的view,表现出来是RecyclerView不显示等

24.禁止使用BigDecimal的参数为double类型的构造函数:BigDecimal(double val)

反例:

BigDecimal b1 = new BigDecimal(0.1);
BigDecimal b2 = new BigDecimal(0.5);
System.out.println("b1="+b1+"\nb2="+b2);

---------------结果----------------------
b1=0.1000000000000000055511151231257827021181583404541015625
b2=0.5

因为二进制运算不能提供准确的值,其实不能说二进制不能提供准确的值,而是二进制不能准确的表示一个小数,就像十进制不能准确的表示1/3,1/6等(因为1/3=0.333333333333333…,我们始终不能说清楚这后面有多少个3,所以说十进制表示不了1/3)。

正例:

//使用String构造
BigDecimal b1 = new BigDecimal("0.1");
//或者是:
BigDecimal b1 = BigDecimal.valueOf(0.1);

25.一些推荐的习惯

1.如果仅仅使用到一个方法,不涉及到成员变量,则写成静态方法;

2.适当使用软引用和弱引用;

3.谨慎使用多进程;

4.对于需要用到传applicationcontext的方法,不要直接传activity或fragment等,推荐传context.getApplicationContext();

5.使用静态内部类以免引起内存泄露;

6.谨防静态变量、单例、属性动画等引起内存泄露的情况,注册、解注册要成对出现;

7.对Cursor、Receiver、Sensor、File、Bitmap等对象,需注意回收与解注册;

8.使用LeakCanary检测内存泄漏;

9.减少不必要的成员变量;

10.尽量不要使用枚举;
不可否认enums会使得代码更易读更安全,但是在编写高效Android代码时避免使用枚举,我们在很多经典的Java书已经看到推荐使用枚举来代替int常量了,特别是大型的App中,能不用则不用,因为它会牺牲执行的速度和并大幅增加文件体积,这也是性能优化中减少OOM的一个方面。
参考:https://www.cnblogs.com/zgz345/p/5871351.html

11.尽量不要使用反射;
①与反射相关的代码,经常是难以阅读的。
②反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
总结:如果不是为了实现某些特殊的功能,如调用系统隐藏的api等,尽量少用反射。

12.使用OpenGL进行复杂的绘图操作;

13.使用SurfaceView替代View进行大量、频繁的绘图操作;

14.使用视图缓存,而不是每次都执行inflate()

26.参考与进阶推荐

参考:

1.阿里巴巴 Java 开发手册

2.阿里巴巴Android开发手册

推荐:《Effective Java》

要抠的知识点:设计模式、算法、编程思想(开源与实现原理)

Guess you like

Origin blog.csdn.net/ithouse/article/details/121089560