Android文件上傳

上傳的方式

本文將介紹2中文件上傳的方式:
1.multipart/from-data方式上傳。
2.binary方式上傳。

multipart上傳方式

html代碼

這中上傳方式是我們最常用的上傳方式。比如我們使用網頁上傳文件,其中html代碼大致為這樣:

<form method="post" enctype="multipart/form-data" action="/upload/single">
    文件:<input name="file" type="file"> <br/>
    <input name="submit" type="submit" value="提交">
</form>

其中 enctype設置為multipart/form-data方式。如果是多文件的話,html代碼應該是這樣:

<form method="post" enctype="multipart/form-data" action="/upload/multi_file">
    <input name="file" type="file"><br>
    <input name="file" type="file"><br/>
    ...
    <input name="submit" type="submit" value="提交">
</form>

input標簽中的name就是對應的字段,后臺程序將根據這字段取出文件。

具體的報文

這種方式對應的HTTP報文如下:

POST /upload/binary HTTP/1.1
Host: 192.168.10.63:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache


------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename=""
Content-Type: image/jpeg
二進制文件信息

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename=""
Content-Type: 
二進制文件信息

------WebKitFormBoundary7MA4YWxkTrZu0gW--

通過上面的報文我們看以看出:multipart/form-data方式的一個重要組成部分請求頭Content-Type必須為:
Content-Type:multipart/form-data;boundarty=一個32字節的隨機數,用來分割每個part
然后每個Part之間用“雙橫杠”加bundary來分割,最后一個Part分割符末尾也要加“雙橫杠”

每個Part中必須包含Content-Disposition字段來注明字段文件名等信息,也可以包含Content-Type來說明文件的MeidaType。

Java服務端接受代碼

/**
 * 單個文件上傳。
 * <p>
 * 字段名為 file。
 *
 * @param file 文件
 * @return json
 */
@RequestMapping(method = RequestMethod.POST, value = "/single")
public UpLoadResponse singleReceive(@RequestParam("file") MultipartFile file) {
    List<PartInfo> partInfos = new ArrayList<PartInfo>();
    PartInfo partInfo = new PartInfo();
    partInfo.setMediaType(file.getContentType());
    partInfo.setSize(file.getSize());
    partInfos.add(partInfo);

    FileUtil.saveFile(file);

    System.out.println("接受到文件====" + file.getOriginalFilename());
    UpLoadResponse upLoadResponse = new UpLoadResponse();
    upLoadResponse.setPartInfos(partInfos);
    upLoadResponse.setMsg("上傳成功");
    return upLoadResponse;
}

/**
 * 多文件上傳,公用一個字段 "file"
 *
 * @param files 文件
 * @return json
 */
@RequestMapping(method = RequestMethod.POST, value = "/multi_file")
public UpLoadResponse multiFileReceive(@RequestParam("file") MultipartFile[] files) {
    List<PartInfo> partInfos = new ArrayList<PartInfo>();

    for (MultipartFile file : files) {
        PartInfo partInfo = new PartInfo();
        partInfo.setSize(file.getSize());
        partInfo.setMediaType(file.getContentType());
        partInfos.add(partInfo);

        FileUtil.saveFile(file);

        System.out.println("接受到文件====" + file.getOriginalFilename());
    }
    UpLoadResponse upLoadResponse = new UpLoadResponse();
    upLoadResponse.setPartInfos(partInfos);
    upLoadResponse.setMsg("上傳成功");
    return upLoadResponse;
}

/**
 * 文件+文本一起上傳,其實和多文件上傳一樣的。
 * <p>
 * 字段名為 file、text
 *
 * @param file 文件
 * @param text 文本
 * @return json
 */
@RequestMapping(method = RequestMethod.POST, value = "/multi")
public UpLoadResponse multiReceive(@RequestParam("file") MultipartFile file,
                                   @RequestParam("text") String text) {


    List<PartInfo> partInfos = new ArrayList<PartInfo>();

    PartInfo partInfo = new PartInfo();
    partInfo.setMediaType(file.getContentType());
    partInfos.add(partInfo);
    partInfo.setSize(file.getSize());
    // 保存文件
    FileUtil.saveFile(file);

    System.out.println("接受到文件====" + file.getOriginalFilename());

    PartInfo partInfo1 = new PartInfo();
    partInfo.setText(text);
    partInfo.setSize(text.length());
    partInfos.add(partInfo1);

    System.out.println("接受到文本====" + text);

    UpLoadResponse upLoadResponse = new UpLoadResponse();
    upLoadResponse.setPartInfos(partInfos);
    upLoadResponse.setMsg("提交成功");

    return upLoadResponse;
}

上面的代碼演示了,Java服務端通過@RequestParam("file") MultipartFile file就可以獲得文件信息了,如果客戶端傳的Part類型為String,也可以直接用String類型獲取文本信息。

客戶端使用Retrofit上傳

客戶端這邊使用Retrofit上傳文件可以有2中方式,一種是使用Multipart.Part上傳,另一種直接使用RequestBody上傳。需要注意的是如果使用RequestBody上傳的時候,我們需要在@Part注解中將 字段名和文件名(filename)拼接出來,如果不拼filename的話,服務端則會報錯
如果是上傳文本信息的話,可以不用拼接“filename" 也可以不用Multipart.PartRequestBody直接使用String類型就行。

例如:

package com.blueberry.multipart.api;

import com.blueberry.multipart.entity.UpLoadResponse;

import java.util.HashMap;
import java.util.List;

import io.reactivex.Observable;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;
import retrofit2.http.PartMap;

/**
 * Created by blueberry on 7/6/2017.
 * <p>
 * 如果使用Multipart.Part需要注意 @Part直接中不要有參數。
 * 如果使用RequestBody需要注意:如果上傳文件name應該包含有filename(文件名)這個字段,負責后臺可能會出錯,
 * 比如我們要上傳一個文件我們的name值應該為:file";filename="image.jpg;注意前后沒有雙引號,中間有2個雙引號,
 * 這是因為Retrofit會自動幫我們拼接Content-Disposition;它拼接的方式為 form-data; name="我們設置的值",如
 * 果用MultipartBody.Part我們則不需要這么費事的拼接,因為Multipart.Part.createFormData()放我們完成了該操
 * 作;
 * {@link okhttp3.MultipartBody.Part#createFormData(String, String, RequestBody)}
 */

public interface MultipartApi {

    /**
     * 單個文件上傳
     */
    String SINGLE = "upload/single";

    /**
     * 多個文件上傳(使用同一個字段名)
     */
    String MULTI_FILE = "upload/multi_file";

    /**
     * 文件+文本一起上傳
     */
    String MULTI = "upload/multi";


    /**
     * 單個文件上傳,使用MultipartBody.Part。
     *
     * @param part
     * @return
     */
    @Multipart
    @POST(SINGLE)
    Observable<UpLoadResponse> singlePart(@Part MultipartBody.Part part);

    /**
     * 單個文件上傳,使用RequestBody。
     *
     * @param body
     * @return
     */
    @Multipart
    @POST(SINGLE)
    Observable<UpLoadResponse> singleRequestBody(@Part("file\";filename=\"image.jpg") RequestBody body);


    /**
     * 多文件上傳使用 List<MultipartBody.Part>。
     *
     * @param parts
     * @return
     */
    @Multipart
    @POST(MULTI_FILE)
    Observable<UpLoadResponse> multiFilePart(@Part List<MultipartBody.Part> parts);

    /**
     * 多文件上傳使用 HashMap<String, RequestBody> map。
     *
     * @param map
     * @return
     */
    @Multipart
    @POST(MULTI_FILE)
    Observable<UpLoadResponse> multiFileRequestBody(@PartMap HashMap<String, RequestBody> map);


    /**
     * 文件+文本上傳。文件使用 RequestBody
     *
     * @param body
     * @param string
     * @return
     */
    @Multipart
    @POST(MULTI)
    Observable<UpLoadResponse> multiRequestBody(@Part("file\";filename=\"image.jpg") RequestBody body,
                                                @Part("text") String string);

    /**
     * 文件+文本上傳。文件使用 MultipartBody.Part上傳
     *
     * @param part
     * @param string
     * @return
     */
    @Multipart
    @POST(MULTI)
    Observable<UpLoadResponse> multiPart(@Part MultipartBody.Part part,
                                         @Part("text") String string);

}

上面演示了好幾種方式提交文件,包含:
1.單個文件上傳,使用RequestBody,和使用MultiBody.Part方式
2.多個文件上傳(part都使用同一個字段),RequestBody和MulipartBody.Part的寫法。
3.文件+文本上傳,RequestBody和MulipartBody.Part方式的寫法。

使用MulipartBody.Part具體的實現:


private MultipartApi mService;

private MultipartRepositoryPartImpl() {
    mService = RetrofitHelper
            .getInstance()
            .getRetrofit()
            .create(MultipartApi.class);
}

