Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中自定義MultipartEntity實現文件上傳以及使用Volley庫實現文件上傳

Android中自定義MultipartEntity實現文件上傳以及使用Volley庫實現文件上傳

編輯:關於Android編程


最近在參加CSDN博客之星,希望大家給投一票,謝謝啦~ 點這裡投我一票吧~

前言

在開發當中,我們常常需要實現文件上傳,比較常見的就是圖片上傳,比如修改個頭像什麼的。但是這個功能在Android和iOS中都沒有默認的實現類,對於Android我們可以使用Apache提供的HttpClient.jar來實現這個功能,其中依賴的類就是Apache的httpmime.jar中的MultipartEntity這個類。我就是要實現一個文件上傳功能,但是我還得下載一個jar包,而這個jar包幾十KB,這尼瑪仿佛並非人間!今天我們就來自己實現文件上傳功能,並且弄懂它們的原理。

在上一篇文章HTTP POST請求報文格式分析與Java實現文件上傳中我們介紹了HTTP POST報文格式,如果有對POST報文格式不了解的同學可以先閱讀這篇文章。


自定義實現MultipartEntity

我們知道,使用網絡協議傳輸數據無非就是要遵循某個協議,我們在開發移動應用時基本上都是使用HTTP協議。HTTP協議說白了就是基於TCP的一套網絡請求協議,你根據該協議規定的格式傳輸數據,然後服務器返回給你數據。你的協議參數要是傳遞錯了,那麼服務器只能給你返回錯誤。

這跟間諜之間對暗號有點相似,他們有一個規定的暗號,雙方見面,A說: 天王蓋地虎,B對: 寶塔鎮河妖。對上了,說事;對不上,弄死這B。HTTP也是這樣的,在HTTP請求時添加header和參數,服務器根據參數進行解析。形如 :

POST /api/feed/ HTTP/1.1
這裡是header數據

--分隔符
參數1
--分隔符
參數2
只要根據格式來向服務器發送請求就萬事大吉了!下面我們就來看MultipartEntity的實現:

public class MultipartEntity implements HttpEntity {

    private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            .toCharArray();
    /**
     * 換行符
     */
    private final String NEW_LINE_STR = "\r\n";
    private final String CONTENT_TYPE = "Content-Type: ";
    private final String CONTENT_DISPOSITION = "Content-Disposition: ";
    /**
     * 文本參數和字符集
     */
    private final String TYPE_TEXT_CHARSET = "text/plain; charset=UTF-8";

    /**
     * 字節流參數
     */
    private final String TYPE_OCTET_STREAM = "application/octet-stream";
    /**
     * 二進制參數
     */
    private final byte[] BINARY_ENCODING = "Content-Transfer-Encoding: binary\r\n\r\n".getBytes();
    /**
     * 文本參數
     */
    private final byte[] BIT_ENCODING = "Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes();

    /**
     * 分隔符
     */
    private String mBoundary = null;
    /**
     * 輸出流
     */
    ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();

    public MultipartEntity() {
        this.mBoundary = generateBoundary();
    }

    /**
     * 生成分隔符
     * 
     * @return
     */
    private final String generateBoundary() {
        final StringBuffer buf = new StringBuffer();
        final Random rand = new Random();
        for (int i = 0; i < 30; i++) {
            buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
        }
        return buf.toString();
    }

    /**
     * 參數開頭的分隔符
     * 
     * @throws IOException
     */
    private void writeFirstBoundary() throws IOException {
        mOutputStream.write(("--" + mBoundary + "\r\n").getBytes());
    }

    /**
     * 添加文本參數
     * 
     * @param key
     * @param value
     */
    public void addStringPart(final String paramName, final String value) {
        writeToOutputStream(paramName, value.getBytes(), TYPE_TEXT_CHARSET, BIT_ENCODING, "");
    }

