【Android】圖片(文件)上傳的請求分析結構


  怎么在android中上傳文件,即怎么用Java向服務器上傳文件、上傳圖片,這是個老問題了,在網上能搜到現成的代碼,很多朋友用起來也比較熟了,但是為什么這么寫,可能很多朋友並不清楚,這篇文章就來分析一下Web中文件上傳的請求包內容。如有問題請聯系zhfch.class@gmail.com。

  當然說,這篇文章被我冠了一個“Android”的前綴,因為我們在平常的非移動開發中,很少需要用最原始的方法來構造請求內容,但實際上,這種上傳文件的方法和android是沒有必然關系的,做一個C/S模式的客戶端文件上傳工具,也可以用這樣的方法。這篇文章分為Web應用中文件上傳的請求結構和用Java(在android中)上傳文件/圖片的方法這兩部分。如果只想看怎么上傳文件,推薦直接看第三部分,用開源項目提供的類庫上傳。

1. Web中文件上傳的請求包結構分析

2. Java(Android)中的文件/圖片上傳代碼

3. 開源庫推薦

 

1. Web中文件上傳的請求包結構分析  

  首先寫了簡單的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--”表示終止。

 

2. Java(Android)中的文件/圖片上傳代碼

   這一部分的代碼具體含義我就不多做說明了,就是在拼上面的請求串,核心類的內容:

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);
    }
}

 

3. 推薦開源項目:android-async-http

  實際開發當中,在理解原理之后,當然還是應該站在巨人的肩膀上寫程序了,我做URL請求,都是用的android-async-http這個開源庫:

  http://loopj.com/android-async-http/

  用起來很簡單,網站上也有一些建議的用法和API,上傳文件就簡單地用

RequestParams params = new RequestParams();
params.put("image", new File(path));

  來指定參數就可以了。

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM