Android中自訂MultipartEntity實現檔案上傳以及使用Volley庫實現檔案上傳

來源:互聯網
上載者:User

標籤:android   http   檔案上傳   multipartentity   post   


最近在參加CSDN部落格之星,希望大家給投一票,謝謝啦~                       點這裡投我一票吧~前言

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

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


自訂實現MultipartEntity

我們知道,使用網路通訊協定傳輸資料無非就是要遵循某個協議,我們在開發行動裝置 App時基本上都是使用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.1Content-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.comConnection: Keep-AliveAccept-Encoding: gzipContent-Length: 168518--o3Fhj53z-oKToduAElfBaNU4pZhp4-Content-Type: text/plain; charset=UTF-8Content-Disposition: form-data; name="type"Content-Transfer-Encoding: 8bitThis my type--o3Fhj53z-oKToduAElfBaNU4pZhp4-Content-Type: application/octet-streamContent-Disposition: form-data; name="images"; filename="no-file"Content-Transfer-Encoding: binary這裡是bitmap的位元據--o3Fhj53z-oKToduAElfBaNU4pZhp4-Content-Type: application/octet-streamContent-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<String> {    MultipartEntity mMultiPartEntity = new MultipartEntity();    public MultipartRequest(HttpMethod method, String url,            Map<String, String> params, RequestListener<String> 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<String> 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<String>() {                    @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到我的應用程式的 :                                                                       

Android中自訂MultipartEntity實現檔案上傳以及使用Volley庫實現檔案上傳

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.