    /**
     * 將數據寫入到輸出流中
     * 
     * @param key
     * @param rawData
     * @param type
     * @param encodingBytes
     * @param fileName
     */
    private void writeToOutputStream(String paramName, byte[] rawData, String type,
            byte[] encodingBytes,
            String fileName) {
        try {
            writeFirstBoundary();
            mOutputStream.write((CONTENT_TYPE + type + NEW_LINE_STR).getBytes());
            mOutputStream
                    .write(getContentDispositionBytes(paramName, fileName));
            mOutputStream.write(encodingBytes);
            mOutputStream.write(rawData);
            mOutputStream.write(NEW_LINE_STR.getBytes());
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 添加二進制參數, 例如Bitmap的字節流參數
     * 
     * @param key
     * @param rawData
     */
    public void addBinaryPart(String paramName, final byte[] rawData) {
        writeToOutputStream(paramName, rawData, TYPE_OCTET_STREAM, BINARY_ENCODING, "no-file");
    }

    /**
     * 添加文件參數,可以實現文件上傳功能
     * 
     * @param key
     * @param file
     */
    public void addFilePart(final String key, final File file) {
        InputStream fin = null;
        try {
            fin = new FileInputStream(file);
            writeFirstBoundary();
            final String type = CONTENT_TYPE + TYPE_OCTET_STREAM + NEW_LINE_STR;
            mOutputStream.write(getContentDispositionBytes(key, file.getName()));
            mOutputStream.write(type.getBytes());
            mOutputStream.write(BINARY_ENCODING);

            final byte[] tmp = new byte[4096];
            int len = 0;
            while ((len = fin.read(tmp)) != -1) {
                mOutputStream.write(tmp, 0, len);
            }
            mOutputStream.flush();
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            closeSilently(fin);
        }
    }

    private void closeSilently(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    private byte[] getContentDispositionBytes(String paramName, String fileName) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(CONTENT_DISPOSITION + "form-data; name=\"" + paramName + "\"");
        // 文本參數沒有filename參數,設置為空即可
        if (!TextUtils.isEmpty(fileName)) {
            stringBuilder.append("; filename=\""
                    + fileName + "\"");
        }

        return stringBuilder.append(NEW_LINE_STR).toString().getBytes();
    }

    @Override
    public long getContentLength() {
        return mOutputStream.toByteArray().length;
    }

    @Override
    public Header getContentType() {
        return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + mBoundary);
    }

    @Override
    public boolean isChunked() {
        return false;
    }

    @Override
    public boolean isRepeatable() {
        return false;
    }

    @Override
    public boolean isStreaming() {
        return false;
    }

    @Override
    public void writeTo(final OutputStream outstream) throws IOException {
        // 參數最末尾的結束符
        final String endString = "--" + mBoundary + "--\r\n";
        // 寫入結束符
        mOutputStream.write(endString.getBytes());
        //
        outstream.write(mOutputStream.toByteArray());
    }

    @Override
    public Header getContentEncoding() {
        return null;
    }

    @Override
    public void consumeContent() throws IOException,
            UnsupportedOperationException {
        if (isStreaming()) {
            throw new UnsupportedOperationException(
                    "Streaming entity does not implement #consumeContent()");
        }
    }

    @Override
    public InputStream getContent() {
        return new ByteArrayInputStream(mOutputStream.toByteArray());
    }
}

用戶可以通過addStringPart、addBinaryPart、addFilePart來添加參數,分別表示添加字符串參數、添加二進制參數、添加文件參數。在MultipartEntity中有一個ByteArrayOutputStream對象,先將這些參數寫到這個輸出流中,當執行網絡請求時,會執行
writeTo(final OutputStream outstream) 
方法將所有參數的字節流數據寫入到與服務器建立的TCP連接的輸出流中,這樣就將我們的參數傳遞給服務器了。當然在此之前,我們需要按照格式來向ByteArrayOutputStream對象中寫數據。
例如我要向服務器發送一個文本、一張bitmap圖片、一個文件,即這個請求有三個參數。代碼如下 :

        MultipartEntity multipartEntity = new MultipartEntity();
        // 文本參數
        multipartEntity.addStringPart("type", "我的文本參數");
        Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
        // 二進制參數
        multipartEntity.addBinaryPart("images", bitmapToBytes(bmp));
        // 文件參數
        multipartEntity.addFilePart("images", new File("storage/emulated/0/test.jpg"));
        
