HttpURLConnection 網絡請求更嚴謹的處理

可能會有人問爲什麼現在還用HttpURLConnection?畢竟 HttpClient 已被遺棄了,感覺手寫請求很low?呃呃呃.....我想你總有一天會重拾手寫請求!!!加油

在很多示例代碼中得到 輸入流(byteStream = httpConn.getInputStream();)後通常就會直接對流進行操作,一頓讀流操作,得到數據. 

 這樣操作是簡單直接粗暴的,是按照最嚴重情況處理的(嚴重在於不知道數據長度),並且沒有考慮線程中斷的情況,通常如下:

//創建URL對象,xxx是服務器API
URL url = new URL("xxx");
//調用URL對象的openConnection( )來獲取HttpURLConnection對象實例
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//請求方法爲GET
conn.setRequestMethod("GET");
//設置連接超時爲5秒
conn.setConnectTimeout(5000);
//服務器返回東西了,先對響應碼判斷
if (conn.getResponseCode() == 200) {
    //用getInputStream()方法獲得服務器返回的輸入流
    InputStream is = conn.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len = 0;
    while ((len = is.read(buffer)) != -1) {
         baos.write(buffer, 0, len);
    }
    baos.close();
    is.close();
    byte[] data = baos.toByteArray();
    String html = new String(data, "UTF-8");
    System.out.println(html);
    in.close();
}

在實際的使用過程中會更加複雜,必須考慮線程的問題,不然內存泄漏導致程序崩潰也是不能接受的情況,讀寫過程中對線程狀態的判斷,及異常的處理,使得代碼更加的穩健. 

這裏邏輯的嚴謹之處在於,沒有一概而論,保證了在可讀取數據長度的時候一定讀完數據!僞代碼:

int contentSize = httpConn.getContentLength();
if(-1 == contentSize){
    //不可讀取數據長度
    //讀到  readResult == -1 時就完成讀流
    readResult = byteStream.read(tempBuffer, bufferOffset,
                                        bufferLeft);
}else{
    //可讀取數據長度
    //必須讀到 數據長度爲 contentSize 後才完成讀流
}

雖然InputStream#read()有明確說明返回-1既是讀取完畢,但是你去看所有的實現類,都只是對流對象中的數據長度,及末尾字符的空判斷,所以在異步的過程中是存在漏洞的 

InputStream #read(byte b[], int off, int len)  :

    /* 
     * @param      b     the buffer into which the data is read.
     * @param      off   the start offset in array <code>b</code>
     *                   at which the data is written.
     * @param      len   the maximum number of bytes to read.
     * @return     the total number of bytes read into the buffer, or
     *             <code>-1</code> if there is no more data because the end of
     *             the stream has been reached.
     * @exception  IOException If the first byte cannot be read for any reason
     * other than end of file, or if the input stream has been closed, or if
     * some other I/O error occurs.
     * @exception  NullPointerException If <code>b</code> is <code>null</code>.
     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
     * <code>len</code> is negative, or <code>len</code> is greater than
     * <code>b.length - off</code>
     * @see        java.io.InputStream#read()
     */
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();//<-------有無數據的判斷
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

其中一個子類BufferInputStream 對 read 的實現如下(其他子類也差不多): 

除了方法有鎖以外,裏面的所有引用沒有做異步處理,所以,如此簡單的實現,你看出來了異步過程中的問題了吧, 返回-1時並不是一定真正的讀完了! (因爲我加載"www.baidu.com"時,什麼都沒有得到,這也是我發問並找到方案的原因!)

  public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

 這是google的示例代碼,對網絡請求圖片的處理,供你參看:


