Android NIO(Noblocking I/O非阻塞I/O)小結


參考:http://www.cnblogs.com/cpcpc/archive/2011/06/27/2123009.html

 

對於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完成端口可以解決更高級的負載,當然了今天主要給大家講解下為什么使用NIO在Android中有哪些用處。

   NIO我們分為幾個類型分別描述,作為Java的特性之一,我們需要了解一些新的概念,比如ByteBuffer類,Channel,SocketChannel,ServerSocketChannel,Selector和SelectionKey。有關具體的使用,可以在Android SDK文檔中看下java.nio和java.nio.channels兩個包了解。

 

Android NIO主要分為三大類,ByteBuffer、FileChannel和SocketChannel。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類繼承而來的,提醒大家看Android SDK文檔時注意Inherited Methods,而JDK的文檔就比較直接了,同時復制文件使用FileChannel的transferTo(long position, long count, WritableByteChannel target) 這個方法可以快速的復制文件,無需自己管理ByteBuffer緩沖區。

 

http://www.jb51.net/article/64733.htm

 

一起來看看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緩沖區,進行網絡數據傳輸。

 ========對於開發安卓應用,還沒有具體場景用NIO。===========================================

但是,在官方給出的OpenGL教程中卻用到了,所以,記錄一下:

/*
 * Copyright (C) 2011 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.edaixi.opengl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

/**
 * A two-dimensional square for use as a drawn object in OpenGL ES 1.0/1.1.
 */
public class Square {

    private final FloatBuffer vertexBuffer;
    private final ShortBuffer drawListBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
             0.5f, -0.5f, 0.0f,   // bottom right
             0.5f,  0.5f, 0.0f }; // top right

    private final short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices

    float color[] = { 0.2f, 0.709803922f, 0.898039216f, 1.0f };

    /**
     * Sets up the drawing object data for use in an OpenGL ES context.
     */
    public Square() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
        // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }

    /**
     * Encapsulates the OpenGL ES instructions for drawing this shape.
     *
     * @param gl - The OpenGL ES context in which to draw this shape.
     */
    public void draw(GL10 gl) {
        // Since this shape uses vertex arrays, enable them
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

        // draw the shape
        gl.glColor4f(       // set color
                color[0], color[1],
                color[2], color[3]);
        gl.glVertexPointer( // point to vertex data:
                COORDS_PER_VERTEX,
                GL10.GL_FLOAT, 0, vertexBuffer);
        gl.glDrawElements(  // draw shape:
                GL10.GL_TRIANGLES,
                drawOrder.length, GL10.GL_UNSIGNED_SHORT,
                drawListBuffer);

        // Disable vertex array drawing to avoid
        // conflicts with shapes that don't use it
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    }
}

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM