2014年10月22日 星期三

Java,InputStream,Socket阻塞.(关于HTTP请求的IO问题自我总结)

http://ansonlai.iteye.com/blog/556287

前言:

由于项目的需求,需要实现以下流程:
1. Client发送HTTP请求到Server.
2. Server接收HTTP请求并显示出请求的内容(包含请求的头及Content的内容)

服务端实现: 

Server部分代码如下:
Java代码  收藏代码
  1. import java.net.Socket;  
  2. import java.net.ServerSocket;  
  3. import java.net.InetAddress;  
  4. import java.io.InputStream;  
  5. import java.io.OutputStream;  
  6. import java.io.IOException;  
  7.   
  8. /** 
  9.  *   
  10.  * @author Anson 
  11.  */  
  12. public class Server{  
  13.   
  14.     private String ServerIP;  
  15.     private int ServerPort;  
  16.     private boolean shutdown = false;  
  17.   
  18.     /** 
  19.        * Init Server 
  20.        */  
  21.     public void init(){  
  22.           
  23.         ServerIP = "192.168.0.1";  
  24.         ServerPort = 8082;  
  25.     }  
  26.       
  27.     /** 
  28.      *   
  29.      */  
  30.     public void await(){  
  31.           
  32.         ServerSocket serverSocket = null;  
  33.           
  34.         try {  
  35.             serverSocket =  new ServerSocket(ServerPort, 1, InetAddress.getByName(ServerIP));  
  36.         }catch (IOException e) {  
  37.             e.printStackTrace();  
  38.             System.exit(1);  
  39.         }  
  40.           
  41.         // Loop waiting for a request  
  42.         while(!shutdown){  
  43.             Socket          socket  = null;  
  44.             InputStream     input   = null;  
  45.             OutputStream    output  = null;  
  46.   
  47.             try {  
  48.                 socket  = serverSocket.accept();  
  49.                 input   = socket.getInputStream();  
  50.                 output  = socket.getOutputStream();  
  51.                   
  52.                 this.parse(input);  
  53.                    
  54.                
  55.                  // Close the socket  
  56.                 socket.close();  
  57.                   
  58.             }catch (Exception e) {  
  59.                 e.printStackTrace();  
  60.                 continue;  
  61.             }  
  62.         }  
  63.     }  
  64.     public void parse(InputStream input) {  
  65.         // Read a set of characters from the socket  
  66.         StringBuffer request = new StringBuffer(2048);  
  67.         int i;  
  68.         byte[] buffer = new byte[2048];  
  69.         try {  
  70.             i = input.read(buffer);  
  71.         }catch (IOException e) {  
  72.             e.printStackTrace();  
  73.             i = -1;  
  74.         }  
  75.         for (int j=0; j<i; j++) {  
  76.             request.append((char) buffer[j]);  
  77.         }  
  78.         System.out.println(request.toString());  
  79.     }  
  80.            
  81. }  
 再编写StartServer.java
Java代码  收藏代码
  1. ....  
  2. public class StartServer{  
  3.     public static void main(String[] args){  
  4.         Server server = new Server();  
  5.         server.init();  
  6.         server.await();  
  7.         .....  
  8.     }  
  9. }  
跟着,客户端的代码做法有很多,
可以用最简单的Socket,
也可以用HttpClient,
或其它客户端如:OC
具体做法,网上有很多,在此不细述.
我用的是OC,
当然,可能有些做法,并不会出现这样的问题,
毕竟,我,并非一个高手.
我只描述我遇到的状况,
成功实现的可以忽略.

问题描述:

当客户端发送的数据量小于一定长度(如2048byte)的时候,
Server运行正常,即可以正常打印出请求的内容.
如:GET index HTTP/1.1
    ........
请求的头加上一些Content.
但当数据量大于2048的时候,奇怪的问题出现了.
有时会发现数据读不全.
例如,在请求的头下面,Content的内容是一串XML的String:
<?xml version="1.0" encoding="UTF-8" ?><label1>label1></label1>......
当请求的头加上XML的String的总长度超过2048,那么,可能出现在情况就是超过的部分丢失.

解决办法:

可能已经有人发觉,在上面的Server里面的parse(InputStream input)这里的处理有问题.
因为很大程度上这个2048在StringBuffer的定义时就出现了.
所以,第一个失败的尝试便是修改2048.
第一次变成10*2048
跟着是100*2048....
最后是1000*2048....
但结果可以发现:数据量的限制并不会随着这个数值的成倍增长而以相同倍数增长.有时可能是为了增加一倍,
而这个数值必需增加20倍.....
最后发现这个办法不可能,
于是,改变了读取的办法.
用了其它的如StreamBufferReader还有很多其它的来尝试读取,
结果却令人失望,
甚至于这时候出现了阻塞.
即,在读取的过停中,卡住了,
直到等到客户端的请求超时,才跳出来.
所以这些方法也失败了.
具体的情况也记不大清楚了.

之后的另一个方法:
         String request = "";
           //如果不先读一次,那么下面的input.available()有可能是0值.
           char firstbyte = (char) input.read();
            request += firstbyte;
            int ava = 0;
            while ((ava = input.available()) > 0) {
                try {
                    // 在此睡眠0.1秒,很重要
                    Thread.sleep(100);
                } catch (Exception t) {
                    t.printStackTrace();
                }
                byte[] bufferedbyte = new byte[ava];
                input.read(bufferedbyte, 0, ava);
                request += new String(bufferedbyte, "UTF-8");
            }
 这是一个成功解决的方法,
 之所以要睡眠0.1秒,等待高手帮忙解答,
