Android文件上傳

上傳的方式

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

multipart上傳方式

html代碼

這中上傳方式是我們最常用的上傳方式。比如我們使用網(wǎng)頁(yè)上傳文件,其中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設(shè)置為multipart/form-data方式。如果是多文件的話,html代碼應(yīng)該是這樣:

<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標(biāo)簽中的name就是對(duì)應(yīng)的字段,后臺(tái)程序?qū)⒏鶕?jù)這字段取出文件。

具體的報(bào)文

這種方式對(duì)應(yīng)的HTTP報(bào)文如下:

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
二進(jìn)制文件信息

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

------WebKitFormBoundary7MA4YWxkTrZu0gW--

通過(guò)上面的報(bào)文我們看以看出:multipart/form-data方式的一個(gè)重要組成部分請(qǐng)求頭Content-Type必須為:
Content-Type:multipart/form-data;boundarty=一個(gè)32字節(jié)的隨機(jī)數(shù),用來(lái)分割每個(gè)part。
然后每個(gè)Part之間用“雙橫杠”加bundary來(lái)分割,最后一個(gè)Part分割符末尾也要加“雙橫杠”

每個(gè)Part中必須包含Content-Disposition字段來(lái)注明字段文件名等信息,也可以包含Content-Type來(lái)說(shuō)明文件的MeidaType。

Java服務(wù)端接受代碼

/**
 * 單個(gè)文件上傳。
 * <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;
}

/**
 * 多文件上傳,公用一個(gè)字段 "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;
}

/**
 * 文件+文本一起上傳,其實(shí)和多文件上傳一樣的。
 * <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服務(wù)端通過(guò)@RequestParam("file") MultipartFile file就可以獲得文件信息了,如果客戶端傳的Part類(lèi)型為String,也可以直接用String類(lèi)型獲取文本信息。

客戶端使用Retrofit上傳

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

例如:

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直接中不要有參數(shù)。
 * 如果使用RequestBody需要注意:如果上傳文件name應(yīng)該包含有filename(文件名)這個(gè)字段,負(fù)責(zé)后臺(tái)可能會(huì)出錯(cuò),
 * 比如我們要上傳一個(gè)文件我們的name值應(yīng)該為:file";filename="image.jpg;注意前后沒(méi)有雙引號(hào),中間有2個(gè)雙引號(hào),
 * 這是因?yàn)镽etrofit會(huì)自動(dòng)幫我們拼接Content-Disposition;它拼接的方式為 form-data; name="我們?cè)O(shè)置的值",如
 * 果用MultipartBody.Part我們則不需要這么費(fèi)事的拼接,因?yàn)镸ultipart.Part.createFormData()放我們完成了該操
 * 作;
 * {@link okhttp3.MultipartBody.Part#createFormData(String, String, RequestBody)}
 */

public interface MultipartApi {

    /**
     * 單個(gè)文件上傳
     */
    String SINGLE = "upload/single";

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

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


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

    /**
     * 單個(gè)文件上傳,使用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.單個(gè)文件上傳,使用RequestBody,和使用MultiBody.Part方式
2.多個(gè)文件上傳(part都使用同一個(gè)字段),RequestBody和MulipartBody.Part的寫(xiě)法。
3.文件+文本上傳,RequestBody和MulipartBody.Part方式的寫(xiě)法。

使用MulipartBody.Part具體的實(shí)現(xiàn):


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的具體實(shí)現(xiàn):

@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方式的實(shí)現(xiàn):

@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實(shí)現(xiàn)


@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實(shí)現(xiàn)

@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上傳方式,就是整個(gè)請(qǐng)求體就是二進(jìn)制數(shù)據(jù)?。?就是這么粗暴!

報(bào)文形式

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


二進(jìn)制數(shù)據(jù)

Java服務(wù)端接收

后臺(tái)接受的話,直接拿到HttpServeletRequest#inputStream讀數(shù)據(jù)就好了!

當(dāng)然,如果用戶想傳入文件信息,也可以通過(guò)請(qǐng)求頭來(lái)傳遞。

/**
 * 二進(jìn)制上傳。
 *
 * @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("二進(jìn)制上傳成功");
    return upLoadResponse;
}
``

### 客戶端代碼

```java

public interface BinaryApi {

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

實(shí)現(xiàn)代碼:

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 主題 記錄安卓端上傳模塊優(yōu)化的經(jīng)歷。通過(guò)本次分享,咱們可以知道 一個(gè)文件經(jīng)歷了幾個(gè)步驟才能從手機(jī)上傳到服務(wù)端 能知...
    紫闞閱讀 4,703評(píng)論 1 10
  • 文件上傳與下載 文件上傳 -- 服務(wù)端 以Tomcat為服務(wù)器,Android客服端訪問(wèn)Servlet,經(jīng)Serv...
    sunhaiyu閱讀 14,360評(píng)論 2 20
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,039評(píng)論 25 709
  • 文件上傳在B/S應(yīng)用中是一種十分常見(jiàn)的功能,那么在Android平臺(tái)下是否可以實(shí)現(xiàn)像B/S那樣的文件上傳功能呢?答...
    0dce86ba3565閱讀 539評(píng)論 0 1
  • 真實(shí),是人生的最高境界。什麼是真實(shí)?就是不撒謊、不做作、不違背良心,純乎心性而行。我一個(gè)90後,大學(xué)畢業(yè)後...
    美梅閱讀 237評(píng)論 0 1

友情鏈接更多精彩內(nèi)容