android使用HttpURLConnection實現帶引數檔案上傳
檔案上傳是常見功能,然而android網上大多數的檔案上傳都使用httpclient,而且需要新增一個httpmine-jar,其實HttpURLConnection也可以實現檔案上傳,但是它在移動端有個弊端,就是不能上傳大檔案,所以這次說的方式,只能上傳一些較小的檔案。
檔案上傳,並且帶上一些引數,這需要我們瞭解http請求的構造方式,也就是它的格式。
HttpURLConnection需要我們自己構造請求頭部,也就是我們要拼接出一個正確完整的請求。
下面來看一個典型的例子
POST /api/feed/ HTTP/1.1 Accept-Encoding: gzip Content-Length: 225873 Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Host: www.myhost.com Connection: Keep-Alive --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Content-Disposition: form-data; name="param1" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 888 --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Content-Disposition: form-data; name="param2" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "nihao" --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg" Content-Type: application/octet-stream Content-Transfer-Encoding: binary 這裡是圖片的二進位制資料 --OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--
上面的例子中,我們首先看
POST /api/feed/ HTTP/1.1
Accept-Encoding: gzip
Content-Length: 225873
Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Host: www.myhost.com
Connection: Keep-Alive
第一行:為POST方式,要請求的子路徑為/api/feed/,例如我們的伺服器地址為www.myhost.com,然後我們的這個請求的完整路徑就是www.myhost.com/api/feed/,最後說明了HTTP協議的版本號為1.1
第二行:資料壓縮方式
第三行:資料長度
第四行:multipart/form-data;是指上傳的資料型別,這裡是指檔案形式。boundary是我們必須指定的一個分界符,不同引數之間要用這個分界符隔開。而OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp就是具體的分界符,這個引數我們可以自己隨機生成的。
第五行:主機地址
第六行:持久連線,Keep-Alive功能避免了建立或者重新建立連線
第七行:換行,這個換行是必須的,我們使用\r\n來進行換行
然後就是引數內容部分了,先來看
我們把上面的看成一個整體--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp Content-Disposition: form-data; name="param1" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 888
第一行:我要先用分隔符來宣告一個引數的開始。注意,分隔符前面還加了兩橫“--”,這個也是必須加上的!
第二行:name="param1",其實param1就是傳遞的引數的鍵值,例如在get方式中,我們這樣寫http://www.baidu.com?param1=888
第三行:同樣是內容格式,不過這次是指定傳文字,所以是text/plain; 另外,指定了編碼方式charset=UTF-8
第四行:描述的是訊息請求(request)和響應(response)所附帶的實體物件(entity)的傳輸形式,簡單文字資料我們設定為8bit,檔案引數我們設定為binary就行
第五行:換行,這個是必須的!
第六行:引數值,例如http://www.baidu.com?param1=888,就是888
OK,我們看下一個引數,也是同理
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="param2"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
"nihao"
然後下一個引數,就是檔案了
雖然指定的內容不一樣,但是格式是一樣的
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
這裡是圖片的二進位制資料
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--
OK,大家仔細看上面的格式,不能出一點差錯,因為格式不對,就上傳不了了。
接下來,我們直接看我寫的一個帶引數檔案上傳工具類
/**
* Created by kaiyi.cky on 2015/8/16.
*/
public class FileUploader {
private static final String TAG = "uploadFile";
private static final int TIME_OUT = 10*10000000; //超時時間
private static final String CHARSET = "utf-8"; //設定編碼
private static final String PREFIX = "--";
private static final String LINE_END = "\r\n";
public static void upload(String host,File file,Map<String,String> params,FileUploadListener listener){
String BOUNDARY = UUID.randomUUID().toString(); //邊界標識 隨機生成 String PREFIX = "--" , LINE_END = "\r\n";
String CONTENT_TYPE = "multipart/form-data"; //內容型別
try {
URL url = new URL(host);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(TIME_OUT);
conn.setConnectTimeout(TIME_OUT);
conn.setRequestMethod("POST"); //請求方式
conn.setRequestProperty("Charset", CHARSET);//設定編碼
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);
conn.setDoInput(true); //允許輸入流
conn.setDoOutput(true); //允許輸出流
conn.setUseCaches(false); //不允許使用快取
if(file!=null) {
/** * 當檔案不為空,把檔案包裝並且上傳 */
OutputStream outputSteam=conn.getOutputStream();
DataOutputStream dos = new DataOutputStream(outputSteam);
StringBuffer sb = new StringBuffer();
sb.append(LINE_END);
if(params!=null){//根據格式,開始拼接文字引數
for(Map.Entry<String,String> entry:params.entrySet()){
sb.append(PREFIX).append(BOUNDARY).append(LINE_END);//分界符
sb.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINE_END);
sb.append("Content-Type: text/plain; charset=" + CHARSET + LINE_END);
sb.append("Content-Transfer-Encoding: 8bit" + LINE_END);
sb.append(LINE_END);
sb.append(entry.getValue());
sb.append(LINE_END);//換行!
}
}
sb.append(PREFIX);//開始拼接檔案引數
sb.append(BOUNDARY); sb.append(LINE_END);
/**
* 這裡重點注意:
* name裡面的值為伺服器端需要key 只有這個key 才可以得到對應的檔案
* filename是檔案的名字,包含字尾名的 比如:abc.png
*/
sb.append("Content-Disposition: form-data; name=\"img\"; filename=\""+file.getName()+"\""+LINE_END);
sb.append("Content-Type: application/octet-stream; charset="+CHARSET+LINE_END);
sb.append(LINE_END);
//寫入檔案資料
dos.write(sb.toString().getBytes());
InputStream is = new FileInputStream(file);
byte[] bytes = new byte[1024];
long totalbytes = file.length();
long curbytes = 0;
Log.i("cky","total="+totalbytes);
int len = 0;
while((len=is.read(bytes))!=-1){
curbytes += len;
dos.write(bytes, 0, len);
listener.onProgress(curbytes,1.0d*curbytes/totalbytes);
}
is.close();
dos.write(LINE_END.getBytes());\\一定還有換行
byte[] end_data = (PREFIX+BOUNDARY+PREFIX+LINE_END).getBytes();
dos.write(end_data);
dos.flush();
/**
* 獲取響應碼 200=成功
* 當響應成功,獲取響應的流
*/
int code = conn.getResponseCode();
sb.setLength(0);
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while((line=br.readLine())!=null){
sb.append(line);
}
listener.onFinish(code,sb.toString(),conn.getHeaderFields());
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public interface FileUploadListener{
public void onProgress(long pro,double precent);
public void onFinish(int code,String res,Map<String,List<String>> headers);
}
}
使用方式是這樣的:
public class MainActivity extends FragmentActivity {
File sdDir;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED); //判斷sd卡是否存在
if(sdCardExist) {
sdDir = Environment.getExternalStorageDirectory();//獲取跟目錄
}
final HashMap<String,String> map = new HashMap<String,String>();
map.put("aa","bb");
new Thread(){
@Override
public void run() {
FileUploader.upload("上傳地址", new File(sdDir.getPath() + "/檔名"), map, new FileUploader.FileUploadListener() {
@Override
public void onProgress(long pro, double precent) {
Log.i("cky", precent+"");
}
@Override
public void onFinish(int code, String res, Map<String, List<String>> headers) {
Log.i("cky", res);
}
});
}
}.start();
}
}