这也许跟网络有关系,
当然,数据量越大,睡眠的时应该有小小的加长.(缓冲时间).
虽然以上的做法成功实现了,但对于服务器来说,效率是个问题,所以只能寻找更优的办法.

第三次更改:

Java代码  收藏代码
  1.           int readInt = 0;  
  2. int availableCount = 0;  
  3. char requestHead = (char)(input.read());  
  4. request += requestHead;  
Java代码  收藏代码
  1. //取得请求的类型是PUT或GET  
  2.             if(requestHead == 'P') {//主要是PUT的方法里面会带有XML.  
  3.                 while((readInt=input.read()) != -1) {  
  4.                     request += (char)readInt;  
  5.                     if(request.indexOf("</endXML>") != -1){  
  6.                         break;  
  7.                     }  
  8.                 }  
  9.                 System.out.println("this is put\n" +request);  
  10.             } else if(requestHead=='G') {//GET的方法内容比较少,用以下的方法可以正常实现  
  11.                 while((availableCount=input.available()) > 0){  
  12.                     byte[] requestbuffer = new byte[availableCount];  
  13.                     input.read(requestbuffer);  
  14.                     request += new String(requestbuffer, "UTF-8");  
  15.                 }   
*代码并不完整.
这种做法,我在读取XML的String里加上了一个结束的判断即对"</endXML>"的判断(当然,这是在知道Content内容的基础上这种做法才可行).
虽然暂时解决了问题,但仍然不能完美解决存在的问题.

第四次更改:
这也是最后一次更改,办法是:
在PUT的请求里面,先取得一个Content-Length:的请求头的值length.
这里的大小就是Content的长度.
在这个基础上,
先判断是否开始读取Content:
if(requestString.indexOf("\r\n\r\n") != -1)
.....

之后再循环读取:
    for(int i=0; i < length; i++){
        requestString += (char)input.read();
    }
当读完length后,跳出循环,
并跳出读取input的读取.
.......
问题在此告一段落.

若有哪位朋友懂得其中原理,恳请告知,
知其然而不知其所以然,心里不免有个结.

结言:

以上做法,仅供参考.
若有错误,请不啬指出.

欢迎转载
转载进请注明转自:http://ansonlai.iteye.com

分享到:  
评论
13 楼 A_L_85 2014-07-29  
a_bin 写道
垃圾,说了那么多又那么乱,没个主题,死吧你

你死了吗?
我可还活着!
12 楼 a_bin 2014-05-27  
垃圾,说了那么多又那么乱,没个主题,死吧你
11 楼 A_L_85 2012-05-24  
macemers 写道
同在学习中,楼主能给出客户端的代码么?

哈,你想要什么代码,这是我两年前的代码了
10 楼 bluedest 2012-05-21  
A_L_85 写道
bluedest 写道
基本上,我认为这是
read(byte b[])方法造成的结果。
1.底层windows在网络缓冲区开辟一块内存,这块内存用于接收别人发给自己的数据包(网络层的packet)。
2.网络数据包是有大小的,根据各自的网络情况,可能实际情况有所不同。
3.read(byte b[])向底层操作系统读数据包时,只是取到了一个瞬间在缓冲区中的字节,然后就返回到Java(应用层)。所以实际上你的数据包还没有收完。
4.可以做一个测试,客户端发数据,服务端收数据。客户端连接发10字节就睡5秒,在服务端你读取read(byte b[1024]),虽然定义得很长,但是服务端只是读到10字节就返回了,一样的道理。

在第4点的测试中,如果以这种情况下,会不会出现服务器读到10字节之的后便处在不断等待数据包的情况?




不会的,你可以试一下.read(byte[])方法将会立即返回,不阻塞
9 楼 macemers 2012-05-21  
同在学习中,楼主能给出客户端的代码么?
8 楼 A_L_85 2011-03-09  
charseller 写道
Java6支持httpserver,其中的httpExchange.getRequestBody()得到的InputStream的同样处理就不存在这个问题

GOOD!
7 楼 charseller 2011-03-06  
Java6支持httpserver,其中的httpExchange.getRequestBody()得到的InputStream的同样处理就不存在这个问题
6 楼 charseller 2011-03-05  
也按照1楼同学的提示,对一个socket的先发一段,睡10秒,100秒再发后面一段,服务器都可以正常接收,客户端也正常收到返回数据
5 楼 charseller 2011-03-05  
在我的程序中同样出现了这个问题,搜索到此。3楼的方案虽然解决了问题,仍然没有说明问题所在。用中文搜索没找到满意解决,只得英文搜索,搜索到:http://stackoverflow.com/questions/611760/java-inputstream-read-blocking

其中有段话有启发:It returns -1 if it's end of stream. If stream is still open (i.e. socket connection) but no data has reached the reading side (server is slow, networks is slow,...) the read() blocks.

最后发现其实有关 read()本身没问题,而是client端没有做好。

在socket.getOutputStream().write(data)后,尝试socket.getOutputStream().close()。服务器端没问题了,read()会返回-1咯。。。呵呵,但是(最怕但是),客户端close OutputStream后等带服务器的回应的InputSteam.read()出现了:socket closed Exception

最后发现Socket有 shutdownOutput()方法!哈哈,在客户端write,flush后再调用shutdownOutput(),成了!后面的inputStream仍然可以。。。

当然,如果客户端和服务器程序是各自开发,楼主及3楼的方法还是必要。

这次我碰到的问题其实说明这个现在还是普便存在的:本来用http实现通信的,用httpserver的httpExchange.getRequestBody()得到的InputStream处理就没有出现这个问题。后来到现场才发现服务器的开发单位最终是用socket实现的通信,还专门提出了socket的头6个字节表示长度,俺这么一写,才发现对方可能也是碰到这个问题,采取了长度方法来处理。(我是在做服务器端simulator,同时也要做这个simulator的测试,所以客户端simulator也做,说的没听糊涂吧:)

shutdownOutput
public void shutdownOutput()
                    throws IOException
Disables the output stream for this socket. For a TCP socket, any previously written data will be sent followed by TCP's normal connection termination sequence. If you write to a socket output stream after invoking shutdownOutput() on the socket, the stream will throw an IOException.

4 楼 A_L_85 2011-01-10  
 特此感谢3楼兄弟~~
受益良多啊...
3 楼 xiaod0510 2010-12-19  

首先在你的parse(InputStream input)方法里
下面的代码段
#  byte[] buffer = new byte[2048]; 
#         try { 
#             i = input.read(buffer); 
#         }catch (IOException e) { 
#             e.printStackTrace(); 
#             i = -1; 
#         } 

buffer是定长的,A_L_85也意识到这个问题
其实可以用ByteArrayOutputStream解决的

ByteArrayOutputStream可以作为一个变相的byte动态数组,A_L_85可以看一下相关API文档

代码修改如下

ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
try {
while ((i = input.read(buffer)) != -1) {
bos.write(buffer,0,i);
}
} catch (IOException e) {
e.printStackTrace();
}
//toByteArray后会将内存中的数据片段连接返回一个Byte[] 
//类似StringBuffer
buffer = bos.toByteArray();


然后再说第二个问题
当Socket.getInputStream()的缓冲区内没有数据时
调用read(...)方法会出现阻塞现象
1楼说的意思大致是正确的
当你读取"InputStream缓冲区"的速度大于网络传输速度时
会出现阻塞现象,其实这是因为java程序在读完缓冲区内的数据后,无法判断客户端是不是还在写入数据,所以会在那里等着.

这也就是下面的代码为什么需要sleep,其实是在等待客户端向缓冲区写数据

try {
    // 在此睡眠0.1秒,很重要
    Thread.sleep(100);
} catch (Exception t) {
   t.printStackTrace();
}


这个问题可以如下解决的

int before = input.available();//缓冲区内可读数据
while(true){
    Thread.sleep(100);//try代码省略 sleep时间可以按需要调整
    int ava = input.available();
    /**相隔一段时间后 缓冲区内的可读数据没变的话说明客户端已经写入完成 退出循环 一次性将剩余数据取出*/
    if(ava==before){
        break;
    }
    before = ava;
}

byte[] buffer = new byte[input.available()];
input.read(buffer);

第一,二个问题相结合就能正确的读取缓冲区内的数据了

还有就是看到作者这样的代码

    for(int i=0; i < length; i++){
        requestString += (char)input.read();
    }
着实吓了我一跳,真的...




2 楼 A_L_85 2010-02-10  
bluedest 写道
基本上,我认为这是
read(byte b[])方法造成的结果。
1.底层windows在网络缓冲区开辟一块内存,这块内存用于接收别人发给自己的数据包(网络层的packet)。
2.网络数据包是有大小的,根据各自的网络情况,可能实际情况有所不同。
3.read(byte b[])向底层操作系统读数据包时,只是取到了一个瞬间在缓冲区中的字节,然后就返回到Java(应用层)。所以实际上你的数据包还没有收完。
4.可以做一个测试,客户端发数据,服务端收数据。客户端连接发10字节就睡5秒,在服务端你读取read(byte b[1024]),虽然定义得很长,但是服务端只是读到10字节就返回了,一样的道理。

在第4点的测试中,如果以这种情况下,会不会出现服务器读到10字节之的后便处在不断等待数据包的情况?
1 楼 bluedest 2010-02-09  
基本上,我认为这是
read(byte b[])方法造成的结果。
1.底层windows在网络缓冲区开辟一块内存,这块内存用于接收别人发给自己的数据包(网络层的packet)。
2.网络数据包是有大小的,根据各自的网络情况,可能实际情况有所不同。
3.read(byte b[])向底层操作系统读数据包时,只是取到了一个瞬间在缓冲区中的字节,然后就返回到Java(应用层)。所以实际上你的数据包还没有收完。
4.可以做一个测试,客户端发数据,服务端收数据。客户端连接发10字节就睡5秒,在服务端你读取read(byte b[1024]),虽然定义得很长,但是服务端只是读到10字节就返回了,一样的道理。

2014年10月21日 星期二

怎么避免AppStore审核失败及解决办法

http://chuansongme.com/n/163185


怎么避免AppStore审核失败及解决办法

2013-08-31   App运营之家
 感谢您关注微信号  app888app 
请分享到朋友圈,携手共同进步!

最近,cocoachina交流社区发起了一个关于iOS开发者遇到审核失败的原因及解决办法的主题讨论,现简单整理有价值回复如下。

wubo9935

App中设计的图标与Apple原生图标类似,Apple原生图标有专利保护,并且在Design Guideline里面规定,App的图标不能与Apple图标雷同,如iTunes,App Store, iPod等的图标。若出现雷同App将被拒。

逐风

app的设置界面、按钮使用了类似iphone的操作方式以及icon的圆角设计 -> 重新设计...
app的年龄设置太低 -> 改了年龄...
app里有实物奖励  -> 免责声明,和苹果无关...
app描述里提了后续版本的功能的字样 -> 删除...
app有打分的功能 -> 有reject的,也有通过的...
app需要使用location,没有提示用户 -> 加了提示,允许用户拒绝...
app没提供测试账号 -> 提供...
app里有私有api -> 修改...

numbbuaa

遇到过两个问题:
1.第三方静态库包含私有api的调用(联系第三方技术支持,更新静态库);
2.包含潜在的色情,暴力等内容(调整应用年龄限制等级,并加入举报功能)

armywin

游戏中包含可以跳转的URL,被拒
游戏中包含推广非本账号下的APP的,被拒
APP界面设计太像一个网页了,被拒
游戏内购时候做了服务器验证,服务器不稳定,导致测试账号无法充值,被拒
游戏中提供了月卡功能,但是不支持玩家在不同设备中使用,被拒

wode211

1: 做浏览器的,分级必须选17+
2: 类似于Android widgets 桌面的应用被拒(不符合用户习惯)
3: Term of service 的URL链接大网页与 “Term of service” 内容不符合,被拒
4: 某个button或者控件的响应,没有与说明描述的一致,被拒
5:iPad应用,UIPopoverController的那个箭头,没有指向对应的按钮或者控件,被拒。(转屏后如果没有指对,也被拒)
6:iPhone程序不能在iPad上跑,或者跑得不好,被拒
7:Documents里的文件,没有按照iCould的指导文档处理好,被拒

野猪洋洋笨

app的年龄设置太低 -> 改了改高年龄...
app里有提示用户评价打分的按钮功能 -> 删除...
没有在多个设备测试,iphone5出现界面扭曲->改
app里用了第三方的api -> 修改...

ywlcjl5

游戏界面丑不符合iPhone用户的期望值,连续被拒2次。   ---重画。
永久购买的IAP没有添加恢复购买功能。    ---添加。
添加了退出程序的功能不符合人机交互功能。   ---删除。

xin814

1、和苹果的app store风格类似  修改
2、使用私有API   删除
3、别人的,界面中的iPhone写错成IPhone  修改

linaicai_rename

1)app内的第三方登陆通过内置浏览器跳转出去的被拒   修改成webView登陆
2)墙纸类应用因为无法控制第三方数据导致部分色情图片的出现会被拒  删除
3)app名称或者内部数据使用到一些被注册商标的名称会被拒 修改名称
4)应用太多简单,界面太过少或者严重违背苹果界面设计准则被拒 重新设计