        // POST請求
        HttpPost post = new HttpPost("url") ;
        // 將multipartEntity設置給post
        post.setEntity(multipartEntity);
        // 使用http client來執行請求
        HttpClient httpClient = new DefaultHttpClient() ;
        httpClient.execute(post) ;

MultipartEntity的輸出格式會成為如下的格式 :

POST /api/feed/ HTTP/1.1
Content-Type: multipart/form-data; boundary=o3Fhj53z-oKToduAElfBaNU4pZhp4-
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.4; M040 Build/KTU84P)
Host: www.myhost.com
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 168518

--o3Fhj53z-oKToduAElfBaNU4pZhp4-
Content-Type: text/plain; charset=UTF-8
Content-Disposition: form-data; name="type"
Content-Transfer-Encoding: 8bit

This my type
--o3Fhj53z-oKToduAElfBaNU4pZhp4-
Content-Type: application/octet-stream
Content-Disposition: form-data; name="images"; filename="no-file"
Content-Transfer-Encoding: binary

這裡是bitmap的二進制數據
--o3Fhj53z-oKToduAElfBaNU4pZhp4-
Content-Type: application/octet-stream
Content-Disposition: form-data; name="file"; filename="storage/emulated/0/test.jpg"
Content-Transfer-Encoding: binary

這裡是圖片文件的二進制數據
--o3Fhj53z-oKToduAElfBaNU4pZhp4---

看到很熟悉吧,這就是我們在文章開頭時提到的POST報文格式。沒錯!HttpEntity就是負責將參數構造成HTTP的報文格式,文本參數該是什麼格式、文件該是什麼格式,什麼類型,這些格式都是固定的。構造完之後,在執行請求時會將http請求的輸出流通過writeTo(OutputStream) 函數傳遞進來,然後將這些參數數據全部輸出到http輸出流中即可。
明白了這些道理,看看代碼也就應該明白了吧。


Volley中實現文件上傳

Volley是Google官方推出的網絡請求庫,這個庫很精簡、優秀,但是他們也沒有默認添加文件上傳功能的支持。我們今天就來自定義一個Request實現文件上傳功能,還是需要借助上面的MultipartEntity類,下面看代碼:
/**
 * MultipartRequest,返回的結果是String格式的
 * @author mrsimple
 */
public class MultipartRequest extends Request {

    MultipartEntity mMultiPartEntity = new MultipartEntity();

    public MultipartRequest(HttpMethod method, String url,
            Map params, RequestListener listener) {
        super(method, url, params, listener);
    }

    /**
     * @return
     */
    public MultipartEntity getMultiPartEntity() {
        return mMultiPartEntity;
    }

    @Override
    public String getBodyContentType() {
        return mMultiPartEntity.getContentType().getValue();
    }

    @Override
    public byte[] getBody() {

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try
        {
            // 將mMultiPartEntity中的參數寫入到bos中
            mMultiPartEntity.writeTo(bos);
        } catch (IOException e) {
            Log.e("", "IOException writing to ByteArrayOutputStream");
        }
        return bos.toByteArray();
    }

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

}

使用示例代碼:
        

        MultipartRequest multipartRequest = new MultipartRequest(HttpMethod.POST,
                "http://服務器地址",
                null, new RequestListener() {

                    @Override
                    public void onStart() {
                        // TODO Auto-generated method stub

                    }

                    @Override
                    public void onComplete(int stCode, String response, String errMsg) {

                    }
                });
        // 獲取MultipartEntity對象
        MultipartEntity multipartEntity = multipartRequest.getMultiPartEntity();
        multipartEntity.addStringPart("content", "hello");
        //
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
        // bitmap參數
        multipartEntity.addBinaryPart("images", bitmapToBytes(bitmap));
        // 文件參數
        multipartEntity.addFilePart("images", new File("storage/emulated/0/test.jpg"));

        // 構建請求隊列
        RequestQueue queue = RequestQueue.newRequestQueue(Context);
        // 將請求添加到隊列中
        queue.addRequest(multipartRequest);

效果圖

這是我post到我的應用的截圖 :

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved