天天看點

自己動手實作類似Httpclient的網絡通路層

當我們在進行用戶端的api通路伺服器端的時候,我們會選擇封裝請求參數,使用urlconnection進行伺服器端的通路,但是urlconnection在涉及到圖檔上傳的時候就比較複雜了,于是,Android推薦了Httpclient一個第三方的開源架構,但是此架構有點太大了,而我們要用的隻是其中的一部分功能,是以本功能是将HttpUrlConnection進行了封裝,用戶端隻需調用ApiClient.uploadFile(context, uploadUrl, null,formFiles);将上下文context,url位址,form表單,檔案本地路徑。具體實作方式見如下代碼:

package org.hubu.yiyu.app;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.List;
import java.util.Map;

import org.hubu.yiyu.entity.FormFile;
import org.hubu.yiyu.utils.ACache;
import org.hubu.yiyu.utils.StringUtils;

import android.content.Context;
import android.util.Log;

/**
 * 用戶端網絡接口,用于網絡通路
 * 
 * @author wk
 * 
 */
public class ApiClient {

	private static final String TAG = "ApiClient";

	/**
	 * 發送POST請求獲得輸入流
	 * 
	 * @param context
	 * @param url
	 * @param params
	 * @return
	 * @throws Exception
	 */
	public static InputStream sendPOSTRequest(Context context, String url,
			Map<String, String> params) throws Exception {

		StringBuilder dataBuilder = new StringBuilder();
		if (params != null && !params.isEmpty()) {
			for (Map.Entry<String, String> entry : params.entrySet()) {
				dataBuilder.append(entry.getKey()).append("=");
				dataBuilder.append(entry.getValue());
				dataBuilder.append("&");
			}
			dataBuilder.deleteCharAt(dataBuilder.length() - 1);
			Log.i(TAG, dataBuilder.toString());
		}
		byte[] entity = dataBuilder.toString().getBytes();// 生成實體資料
		HttpURLConnection conn = getPostConnection(context, url);
		OutputStream outStream = conn.getOutputStream();
		outStream.write(entity);
		if (conn.getResponseCode() == 200) {
			String cookies = getCookie(conn);
			ACache.get(context).put("cookie", cookies,
					StringUtils.ONE_MOUTH_SECONDS);// 儲存獲得的cookie對象

			InputStream inStream = conn.getInputStream();
			return inStream;
		}

		return null;
	}

	/**
	 * 擷取POST連接配接對象
	 * 
	 * @param context
	 * @param url
	 * @param entity
	 * @return
	 * @throws IOException
	 * @throws MalformedURLException
	 * @throws ProtocolException
	 */
	private static HttpURLConnection getPostConnection(Context context,
			String url) throws IOException, MalformedURLException,
			ProtocolException {
		HttpURLConnection conn = (HttpURLConnection) new URL(url)
				.openConnection();
		conn.setConnectTimeout(5000);
		conn.setRequestMethod("POST");
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setRequestProperty("Content-Type",
				"application/x-www-form-urlencoded");
		conn.setRequestProperty("Connection", "keep-alive");
		conn.setRequestProperty("Charset", "UTF-8");
		conn.setRequestProperty("Content-Type",
				"multipart/form-data;boundary=******");// 必須在Content-Type(表示POST請求的類型)
														// 請求頭中指定分界符中的任意字元串
		String cookie = ACache.get(context).getAsString("cookie");
		Log.i(TAG, "本地獲得的cookie值" + cookie);
		conn.setRequestProperty("Cookie", cookie);
		return conn;
	}

	/**
	 * 根據建立的連接配接對象獲得cookie值
	 * 
	 * @param conn
	 * @return
	 */
	private static String getCookie(HttpURLConnection conn) {
		String sessionId = "";
		String cookieVal = "";
		String key = null;
		// 取cookie
		for (int i = 1; (key = conn.getHeaderFieldKey(i)) != null; i++) {
			if (key.equalsIgnoreCase("set-cookie")) {
				cookieVal = conn.getHeaderField(i);
				cookieVal = cookieVal.substring(0, cookieVal.indexOf(";"));
				sessionId = sessionId + cookieVal + ";";
			}
		}
		Log.i(TAG, "傳回的cookie值" + sessionId);
		return sessionId;
	}

	/**
	 * 發送get請求并獲得傳回的輸入流
	 * 
	 * @param context
	 * @param url
	 * @param params
	 * @return
	 * @throws Exception
	 */
	public static InputStream sendGETRequest(Context context, String url,
			Map<String, String> params) throws Exception {

		StringBuilder urlBuilder = new StringBuilder(url);
		urlBuilder.append("?");
		for (Map.Entry<String, String> entry : params.entrySet()) {
			urlBuilder.append(entry.getKey()).append("=");
			urlBuilder.append(entry.getValue());
			urlBuilder.append("&");
		}
		urlBuilder.deleteCharAt(urlBuilder.length() - 1);
		HttpURLConnection conn = (HttpURLConnection) new URL(
				urlBuilder.toString()).openConnection();
		conn.setConnectTimeout(5000);
		conn.setRequestMethod("GET");
		if (conn.getResponseCode() == 200) {
			InputStream inStream = conn.getInputStream();
			return inStream;
		}
		return null;
	}