import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * This task downloads bytes from a resource addressed by a URL.  When the task
 * has finished, it calls handleState to report its results.
 * <p>
 * Objects of this class are instantiated and managed by instances of PhotoTask, which
 * implements the methods of {@link TaskRunnableDecodeMethods}. PhotoTask objects call
 * {@link #PhotoDownloadRunnable(TaskRunnableDownloadMethods) PhotoDownloadRunnable()} with
 * themselves as the argument. In effect, an PhotoTask object and a
 * PhotoDownloadRunnable object communicate through the fields of the PhotoTask.
 */
class PhotoDownloadRunnable implements Runnable {
    // Sets the size for each read action (bytes)
    private static final int READ_SIZE = 1024 * 2;

    // Sets a tag for this class
    @SuppressWarnings("unused")
    private static final String LOG_TAG = "PhotoDownloadRunnable";

    // Constants for indicating the state of the download
    static final int HTTP_STATE_FAILED = -1;
    static final int HTTP_STATE_STARTED = 0;
    static final int HTTP_STATE_COMPLETED = 1;

    // Defines a field that contains the calling object of type PhotoTask.
    final TaskRunnableDownloadMethods mPhotoTask;

    /**
     * An interface that defines methods that PhotoTask implements. An instance of
     * PhotoTask passes itself to an PhotoDownloadRunnable instance through the
     * PhotoDownloadRunnable constructor, after which the two instances can access each other's
     * variables.
     */
    interface TaskRunnableDownloadMethods {

        /**
         * Sets the Thread that this instance is running on
         *
         * @param currentThread the current Thread
         */
        void setDownloadThread(Thread currentThread);

        /**
         * Returns the current contents of the download buffer
         *
         * @return The byte array downloaded from the URL in the last read
         */
        byte[] getByteBuffer();

        /**
         * Sets the current contents of the download buffer
         *
         * @param buffer The bytes that were just read
         */
        void setByteBuffer(byte[] buffer);

        /**
         * Defines the actions for each state of the PhotoTask instance.
         *
         * @param state The current state of the task
         */
        void handleDownloadState(int state);

        /**
         * Gets the URL for the image being downloaded
         *
         * @return The image URL
         */
        URL getImageURL();
    }

    /**
     * This constructor creates an instance of PhotoDownloadRunnable and stores in it a reference
     * to the PhotoTask instance that instantiated it.
     *
     * @param photoTask The PhotoTask, which implements TaskRunnableDecodeMethods
     */
    PhotoDownloadRunnable(TaskRunnableDownloadMethods photoTask) {
        mPhotoTask = photoTask;
    }

    /*
     * Defines this object's task, which is a set of instructions designed to be run on a Thread.
     */
    @SuppressWarnings("resource")
    @Override
    public void run() {
        /*
         * Stores the current Thread in the the PhotoTask instance, so that the instance
         * can interrupt the Thread.
         */
        mPhotoTask.setDownloadThread(Thread.currentThread());
        // Moves the current Thread into the background
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
        /*
         * Gets the image cache buffer object from the PhotoTask instance. This makes the
         * to both PhotoDownloadRunnable and PhotoTask.
         */
        byte[] byteBuffer = mPhotoTask.getByteBuffer();
        /*
         * A try block that downloads a Picasa image from a URL. The URL value is in the field
         * PhotoTask.mImageURL
         */
        // Tries to download the picture from Picasa
        try {
            // Before continuing, checks to see that the Thread hasn't been
            // interrupted
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            // If there's no cache buffer for this image
            if (null == byteBuffer) {
                /*
                 * Calls the PhotoTask implementation of {@link #handleDownloadState} to
                 * set the state of the download
                 */
                mPhotoTask.handleDownloadState(HTTP_STATE_STARTED);
                // Defines a handle for the byte download stream
                InputStream byteStream = null;
                // Downloads the image and catches IO errors
                try {
                    // Opens an HTTP connection to the image's URL
                    HttpURLConnection httpConn =
                            (HttpURLConnection) mPhotoTask.getImageURL().openConnection();
                    // Sets the user agent to report to the server
                    httpConn.setRequestProperty("User-Agent", Constants.USER_AGENT);
                    // Before continuing, checks to see that the Thread
                    // hasn't been interrupted
                    if (Thread.interrupted()) {
                        throw new InterruptedException();
                    }
                    // Gets the input stream containing the image
                    byteStream = httpConn.getInputStream();
                    if (Thread.interrupted()) {
                        throw new InterruptedException();
                    }
                    /*
                     * Gets the size of the file being downloaded. This
                     * may or may not be returned.
                     */
                    int contentSize = httpConn.getContentLength();
                    /*
                     * If the size of the image isn't available
                     */
                    if (-1 == contentSize) {
                        // Allocates a temporary buffer
                        byte[] tempBuffer = new byte[READ_SIZE];
                        // Records the initial amount of available space
                        int bufferLeft = tempBuffer.length;
                        /*
                         * Defines the initial offset of the next available
                         * byte in the buffer, and the initial result of
                         * reading the binary
                         */
                        int bufferOffset = 0;
                        int readResult = 0;
                        /*
                         * The "outer" loop continues until all the bytes
                         * have been downloaded. The inner loop continues
                         * until the temporary buffer is full, and then
                         * allocates more buffer space.
                         */
                        outer1:
                        do {
                            while (bufferLeft > 0) {
                                /*
                                 * Reads from the URL location into
                                 * the temporary buffer, starting at the
                                 * next available free byte and reading as
                                 * many bytes as are available in the
                                 * buffer.
                                 */
                                readResult = byteStream.read(tempBuffer, bufferOffset,
                                        bufferLeft);
                                /*
                                 * InputStream.read() returns zero when the
                                 * file has been completely read.
                                 */
                                if (readResult < 0) {
                                    // The read is finished, so this breaks
                                    // the to "outer" loop
                                    break outer1;
                                }
                                /*
                                 * The read isn't finished. This sets the
                                 * next available open position in the
                                 * buffer (the buffer index is 0-based).
                                 */
                                bufferOffset += readResult;
                                // Subtracts the number of bytes read from
                                // the amount of buffer left
                                bufferLeft -= readResult;
                                if (Thread.interrupted()) {
                                    throw new InterruptedException();
                                }
                            }
                            /*
                             * The temporary buffer is full, so the
                             * following code creates a new buffer that can
                             * contain the existing contents plus the next
                             * read cycle.
                             */

                            // Resets the amount of buffer left to be the
                            // max buffer size
                            bufferLeft = READ_SIZE;
                            /*
                             * Sets a new size that can contain the existing
                             * buffer's contents plus space for the next
                             * read cycle.
                             */
                            int newSize = tempBuffer.length + READ_SIZE;
                            /*
                             * Creates a new temporary buffer, moves the
                             * contents of the old temporary buffer into it,
                             * and then points the temporary buffer variable
                             * to the new buffer.
                             */
                            byte[] expandedBuffer = new byte[newSize];
                            System.arraycopy(tempBuffer, 0, expandedBuffer, 0,
                                    tempBuffer.length);
                            tempBuffer = expandedBuffer;
                        } while (true);

                        /*
                         * When the entire image has been read, this creates
                         * a permanent byte buffer with the same size as
                         * the number of used bytes in the temporary buffer
                         * (equal to the next open byte, because tempBuffer
                         * is 0=based).
                         */
                        byteBuffer = new byte[bufferOffset];
                        // Copies the temporary buffer to the image buffer
                        System.arraycopy(tempBuffer, 0, byteBuffer, 0, bufferOffset);
                        /*
                         * The download size is available, so this creates a
                         * permanent buffer of that length.
                         */
                    } else {
                        byteBuffer = new byte[contentSize];
                        // How much of the buffer still remains empty
                        int remainingLength = contentSize;
                        // The next open space in the buffer
                        int bufferOffset = 0;
                        /*
                         * Reads into the buffer until the number of bytes
                         * equal to the length of the buffer (the size of
                         * the image) have been read.
                         */
                        while (remainingLength > 0) {
                            int readResult = byteStream.read(
                                    byteBuffer,
                                    bufferOffset,
                                    remainingLength);
                            /*
                             * EOF should not occur, because the loop should
                             * read the exact # of bytes in the image
                             */
                            if (readResult < 0) {
                                // Throws an EOF Exception
                                throw new EOFException();
                            }
                            // Moves the buffer offset to the next open byte
                            bufferOffset += readResult;
                            // Subtracts the # of bytes read from the
                            // remaining length
                            remainingLength -= readResult;
                            if (Thread.interrupted()) {
                                throw new InterruptedException();
                            }
                        }
                    }
                    if (Thread.interrupted()) {
                        throw new InterruptedException();
                    }
                    // If an IO error occurs, returns immediately
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                    /*
                     * If the input stream is still open, close it
                     */
                } finally {
                    if (null != byteStream) {
                        try {
                            byteStream.close();
                        } catch (Exception ignored) {
                        }
                    }
                }
            }

            /*
             * Stores the downloaded bytes in the byte buffer in the PhotoTask instance.
             */
            mPhotoTask.setByteBuffer(byteBuffer);

            /*
             * Sets the status message in the PhotoTask instance. This sets the
             * ImageView background to indicate that the image is being
             * decoded.
             */
            mPhotoTask.handleDownloadState(HTTP_STATE_COMPLETED);

            // Catches exceptions thrown in response to a queued interrupt
        } catch (InterruptedException e1) {

            // Does nothing

            // In all cases, handle the results
        } finally {

            // If the byteBuffer is null, reports that the download failed.
            if (null == byteBuffer) {
                mPhotoTask.handleDownloadState(HTTP_STATE_FAILED);
            }

            /*
             * The implementation of setHTTPDownloadThread() in PhotoTask calls
             * PhotoTask.setCurrentThread(), which then locks on the static ThreadPool
             * object and returns the current thread. Locking keeps all references to Thread
             * objects the same until the reference to the current Thread is deleted.
             */

            // Sets the reference to the current Thread to null, releasing its storage
            mPhotoTask.setDownloadThread(null);

            // Clears the Thread's interrupt flag
            Thread.interrupted();
        }
    }
}

 

發佈了10 篇原創文章 · 獲贊 2 · 訪問量 2605
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章