对于Android的网络通讯性能的提高,我们可以使用Java上高性能的NIO (New I/O) 技术进行处理,NIO是从JDK 1.4开始引入的,NIO的N我们可以理解为Noblocking即非阻塞的意思,相对应传统的I/O,比如Socket的accpet()、read()这些方法而言都是阻塞的。
NIO主要使用了Channel和Selector来实现,Java的Selector类似Winsock的Select模式,是一种基于事件驱动的,整个处理方法使用了轮训的状态机,如果你过去开发过Symbian应用的话这种方式有点像活动对象,好处就是单线程更节省系统开销,NIO的好处可以很好的处理并发,对于Android网游开发来说比较关键,对于多点Socket连接而言使用NIO可以大大减少线程使用,降低了线程死锁的概率,毕竟手机游戏有UI线程,音乐线程,网络线程,管理的难度可想而知,同时I/O这种低速设备将影响游戏的体验。
NIO作为一种中高负载的I/O模型,相对于传统的BIO (Blocking I/O)来说有了很大的提高,处理并发不用太多的线程,省去了创建销毁的时间,如果线程过多调度是问题,同时很多线程可能处于空闲状态,大大浪费了CPU时间,同时过多的线程可能是性能大幅下降,一般的解决方案中可能使用线程池来管理调度但这种方法治标不治本。使用NIO可以使并发的效率大大提高。当然NIO和JDK 7中的AIO还存在一些区别,AIO作为一种更新的当然这是对于Java而言,如果你开发过Winsock服务器,那么IOCP这样的I/O完成端口可以解决更高级的负载,当然了今天Android123主要给大家讲解下为什么使用NIO在Android中有哪些用处。
NIO我们分为几个类型分别描述,作为Java的特性之一,我们需要了解一些新的概念,比如ByteBuffer类,Channel,SocketChannel,ServerSocketChannel,Selector和SelectionKey。有关具体的使用,Android开发网将在明天详细讲解。网友可以在Android SDK文档中看下java.nio和java.nio.channels两个包了解。
---------------------------------------------------------------2---------------------------------------------------------------------------
有关Android NIO我们主要分为三大类,ByteBuffer、FileChannel和SocketChannel。由于篇幅原因今天Android123只对前两个做说明。NIO和传统的I/O比较大的区别在于传输方式非阻塞,一种基于事件驱动的模式,将会使方法执行完后立即返回,传统I/O主要使用了流Stream的方式,而在New I/O中,使用了字节缓存ByteBuffer来承载数据。
ByteBuffer位于java.nio包中,目前提供了Java基本类型中除Boolean外其他类型的缓冲类型,比如ByteBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer 。同时还提供了一种更特殊的映射字节缓冲类型MappedByteBuffer。在传统IO的输入输出流中,InputStream中只提供了字节型或字节数组的访问对应NIO就是ByteBuffer,但是处理传统的DataInputStream的int等类型,就是IntBuffer,但是缓冲类型并没有提供UTF这样的类型处理,所以我们仍然需要使用ByteBuffer处理字符串,但是NIO提供了一个封装的类在java.nio.charset包中,通过字符的编码CharsetEncoder和解码CharsetDecoder类来处理字符串,同时这些类可以方便转换编码比如GBK或UTF等等。
一、ByteBuffer类
1) 实例化
直接使用ByteBuffer类的静态方法static ByteBuffer allocate(int capacity) 或 static ByteBuffer allocateDirect(int capacity) 这两个方法来分配内存空间,两种方法的区别主要是后者更适用于繁复分配的字节数组。而 put(ByteBuffer src) 可以从另一个ByteBuffer中构造,也可以通过wrap方法从byte[]中构造,具体参考下面的类型转化内容。
2) 类型转化
ByteBuffer可以很好的和字节数组byte[]转换类型,通过执行ByteBuffer类的final byte[] array() 方法就可以将ByteBuffer转为byte[]。从byte[]来构造ByteBuffer可以使用wrap方法,目前Android或者说Java提供了两种重写方法,比如为static ByteBuffer wrap(byte[] array) 和 static ByteBuffer wrap(byte[] array, int start, int len) ,第二个重载方法中第二个参数为从array这个字节数组的起初位置,第三个参数为array这个字节数组的长度。
3) 往ByteBuffer中添加元素
目前ByteBuffer提供了多种put重写类型来添加,比如put(byte b) 、putChar(char value) 、putFloat(float value) 等等,需要注意的是,按照Java的类型长度,一个byte占1字节,一个char类型是2字节,一个float或int是4字节,一个long则为8字节,和传统的C++有些区别。所以内部的相关位置也会发生变化,同时每种方法还提供了定位的方法比如ByteBuffer put(int index, byte b)
4) 从ByteBuffer中获取元素
同上面的添加想法,各种put被换成了get,比如byte get() 、float getFloat() ,当然了还提供了一种定位的方式,比如double getDouble(int index)
5) ByteBuffer中字节顺序
对于Java来说默认使用了BIG_ENDIAN方式存储,和C正好相反的,通过
final ByteOrder order() 返回当前的字节顺序。
final ByteBuffer order(ByteOrder byteOrder) 设置字节顺序,ByteOrder类的值有两个定义,比如LITTLE_ENDIAN、BIG_ENDIAN,如果使用当前平台则为ByteOrder.nativeOrder()在Android中则为 BIG_ENDIAN,当然如果设置为order(null) 则使用LITTLE_ENDIAN。
二、FileChannel类
在NIO中除了Socket外,还提供了File设备的通道类,FileChannel位于java.nio.channels.FileChannel包中,在Android SDK文档中我们可以方便的找到,对于文件复制我们可以使用ByteBuffer方式作为缓冲,比如
String infile = "/sdcard/cwj.dat";
String outfile = "/sdcard/android123-test.dat";
FileInputStream fin = new FileInputStream( infile );
FileOutputStream fout = new FileOutputStream( outfile );
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); //分配1KB作为缓冲区
while (true) {
buffer.clear(); //每次使用必须置空缓冲区
int r = fcin.read( buffer );
if (r==-1) {
break;
}
buffer.flip(); //写入前使用flip这个方法
fcout.write( buffer );
}
flip和clear这两个方法是java.nio.Buffer包中,ByteBuffer的父类是从Buffer类继承而来的,这点Android123要提醒大家看Android SDK文档时注意Inherited Methods,而JDK的文档就比较直接了,同时复制文件使用FileChannel的transferTo(long position, long count, WritableByteChannel target) 这个方法可以快速的复制文件,无需自己管理ByteBuffer缓冲区。明天Android开发网介绍NIO主要的Socket相关的内容。
---------------------------------------------------------------3---------------------------------------------------------------------------
有关Android NIO的精髓主要用于高负载的Socket网络传输,相对于传统I/O模型的Socket传输方式的优势,我们已经在 Android开发进阶之NIO非阻塞包(一) 中讲到了,这里不再赘述,一起来看看Android NIO有关Socket操作提供的类吧:
一、ServerSocketChannel 服务器套接字通道在Android SDK中查找package名为 java.nio.channels.ServerSocketChannel
在Java的NIO中,ServerSocketChannel对应的是传统IO中的ServerSocket,通过ServerSocketChannel类的socket() 方法可以获得一个传统的ServerSocket对象,同时从ServerSocket对象的getChannel() 方法,可以获得一个ServerSocketChannel()对象,这点说明NIO的ServerSocketChannel和传统IO的ServerSocket是有关联的,实例化ServerSocketChannel 只需要直接调用ServerSocketChannel 类的静态方法open()即可。
二、 SocketChannel 套接字通道 java.nio.channels.SocketChannel
在Java的New I/O中,处理Socket类对应的东西,我们可以看做是SocketChannel,套接字通道关联了一个Socket类,这一点使用SocketChannel类的socket() 方法可以返回一个传统IO的Socket类。SocketChannel()对象在Server中一般通过Socket类的getChannel()方法获得。
三、SelectionKey 选择键 java.nio.channels.SelectionKey
在NIO中SelectionKey和Selector是最关键的地方,SelectionKey类中描述了NIO中比较重要的事件,比如OP_ACCEPT(用于服务器端)、OP_CONNECT(用于客户端)、OP_READ和OP_WRITE。
四、Selector 选择器 java.nio.channels.Selector
在NIO中注册各种事件的方法主要使用Selector来实现的,构造一个Selector对象,使用Selector类的静态方法open()来实例化。
对于Android平台上我们实现一个非阻塞的服务器,过程如下:
1. 通过Selector类的open()静态方法实例化一个Selector对象。
2. 通过ServerSocketChannel类的open()静态方法实例化一个ServerSocketChannel对象。
3. 显示的调用ServerSocketChannel对象的configureBlocking(false);方法,设置为非阻塞模式,Android123提示网友这一步十分重要。
4. 使用ServerSocketChannel对象的socket()方法返回一个ServerSocket对象,使用ServerSocket对象的bind()方法绑定一个IP地址和端口号
5. 调用ServerSocketChannel对象的register方法注册感兴趣的网络事件,很多开发者可能发现Android SDK文档中没有看到register方法,这里Android开发网给大家一个ServerSocketChannel类的继承关系
java.lang.Object | ||||
↳ | java.nio.channels.spi.AbstractInterruptibleChannel | |||
↳ | java.nio.channels.SelectableChannel | |||
↳ | java.nio.channels.spi.AbstractSelectableChannel | |||
↳ | java.nio.channels.ServerSocketChannel |
这里我们使用的register方法其实来自ServerSocketChannel的父类java.nio.channels.SelectableChannel,该方法原型为 final SelectionKey register(Selector selector, int operations) ,参数为我们执行第1步时的selector对象,参数二为需要注册的事件,作为服务器,我们当然是接受客户端发来的请求,所以这里使用SelectionKey.OP_ACCEPT了。
6. 通过Selector对象的select() 方法判断是否有我们感兴趣的事件发生,这里就是OP_ACCEPT事件了。我们通过一个死循环获取Selector对象执行select()方法的值,SDK中的原始描述为the number of channels that are ready for operation.,就是到底有多少个通道返回。
7. 如果 Selector对象的select()方法返回的结果数大于0,则通过selector对象的selectedKeys()方法获取一个SelectionKey类型的Set集合,我们使用Java的迭代器Iterator类来遍历这个Set集合,注意判断SelectionKey对象,
8. 为了表示我们处理了SelectionKey对象,需要先移除这个SelectionKey对象从Set集合中。这句很关键Android 123提醒网友注意这个地方。
9. 接下来判断SelectionKey对象的事件,因为我们注册的感兴趣的是SelectionKey.OP_ACCEPT事件,我们使用SelectionKey对象的isAcceptable()方法判断,如果是我们创建一个临时SocketChannel对象类似上面的方法继续处理,不过这时这个SocketChannel对象主要处理读写操作,我们注册SelectionKey.OP_READ和SelectionKey.OP_WRITE分配ByteBuffer缓冲区,进行网络数据传输。
有关具体的示例和解释上面的流畅,由于篇幅原因我们明天Android开发网给出源代码,做详细的分析。
---------------------------------------------------------------4--------------------------------------------------------------------------
今天我们通过一个实例详细讲解下Android下NIO非阻塞服务器的开发,对于客户端而言Android123不推荐使用NIO,毕竟NIO相对于传统IO较为复杂,最重要的NIO是为了解决多线程并发问题而解决的技术,可能会因为管理和复杂性降低最终的结果,毕竟NIO是Java的,相关的类型比较难控制,对于客户端而言我们可以使用C++、Java、C#甚至Flash Action Script来编写。
下面我们以一个简单的Echo Server为例子来分析
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
public class Server {
public static void main(String[] args) {
Selector selector = null;
ServerSocketChannel ssc = null;
try {
selector = Selector.open(); //实例化selector
ssc = ServerSocketChannel.open(); //实例化ServerSocketChannel 对象
ssc.socket().bind(new InetSocketAddress(1987)); //绑定端口为1987
ssc.configureBlocking(false); //设置为非阻塞模式
ssc.register(selector, SelectionKey.OP_ACCEPT); //注册关心的事件,对于Server来说主要是accpet了
while (true) {
int n= selector.select(); //获取感兴趣的selector数量
if(n<1)
continue; //如果没有则一直轮训检查
Iterator<SelectionKey> it = selector.selectedKeys().iterator(); //有新的链接,我们返回一个SelectionKey集合
while (it.hasNext()) {
SelectionKey key = it.next(); //使用迭代器遍历
it.remove(); //删除迭代器
if (key.isAcceptable()) { //如果是我们注册的OP_ACCEPT事件
ServerSocketChannel ssc2 = (ServerSocketChannel) key.channel();
SocketChannel channel = ssc2.accept();
channel.configureBlocking(false); //同样是非阻塞
channel.register(selector, SelectionKey.OP_READ); //本次注册的是read事件,即receive接受
System.out.println("CWJ Client :" + channel.socket().getInetAddress().getHostName() + ":" + channel.socket().getPort());
}
else if (key.isReadable()) { //如果为读事件
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024); //1KB的缓冲区
channel.read(buffer); //读取到缓冲区
buffer.flip(); //准备写入
System.out.println("android123 receive info:" + buffer.toString());
channel.write(CharBuffer.wrap("it works".getBytes())); //返回给客户端
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
selector.close();
server.close();
} catch (IOException e) {
}
}
}
}
上面是比较简单的框架,里面存在很多问题,Android123将在下次详细阐述下,上面或者说国内有关NIO资料中的通病,如果你看过Mina或GlassFish的源码,你可能就知道上面的问题大于10种,有关框架的bug占了大多数,作为服务器而言很容易CPU超过100%
---------------------------------------------------------------5--------------------------------------------------------------------------
有关Android NIO的注意点和重点今天Android123着重分析下上次 Android开发进阶之NIO非阻塞包(四) 一文中提到的不足地方改进。由于目前国内很多人资料书籍编写人员没有通过NIO实现服务器的经验,导致了很多例子中存在严重的错误,由于大多数例子为Echo这样的单次交互以及数据量较小所以反映不出问题的所在。
1. 读和写应该分开,NIO使用的是异步的方法但不等于说不会阻塞,在上面的例子中我们可以看到 判断 key.isReadable() 时,对于这个SelectionKey关联的SocketChannel尽量不要使用写入数据量过多时ByteBuffer使用hasRemaining这样的方法,NIO每次读写不一定全部要把数据读完在一次Selector时。
2. 对于上面的解决方法我们可以继续关注感兴趣的事件,比如说使用interestOps方法,而很多资料中均使用了错误的继续用Selector的register方法继续注册事件,这样没有发生错误的原因是每次注册时会替换上次的这个key注册的事件,比较侥幸,从效率上讲还会判断这个key上次注册的是什么事件,并不是一种正统的方法。
3. 我们可以继续判断写入事件,比如key.isWritable,在写入时来处理发送数据。这样可以应对很多低速网络时产生的异常。
有关的细节还有很多,下一次Android开发网总结出常见的问题,并给大家一个较科学完善的框架,减少不必要的麻烦产生。
---------------------------------------------------------------6--------------------------------------------------------------------------
有关Android NIO的相关内容,本次Android123整理并归类如下,为了让大家感觉NIO和Android平台联系的紧密,这里我们结合ADT插件的重要开发工具DDMS中的源码进行分析。在android git中的sdk.git文件中,可以找到ddmlib这个文件夹。有关PC和手机的互通内核在这里使用了Java来完全实现。这里Android开发网一起帮助大家了解下PC同步软件的开发原理同时学习下Java中的New I/O技术。
比较重要的代码段我们贴出,逐一分析,其他的网友可以直接预读源码:
AdbHelper.java文件中
public static SocketChannel open(InetSocketAddress adbSockAddr,
Device device, int devicePort) //这是一个重载版本,主要是关联Device实例。
throws IOException, TimeoutException, AdbCommandRejectedException {
SocketChannel adbChan = SocketChannel.open(adbSockAddr); //构造SocketChannel对象,使用常规的open方法创建
try {
adbChan.socket().setTcpNoDelay(true); //设置TCP非延迟
adbChan.configureBlocking(false); //非阻塞
setDevice(adbChan, device); //本句和NIO没有多大关系,这句是指定具体的设备,比如模拟器,或Android手机的厂家代号,比如宏达电的以HTXXXXX这样的方式
byte[] req = createAdbForwardRequest(null, devicePort); //设置端口转发,这句很关键,否则PC和手机通过USB是无法互通的。
write(adbChan, req); //发送数据
AdbResponse resp = readAdbResponse(adbChan, false); //读取收到的内容
if (resp.okay == false) {
throw new AdbCommandRejectedException(resp.message);
}
adbChan.configureBlocking(true);
} catch (TimeoutException e) { //一般要处理超时异常
adbChan.close(); //释放channel句柄
throw e;
} catch (IOException e) { //处理常规的IO异常
adbChan.close();
throw e;
}
return adbChan;
}
有关读取ADB返回的报文方法
static AdbResponse readAdbResponse(SocketChannel chan, boolean readDiagString)
throws TimeoutException, IOException {
AdbResponse resp = new AdbResponse();
byte[] reply = new byte[4]; //创建4字节数组,主要检测成功与否,adb的协议是成功返回 okay,失败fail,等等。
read(chan, reply); //读取具体的返回
if (isOkay(reply)) { //判断是否成功
resp.okay = true;
} else {
readDiagString = true; // look for a reason after the FAIL
resp.okay = false;
}
// not a loop -- use "while" so we can use "break"
try {
while (readDiagString) {
// length string is in next 4 bytes
byte[] lenBuf = new byte[4];
read(chan, lenBuf); //读取一个字节数组,最终为了转为一个整形
String lenStr = replyToString(lenBuf); //字节数组转为String
int len;
try {
len = Integer.parseInt(lenStr, 16); //String转为整形,这里Android123提示,这种写法可能比较愚蠢,但是下面为Log输出提供了一点点的便利。
} catch (NumberFormatException nfe) {
Log.w("ddms", "Expected digits, got '" + lenStr + "': "
+ lenBuf[0] + " " + lenBuf[1] + " " + lenBuf[2] + " "
+ lenBuf[3]);
Log.w("ddms", "reply was " + replyToString(reply));
break;
}
byte[] msg = new byte[len];
read(chan, msg);
resp.message = replyToString(msg);
Log.v("ddms", "Got reply '" + replyToString(reply) + "', diag='"
+ resp.message + "'");
break;
}
} catch (Exception e) {
// ignore those, since it's just reading the diagnose string, the response will
// contain okay==false anyway.
}
return resp;
}
有关PC上对Android手机屏幕截图的方法之一:
static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device)
throws TimeoutException, AdbCommandRejectedException, IOException {
RawImage imageParams = new RawImage();
byte[] request = formAdbRequest("framebuffer:"); // 读取手机端adbd服务器的framebuffer调用返回的数组
byte[] nudge = {
0
};
byte[] reply;
SocketChannel adbChan = null;
try {
adbChan = SocketChannel.open(adbSockAddr);
adbChan.configureBlocking(false); //非阻塞
setDevice(adbChan, device); //设置我们关系的设备
write(adbChan, request); //发送framebuffer这个请求了
AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
if (resp.okay == false) { //判断返回是否ok。
throw new AdbCommandRejectedException(resp.message);
}
reply = new byte[4];
read(adbChan, reply); //首先返回的是一个协议,目前分为两个版本,主要是兼容模式和标准的模式,兼容模式比较少见,在2.0以后几乎看不到了。部分早期的1.6或更老的T-Mobile G1会使用兼容模式,模式不同,输出的截图中的颜色编码方式略有不同。
ByteBuffer buf = ByteBuffer.wrap(reply);
buf.order(ByteOrder.LITTLE_ENDIAN); //小头字节顺序
int version = buf.getInt(); //ByteBuffer直接转int的方法,比较方便不用自己从字节数组中构造,按位计算
int headerSize = RawImage.getHeaderSize(version); //根据返回的adb截图协议版本判断将收到的字节大小
reply = new byte[headerSize * 4]; //分配空间,具体大小需要看协议版本
read(adbChan, reply);
buf = ByteBuffer.wrap(reply); //从reply数组实例化ByteBuffer
buf.order(ByteOrder.LITTLE_ENDIAN); //注意字节序列,毕竟远端的adbd是工作在linux系统的手机上。
if (imageParams.readHeader(version, buf) == false) { //判断是否有效,兼容这种截图协议。
Log.e("Screenshot", "Unsupported protocol: " + version);
return null;
}
Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size="
+ imageParams.size + ", width=" + imageParams.width
+ ", height=" + imageParams.height); //打印下截图的基本信息,比如bpp代表色深,size是需要分配dib图像的字节数组。比较原始,
write(adbChan, nudge); //发送一个字节,代表准备接收字节数组了
reply = new byte[imageParams.size]; //分配和图像大小一样的字节数组
read(adbChan, reply); //接收图像字节数组,这里Android开发网提示大家对于Android 1.x可能为RGB565,分配大小为 wxhx2xsize ,而2.x以后基本上为32位的RGB8888,分配大小为wxhx4xsize
imageParams.data = reply;
} finally {
if (adbChan != null) {
adbChan.close();
}
}
return imageParams;
}
有关Android平台PC通过USB的ADB方式和手机同步原理和NIO相关技术,Android123明天继续讲解。
---------------------------------------------------------------7--------------------------------------------------------------------------
今天我们继续就Android DDMS源码一起分析NIO非阻塞通讯方式,Android123也会给大家分享下手机和PC互通中的一些技术。在NIO中有关SocketChannel和ByteBuffer的使用细节,可以在今天文章中
static void read(SocketChannel chan, byte[] data, int length, int timeout)
throws TimeoutException, IOException {
ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length); //从字节数组中实例化ByteBuffer
int numWaits = 0;
while (buf.position() != buf.limit()) { //循环接收数据
int count;
count = chan.read(buf);
if (count < 0) {
throw new IOException("EOF"); //读到末尾
} else if (count == 0) {
if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
throw new TimeoutException();
}
try {
Thread.sleep(WAIT_TIME);
} catch (InterruptedException ie) {
}
numWaits++;
} else {
numWaits = 0;
}
}
}
有关SocketChannel的写操作,就是发送数据代码如下:
static void write(SocketChannel chan, byte[] data, int length, int timeout)
throws TimeoutException, IOException {
ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
int numWaits = 0;
while (buf.position() != buf.limit()) {
int count;
count = chan.write(buf); //发送数据从ByteBuffer中
if (count < 0) {
throw new IOException("channel EOF");
} else if (count == 0) {
if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
throw new TimeoutException();
}
try {
Thread.sleep(WAIT_TIME);
} catch (InterruptedException ie) {
}
numWaits++;
} else {
numWaits = 0;
}
}
}
有关ADB如何选择一个具体的设备,可以使用 setDevice 方法,这样当电脑中有模拟器或链接了多个手机,可以通过设备序列号,选择需要通讯的设备。
static void setDevice(SocketChannel adbChan, IDevice device)
throws TimeoutException, AdbCommandRejectedException, IOException {
// if the device is not -1, then we first tell adb we're looking to talk
// to a specific device
if (device != null) {
String msg = "host:transport:" + device.getSerialNumber(); // 最后的获取序列号,android123提示大家在adb命令中是adb get-serialno
byte[] device_query = formAdbRequest(msg);
write(adbChan, device_query);
AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
if (resp.okay == false) {
throw new AdbCommandRejectedException(resp.message,
true/*errorDuringDeviceSelection*/);
}
}
}
通过PC控制手机重启的代码,当然这里需要Root权限才能执行
public static void reboot(String into, InetSocketAddress adbSockAddr,
Device device) throws TimeoutException, AdbCommandRejectedException, IOException {
byte[] request;
if (into == null) {
request = formAdbRequest("reboot:"); //$NON-NLS-1$
} else {
request = formAdbRequest("reboot:" + into); //$NON-NLS-1$
}
SocketChannel adbChan = null;
try {
adbChan = SocketChannel.open(adbSockAddr);
adbChan.configureBlocking(false);
// if the device is not -1, then we first tell adb we're looking to talk
// to a specific device
setDevice(adbChan, device);
write(adbChan, request);
} finally {
if (adbChan != null) {
adbChan.close();
}
}
}
我们可以看到基本上,每个命令的执行,都是用了单独SocketChannel通过非阻塞方式执行,这样大大加强了并发,所以DDMS可以一边处理Logcat打印,显示堆信息,处理文件管理等等,有关NIO服务器的内容,Android开发网将着重分析MonitorThread.java这个文件,一起说下NIO的框架。
---------------------------------------------------------------8--------------------------------------------------------------------------
最后一类MonitorThread延伸主题{
静态最终CLIENT_READY = 2;
私有静态最终诠释CLIENT_DISCONNECTED = 3;
私人挥发性布尔mQuit = FALSE;
私人的ArrayList的<client> mClientList / /用一个数组保存客户端信息;
私人选择mSelector;
私人HashMap的<Integer, ChunkHandler> mHandlerMap;
私人的ServerSocketChannel mDebugSelectedChan; / /一个用于调试的服务器通道
私人诠释mNewDebugSelectedPort;
私人INT mDebugSelectedPort的= -1;
私人客户mSelectedClient = NULL;
私人的静态MonitorThread mInstance;
私人MonitorThread(){
超(“监视器”);
mClientList =新的ArrayList的<client>();
mHandlerMap =新的HashMap <Integer, ChunkHandler>();
mNewDebugSelectedPort = DdmPreferences.getSelectedDebugPort();
}
的静态MonitorThread的CreateInstance(){/ /创建实例
返回mInstance =的新MonitorThread();
}
静态MonitorThread的getInstance(){/ /获取实例
mInstance回报;
}
同步无效setDebugSelectedPort(INT端口)抛出IllegalStateException {/ /设定调试端口号技术
如果(mInstance == NULL){
回报;
}
如果(AndroidDebugBridge.getClientSupport()== FALSE){
;
}
(mDebugSelectedChan = NULL){
Log.d(DDMS“,”更改调试选定的端口“+端口);
mNewDebugSelectedPort =端口;
唤醒()/ /这里用来唤醒所有的选择
}否则{
/ / ; 我们设置mNewDebugSelectedPort代替mDebugSelectedPort因此,它是,自动
mNewDebugSelectedPort =端口;
}
}
同步的“无效setSelectedClient(客户selectedClient){
如果(mInstance == NULL){
返回;
}
(mSelectedClient = selectedClient!){
客户端oldClient = mSelectedClient;
mSelectedClient = selectedClient;
(oldClient = NULL){
oldClient.update(Client.CHANGE_PORT);
}
(mSelectedClient = NULL){
mSelectedClient.update(Client.CHANGE_PORT);
}
} }
客户getSelectedClient(){
返回mSelectedClient的;
}
的布尔getRetryOnBadHandshake(){
返回true; / / TODO?使配置
}
客户端[] getClients的(){
(mClientList)同步{
返回mClientList.toArray(新客户[0]);
}
}
同步无效registerChunkHandler的(int类型,ChunkHandler处理程序){
如果(mInstance == NULL){
回报;
}
同步(mHandlerMap){
如果(mHandlerMap.get(类型)== NULL){
mHandlerMap.put(类型,处理程序);
} }
}
@覆盖
公共无效的run(){/ /本类的主要线程
Log.d(DDMS“,”监视器“);
尝试{
mSelector = Selector.open();
}的catch(IOException异常IOE){
Log.logAndDisplay(LogLevel.ERROR,“DDMS”,
“ 无法初始化监视器的主题:”,+ ioe.getMessage());
回报;
}
而(mQuit){
尝试{
同步(mClientList){
}
尝试{
如果(AndroidDebugBridge.getClientSupport()){
((mDebugSelectedChan == NULL | |
mNewDebugSelectedPort = mDebugSelectedPort)&&!
mNewDebugSelectedPort = -1){
如果(reopenDebugSelectedPort()){
mDebugSelectedPort = mNewDebugSelectedPort;
} }
} } 渔获量( IOException异常IOE){
Log.e(DDMS“,
“ 无法重新选定的客户端调试端口:mNewDebugSelectedPort);
Log.e(DDMS“,IOE);
mNewDebugSelectedPort = mDebugSelectedPort; / /重试
}
诠释计数;
尝试{
指望= mSelector.select();
}的catch(IOException异常IOE){
ioe.printStackTrace();
继续下去;
} {
继续渔获(CancelledKeyException CKE);
}
(== 0){
继续;
}
设置“<SelectionKey>键= mSelector.selectedKeys”()
迭代器<SelectionKey> ITER = keys.iterator; / /使用迭代器获取这个选择键
而(iter.hasNext()){
关键的SelectionKey = iter.next
iter.remove();
尝试{
如果(key.attachment()instanceof的客户端){/ /判断收到的关键的附件是否是客户端的实例
processClientActivity(键);
}
否则如果(key.attachment()instanceof的调试器){/ /如果是调试实例
processDebuggerActivity(键);
}
如果(key.attachment()的instanceof MonitorThread){
processDebugSelectedActivity(键);
}
{
Log.e(DDMS“,”未知的活动的关键“);
} } 赶上(例外五){
log.e(“DDMS”,“从选择的活动过程中的异常。”);
Log.e(“DDMS”,E);
} }
}赶上(例外五){
Log.e(“DDMS”,“异常MonitorThread。 ()“);
Log.e“DDMS”(E);
}
} }
,诠释getDebugSelectedPort(){
返回mDebugSelectedPort;
}
私人的无效processClientActivity(关键的SelectionKey){
Client客户端(客户端)key.attachment();
尝试{
如果(key.isReadable()== FALSE | | key.isValid()== FALSE){
Log.d(“DDMS”,删除客户端+客户端+“从无效的关键”。“);
dropClient(真实的,客户端/ *通知* /);
回报;
}
client.read();
包JdwpPacket = client.getJdwpPacket()
边(包= NULL){
如果(packet.isDdmPacket()){
/ /不请自来的DDM要求-一方面它关闭
断言packet.isReply();!
callHandler(客户端,分组,空 否则,如果(packet.isReply() && client.isResponseToUs的(packet.getId())= NULL));
packet.consume();
}
{!
/ /答复DDM的要求
ChunkHandler处理程序=的客户
isResponseToUs(packet.getId。 ());
(packet.isError())
client.packetFailed(包);
其他(packet.isEmpty())
Log.d(“DDMS”,“我为空的答复为0x”
+ Integer.toHexString(packet.
+“”+客户端)的getId());
其他
callHandler(客户端,分组处理程序);
packet.consume();
client.removeRequestId(packet.getId());
} {
Log.v(“DDMS” “转发客户端”
+(packet.isReply()“的答复”:“事件”)+“0X”
+ Integer.toHexString(packet.getId())+“”
+ client.getDebugger());
client.forwardPacketToDebugger (包);
}
包= client.getJdwpPacket();
}
}渔获(CancelledKeyException E){/ /注意正确处理这个异常
dropClient(客户端,真/ *通知* /);
}的catch(IOException异常前){
dropClient(客户端,真/ *通知* /);
}(前){
Log.e(DDMS“前);
dropClient(客户端,真/ *通知* /);
(当然的instanceof BufferOverflowException){/ /可能存在缓冲区异常
Log.w(“DDMS”,
“ 客户端的数据包超过最大缓冲区大小”
+客户端);
}否则{
/ /不知道这是什么,它显示
log.e(“DDMS”,前);
} }
}
私人无效callHandler(Client客户端,JdwpPacket包,
ChunkHandler处理程序){
(client.ddmSeen()) / /第一的DDM收到数据包,播出了“准备就绪”的消息,如果
广播(CLIENT_READY,客户端);
ByteBuffer的BUF = packet.getPayload();
int类型,长度,
布尔答复= TRUE;
类型= buf.getInt();
长度= buf.getInt();
(处理程序== NULL){
/ /没有答复,弄清楚谁想要它
同步(mHandlerMap){
处理程序(类型);
答复mHandlerMap.get = FALSE;
} }
(处理程序== NULL){
Log.w(DDMS“,”不受支持块类型“
+ ChunkHandler.name(型)+(LEN =“+长度+”)“);
} {
Log.d( “DDMS”,“呼叫处理程序”ChunkHandler.name(类型)
“[”+处理程序+“](LEN =”+长度+“)”);
的ByteBuffer IBUF = buf.slice();
的ByteBuffer roBuf = IBUF的。asReadOnlyBuffer(); / /执行的R /:Ø
roBuf.order(ChunkHandler.CHUNK_ORDER);
同步(mClientList)的{
handler.handleChunk(客户端,类型,roBuf,答复,packet.getId());
}
}
}
的同步无效dropClient的(客户端客户端,布尔通知){
如果(mInstance == NULL){
回报;
}
同步(mClientList){
如果(mClientList.remove(客户端)== FALSE){
;
}
}
client.close(通知);
广播(CLIENT_DISCONNECTED,客户端);
/*
* http://forum.java.sun.com/thread.jspa?threadID=726715&start=0
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5073504
*/
wakeup();
}
/
* 从调试器插座一个流程活动。这可能是一个新的
*连接或数据包
* /
私人无效processDebuggerActivity(关键的SelectionKey){
调试DBG =(调试器)key.attachment();
尝试{
(key.isAcceptable()){/ /处理服务器响应这个事件
尝试{
acceptNewDebugger(DBG,NULL);
}的catch(IOException异常IOE){
Log.w(“DDMS”,“调试接受()失败”)
ioe.printStackTrace();
} } 否则,如果(key.isReadable()){/ /如果什么收到的数据,则可读取
processDebuggerData(键);
}否则{
Log.d(“DDM-调试” “关键在未知状态”);
} } 渔获(CancelledKeyException CKE){/ /记住,NIO的处理这个异常,很多入门的开发者很容易忘记
/ /密钥已被取消,我们可以忽略,。
}
}
私人无效acceptNewDebugger(调试dbg的,acceptChan ServerSocketChannel)/ /这里用到了阻塞方式
抛出:IOException {
同步(mClientList){
陈的SocketChannel;
如果(acceptChan == NULL)
陈= dbg.accept,
否则
陈dbg.accept(acceptChan);
(陈= NULL){
chan.socket()setTcpNoDelay(真);
唤醒();
{
chan.register(mSelector,SelectionKey.OP_READ,DBG);
}的catch(IOException异常IOE){
/ /失败,放弃的连接
dbg.closeData();
抛出IOE;
}的catch(RuntimeException的转口){
/ /失败,降的连接
dbg.closeData();
抛出重;
} } 否则{
Log.w(DDMS“,”忽略重复调试“);
} }
}
私人无效processDebuggerData(关键的SelectionKey){
调试DBG =(调试)key.attachment的();
尝试{
dbg.read();
JdwpPacket包= dbg.getJdwpPacket();
而(!包= NULL){
Log.v(“DDMS”,“转发dbg的REQ 0X”
+ Integer.toHexString(packet.getId())+“”
+ dbg.getClient ());
dbg.forwardPacketToClient(包);
包dbg.getJdwpPacket();
} } 的catch(IOException异常IOE){
Log.d(DDMS“,”关闭连接到调试器“+ dbg有关);
dbg.closeData();
客户端客户端= dbg.getClient();
(client.isDdmAware()){
Log.d(“DDMS”,“(以及回收客户端连接)”);
{ Log.d(“DDMS”,“(以及回收客户端连接)”) / /我们应该放弃客户,但也试图重新打开它, / /这是由
私人无效唤醒(){
mSelector.wakeup();
}
同步无效退出(){
mQuit 下降= TRUE;
唤醒();
Log.d(“DDMS”,“等待监视器线程”);
尝试{
this.join()
/ /因为我们戒烟,让所有客户端和断开
/ / DebugSelectedPort的
同步(mClientList)
{ (客户机C:mClientList){
c.close(假/ *通知* /);
广播(CLIENT_DISCONNECTED,C);
}
mClientList.clear();
}
如果(mDebugSelectedChan = NULL){
mDebugSelectedChan.close();
。mDebugSelectedChan.socket()关闭();
mDebugSelectedChan = NULL;
}
mSelector.close();
}的catch(InterruptedException的IE){
ie.printStackTrace();
}赶上(IOException异常E){
/ / TODO自动生成catch块
论坛主题贴();
}
mInstance = NULL;
}
的同步无效addClient的(Client客户端){
如果(mInstance == NULL){
回报;
}
log.d(“DDMS”,“添加新的客户端+客户端”);
同步(mClientList){
mClientList.add(客户端);
尝试{
唤醒();
client.register(mSelector);
调试DBG = client.getDebugger();
(DBG = NULL){
dbg.registerListener(mSelector);
} } 的catch(IOException异常IOE){
/ /不是真的希望这种情况发生
ioe.printStackTrace();
}
}
}
/
* 广播事件的所有消息处理程序。
* /
私人无效广播(INT事件,客户端客户端){
Log.d(DDMS“,”广播“+事件+”:“+客户端);
/
* 处理对象,每个消息,他们一旦出现在mHandlerMap
*处理。我们要通知他们一旦每个,所以我们转换
到HashSet 的HashMap *之前,我们遍历。
* /
HashSet的<ChunkHandler>设置
同步(mHandlerMap){
收集<ChunkHandler>值= mHandlerMap.values();
一套新的HashSet <ChunkHandler>(值);
}
迭代<ChunkHandler> ITER = set.iterator()
( { handler.clientReady(客户端); } iter.hasNext()){
处理程序ChunkHandler = iter.next();
开关(事件)
{ CLIENT_READY:渔获量( 与客户IOException异常IOE){ / /失败的东西。 / /属于列表下一次我们尝试 / /用它做什么,所以我们放弃 / /异常,并承担清理 / /以后会发生。可能需要传播的更远。 / /麻烦的是,并不是所有的“事件”的值 / /抛出一个异常 Log.w(“DDMS”, “ 异常了,而广播'准备'”); 回报; } 休息; 案件CLIENT_DISCONNECTED: 处理程序。 clientDisconnected(客户端); 休息; 默认: 引发新的操作UnsupportedOperationException(); } }
}
/
*** 开启(或重开)“调试选中”端口监听连接。
* @返回true,如果端口被成功打开。
* @抛出IOException异常
* /
私人,布尔reopenDebugSelectedPort()抛出:IOException {
log.d(“DDMS”,“重新调试选定的端口:”+ mNewDebugSelectedPort);
(mDebugSelectedChan = NULL){
mDebugSelectedChan.close();
}
mDebugSelectedChan ServerSocketChannel.open =();
mDebugSelectedChan.configureBlocking(假); / /需要选择
InetSocketAddress地址=新InetSocketAddress(
InetAddress.getByName(的“localhost”),/ / $ NON-NLS-1 $
mNewDebugSelectedPort),2
。mDebugSelectedChan.socket()setReuseAddress(TRUE); / /启用SO_REUSEADDR命令
{
mDebugSelectedChan.socket()绑定(地址);
(!mSelectedClient = NULL){
mSelectedClient.update(Client.CHANGE_PORT);
}
mDebugSelectedChan.register(mSelector,SelectionKey.OP_ACCEPT,本);
返回true;
}捕捞(java.net.BindException北京时间){
displayDebugSelectedBindError(mNewDebugSelectedPort);
/ /不要试图重新打开它。
mDebugSelectedChan = NULL;
mNewDebugSelectedPort = -1;
返回false;
} }
/
* 端口“选择调试”我们有一些活动。处理它。
* /
私人的无效processDebugSelectedActivity(的SelectionKey键){
断言key.isAcceptable();
的ServerSocketChannel acceptChan =(ServerSocketChannel)key.channel“();
/
* 查找与当前选定的客户端相关的调试。
* /
如果(mSelectedClient = NULL){
调试dbg的mSelectedClient.getDebugger();
(DBG = NULL){
Log.d(“DDMS”,“接受端口”选择调试“连接”);
{
acceptNewDebugger(DBG acceptChan)的;
} {
/ /客户端应该会消失的catch(IOException异常IOE),继续
}
回报;
} }
log.w(“DDMS”,
“” 调试选定的端口上的连接,但没有选择“);
尝试{
陈的SocketChannel = acceptChan.accept();
chan.close();
}的catch(IOException异常IOE){
/ /不预期;客户应了,继续下去
}渔获(NotYetBoundException E){
displayDebugSelectedBindError(mDebugSelectedPort);
}
}
如果它
= String.format(
“ 无法打开选定的VM调试端口(%1 $ D)的 私人的无效displayDebugSelectedBindError(INT端口){ String消息。确保你没有对DDMS或Eclipse插件运行的另一个实例。用别的东西,选择喜好的新端口号“,
口);
Log.logAndDisplay(LogLevel.ERROR,“DDMS”,消息);
} }
从上面来看Android的开源代码有关PC上的写的不是很好,很多实现的地方都是用了严重的缝缝补补方式解决,有些习惯不是很到位,有关本NIO例子由于涉及的项目对象多,理解需要网友深入分析DDMS源码中的每个对象。细节写的不是很理想,Android123推荐大家,画出UML后再分析更清晰。