怎么在android中上傳文件,即怎么用Java向服務器上傳文件、上傳圖片,這是個老問題了,在網上能搜到現成的代碼,很多朋友用起來也比較熟了,但是為什么這么寫,可能很多朋友並不清楚,這篇文章就來分析一下Web中文件上傳的請求包內容。如有問題請聯系zhfch.class@gmail.com。
當然說,這篇文章被我冠了一個“Android”的前綴,因為我們在平常的非移動開發中,很少需要用最原始的方法來構造請求內容,但實際上,這種上傳文件的方法和android是沒有必然關系的,做一個C/S模式的客戶端文件上傳工具,也可以用這樣的方法。這篇文章分為Web應用中文件上傳的請求結構和用Java(在android中)上傳文件/圖片的方法這兩部分。如果只想看怎么上傳文件,推薦直接看第三部分,用開源項目提供的類庫上傳。
首先寫了簡單的Web工程,index.html中寫一個表單,主要的兩個表單項,一個是文本框,輸入一個用戶名,另一個是一個文件上傳的控件,表單提交的url隨便寫,運行這個工程並打開這個頁面:
<form action="index.jsp" method="post"> <input type="text" name="username" /> <input type="file" name="mfile" /> <input type="submit" /> </form>
另外我寫了一個簡單的Web代理服務器,監聽8033端口,把請求數據攔截下來並全部打印出來(這是為了看請求包的完整結構,圖省事的話可以參考瀏覽器開發人員工具中的數據包分析),然后修改瀏覽器中代理服務器的設置,改成localhost的8033端口,此時在文本框中輸入username為tjutester,然后選擇一個桌面上的本地文件test.txt,里面只有一行字:“這里是測試文字”,點擊提交,會看到代理服務器打印出的POST請求內容:
POST http://localhost:8080/ServerDemo/index.jsp HTTP/1.1 Host: localhost:8080
......(一堆其他屬性,這里略過,下文還會貼上) Cookie: JSESSIONID=4FA349F284EEE82AD5E15D571A0E9869 username=tjutester&mfile=test.txt
這是這一次POST請求的內容,發現並沒有文件的內容,當然在服務器上也是拿不到的了,因為form表單沒有設置enctype屬性值,瀏覽器將使用其默認值"application/x-www-form-urlencoded",根據結果可以看到,對於這種編碼方式,所有字段按URL參數的形式排成一行,在服務端可以直接用getParameter方法來處理,這個時候mfile域跟username域在性質上沒有什么區別,都是簡單的文本,現在在<form>標簽內添加enctype屬性:
<form action="getnews" method="post" enctype="multipart/form-data">
刷新網頁后重新發送請求,可以得到下面的頭部信息:
POST http://localhost:8080/ServerDemo/index.jsp HTTP/1.1
Host: localhost:8080
Proxy-Connection: keep-alive
Content-Length: 311
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary22uii9i7wXAwzBvK
Referer: http://localhost:8080/ServerDemo/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
Cookie: JSESSIONID=4FA349F284EEE82AD5E15D571A0E9869
------WebKitFormBoundary22uii9i7wXAwzBvK
Content-Disposition: form-data; name="username"
tjutester
------WebKitFormBoundary22uii9i7wXAwzBvK
Content-Disposition: form-data; name="mfile"; filename="test.txt"
Content-Type: text/plain
這里是測試文字
------WebKitFormBoundary22uii9i7wXAwzBvK--
抽取其精華,也就是如下的結構:
Content-Type: multipart/form-data; boundary=----分隔符字符串
------分隔符字符串
Content-Disposition: form-data; name="字段名"
屬性值
------分隔符字符串
Content-Disposition: form-data; name="字段名"; filename="本地的文件名"
Content-Type: 文件類型
文件內容(在網絡上傳輸就是二進制流了)
------分隔符字符串--
服務器會在這樣的請求中拿到文件內容,那么我們就在Java程序中人工構造這樣的請求內容交給服務器,就完成了文件或圖片的上傳。那么,這個分隔符字符串和“-”有什么講究呢,分隔符字符串是沒什么講究的,前后保持一致就可以了,Chrome生成的請求,都是WebKitFormBoundaryxxxxxxx這樣的形式,用IE的話就是一個普通的數字字母串,如7dd2029110746,每個上傳的參數前面有一個“--boundary”,最后是一個“--boundary--”表示終止。
這一部分的代碼具體含義我就不多做說明了,就是在拼上面的請求串,核心類的內容:
package org.fletcher.android.net; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; public class HttpRequester { private static final String BOUNDARY = "-------45962402127348"; private static final String FILE_ENCTYPE = "multipart/form-data"; public static InputStream post(String urlStr, Map<String, String> params, Map<String, File> images) { InputStream is = null; try { URL url = new URL(urlStr); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5000); con.setDoInput(true); con.setDoOutput(true); con.setUseCaches(false); con.setRequestMethod("POST"); con.setRequestProperty("Connection", "Keep-Alive"); con.setRequestProperty("Charset", "UTF-8"); con.setRequestProperty("Content-Type", FILE_ENCTYPE + "; boundary=" + BOUNDARY); StringBuilder sb = null; DataOutputStream dos = new DataOutputStream(con.getOutputStream());; if (params != null) { sb = new StringBuilder(); for (String s : params.keySet()) { sb.append("--"); sb.append(BOUNDARY); sb.append("\r\n"); sb.append("Content-Disposition: form-data; name=\""); sb.append(s); sb.append("\"\r\n\r\n"); sb.append(params.get(s)); sb.append("\r\n"); } dos.write(sb.toString().getBytes()); } if (images != null) { for (String s : images.keySet()) { File f = images.get(s); sb = new StringBuilder(); sb.append("--"); sb.append(BOUNDARY); sb.append("\r\n"); sb.append("Content-Disposition: form-data; name=\""); sb.append(s); sb.append("\"; filename=\""); sb.append(f.getName()); sb.append("\"\r\n"); sb.append("Content-Type: image/jpeg"); //這里注意!如果上傳的不是圖片,要在這里改文件格式,比如txt文件,這里應該是text/plain sb.append("\r\n\r\n"); dos.write(sb.toString().getBytes()); FileInputStream fis = new FileInputStream(f); byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) != -1) { dos.write(buffer, 0, len); } dos.write("\r\n".getBytes()); fis.close(); } sb = new StringBuilder(); sb.append("--"); sb.append(BOUNDARY); sb.append("--\r\n"); dos.write(sb.toString().getBytes()); } dos.flush(); if (con.getResponseCode() == 200) is = con.getInputStream(); dos.close(); // con.disconnect(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return is; } public static byte[] read(InputStream inStream) throws Exception{ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while( (len = inStream.read(buffer)) != -1){ outputStream.write(buffer, 0, len); } inStream.close(); return outputStream.toByteArray(); } }
在Android界面當中,點擊按鈕選擇圖片,然后調用這個類上傳,在Android中怎么選擇圖片,直接copy了這位仁兄的代碼(http://www.oschina.net/question/157182_53236):
private static final int RESULT_LOAD_IMAGE = 0; TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button b = (Button) findViewById(R.id.button1); tv = (TextView) findViewById(R.id.tv1); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i = new Intent( Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(i, RESULT_LOAD_IMAGE); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) { Uri selectedImage = data.getData(); String[] filePathColumn = { MediaStore.Images.Media.DATA }; Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); final String picturePath = cursor.getString(columnIndex); cursor.close(); new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { // TODO Auto-generated method stub Map<String, File> maps = new HashMap<String, File>(); maps.put("image", new File(picturePath)); InputStream is = HttpRequester.post("http://192.168.199.2/Test/Upload/upload", null, maps); try { return new String(HttpRequester.read(is)); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return ""; } @Override protected void onPostExecute(String result) { // TODO Auto-generated method stub super.onPostExecute(result); tv.setText(result); } }.execute((Void)null); } }
實際開發當中,在理解原理之后,當然還是應該站在巨人的肩膀上寫程序了,我做URL請求,都是用的android-async-http這個開源庫:
http://loopj.com/android-async-http/
用起來很簡單,網站上也有一些建議的用法和API,上傳文件就簡單地用
RequestParams params = new RequestParams(); params.put("image", new File(path));
來指定參數就可以了。