tmxk12388

一、第一次是在审核的时候,app一直提示无法连接到服务器,自己测试没有问题,分析原因可能是Reachability返回无法连接     -改用request返回数据判断后审核通过
二、提交视频类客户端,说没有视频直播的版权  -提供版权说明后通过审核
三、产品仅提供手机号注册,要求提供账号  -提供账号
四、产品的icon和闪屏图片加入了其他公司的logo  -去除logo

doctor_chen

1.关于我们那个页面为了方便用本地webView布局的,仅此一个页面,就因为这个被拒。提示什么没有native特性,如button。。搞了半天才知道这原因,把webview换成个图片,苹果满意了。
2.某应用,其他都没问题,有个使用说明为了美观我把每一项加了个封面做成书架风格,内容纯txt的。苹果当我卖书的,告诉我,xxx like ebook should be xxx on ebook store.我就把这个删了,通过了。我很想不通那么多txt格式的电子书怎么通过的。。
3. 用了个类似优酷那种一点弹出一圈菜单的,说用户会confused疑惑,要有引导说明,没通过。我加上说明也没用。最后还是换了个普通的菜单,通过了。

beiqingbao

程序里有提示用户评价
提示语:亲,给个好评!~    被拒了
改为‘’去APPSTORE评分''通过了

lpluck08

1、app内如果出现苹果设备名称,必须是iPhone、iPad之类的,注意大小写,如果是iphone或者ipad,rejected!!
2、app内如果涉及到登陆或者需要和硬件设备连接才能继续操作的,需要提供测试账号,或者操作视频。
3、私有api的问题,遇到过一次。。。

cocoawill

1.应用内含有有某公司LOGO的图片,没有该公司授权文件,被拒
2.应用关于内含有beta字样,被拒
3.申请证书时勾选了Inter-App Audio,应用内不支持,直接Invalid Binary
4.info.plist里面设置了Required background modes >App plays audio ,审核人员在应用内未发现播放音频的地方,被拒后,在notes里添加音频播放功能说明,通过了
5.注册只局限移动或者联通账号,被拒
6.应用内点击某个功能,提示正在下载,被拒,改为正在加载,过了

bombbomb

非用户产生的数据存放在了Documents目录里,违法icloud备份规范被退回。
应用内搞市场活动送奖品,没有写明和苹果无关,被拒

23105612

被拒原因
我们启用了游戏中心,但是做了限制需要玩家玩到某个程度才能开启,然后被拒
解决方案
邮件沟通后录制了在游戏中使用游戏中心功能的视屏,得到通过

legolasyoung

来个带条款的:

3.10 利用伪造或付费评论的方式在App Store中企图操纵或欺骗用户评价或图表排名的开发程序员(或者采用其他不正当方式)将会从iOS开发者项目中除名

App里有提示用户评论的AlertView:

第一次:give me 5-star rating, you will get 100 coins! 被拒;
第二次:give me 5-star rating, thank you! 被拒;
第三次:plz rate me! 通过。

程序是无法知道用户评了多少评分,所以提示用户给5星算是欺骗用户。而第一条更触犯了付费评论这一点。
小提示,开发者想通过“开关”的形式开控制此提示文本来绕过审核,最好别这么做,坛子里很多人已经因为这个做法被取消IDP了。

11.1 使用App Store以外的软件开启或提供额外功能的应用程序将会被拒绝。

