Java语言规范
遵循标准的java编码规范
1.不要忽略Exceptions
有时,完全忽略异常非常诱人,比如:
1. void setServerPort(String value) {
2. try {
3. serverPort = Integer.parseInt(value);
4. } catch (NumberFormatException e) { }
5. }
绝对不要这么做,上述忽略异常的代码将会在代码中埋下一颗地雷,说不定哪天它就会炸到某个人了。你必须在代码中以某种规矩来处理所有的异常。
可接受的替代方案包括(按照推荐顺序):
一:向方法的调用这抛出异常
1. void setServerPort(String value) throws
2. NumberFormatException {
3. serverPort = Integer.parseInt(value);
4. }
二:根据抽象级别抛出新的异常
1. void setServerPort(String value) throws
2. ConfigurationException {
3. try {
4. serverPort = Integer.parseInt(value);
5. } catch (NumberFormatException e) {
6. throw new ConfigurationException("Port " + value + " is not valid.");
7. }
8. }
三:默默的处理错误并在catch{}语句块中替换为合适的值
1. /** Set port. If value is not a valid number, 80 is substituted. */
2.
3. void setServerPort(String value) {
4. try {
5. serverPort = Integer.parseInt(value);
6. } catch (NumberFormatException e) {
7. serverPort = 80; // default port for server
9. }
10. }
四:捕获异常并抛出一个新的RuntimeException,这种做法比较危险:只有确信发生该错误时,最合适的方法就是崩溃,才会这么做
1. /** Set port. If value is not a valid number, die. */
2.
3. void setServerPort(String value) {
4. try {
5. serverPort = Integer.parseInt(value);
6. } catch (NumberFormatException e) {
7. throw new RuntimeException("port " + value " is invalid, ", e);
9. }
10. }
五:如果确信忽略异常比较合适,那就忽略吧,但必须把理想的原因注释出来:
1. /** If value is not a valid number, original port number is used. */
2. void setServerPort(String value) {
3. try {
4. serverPort = Integer.parseInt(value);
5. } catch (NumberFormatException e) {
6. // Method is documented to just ignore invalid user input.
7. // serverPort will just be unchanged.
8. }
9. }
2.不要捕获顶级的Exception
有时在捕获Exception时偷懒也很是吸引人的,类似如下的处理方式:
1. try {
2. someComplicatedIOFunction(); // may throw IOException
3. someComplicatedParsingFunction(); // may throw ParsingException
4. someComplicatedSecurityFunction(); // may throw SecurityException
5. } catch (Exception e) { // I'll just catch allexceptions
6. handleError(); // with one generic handler!
7. }
不要这么做。绝大部分情况下,捕获顶级的Exception或Throwable都是不合适的,Throwable更不合适,因为它还包含了Error异常。这种捕获非常危险。这意味着本来不必考虑的Exception(包括类似ClassCastException的RuntimeException)被卷入到应用程序级的错误处理中来。这会让代码运行的错误变得模糊不清。这意味着,假如别人在你调用的代码中加入了新的异常,编译器将无法帮助你识别出各种不同的错误类型。绝大部分情况下,无论如何你都不应该用同一种方式来处理各种不同类型的异常。
本规则也有极少数例外情况:期望捕获所有类型错误的特定的测试代码和顶层代码(为了阻止这些错误在用户界面上显示出来,或者保持批量工作的运行)。这种情况下可以捕获顶级的Exception(或Throwable)并进行相应的错误处理。在开始之前,你应该非常仔细地考虑一下,并在注释中解释清楚为什么这么做是安全的。
比捕获顶级Exception更好的方案:
分开捕获每一种异常,在一条try语句后面跟随多个catch 语句块。这样可能会有点别扭,但总比捕获所有Exception要好些。请小心别在catch语句块中重复执行大量的代码。
重新组织一下代码,使用多个try块,使错误处理的粒度更细一些。把IO从解析内容的代码中分离出来,根据各自的情况进行单独的错误处理。
再次抛出异常。很多时候在你这个级别根本就没必要捕获这个异常,只要让方法抛出该异常即可。
请记住:异常是你的朋友!当编译器指出你没有捕获某个异常时,请不要皱眉头。而应该微笑:编译器帮助你找到了代码中的运行时(runtime)问题。
3.不要使用Finalizer
Finalizer提供了一个机会,可以让对象被垃圾回收器回收时执行一些代码。
优点:便于执行清理工作,特别是针对外部资源。
缺点:调用finalizer的时机并不确定,甚至根本就不会调用。
结论:我们不要使用finalizers。大多数情况下,可以用优秀的异常处理代码来执行那些要放入finalizer的工作。如果确实是需要使用finalizer,那就定义一个close()方法(或类似的方法),并且在文档中准确地记录下需要调用该方法的时机。相关例程可以参见InputStream。这种情况下还是适合使用finalizer的,但不需要在finalizer中输出日志信息,因为日志不能因为这个而被撑爆。
4.使用完全限定Import
当需要使用foo包中的Bar类时,存在两种可能的import方式:
import foo.*;
优点:可能会减少import语句。
import foo.Bar;
优点:实际用到的类一清二楚。代码的可读性更好,便于维护。
结论:用后一种写法来import所有的Android代码。不过导入java标准库(java.util.、*java.io.**等) 和单元测试代码 (junit.framework.*)时可以例外。
Java类库规范
使用Android Java类库和工具存在一些惯例。有时这些惯例会作出重大变化,可之前的代码也许会用到过时的模板或类库。如果用到这部分过时的代码,沿用已有的风格就是了(参阅Consistency)。创建新的组件时就不要再使用过时的类库了。
Java编程规范
1.使用Javadoc标准准注释
每个文件的开头都应该有一句版权说明。然后下面应该是package包语句和import语句,每个语句块之间用空行分隔。然后是类或接口的定义。在Javadoc注释中,应描述类或接口的用途。
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.foo;
import android.os.Blah;
import android.view.Yada;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Does X and Y and provides an abstraction for Z.
*/
public class Foo {
...
}
每个类和自建的public方法必须包含Javadoc注释,注释至少要包含描述该类或方法用途的语句。并且该语句应该用第三人称的动词形式来开头。
例如:
/** Returns the correctly rounded positive square root of a double value. */
static double sqrt(double a) {
...
}
或
/**
* Constructs a new String by converting the specified array of
* bytes using the platform's default character encoding.
*/
public String(byte[] bytes) {
...
}
如果所有的Javadoc都会写成“setsFoo”,对于那些无关紧要的类似setFoo()的get和set语句是不必撰写Javadoc的。如果方法执行了比较复杂的操作(比如执行强制约束或者产生很重要的副作用),那就必须进行注释。如果“Foo”属性的意义不容易理解,也应该进行注释。
无论是public的还是其它类型的,所有自建的方法都将受益于Javadoc。public的方法是API的组成部分,因此更需要Javadoc。
Android目前还没有规定自己的Javadoc注释撰写规范,但是应该遵守Sun Javadoc约定。
2.编写简短的方法
为了把规模控制在合理范围内,方法应该保持简短和重点突出。不过,有时较长的方法也是合适的,所以对方法的代码长度并没有硬性的限制。如果方法代码超过了40行,就该考虑是否可以在不损害程序结构的前提下进行分拆。
3.在标准的位置定义字段
字段应该定义在文件开头,或者紧挨着使用这些字段的方法之前。
4.限制变量的作用范围
局部变量的作用范围应该是限制为最小的(Effective Java第29条)。使用局部变量,可以增加代码的可读性和可维护性,并且降低发生错误的可能性。每个变量都应该在最小范围的代码块中进行声明,该代码块的大小只要能够包含所有对该变量的使用即可。
应该在第一次用到局部变量的地方对其进行声明。几乎所有局部变量声明都应该进行初始化。如果还缺少足够的信息来正确地初始化变量,那就应该推迟声明,直至可以初始化为止。
本规则存在一个例外,就是涉及try-catch语句的情况。如果变量是用方法的返回值来初始化的,而该方法可能会抛出一个checked异常,那么必须在try块中进行变量声明。如果需在try块之外使用该变量,那它就必须在try块之前就进行声明了,这时它是不可能进行正确的初始化的。
// Instantiate class cl, which represents some sort of Set
Set s = null;
try {
s = (Set) cl.newInstance();
} catch(IllegalAccessException e) {
throw new IllegalArgumentException(cl + " not
accessible");
} catch(InstantiationException e) {
throw new IllegalArgumentException(cl + " not
instantiable");
}
// Exercise the set
s.addAll(Arrays.asList(args));
但即便是这种情况也是可以避免的,把try-catch 块封装在一个方法内即可:
Set createSet(Class cl) {
// Instantiate class cl, which represents some sort
of Set
try {
return (Set) cl.newInstance();
} catch(IllegalAccessException e) {
throw new IllegalArgumentException(cl + " not
accessible");
} catch(InstantiationException e) {
throw new IllegalArgumentException(cl + " not
instantiable");
}
}
...
// Exercise the set
Set s = createSet(cl);
s.addAll(Arrays.asList(args));
除非理由十分充分,否则循环变量都应该在for语句内进行声明
for (int i = 0; i n; i++) {
doSomething(i);
}
和
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomethingElse(i.next());
}
5.对Import语句的排序
import语句的次序应该如下:
1. Android imports
2. 第三方库(com、junit、net、org)
3. java和javax
为了精确匹配IDE的配置,import顺序应该是:
· 在每组内部按字母排序,大写字母排在小写字母的前面。
· 每个大组之间应该空一行(android、com、junit、net、org、java、javax)。
原先次序是不作为规范性要求的。这意味着要么允许IDE改变顺序,要么使用IDE的开发者不得不禁用import自动管理功能并且人工维护import。这看起来比较糟糕。每当说起java规范,推荐的规范到处都是。符合我们要求的差不多就是“选择一个次序并坚持下去。”于是,我们就选择一个规范,更新规范手册,并让IDE去遵守它。我们期望:不必耗费更多的精力,用IDE编码的用户就按照这种规则去import所有的package。
基于以下原因,选定了本项规则:
· 导入人员期望最先看到的放在最开始位置(android)
· 导入人员期望最后才看到的放在最后(java)
· 风格让人容易遵守
· IDE可以遵守
静态import的使用和位置已经成为略带争议的话题。有些人愿意让静态import和其它import混在一起,另一些人则期望让它们位于其它import之上或者之下。另外,我们还未提到让所有IDE都遵守同一个次序的方法。
因为大多数人都认为这部分内容并不要紧,只要遵守你的决定并坚持下去即可。
6.使用空格进行缩进
我们用8个空格作为换行后的缩进,包括函数调用和赋值。例如这是正确的:
Instrument i =
someLongExpression(that, wouldNotFit, on, one,
line);
而这是错误的:
Instrument i =
someLongExpression(that, wouldNotFit, on, one, line);
7.遵循字段命名惯例
驼峰(Camel)命名法:又称小驼峰命名法,除首单词外,其余所有单词的第一个字母大写。
· 非public的、非static的字段名称以m开头。
· static字段名称以s开头。
· 其它字段以小写字母开头。
· public static final字段(常量)全部字母大写并用下划线分隔。
包名:
包(packages): 采用反域名命名规则,全部使用小写字母。一级包名为com,二级包名为xx(可以是公司或则个人的随便),三级包名根据应用进行命名,四级包名为模块名或层级名
例如:com.l99.bed
类名,接口(interface):
采用大驼峰命名法,尽量避免缩写,除非该缩写是众所周知的, 比如HTML,URL,如果类名称中包含单词缩写,则单词缩写的首子母大写。
根据项目的不同添加大写缩写前缀
例如:CSLoginActivity
方法(methods):小驼峰
变量(variables)(id下划线,变量名将下划线换成驼峰,但是必须保持一致):小驼峰
常量(Constants)
全部大写,采用下划线命名法.例如:MIN_WIDTH
资源文件(图片drawable文件夹下):
全部小写,采用下划线命名法
资源布局文件
全部小写,采用下划线命名法
例如:
public class MyClass {
public static final int SOME_CONSTANT = 42;
public int publicField;
private static MyClass sSingleton;
int mPackagePrivate;
private int mPrivate;
protected int mProtected;
}
8.使用标准的大括号分格
大括号不单独占用一行;它们紧接着上一行书写。就像这样:
class MyClass {
int func() {
if (something) {
// ...
} else if (somethingElse) {
// ...
} else {
// ...
}
}
}
我们需要用大括号来包裹条件语句块。不过也有例外,如果整个条件语句块(条件和语句本身)都能容纳在一行内,也可以(但不是必须)把它们放入同一行中。也就是说,这是合法的:
if (condition) {
body();
}
这也是合法的:
if (condition) body();
但这是非法的:
if (condition)
body(); // bad!
9.简称等同于单词
好 差
XmlHttpRequest XMLHTTPRequest
getCustomerId getCustomerID
class Html class HTML
String url String URL
long id long ID
如何对待简称,JDK和Android底层代码存在很大的差异。因此,你几乎不大可能与其它代码取得一致。别无选择,把简称当作完整的单词看待吧。
10.使用TODO注释
TODO注释应该包含全部大写的TODO,后跟一个冒号:
// TODO: Remove this code after the UrlTable2 has been
checked in.
和
// TODO: Change this to use a flag instead of a constant.
如果TODO注释是“将来要做某事”的格式,则请确保包含一个很明确的日期(“在2005年11月会修正”),或是一个很明确的事件(“在所有代码整合人员理解了V7协议之后删除本段代码”)。
4.其他注意事项
java代码中不出现中文,最多注释中可以出现中文,中文统一写在strings.xml中;
遵循只做一件事原则,一个方法,一个类,只做一件事,降低藕合度,降低逻辑复杂度
styles.xml:将layout中不断重现的style提炼出通用的style通用组件,放到styles.xml中;
图片尽量分拆成多个可重用的图片
图片要.9.png处理(看情况)
使用静态变量方式实现界面间共享要慎重
不要重用父类的handler,对应一个类的handler也不应该让其子类用到,否则会导致message.what冲突
strings.xml中使用%1$s实现字符串的通配
如果多个Activity中包含共同的UI处理,那么可以提炼一个CommonActivity,把通用部分叫由它来处理,其他activity只要继承它即可
数据一定要效验,例如字符型转数字型,如果转换失败一定要有缺省值;服务端响应数据是否有效判断
在getItemView中,判断convertView是否为空,如果不为空,可复用。如果couvertview中的view需要添加listerner,代码一定要在if(convertView==null){}之外。
如果BaseAdapter的实体类有属性非常消耗内存,可以将保存到文件;为提高性能,可以进行缓存,并限制缓存大小。
使用线程池,分为核心线程池和普通线程池,下载图片等耗时任务放置在普通线程池,避免耗时任务阻塞线程池后,导致所有异步任务都必须等待
异步任务,分为核心任务和普通任务,只有核心任务中出现的系统级错误才会报错,异步任务的ui操作需要判断原activity是否处于激活状态,fragment是否isAdded();
尽量避免static成员变量引用资源耗费过多的实例,比如Context
.使用WeakReference代替强引用,弱引用可以让您保持对对象的引用,同时允许GC在必要时释放对象,回收内存。对于那些创建便宜但耗费大量内存的对象,即希望保持该对象,又要在应用程序需要时使用,同时希望GC必要时回收时,可以考虑使用弱引用。
超级大胖子Bitmap及时的销毁(Activity的onDestroy时将bitmap回收,在被UI组件使用后马上进行回收会抛 RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap)
设置一定的采样率(有开发者提供的图片无需进行采样,对于有用户上传或第三方的大小不可控图片,可进行采样减少图片所占的内存),从服务端返回图片,建议同时反馈图片的size
巧妙的运用软引用
drawable对应resid的资源,bitmap对应其他资源
任何类型的图片,如果获取不到(例如文件不存在,或者读取文件时跑OutOfMemory异常),应该有对应的默认图片(默认图片放在在apk中,通过resid获取);
保证Cursor 占用的内存被及时的释放掉,而不是等待GC来处理。并且 Android明显是倾向于编 程者手动的将Cursor close掉
线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程 生命周期的不可控
应用开发中自定义View的时候,交互部分,千万不要写成线程不断刷新界面显示,而是根据TouchListener事件主动触发界面的更新
ui组件需要用到的图片是apk包自带的,那么一律用setImageResource或者setBackgroundResource,而不要根据resourceid
注意:get(getResources(), R.drawable.btn_achievement_normal)该方法通过resid转换为drawable,需要考虑回收的问题,如果drawable是对象私有对象,在对象销毁前是肯定不会释放内存的。
复用、回收Activity对象,临时的activity及时finish,主界面设置为singleTask,一般界面设置为singleTop
新增:
1:观察者模式的时候,注册的时候在onCreate中调用,反注册在onDestroy中调用
2:Handler的使用必须使用static内部类,然后用弱应用引用外部类
3:不建议使用压制警告
4:警告应该尽量避免
5:不要自己创建线程,会发生线程安全问题,应该使用统一的线程池管理类