在Android的客戶端編程中(特別是SNS 類型的客戶端),經(jīng)常需要實(shí)現(xiàn)注冊(cè)功能Activity,要用戶輸入用戶名,密碼,郵箱,照片后注冊(cè)。但這時(shí)就有一個(gè)問(wèn)題,在HTML中用form表單就 能實(shí)現(xiàn)如上的注冊(cè)表單,需要的信息會(huì)自動(dòng)封裝為完整的HTTP協(xié)議,但在Android中如何把這些參數(shù)和需要上傳的文件封裝為HTTP協(xié)議呢?
我們可以先做個(gè)試驗(yàn),看一下form表單到底封裝了什么樣的信息。
第一步:編寫一個(gè)Servlet,把接收到的HTTP信息保存在一個(gè)文件中,代碼如下:
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- //獲取輸入流,是HTTP協(xié)議中的實(shí)體內(nèi)容
- ServletInputStream sis=request.getInputStream();
- //緩沖區(qū)
- byte buffer[]=new byte[1024];
- FileOutputStream fos=new FileOutputStream("d:\\file.log");
- int len=sis.read(buffer, 0, 1024);
- //把流里的信息循環(huán)讀入到file.log文件中
- while( len!=-1 )
- {
- fos.write(buffer, 0, len);
- len=sis.readLine(buffer, 0, 1024);
- }
- fos.close();
- sis.close();
- }
第二步:實(shí)現(xiàn)如下一個(gè)表單頁(yè)面, 詳細(xì)的代碼如下:
- <form action="servlet/ReceiveFile" method="post" enctype="multipart/form-data">
- 第一個(gè)參數(shù)<input type="text" name="name1"/> <br/>
- 第二個(gè)參數(shù)<input type="text" name="name2"/> <br/>
- 第一個(gè)上傳的文件<input type="file" name="file1"/> <br/>
- 第二個(gè)上傳的文件<input type="file" name="file2"/> <br/>
- <input type="submit" value="提交">
- </form>
注意了,由于要上傳附件,所以一定要設(shè)置enctype為multipart/form-data,才可以實(shí)現(xiàn)附件的上傳。
第三步:填寫完信息后按“提交”按鈕后,在D盤下查找file.log文件用記事本打開(kāi),數(shù)據(jù)如下:
—————————–7d92221b604bc
Content-Disposition: form-data; name=”name1″
hello
—————————–7d92221b604bc
Content-Disposition: form-data; name=”name2″
world
—————————–7d92221b604bc
Content-Disposition: form-data; name=”file1″; filename=”C:\2.GIF”
Content-Type: image/gif
GIF89a
€ € €€ €€ € €€€€€覽? 3 f 3 33 3f 3 3 3 f f3 ff f f f ? 檉 櫃 櫶 ? ? 蘤 虣 燙 ? 3 f 3 3 33 f3 ? ? 33 33333f33?3?33f 3f33ff3f?f?f3 3?3檉3櫃3櫶3?3 3?3蘤3虣3燙3?3 333f3??f f 3f ff 檉 蘤 f3 f33f3ff3檉3蘤3ff ff3fffff檉f蘤ff f?f檉f櫃f櫶f?f f?f蘤f虣f燙f?f f3fff檉蘤 3 f 櫃 虣 ? ?3?f?櫃3虣3檉 檉3檉f檉櫃f虣f櫃 櫃3櫃f櫃櫃櫶櫃櫶 櫶3櫶f櫶櫃燙櫶? ?3?f?櫃虣 3 f 櫶 燙 ? ?3?f?櫶3燙3蘤 蘤3蘤f蘤櫶f燙f虣 虣3虣f虣櫶櫶虣燙 燙3燙f燙櫶燙燙? ?3?f?櫶燙 3 f ? ? 3 333f3?3?3f f3fff?f?f ?檉櫃櫶??蘤虣燙? 3f??!? ,
e ??羵Q鸚M!C囑lH馉脝遠(yuǎn)5荑p釩?3R?R愣?MV39V5?談re琷?試 3??qn?薵Q燚c?獖i鄲EW艗赥戟j ;
—————————–7d92221b604bc
Content-Disposition: form-data; name=”file2″; filename=”C:\2.txt”
Content-Type: text/plain
hello everyone!!!
—————————–7d92221b604bc–
從表單源碼可知,表單上傳的數(shù)據(jù)有4個(gè):參數(shù)name1和name2,文件file1和file2
首先從file.log觀察兩個(gè)參數(shù)name1和name2的情況。這時(shí)候使用UltraEdit打開(kāi)file.log(因?yàn)橛行┳址谟浭卤纠镲@示不出來(lái),所以要用16進(jìn)制編輯器)
結(jié)合16進(jìn)制數(shù)據(jù)和記事本顯示的數(shù)據(jù)可知上傳參數(shù)部分的格式規(guī)律:
1. 第一行是“—————————–7d92221b604bc”作為分隔符,然后是“\r\n”(即16進(jìn)制編輯器顯示的0D 0A)回車換行符。
2. 第二行
(1) 首先是HTTP中的擴(kuò)展頭部分“Content-Disposition: form-data;”,表示上傳的是表單數(shù)據(jù)。
(2) “name=”name1″”參數(shù)的名稱。
(3) “\r\n”(即16進(jìn)制編輯器顯示的0D 0A)回車換行符。
3. 第三行:“\r\n”(即16進(jìn)制編輯器顯示的0D 0A)回車換行符。
4. 第四行:參數(shù)的值,最后是“\r\n”(即16進(jìn)制編輯器顯示的0D 0A)回車換行符。
由觀察可得,表單上傳的每個(gè)參數(shù)都是按照以上1—4的格式構(gòu)造HTTP協(xié)議中的參數(shù)部分。
結(jié)合16進(jìn)制數(shù)據(jù)和記事本顯示的數(shù)據(jù)可知上傳文件部分的格式規(guī)律:
1. 第一行是“—————————–7d92221b604bc”作為分隔符,然后是“\r\n”(即16進(jìn)制編輯器顯示的0D 0A)回車換行符。
2. 第二行:
a) 首先是HTTP中的擴(kuò)展頭部分“Content-Disposition: form-data;”,表示上傳的是表單數(shù)據(jù)。
b) “name=”file2″;”參數(shù)的名稱。
c) “filename=”C:\2.txt””參數(shù)的值。
d) “\r\n”(即16進(jìn)制編輯器顯示的0D 0A)回車換行符。
3. 第三行:HTTP中的實(shí)體頭部分“Content-Type: text/plain”:表示所接收到得實(shí)體內(nèi)容的文件格式。計(jì)算機(jī)的應(yīng)用中有多種多種通用的文件格式,人們?yōu)槊糠N通用格式都定義了一個(gè)名稱,稱為 MIME,MIME的英文全稱是”Multipurpose Internet Mail Extensions” (多功能Internet 郵件擴(kuò)充服務(wù))
4. 第四行:“\r\n”(即16進(jìn)制編輯器顯示的0D 0A)回車換行符。
5. 第五行開(kāi)始:上傳的內(nèi)容的二進(jìn)制數(shù)。
6. 最后是結(jié)束標(biāo)志“—————————–7d92221b604bc–”,注意:這個(gè)結(jié)束標(biāo)志和分隔符的區(qū)別是最后多了“–”部分。
但現(xiàn)在還有一個(gè)問(wèn)題,就是分隔符“—————————–7d92221b604bc”是怎么確定的呢?是不是一定要“7d92221b604bc”這串?dāng)?shù)字?
我們以前的分析只是觀察了HTTP請(qǐng)求的實(shí)體部分,可以借用工具觀察完整的HTTP請(qǐng)求看一看有沒(méi)有什么線索?
在IE下用HttpWatch,在Firefox下用Httpfox這個(gè)插件,可以實(shí)現(xiàn)網(wǎng)頁(yè)數(shù)據(jù)的抓包,從圖4可看出,原來(lái)在Content-Type部分指定了分隔符所用的字符串。
根據(jù)以上總結(jié)的注冊(cè)表單中的參數(shù)傳遞和文件上傳的規(guī)律,我們可以能寫出Android中實(shí)現(xiàn)一個(gè)用戶注冊(cè)功能(包括個(gè)人信息填寫和上傳圖片部分)的工具類,
首先,要有一個(gè)javaBean類FormFile封裝文件的信息:
- public class FormFile {
- /* 上傳文件的數(shù)據(jù) */
- private byte[] data;
- /* 文件名稱 */
- private String filname;
- /* 表單字段名稱*/
- private String formname;
- /* 內(nèi)容類型 */
- private String contentType = "application/octet-stream"; //需要查閱相關(guān)的資料
- public FormFile(String filname, byte[] data, String formname, String contentType) {
- this.data = data;
- this.filname = filname;
- this.formname = formname;
- if(contentType!=null) this.contentType = contentType;
- }
- public byte[] getData() {
- return data;
- }
- public void setData(byte[] data) {
- this.data = data;
- }
- public String getFilname() {
- return filname;
- }
- public void setFilname(String filname) {
- this.filname = filname;
- }
- public String getFormname() {
- return formname;
- }
- public void setFormname(String formname) {
- this.formname = formname;
- }
- public String getContentType() {
- return contentType;
- }
- public void setContentType(String contentType) {
- this.contentType = contentType;
- }
- }
實(shí)現(xiàn)文件上傳的代碼如下:
/**
* 直接通過(guò)HTTP協(xié)議提交數(shù)據(jù)到服務(wù)器,實(shí)現(xiàn)表單提交功能
* @param actionUrl 上傳路徑
* @param params 請(qǐng)求參數(shù) key為參數(shù)名,value為參數(shù)值
* @param file 上傳文件
*/
public static String post(String actionUrl, Map<String, String> params, FormFile[] files) {
try {
String BOUNDARY = “———7d4a6d158c9″; //數(shù)據(jù)分隔線
String MULTIPART_FORM_DATA = “multipart/form-data”;
URL url = new URL(actionUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);//允許輸入
conn.setDoOutput(true);//允許輸出
conn.setUseCaches(false);//不使用Cache
conn.setRequestMethod(”POST”);
conn.setRequestProperty(”Connection”, “Keep-Alive”);
conn.setRequestProperty(”Charset”, “UTF-8″);
conn.setRequestProperty(”Content-Type”, MULTIPART_FORM_DATA + “; boundary=” + BOUNDARY);
StringBuilder sb = new StringBuilder();
//上傳的表單參數(shù)部分,格式請(qǐng)參考文章
for (Map.Entry<String, String> entry : params.entrySet()) {//構(gòu)建表單字段內(nèi)容
sb.append(”–”);
sb.append(BOUNDARY);
sb.append(”\r\n”);
sb.append(”Content-Disposition: form-data; name=\”"+ entry.getKey() + “\”\r\n\r\n”);
sb.append(entry.getValue());
sb.append(”\r\n”);
}
DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
outStream.write(sb.toString().getBytes());//發(fā)送表單字段數(shù)據(jù)
//上傳的文件部分,格式請(qǐng)參考文章
for(FormFile file : files){
StringBuilder split = new StringBuilder();
split.append(”–”);
split.append(BOUNDARY);
split.append(”\r\n”);
split.append(”Content-Disposition: form-data;name=\”"+ file.getFormname()+”\”;filename=\”"+ file.getFilname() + “\”\r\n”);
split.append(”Content-Type: “+ file.getContentType()+”\r\n\r\n”);
outStream.write(split.toString().getBytes());
outStream.write(file.getData(), 0, file.getData().length);
outStream.write(”\r\n”.getBytes());
}
byte[] end_data = (”–” + BOUNDARY + “–\r\n”).getBytes();//數(shù)據(jù)結(jié)束標(biāo)志
outStream.write(end_data);
outStream.flush();
int cah = conn.getResponseCode();
if (cah != 200) throw new RuntimeException(”請(qǐng)求url失敗”);
InputStream is = conn.getInputStream();
int ch;
StringBuilder b = new StringBuilder();
while( (ch = is.read()) != -1 ){
b.append((char)ch);
}
outStream.close();
conn.disconnect();
return b.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}