可能會有人問爲什麼現在還用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();
}
}
}