App里,允许用户可以通过分享游戏结果到facebook、邀请facebook好友玩游戏等操作,获得免费金币。被拒;
将这些操作改成不给金币,通过。

“分享结果到facebook”和“邀请facebook好友“属于“app store以外的软件”,“获得免费金币”属于“提供额外的功能”。

10.2 与App Store、iTunes Store和iBookstore等提供的iPhone捆绑应用程序类似的应用程序将会被拒绝。

一、之前制作的一款App有用户书架功能,书架界面类似于iBooks将书的封面一本一本的排列在书架上。手指长按书的封面,书架进入编辑模式,封面会抖动。这个编辑功能被拒。改成进入编辑模式后,封面不抖动,通过。

二、之前制作的一款软件有IM功能,用户之间的对话显示高仿系统自带的短信气泡(鲸鱼体),被拒;改成非鲸鱼体的UI,通过。10.1 应用程序必须遵守苹果《iPhone用户界面指导原则》以及《iPad用户界面指导原则》中解释的所有条款和条件。

苹果是不允许应用程序遮盖状态栏的。
之前使用了MTStatusBarOverlay这个开源库,遮盖了状态栏显示任务和进度,被拒;
后来换成别的库不遮盖状态栏,通过。

zsx923

1. app内评分弹出alert,文字不能诱导用户,比如"好评","5星评价"之类的,统统会被reject
2.涉及到音乐,视频类的数据,特别是国外的,如在提交时没有提及版权协议之类的,也会毫不留情被reject,国内的倒还好
======================================
下周预告:
下周一开始木木哥会在微信公众号连载我的长篇自传体小说,用亲身经历,让你了解 民工兄弟,夜~场~妹~子,电子厂操作工,桑~拿~技~师,发~廊~洗~头~妹,黑车司机等特殊人群的手机上装了哪些App,平时玩哪些手游。用独特的视角带你领略边缘人群的移动互联网生活。绝对接地气够劲爆。快来关注微信公众号app888app 
现接受各种合作预订,广告主快到碗里来!
====================================
App运营之家】
本号由木木哥运营,与任职公司无关。
线下活动、妹纸选妃、干货分享、内幕爆料、招聘求职移动互联网从业者混圈子刷脸必备利器!微信号:app888app。让我们在移动互联网之路上不再孤单!
合作QQ:1403784232。更多精彩内容请查看历史消息

当前位置: 传送门 ›› App运营之家

2014年10月15日 星期三

Android开源项目-编码风格规范-Code Style Guidelines for Contributors[原创译文]

http://blog.sina.com.cn/s/blog_48d491300100zwzg.html

Code Style Guidelines for Contributors
版本:Android 4.0 r1
以下规则并非指导或推荐的性质,而是必须遵守的规定。如果不遵守这些规定,Android通常不会接受投稿。
已有的代码未必全部遵守了这些规定,但是新的代码全部都应该遵守。
在本文中
我们遵循标准的Java编码规范,并加入了新的规则:
有时,完全忽略异常是非常诱人的,比如:
void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) { }
}
绝对不要这么做。也许你会认为:你的代码永远不会碰到这种出错的情况,或者处理异常并不重要,可类似上述忽略异常的代码将会在代码中埋下一颗地雷,说不定哪天它就会炸到某个人了。你必须在代码中以某种规矩来处理所有的异常。根据情况的不同,处理的方式也会不一样。
无论何时,空的catch语句都会让人感到不寒而栗。虽然很多情况下确实是一切正常,但至少你不得不去忧虑它。在Java中你无法逃离这种恐惧感。 -James Gosling
可接受的替代方案包括(按照推荐顺序):
· 向方法的调用者抛出异常。
        void setServerPort(String value) throws NumberFormatException {
            serverPort = Integer.parseInt(value);
        }
· 根据抽象级别抛出新的异常。
        void setServerPort(String value) throws ConfigurationException {
            try {
                serverPort = Integer.parseInt(value);
            } catch (NumberFormatException e) {
                throw new ConfigurationException("Port " + value + " is not valid.");
            }
        }
· 默默地处理错误并在catch {}语句块中替换为合适的值。
        void setServerPort(String value) {
            try {
                serverPort = Integer.parseInt(value);
            } catch (NumberFormatException e) {
                serverPort = 80;  // default port for server
            }
        }
· 捕获异常并抛出一个新的RuntimeException。这种做法比较危险:只有确信发生该错误时最合适的做法就是崩溃,才会这么做。
        void setServerPort(String value) {
            try {
                serverPort = Integer.parseInt(value);
            } catch (NumberFormatException e) {
                throw new RuntimeException("port " + value " is invalid, ", e);
            }
        }
请记住,最初的异常是传递给构造方法的RuntimeException。如果代码必须在Java 1.3版本下编译,需要忽略该异常。
· 最后一招:如果确信忽略异常比较合适,那就忽略吧,但必须把理想的原因注释出来:
        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.
            }
        }