	/**
	 * 發動post請求獲得傳回的輸入流
	 * 
	 * @param context 應用程式上下文
	 * @param url
	 *            請求的位址
	 * @param params
	 *            請求參數
	 * @param files
	 *            上傳的檔案
	 * @return
	 * @throws Exception
	 */
	public static InputStream uploadFile(Context context, String url,
			Map<String, String> params, List<FormFile> files) throws Exception {
		HttpURLConnection conn = getPostConnection(context, url);

		// 定義資料寫入流,準備上傳檔案
		DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
		String endLine = "\r\n";// 回車換行
		String twoHyphens = "--"; // 兩個連字元
		String boundary = "******"; // 分界符的字元串
		if (params != null) {
			StringBuilder dataBuilder = new StringBuilder();
	        for (Map.Entry<String, String> entry : params.entrySet()) {//構造文本類型參數的實體資料
	        	dataBuilder.append("--");
	        	dataBuilder.append("******");
	        	dataBuilder.append("\r\n");
	        	dataBuilder.append("Content-Disposition: form-data; name=\""+ entry.getKey() + "\"\r\n\r\n");
	        	dataBuilder.append(entry.getValue());
	        	dataBuilder.append("\r\n");
	        }
			byte[] entity = dataBuilder.toString().getBytes();// 生成實體資料
			dos.write(entity);// 将請求參數進行上傳
		}
		if (files != null) {

			for (FormFile formFile : files) {
				dos.writeBytes(twoHyphens + boundary + endLine);
				// 設定與上傳檔案相關的資訊
				dos.writeBytes("Content-Disposition: form-data; name=\""
						+ formFile.getParameterName() + "\"; filename=\""
						+ formFile.getFilname() + "\"" + endLine);
				dos.writeBytes(endLine);

				FileInputStream fis = new FileInputStream(
						formFile.getFilePath());
				byte[] buffer = new byte[2048]; // 2k
				int count = 0;
				// 讀取檔案夾内容,并寫入OutputStream對象
				while ((count = fis.read(buffer)) != -1) {
					dos.write(buffer, 0, count);
				}
				fis.close();// 關閉檔案讀入流
				dos.writeBytes(endLine);
			}
			// 下面發送資料結束标志,表示資料已經結束
			dos.writeBytes(twoHyphens + boundary + twoHyphens + endLine);
		}else if(files==null){
			dos.writeBytes(twoHyphens + boundary + endLine);
			// 設定與上傳檔案相關的資訊
			dos.writeBytes("Content-Disposition: form-data; name=\""
					+ "file" + "\"; filename=\""
					+ "" + "\"" + endLine);
			dos.writeBytes(endLine);

			dos.writeBytes(endLine);
			// 下面發送資料結束标志,表示資料已經結束
			dos.writeBytes(twoHyphens + boundary + twoHyphens + endLine);
		}
		dos.flush();// 關閉輸出流
		if (conn.getResponseCode() == 200) {
			Log.i(TAG, "檔案上傳成功");

			String cookies = getCookie(conn);
			ACache.get(context).put("cookie", cookies,
					StringUtils.ONE_MOUTH_SECONDS);// 儲存獲得的cookie對象

			// 讀取服務端傳回的資訊并傳回
			InputStream inStream = conn.getInputStream();
			return inStream;
		}
		return null;
	}

}
           

通過使用A piClient.uploadFile(context,uploadUrl, “檔案本地路徑” , formFiles);将上下文context,url位址,form表單,檔案本地路徑傳給方法uploadFile則可以将表單參數以及本地檔案一同上傳至伺服器,當然,如果沒有則可以直接傳入null即可。做過web開發的同學肯定一眼就能看出其實這就是按照浏覽器上傳檔案的做法進行模仿的;此處其中的formFiles是一個封裝好的實體類如下所示:

package org.hubu.yiyu.entity;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

/**
 * 要上傳的檔案
 * 
 * @author wk
 * 
 */
public class FormFile {
	// 上傳檔案的路徑
	private String filePath;
	//檔案名稱
	private String fileName = "file";
	// 請求參數名稱 
	private String parameterName;
	// 内容類型 
	private String contentType = "application/octet-stream";

	public FormFile(String filePath){
		this.fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
		this.filePath = filePath;
	}
	public FormFile(String filePath,String parameterName){
		this.fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
		this.filePath = filePath;
		this.parameterName = parameterName;
	}
	public FormFile(String fileName,String filePath,String parameterName){
		this.fileName = fileName;
		this.filePath = filePath;
		this.parameterName = parameterName;
	}
	public FormFile(String fileName, String filePath, String parameterName,
			String contentType) {
		this.filePath = filePath;
		this.fileName = fileName;
		this.parameterName = parameterName;
		if (contentType != null)
			this.contentType = contentType;
	}


	public String getFilePath() {
		return filePath;
	}

	public String getFilname() {
		return fileName;
	}

	public void setFilname(String fileName) {
		this.fileName = fileName;
	}

	public String getParameterName() {
		return parameterName;
	}

	public void setParameterName(String parameterName) {
		this.parameterName = parameterName;
	}

	public String getContentType() {
		return contentType;
	}

	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

}
           

根據uploadFile方法得到伺服器傳回的輸入流,此時可以自定義一個解析輸入流的工具類來解析為byte數組:

package org.hubu.yiyu.service;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class StreamTool {

	/**
	 * 讀取流中的資料
	 * 
	 * @param inStream
	 * @return
	 * @throws Exception
	 */
	public static byte[] read(InputStream inStream) throws Exception {
		ByteArrayOutputStream outStream = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int len = 0;
		while ((len = inStream.read(buffer)) != -1) {
			outStream.write(buffer, 0, len);
		}
		inStream.close();//對輸入流進行關閉
		return outStream.toByteArray();
	}

}
           

至于接下來是要解析成json還是字元串或是xml,就是業務類要做的事情了。

繼續閱讀