1. 简介
A. 目的
本文提供一整套编写高效可靠的 Java 代码的标准、约定和指南。它们以安全可靠的软件工程原则为基础,使代码易于理解、维护和增强。而且,通过遵循这些程序设计标准,你作为一个 Java 软件开发者的生产效率会有显著提高。经验证明,若从一开始就花时间编写高质量的代码,则在软件开发阶段,对代码的修改要容易很多。最后,遵循一套通用的程序设计标准将带来更大的一致性,使软件开发团队的效率明显提高。
Note: 规范编码的目的是让开发者更容易理解其他人的代码,而不是约束你编码的方式
B. 范围
本指南仅适用于使用Java编写的 Android 程序代码。
C. 参考资料
http://www.oracle.com/technetwork/java/codeconvtoc-136057.html
http://source.android.com/source/code-style.html
《LeOS开发规范v1.2.doc》
2. 快速入门
l 驼峰式命名(大小写交替)
错误:
public class myClass
public interface foo
public final static int max = 100
public int FooValue
正确:
public class MyClass
public interface IFoo
public final static int MAX = 100
public int fooValue
l 使用Javadoc识别的注释
在函数声明前添加注释(输入”/**”+回车)
l 精简函数体(最好不要超过40行)
l 局部变量随用随声明(尽量缩小变量的作用域)
l 每行最长100字符
l 缩进用4个空格代替TAB
l private变量名用m开头
l 多用大括号
l 前大括号”{“不要单占一行
错误:
if (condition)
{
//do something
}
正确:
if (condition) {
//do something
}
l 尽可能多的处理异常(不要只catch不处理)
l 多利用标准注解
错误:
public void onCreate(Bundle savedInstanceState) {
正确:
@Override
public void onCreate(Bundle savedInstanceState) {
l 少用不常见缩写
错误:opnDlg
正确: openDialog
3. 命名
基本 Java 命名规范
命名应符合驼峰式规则,利用大小写字母交替来区分名称中的不同单词,即应采用小写字母,但类名、接口名以及任何非初始单词的第一个字母要大写。
1. 尽量使用完整的英文描述符,采用适用于该领域的术语
2. 采用大小写混合使名字可读
3. 尽量少用缩写,但如果用了,要明智地使用。
4. 避免使用长的名字(小于 15 个字母是个好主意)
5. 避免使用类似的名字,或者仅仅是大小写不同的名字
类型 |
规则 |
示例 |
包名 |
全部小写字母,URL倒序 |
package com.baidu.dubicycle |
类名 |
首字母大写,使用描述性强的名词,避免使用缩写,除非该缩写十分通用(Url、Html) |
class Raster; class ImageSprite; |
接口 |
同上 |
interface RasterDelegate; interface Storing; |
方法 |
使用动词,驼峰式命名,首字母小写 |
run(); runFast(); getBackground(); |
变量 |
使用有意义的简短的单词,驼峰式,首字母小写,临时变量一般int型使用i,j,k,m,n; char型使用c,d,e Private protected变量m开头 static变量s开头 |
int i; float myWidth; private int mPrivate; protected int mProtected; private static MyClass sSingleton; |
常量 |
全部大写,用下划线分割单词 |
int MIN_WIDTH = 4; int MAX_WIDTH = 999; int GET_THE_CPU = 1; |
Android 命名相关
Ø Activity命名:xxxxActivity
Ø View命名:xxxxView 或者 xxxxLayout
Ø Service命名:xxxxService
Ø BroadcastReceiver命名:xxxxReceiver
Ø Layout文件命名:
Activity的Content View应使用:act_xxxx.xml的形式
自定义View应使用:vw_xxxx.xml
Ø 文件名全部小写,单词之间使用下划线连接。例如:click_button.png。类似click_button.PNG 和 clickButton.png 等都是错误的形式
Ø 关于StateListDrawable各种状态的命名后缀:
xxxx_checked.png
xxxx_focused.png
xxxx_selected.png
xxxx_pressed.png
xxxx_disabled.png
xxxx_normal.png
相关的XML文件以 xxxx.xml 命名
Ø 常量文件的命名:
strings.xml
dimens.xml
drawables.xml
styles,xml
themes.xml
colors.xml
ids.xml
4. 声明
l 每行声明一个(方便添加注释)
错误:
int level, size;
正确:
int level; // indentation level
int size; // sizeof table
l 不同类型声明不要声明在同一行
错误:
int foo, fooArray[];
正确:
int foo;
int[] fooArray;
l 缩小局部变量的作用域
局部变量使用时在声明,声明时就进行初始化,如果声明时无法获得足够的信息初始化,考虑推迟声明变量的时机。
正确:
for(int i=0; i<0; i++)
错误:
int i;
//do something.....
for(i=0; i<0; i++)
例外:try-catch语句会破坏这条规则,因此这种情况下可以提前声明变量
// Instantiate class cl, which represents some sort of Set
Set s = null;
try {
s = (Set) cl.newInstance();
} catch(IllegalAccessException e) {
throw newIllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
throw newIllegalArgumentException(cl + " not instantiable");
}
// Exercise the set
s.addAll(Arrays.asList(args));
5. 语句
l 一行一句
错误:
argv++; argc--;//
正确:
argv++;
argc--;
return简单值不要加括号
错误:
return (1);
正确:
return 1;
return (size ? size: defaultSize);
不要省略大括弧
正确:
if(condition){
statements;
}
if(condition){
statements;
}else{
statements;
}
if(condition){
statements;
}else if(condition){
statements;
}else if(condition){
statements;
}
错误:
if (condition)//没有大括号
statement;
不要省略大括弧
正确:
for(initialization;condition; update){
statements;
}
在initialization中不要超过3个变量声明
不要省略大括弧
正确:
while(condition){
statements;
}
不要省略大括弧
正确:
do{
statements;
}while(condition);
l 不要省略大括弧
l 必须有default
l 没有break的case要加注释
l default中最好也添加break语句(以免在后面添加case,造成错误)
switch(condition){
case ABC:
statements;
/*没有break,解释原因*/
case DEF:
statements;
break;
case XYZ:
statements;
break;
default:
statements;
break;
}
不要省略大括弧
正确:
try{
statements;
}catch(ExceptionClass e){
statements;
}
6. 缩写的使用
l 尽量少使用缩写,除非该缩写很常见(Html、Url)
l 把缩写当成一个普通单词,不要都大写
正确 |
错误 |
XmlHttpRequest |
XMLHTTPRequest |
getCustomerId |
getCustomerID |
class Html |
class HTML |
String url; |
String URL; |
long id; |
long ID; |
7. 缩进和空格
在代码中,’,’ ‘;’等标点后应有一个空格;’+’ ‘-‘‘*’等运算符前后均应有一个空格;其他符号如’.’ ‘(‘’)’ 等原则上不须空格。
在代码中逻辑性代码块的起始、结尾处,都应该加入空行,并在起始处写注释。
相对独立的程序块之间、变量说明之后必须加空行。
在一个代码块中应该缩进,包括有:If/for/while 等语句;
在续行的代码中应缩进;
缩进取4格,使用Tab字符,Java中Tab应取4格;
注意:默认情况下,Eclipse不会在switch块中缩进,这个应该注意参考相关工程说明。
在eclipse中调整tab为空格的方法:
1)在Window > Preferences中找到Java > Code Style >Formatter
2)点击New,新建一份profile
3)设置Tab Policy
较长的语句、表达式或参数(>100字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。
例外:
1) import所在行可以超过100字符
2) 注释中含有url,为了方便复制或剪切,可以超过100字符
8. 大括号的使用
为了代码紧凑,前大括号不要单独占用一行
正确:
class Test {
public void fun() {
if (condition_0) {
do some thing here;
} else {
do some thing here;
}
}
}
错误:
class Test
{
public void fun()
{
if (condition_0)
{
do some thing here;
} else {
do some thing here;
}
}
}
9. 注释
注释要求
l 无论是用户端表示层程序还是组件程序,注释必不可少。
l 要求能占程序总量的10%以上。
l 注释必须在程序改变时实时更新。
l 简单明了,确保任何程序员都可以读懂。
l 注释一般应写在代码之前,并与代码分开不同的行;但对于说明变量含义的注释,一般应写在变量说明的同行的行末,并且在各行间保持对齐;
l 如果可能,尽量使用单行注释‘//’。
l 对于代码维护,应在过程或方法中记录相应修改的作者、时间,并在具体修改的位置用特殊的标签注明(标签一般应包含修改人的名字汉语拼音或拼音简写、日期戳和可选的修改项目名字)。
使用JavaDoc辅助生成统一的注释
为规范、方便生成统一的程序文档,应在代码注释中遵循JavaDoc的注释规范(建议在开发工具中输入“/**回车”,填入所有自动弹出项。)
模块名称: See
功能描述: Todo
作者: Author
版本: Version
|
功能描述: Method Comments
参数说明: Parameter Comments
返回值说明: Returns
最后更新作者: 如有改动,自己添加
最后更新日期: 如有改动,自己添加
|
1) 在Window > Preferences中找到Java > Code Style >Code Templates
2) 双击要修改的类型,进行编辑
10. 注解
使用常用的注解可以使程序具有更好的可读性
@Deprecated
当函数,变量,类等不推荐使用,可以用@Deprecated注解,提示后续开发人员避免使用该代码
@Overide
当重载或实现父类(接口)的方法时,用Overide做注解。
@SuppressWarnings
当使用代码会产生编译器warning,同时可以保证该代码没有问题,使用SuppressWarnings注解,同时在前面加上TODO注释来解释原因。
例如:
// TODO: The third-party classcom.third.useful.Utility.rotate() needs generics
@SuppressWarnings("generic-cast")
List<String> blix =Utility.rotate(blax);
11. 异常处理
异常是我们的朋友,帮助我们定位潜在的错误,因此要小心的处理
错误:
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
}catch (NumberFormatException e) { }
}
这段代码忽略了可能发生的异常,真正发生异常时,我们无法得到提示。
我们可以这样做(从最好到最差的解决方案)
1)把异常抛给调用者
void setServerPort(String value) throwsNumberFormatException {
serverPort = Integer.parseInt(value);
}
2)抛出自定义类型异常
void setServerPort(String value) throwsConfigurationException {
try {
serverPort = Integer.parseInt(value);
}catch (NumberFormatException e) {
throw new ConfigurationException("Port " + value + " isnot valid.");
}
}
3)尝试恢复错误
/** Set port. If value is not a validnumber, 80 is substituted. */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
}catch (NumberFormatException e) {
serverPort = 80; // default portfor server
}
}
4)抛出RuntimeException让应用crash
/** Set port. If value is not a valid number,die. */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
}catch (NumberFormatException e) {
throw new RuntimeException("port " + value " is invalid,", e);
}
}
5)如果确实需要忽略异,添加注释来说明
/** If value is not a valid number,original port number is used. */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
}catch (NumberFormatException e) {
// Method is documented to just ignore invalid user input.
// serverPort will just be unchanged.
}
}
不要因为懒惰写出如下代码
try {
someComplicatedIOFunction(); // may throw IOException
someComplicatedParsingFunction(); // may throw ParsingException
someComplicatedSecurityFunction(); // may throw SecurityException
// phew, made it all theway
} catch (Exception e) { // I'll just catch all exceptions
handleError(); // with one generichandler!
}
解释:任何异常都被catch住了,当出现问题时,我们的代码也不会抛出exceptio,从而无法有效定位错误。
12. 使用Log
l 充足的log可以便于定位错误
l Log太多会影响程序性能(I/O耗时)
l Log使用的黄金原则——不要把其他程序的log挤出缓存
l Log.v()的输出不要包含到最后的产品中
l 应该设置开关控制是否开启Log.d()的输出信息
13. 和周围代码风格一致
编码时注意代码风格和周围尽量保持一致性. 如果周围的代码在if和后面的括号间使用空格,或者用小方框的方式包围一段注释,我们最好也要这么做。
14. 其他
finalizer是object在被垃圾回收时执行的一段代码,用finalizer的好处是可以在一段代码里释放不需要的资源,但Google建议不要用finalizer。因为不能保证什么时候会调用finalize的代码,甚至不能保证是否会调用finalizer的代码。
可以对异常处理进行精心设计,在异常处理中释放资源。
当函数体长度超过40行,应考虑拆分成多个函数
如果你需要从package foo中引入class Bar, 有以下两种方式
1. import foo.*;
2. import foo.Bar
在android中,请用第2种import方式。
类属性和类方法不要交叉放置,不同存取范围的属性或者方法也尽量不要交叉放置。
格式:
类定义{
类的公有属性定义
类的保护属性定义
类的私有属性定义
类的公有方法定义
类的保护方法定义
类的私有方法定义
*内部类定义
*接口、枚举定义
}
15. 性能和稳定性原则
原则1:及时回收Bitmap资源
对于在代码中创建出来的Bitmap资源,一定要在适当的时候通过Bitmap.recycle()予以销毁,系统不会对通过Bitmap.createBitmap方式创建出来的Bitmap进行回收。合理的时候比如在Activity::onDestroy()中销毁Bitmap.
示例:
创建:
Bitmap mybitmap = Bitmap.createBitmap();
回收:
mybitmap.recycle();
原则2:register注册的响应函数,一定要有unRegister的时候
无论何时register注册的响应钩子,一定要有unRegister的时候,否则该引用不能被系统正确回收。
示例:
publicvoid onCreate(){
setCallback(mycallback);
registerReceiver(myReceiver);
}
public voidonStop(){
setCallback(null);
unRegisterReceiver(myReceiver);
}
Regiser注册的内容很多,请务必仔细,如contentObserver, Cursor, CallBack函数都是需要清理的对象。
原则3:使用Adapter时要尽量使用convertView缓存引用
在使用Adapter的时候,要尽量使用convertView缓存引用,以便节省内存空间。
示例:
View Adapter::getView(View converView , int pos)
{
View v ;
if(convertView != null) //如果converView存在,那么使用converView来作为创建出的View引用。
v = convertView;
else //如果convertView不存在,则创建新的View引用。
v = makeNewView();
v.setData();//给View引用设置数据
return v;
}
原则4:从网络下载的数据和保存的临时文件要及时清理
原则5:内存使用不能超过上限
Rocket系统中,除特殊应用外,所有应用的内存上限是32M,超过这个上限应用程序就会崩溃,我们在设计UI和编写代码的时候应该警惕内存使用,尤其是图片加载。
在使用过程中,我们有时候会遇到界面无响应,弹出对话框询问是关闭进程还是继续等待,发生该异常的原因是应用主线程在某处阻塞,导致新的系统消息(如广播Intent, Key事件, Touch事件)不能正常处理,系统端迟迟收不到应用返回的消息已处理事件,从而判定应用无响应,弹出提示对话框。同时,长时间的阻塞主线程可能会导致WindowManagerService不能正确处理窗口切换刷新,引起打开/关闭Activity时出现刷新不正常。
发生异常时常见的Log有:
Activitiy Idle timeout …
Activity Launch timeout
Service execute timeout
Key dispatch timeout
Broadcast Receiver Intent *** timeout
避免这个问题有以下几个原则。
原则1:需要时间很长的操作,须要单独启动新的线程执行。
例如:
搜索网络,Wifi
网络访问
加载/卸载Sd卡
大量读写数据库
读写较大文件(比如大尺寸的图片)
通常与底层数据读取相关的函数需要重点关注。
启动新线程要使用Thread.start()函数,而不是用Thread.run()函数。使用线程一定要注意synchronized同步以及资源回收。
原则2 使用Handler异步处理
对于结构不易另外开新线程的代码, 或者调用Binder与其它能够联系的代码 , 应该尽量避免在系统回调函数中直接运行,要通过sendMessage来异步调用,这样可以保证系统能够及时从应用获得消息处理完毕的事件。
例如:
startActivity() 打开Activity
startService() 打开Service
sendBroadcast() 发Intent广播
showDialog() 显示对话框
耗时长的事件可能较多,需要大家实际分析,比如获取当前网络,硬件,电话等底层状态都可能会导致阻塞
错误:
public void onCreate(){
sendBroadcast(testIntent);
}
在onCreate回调函数中发送广播以及显示对话框,有可能会造成系统端阻塞
正确:
publicint TEST_MSG = 0;
publicHandler myHandler = new Handler(
publicvoid handleMessage(Message msg){
switch(msg.what){
caseTEST_MSG:
sendBroadCast(myintent);
break;
default:
break;
}
}
};
publicvoid onCreate(){
myHandler.sendMessage(myHandler.obtainMessage(TEST_MSG));
}
通过发送Message的方式来异步调用。
注意:发送Message只是将要执行的操作放到onCreate()回调函数之外去操作,并不是启动新线程进行,如果需要时间过长,同样可能引发阻塞。
原则3 使用Http连接需要加入超时时间限制
示例:
HttpUrlConnection mHttpConnection = url;
mHttpConnection.setConnectTimeout(5000 );
原则4 尽量避免使用System.exit(0)的方式来退出应用
尽量避免使用System.exit(0)的方式来退出应用,尤其需要注意当有其它并发线程的时候,System.exit(0)可能会带来一系列风险。
使用System.exit(0) . Process.killProcess会直接杀掉当前进程,而android中默认是不会关闭进程的,在杀掉当前进程的时候需要考虑并发线程的状态,例如如果并发线程当前正在读取文件,操作数据库,跨进程访问,这时关闭进程就会导致一系列问题甚至引起系统崩溃。
原则5 减少使用数据库的次数,避免频繁调用
目前数据库在使用过程中有偶然变慢甚至阻塞崩溃的可能性,因此在代码设计时一定要减少数据库的操作次数。
a. 避免在不同线程中同时刷新数据或是改变Adapter内容,或者改变Cursor内容的情况,在需要并发刷新的时候要用synchronized来同步,这样可以避免出现ListView崩溃。
b. 使用convertView来减小内存开销
c. 减少Adapter改变次数,每次Adapter都会带来ListView的重新刷新。