有时在捕获Exception时偷懒也是很吸引人的,类似如下的处理方式:
try {
    someComplicatedIOFunction();        // may throw IOException
    someComplicatedParsingFunction();   // may throw ParsingException
    someComplicatedSecurityFunction();  // may throw SecurityException
    // phew, made it all the way
} catch (Exception e) {                 // I'll just catch all exceptions
    handleError();                      // with one generic handler!
}
不要这么做。绝大部分情况下,捕获顶级的ExceptionThrowable都是不合适的,Throwable更不合适,因为它还包含了Error异常。这种捕获非常危险。这意味着本来不必考虑的Exception(包括类似ClassCastExceptionRuntimeException)被卷入到应用程序级的错误处理中来。这会让代码运行的错误变得模糊不清。这意味着,假如别人在你调用的代码中加入了新的异常,编译器将无法帮助你识别出各种不同的错误类型。绝大部分情况下,无论如何你都不应该用同一种方式来处理各种不同类型的异常。
本规则也有极少数例外情况:期望捕获所有类型错误的特定的测试代码和顶层代码(为了阻止这些错误在用户界面上显示出来,或者保持批量工作的运行)。这种情况下可以捕获顶级的Exception(或Throwable)并进行相应的错误处理。在开始之前,你应该非常仔细地考虑一下,并在注释中解释清楚为什么这么做是安全的。
比捕获顶级Exception更好的方案:
· 分开捕获每一种异常,在一条try语句后面跟随多个catch 语句块。这样可能会有点别扭,但总比捕获所有Exception要好些。请小心别在catch语句块中重复执行大量的代码。
· 重新组织一下代码,使用多个try块,使错误处理的粒度更细一些。把IO从解析内容的代码中分离出来,根据各自的情况进行单独的错误处理。
· 再次抛出异常。很多时候在你这个级别根本就没必要捕获这个异常,只要让方法抛出该异常即可。
请记住:异常是你的朋友!当编译器指出你没有捕获某个异常时,请不要皱眉头。而应该微笑:编译器帮助你找到了代码中的运行时(runtime)问题。
Finalizer提供了一个机会,可以让对象被垃圾回收器回收时执行一些代码。
优点:便于执行清理工作,特别是针对外部资源。
缺点:调用finalizer的时机并不确定,甚至根本就不会调用。
结论:我们不要使用finalizers。大多数情况下,可以用优秀的异常处理代码来执行那些要放入finalizer的工作。如果确实是需要使用finalizer,那就定义一个close()方法(或类似的方法),并且在文档中准确地记录下需要调用该方法的时机。相关例程可以参见InputStream。这种情况下还是适合使用finalizer的,但不需要在finalizer中输出日志信息,因为日志不能因为这个而被撑爆。
当需要使用foo包中的Bar类时,存在两种可能的import方式:
1.   import foo.*;
优点:可能会减少import语句。
1.   import foo.Bar;
优点:实际用到的类一清二楚。代码的可读性更好,便于维护。
结论:用后一种写法来import所有的Android代码。不过导入java标准库(java.util.*java.io.*) 和单元测试代码(junit.framework.*)时可以例外。
使用Android Java类库和工具存在一些惯例。有时这些惯例会作出重大变化,可之前的代码也许会用到过时的模板或类库。如果用到这部分过时的代码,沿用已有的风格就是了(参阅Consistency)。创建新的组件时就不要再使用过时的类库了。
每个文件的开头都应该有一句版权说明。然后下面应该是package包语句和import语句,每个语句块之间用空行分隔。然后是类或接口的定义。在Javadoc注释中,应描述类或接口的用途。
package com.android.internal.foo;

import android.os.Blah;
import android.view.Yada;

import java.sql.ResultSet;
import java.sql.SQLException;