@Override
public void singleFileUpload(File file, Observer<UpLoadResponse> observer) {
    mService.singlePart(MultipartBody.Part
            .createFormData("file", "temp.jpg",
                    RequestBody.create(MediaType.parse("image/jpg"), file)))
            .compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

使用RequestBody的具體實現:

@Override
public void singleFileUpload(File file, Observer<UpLoadResponse> observer) {
    mService.singleRequestBody(RequestBody.create(MediaType.parse("image/jpg"), file))
            .compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

多文件上傳,RequestBodyt方式的實現:

@Override
public void multiFileUpload(final File[] files, Observer<UpLoadResponse> observer) {
    mService.multiFileRequestBody(new HashMap<String, RequestBody>() {
        {
            for (File file : files) {

                put("file\";filename=\"" + file.getName(),
                        RequestBody.create(MediaType.parse("image/jpg"), file));
            }
        }
    }).compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

多文件上傳,MultipartBody.Part方式上傳

@Override
public void multiFileUpload(final File[] files, Observer<UpLoadResponse> observer) {
    mService.multiFilePart(new ArrayList<MultipartBody.Part>() {
        {
            for (File file : files) {
                add(MultipartBody.Part.createFormData("file", file.getName(),
                        RequestBody.create(MediaType.parse("image/jpg"), file)));
            }
        }
    }).compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

文件+文本RequestBody實現


@Override
public void multiUpload(File file, String text, Observer<UpLoadResponse> observer) {
    mService.multiRequestBody(RequestBody.create(MediaType.parse("image/jpg"), file), text)
            .compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

文件+文本MultiaprtBody.Part實現

@Override
public void multiUpload(File file, String text, Observer<UpLoadResponse> observer) {
    mService.multiPart(MultipartBody.Part.createFormData("file", file.getName(), RequestBody
            .create(MediaType.parse("image/jpg"), file)), text)
            .compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

Binary方式上傳

Binary上傳方式,就是整個請求體就是二進制數據!! 就是這么粗暴!

報文形式

POST /upload/binary HTTP/1.1
Host: 192.168.10.63:8080
Content-Type: xxx
Cache-Control: no-cache


二進制數據

Java服務端接收

后臺接受的話,直接拿到HttpServeletRequest#inputStream讀數據就好了!

當然,如果用戶想傳入文件信息,也可以通過請求頭來傳遞。

/**
 * 二進制上傳。
 *
 * @param request
 * @return
 */
@RequestMapping(method = RequestMethod.POST, value = "/binary")
public UpLoadResponse binaryReceive(HttpServletRequest request) {

    String contentType = request.getHeader("Content-Type");

    String size = request.getHeader("Content-Length");
    try {
        InputStream in = request.getInputStream();
        FileUtil.saveInputStream(in);
    } catch (IOException e) {
        e.printStackTrace();
    }


    List<PartInfo> partInfos = new ArrayList<PartInfo>();

    PartInfo partInfo = new PartInfo();
    partInfo.setMediaType(contentType + "");
    partInfo.setSize(Integer.parseInt(size));
    partInfos.add(partInfo);
    UpLoadResponse upLoadResponse = new UpLoadResponse();
    upLoadResponse.setPartInfos(partInfos);
    upLoadResponse.setMsg("二進制上傳成功");
    return upLoadResponse;
}
``

### 客戶端代碼

```java

public interface BinaryApi {

    /**
     * binary方式上傳,這種方式整個請求體直接就是二進制數據。服務端只需要拿到request#iputsteam就可以獲得。
     *
     * @param body
     * @return
     */
    @POST("upload/binary")
    Observable<UpLoadResponse> binary(@Body RequestBody body);
}

實現代碼:

public void uploadBinary(File file, Observer<UpLoadResponse> observer){
    RetrofitHelper.getInstance()
            .getRetrofit()
            .create(BinaryApi.class)
            .binary(RequestBody.create(MediaType.parse("image/jpg"),file))
            .compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

我們也可以直接傳流,例如:

public void uploadBinary(InputStream input, Observer<UpLoadResponse> observer){
    RetrofitHelper.getInstance()
            .getRetrofit()
            .create(BinaryApi.class)
            .binary(RequestBodyUtil.create(MediaType.parse("image/jpg"),input))
            .compose(TransformUtil.<UpLoadResponse>applySchedulerTransformer())
            .subscribe(observer);
}

RequestBodyUtil.java

public class RequestBodyUtil {

    public static RequestBody create(final MediaType mediaType, final InputStream inputStream) {
        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return mediaType;
            }

            @Override
            public long contentLength() throws IOException {
                return inputStream.available();
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                Source source=null;
                try {
                    source = Okio.source(inputStream);
                    sink.writeAll(source);
                }finally {
                    if(source!=null){
                        source.close();
                    }
                }
            }
        };
    }
}

完整代碼

https://github.com/blueberryCoder/MultipartExample

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容

  • 主題 記錄安卓端上傳模塊優化的經歷。通過本次分享,咱們可以知道 一個文件經歷了幾個步驟才能從手機上傳到服務端 能知...
    紫闞閱讀 4,485評論 1 10
  • 文件上傳與下載 文件上傳 -- 服務端 以Tomcat為服務器,Android客服端訪問Servlet,經Serv...
    sunhaiyu閱讀 14,171評論 2 20
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,692評論 25 708
  • 文件上傳在B/S應用中是一種十分常見的功能,那么在Android平臺下是否可以實現像B/S那樣的文件上傳功能呢?答...
    0dce86ba3565閱讀 517評論 0 1
  • 真實,是人生的最高境界。什麼是真實?就是不撒謊、不做作、不違背良心,純乎心性而行。我一個90後,大學畢業後...
    美梅閱讀 208評論 0 1