public class Foo {
    ...
}
每个类和自建的public方法必须包含Javadoc注释,注释至少要包含描述该类或方法用途的语句。并且该语句应该用第三人称的动词形式来开头。
例如:
static double sqrt(double a) {
    ...
}
public String(byte[] bytes) {
    ...
}
如果所有的Javadoc都会写成“sets Foo”,对于那些无关紧要的类似setFoo()getset语句是不必撰写Javadoc的。如果方法执行了比较复杂的操作(比如执行强制约束或者产生很重要的副作用),那就必须进行注释。如果“Foo”属性的意义不容易理解,也应该进行注释。
无论是public的还是其它类型的,所有自建的方法都将受益于Javadocpublic的方法是API的组成部分,因此更需要Javadoc
Android目前还没有规定自己的Javadoc注释撰写规范,但是应该遵守Sun Javadoc约定
为了把规模控制在合理范围内,方法应该保持简短和重点突出。不过,有时较长的方法也是合适的,所以对方法的代码长度并没有硬性的限制。如果方法代码超过了40行,就该考虑是否可以在不损害程序结构的前提下进行分拆。
字段应该定义在文件开头,或者紧挨着使用这些字段的方法之前。
局部变量的作用范围应该是限制为最小的(Effective Java29条)。使用局部变量,可以增加代码的可读性和可维护性,并且降低发生错误的可能性。每个变量都应该在最小范围的代码块中进行声明,该代码块的大小只要能够包含所有对该变量的使用即可。
应该在第一次用到局部变量的地方对其进行声明。几乎所有局部变量声明都应该进行初始化。如果还缺少足够的信息来正确地初始化变量,那就应该推迟声明,直至可以初始化为止。
本规则存在一个例外,就是涉及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());
}
import语句的次序应该如下:
1.   Android imports
2.   第三方库(comjunitnetorg
3.   javajavax
为了精确匹配IDE的配置,import顺序应该是:
· 在每组内部按字母排序,大写字母排在小写字母的前面。
· 每个大组之间应该空一行(androidcomjunitnetorgjavajavax)。
原先次序是不作为规范性要求的。这意味着要么允许IDE改变顺序,要么使用IDE的开发者不得不禁用import自动管理功能并且人工维护import。这看起来比较糟糕。每当说起java规范,推荐的规范到处都是。符合我们要求的差不多就是选择一个次序并坚持下去。于是,我们就选择一个规范,更新规范手册,并让IDE去遵守它。我们期望:不必耗费更多的精力,用IDE编码的用户就按照这种规则去import所有的package
基于以下原因,选定了本项规则:
· 导入人员期望最先看到的放在最开始位置(android
· 导入人员期望最后才看到的放在最后(java
· 风格让人容易遵守
· IDE可以遵守
静态import的使用和位置已经成为略带争议的话题。有些人愿意让静态import和其它import混在一起,另一些人则期望让它们位于其它import之上或者之下。另外,我们还未提到让所有IDE都遵守同一个次序的方法。
因为大多数人都认为这部分内容并不要紧,只要遵守你的决定并坚持下去即可。
我们的代码块缩进使用4个空格。我们从不使用制表符tab。如果存在疑惑,与前后的其它代码保持一致即可。
我们用8个空格作为换行后的缩进,包括函数调用和赋值。例如这是正确的:
Instrument i =
        someLongexpression_r(that, wouldNotFit, on, one, line);
而这是错误的:
Instrument i =
    someLongexpression_r(that, wouldNotFit, on, one, line);
· public的、非static的字段名称以m开头。
· static字段名称以s开头。
· 其它字段以小写字母开头。
· public static final字段(常量)全部字母大写并用下划线分隔。
例如:
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;
}
大括号不单独占用一行;它们紧接着上一行书写。就像这样:
class MyClass {
    int func() {
        if (something) {
            // ...
        } else if (somethingElse) {
            // ...
        } else {
            // ...
        }
    }
}
我们需要用大括号来包裹条件语句块。不过也有例外,如果整个条件语句块(条件和语句本身)都能容纳在一行内,也可以(但不是必须)把它们放入同一行中。也就是说,这是合法的:
if (condition) {
    body();
}
这也是合法的:
if (condition) body();
但这是非法的:
if (condition)
    body();  // bad!
每行代码的长度应该不超过100个字符。
有关本规则的讨论有很多,最后的结论还是最多不超过100个字符。
例外:如果注释行包含了超过100个字符的命令示例或者URL文字,为了便于剪切和复制,其长度可以超过100个字符。
例外:import行可以超过限制,因为很少有人会去阅读它。这也简化了编程工具的写入操作。
Annotation应该位于Java语言元素的其它修饰符之前。 简单的marker annotation@Override等)可以和语言元素放在同一行。 如果存在多个annotation,或者annotation是参数化的,则应按字母顺序各占一行来列出。
对于Java 内建的三种annotationAndroid标准的实现如下:
· @Deprecated:只要某个语言元素已不再建议使用了,就必须使用@Deprecated annotation。如果使用了@Deprecated annotation,则必须同时进行@deprecated Javadoc标记,并且给出一个替代的实现方式。此外请记住,被@Deprecated的方法仍然是能正常执行的。
如果看到以前的代码带有@deprecated Javadoc标记,也请加上@Deprecated annotation
· @Override:只要某个方法覆盖了已过时的或继承自超类的方法,就必须使用@Override annotation
例如,如果方法使用了@inheritdocs Javadoc标记,且继承自超类(而不是interface),则必须同时用@Override标明覆盖了父类方法。
· @SuppressWarnings@SuppressWarnings annotation仅用于无法消除编译警告的场合。 如果警告确实经过测试不可能消除,则必须使用@SuppressWarnings annotation,以确保所有的警告都能真实反映代码中的问题。
当需要使用@SuppressWarnings annotation时,必须在前面加上TODO注释行,用于解释不可能消除警告的条件。通常是标明某个令人讨厌的类用到了某个拙劣的接口。比如:
// TODO: The third-party class com.third.useful.Utility.rotate() needs generics
@SuppressWarnings("generic-cast")
List<String> blix = Utility.rotate(blax);
如果需要使用@SuppressWarnings annotation,应该重新组织一下代码,把需要应用annotation的语言元素独立出来。
简称和缩写都视为变量名、方法名和类名。以下名称可读性更强:
XmlHttpRequest
XMLHTTPRequest
getCustomerId
getCustomerID
class Html
class HTML
String url
String URL
long id
long ID
如何对待简称,JDKAndroid底层代码存在很大的差异。因此,你几乎不大可能与其它代码取得一致。别无选择,把简称当作完整的单词看待吧。
关于本条规则的进一步解释,请参阅Effective Java38条和Java Puzzlers68条。
对那些临时性的、短期的、够棒但不完美的代码,请使用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注释是将来要做某事的格式,则请确保包含一个很明确的日期(200511月会修正),或是一个很明确的事件(在所有代码整合人员理解了V7协议之后删除本段代码)。
记录日志会对性能产生显著的负面影响。如果日志内容不够简炼的话,很快会丧失可用性。日志功能支持五种不同的级别。以下列出了各个级别及其使用场合和方式。
· ERROR: 该级别日志应该在致命错误发生时使用,也就是说,错误的后果能被用户看到,但是不明确删除部分数据、卸装程序、清除数据区或重新刷机(或更糟糕)就无法恢复。该级别总是记录日志。需要记录ERROR级别日志的事件一般都应该向统计信息收集(statistics-gathering )服务器报告。
· WARNING: 该级别日志应该用于那些重大的、意外的事件,也就是说,错误的后果能被用户看到,但是不采取明确的动作可能就无法无损恢复,从等待或重启应用开始,直至重新下载新版程序或重启设备。该级别总是记录日志。需记录WARNING级别日志的事件也可以考虑向统计信息收集服务器报告。
· INFORMATIVE: 该级别的日志应该用于记录大部分人都会感兴趣的事件,也就是说,如果检测到事件的影响面可能很广,但不一定是错误。应该只有那些拥有本区域内最高级别身份认证的模块才能记录这些日志(为了避免级别不足的模块重复记录日志)。该级别总是记录日志。
· DEBUG: 该级别的日志应该用于进一步记录有关调查、调试意外现象的设备事件。应该只记录那些有关控件运行所必需的信息。如果debug日志占用了太多的日志空间,那就应该使用详细级别日志(verbose)才更为合适。
即使是发行版本(release build),该级别也会被记录,并且需用if (LOCAL_LOG)if (LOCAL_LOGD)语句块包裹,这里的LOCAL_LOG[D]在你的类或子控件中定义。这样就能够一次性关闭所有的调试日志。因此在if (LOCAL_LOG)语句块中不允许存在逻辑判断语句。所有日志所需的文字组织工作也应在if (LOCAL_LOG)语句块内完成。如果对记录日志的调用会导致在if (LOCAL_LOG)语句块之外完成文字组织工作,那该调用就必须控制在一个方法内完成
还存在一些代码仍然在使用if (localLOGV)。这也是可以接受的,虽然名称不是标准的。
· VERBOSE: 该级别日志应用于所有其余的事件。该级别仅会在调试版本(debug build)下记录日志,并且需用if (LOCAL_LOGV)语句块(或等效语句)包裹,这样该部分代码默认就不会编译进发行版本中去了。所有构建日志文字的代码将会在发行版本中剥离出去,并且需包含在if (LOCAL_LOGV)语句块中。
注意:
· 除了VERBOSE级别外,在同一个模块中同一个错误应该尽可能只报告一次:在同一个模块内的一系列层层嵌套的函数调用中,只有最内层的函数才返回错误;并且只有能为解决问题提供明显帮助的时候,同一模块中的调用方才写入一些日志。
· 除了VERBOSE级别外,在一系列嵌套的模块中,当较低级别的模块对来自较高级别模块的非法数据进行检测时,应该只把检测情况记录在DEBUG日志中,并且只记录那些调用者无法获取的信息。特别是不需要记录已经抛出异常的情况(异常中应该包含了全部有价值的信息),也不必记录那些只包含错误代码的信息。当应用程序与系统框架间进行交互时,这一点尤为重要。系统框架已能正确处理的第三方应用程序,也不应该记录大于DEBUG级别的日志。仅当一个模块或应用程序检测到自身或来自更低级别模块的错误时,才应该记录INFORMATIVE及以上级别的日志。
· 如果一个通常要记录日志的事件可能会多次发生,则采取一些频次限制措施或许是个好主意,以防日志被很多重复(或类似)的信息给撑爆了。
· 网络连接的丢失可被视为常见现象,也是完全可以预见的,不应该无缘无故就记录进日志。影响范围限于应用程序内部的网络中断应该记录在DEBUGVERBOSE级别的日志中(根据影响的严重程度及意外程度,再来确定是否在发行版本中也记录日志)。
· 有权访问的文件系统或第三方应用程序发起的系统空间满,应该记录大于INFORMATIVE级别的日志。
· 来自任何未授信源的非法数据(包括共享存储上的任何文件,或来自任何网络连接的数据)可被视为可预见的,如果检测到非法数据也不应该记录大于DEBUG级别的日志(即使记录也应尽可能少)。
· 请记住,对字符串使用+操作符时,会在后台以默认大小(16个字符)缓冲区创建一个StringBuilder对象,并且可能还会创建一些其它的临时String对象。换句话说,显式创建StringBuilders对象的代价并不会比用'+'操作符更高(事实上效率还将会提高很多)。还要记住,即使不会再去读取这些日志,调用Log.v()的代码也将编译进发行版中并获得执行,包括创建字符串的代码。
· 所有要被人阅读并存在于发行版本中的日志,都应该简洁明了、没有秘密、容易理解。这里包括所有DEBUG以上级别的日志。
· 只要有可能,日志就应该一句一行。行长最好不超过80100个字符,尽可能避免超过130160个字符(包括标识符)的行。
· 报告成功的日志记录绝不应该出现在大于VERBOSE级别的日志中。
· 用于诊断难以重现事件的临时日志应该限于DEBUGVERBOSE级别,并且应该用if语句块包裹,以便在编译时能够一次全部关闭。
· 小心日志会泄漏隐私。应该避免将私人信息记入日志,受保护的内容肯定也不允许记录。这在编写系统框架级代码时尤为重要,因为很难预知哪些是私人信息和受保护信息。
· 绝对不要使用System.out.println() (或本地代码中的printf())。System.out  System.err会重定向到/dev/null,因此print语句不会产生任何可见的效果。可是,这些调用中的所有字符串创建工作都仍然会执行。
· 日志的黄金法则是:你的日志记录不会导致其它日志的缓冲区溢出,正如其他人的日志也不会让你的溢出一样。
我们的最终想法是:保持一致。如果你正在编写代码,请花几分钟浏览一下前后的其它代码,以确定它们的风格。如果它们在if语句前后使用了空格,那你也应该遵循。如果它们的注释是用星号组成的框框围起来的,那也请你照办。
保持风格规范的重点是有一个公共的代码词汇表,这样大家就可以把注意力集中于你要说什么,而不是你如何说。我们在这里列出了全部的风格规范,于是大家也知道了这些词汇。不过本地化的风格也很重要。如果你要加入的代码和已存在的代码风格迥异,那就会突然打破阅读的节奏。请努力避免这种情况的发生。
命名测试方法时,可以用下划线来分隔测试的条件。这种风格可以让测试的条件一目了然。
比如:
testMethod_specificCase1 testMethod_specificCase2
void testIsDistinguishable_protanopia() {
    ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA)
    assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK))
    assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y))
}