前面一篇文章介绍了Retrofit2的基本使用,这篇文章接着介绍使用Retrofit2实现文件上传和文件下载,以及上传下载过程中如何实现进度的显示。

文件上传

定义接口


1

2

3



@Multipart

@POST("fileService")

Call<User> uploadFile(@Part MultipartBody.Part file);


构造请求体上传


1

2

3

4

5



File file = new File(filePath);

RequestBody body = RequestBody.create(MediaType.parse("application/otcet-stream"), file);

MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), body);

Call<User> call = getRetrofitService().uploadOneFile(part);

call.enqueue(callback);


这样就可以将这个文件上传到服务器,但就这样上传操作不够友好,最好加上文件上传进度。而Retrofit本身是不支持文件上传进度显示的,所以就需要我们自己扩展OkHttp来实现文件上传进度。

我的做法是直接扩展一个RequestBody来实现进度显示,实现完成之后只需要将上面body进行包装转换即可

上传进度显示


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31



RetrofitCallback<User> callback = new RetrofitCallback<User>() {

@Override

public void onSuccess(Call<User> call, Response<User> response) {

runOnUIThread(activity, response.body().toString());

//进度更新结束

}

@Override

public void onFailure(Call<User> call, Throwable t) {

runOnUIThread(activity, t.getMessage());

//进度更新结束

}

@Override

public void onLoading(long total, long progress) {

super.onLoading(total, progress);

//此处进行进度更新

}

};

RequestBody body1 = RequestBody.create(MediaType.parse("application/otcet-stream"), file);

//通过该行代码将RequestBody转换成特定的FileRequestBody

FileRequestBody body = new FileRequestBody(body1, callback);

MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), body);

Call<User> call = getRetrofitService().uploadOneFile(part);

call.enqueue(callback);


回调RetrofitCallback


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18



public abstract class RetrofitCallback<T> implements Callback<T> {

@Override

public void onResponse(Call<T> call, Response<T> response) {

if(response.isSuccessful()) {

onSuccess(call, response);

} else {

onFailure(call, new Throwable(response.message()));

}

}

public abstract void onSuccess(Call<T> call, Response<T> response);

public void onLoading(long total, long progress) {

}

}


FileRequestBody


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74



/**

* 扩展OkHttp的请求体,实现上传时的进度提示

*

* @param <T>

*/

public final class FileRequestBody<T> extends RequestBody {

/**

* 实际请求体

*/

private RequestBody requestBody;

/**

* 上传回调接口

*/

private RetrofitCallback<T> callback;

/**

* 包装完成的BufferedSink

*/

private BufferedSink bufferedSink;

public FileRequestBody(RequestBody requestBody, RetrofitCallback<T> callback) {

super();

this.requestBody = requestBody;

this.callback = callback;

}

@Override

public long contentLength() throws IOException {

return requestBody.contentLength();

}

@Override

public MediaType contentType() {

return requestBody.contentType();

}

@Override

public void writeTo(BufferedSink sink) throws IOException {

if (bufferedSink == null) {

//包装

bufferedSink = Okio.buffer(sink(sink));

}

//写入

requestBody.writeTo(bufferedSink);

//必须调用flush,否则最后一部分数据可能不会被写入

bufferedSink.flush();

}

/**

* 写入,回调进度接口

* @param sink Sink

* @return Sink

*/

private Sink sink(Sink sink) {

return new ForwardingSink(sink) {

//当前写入字节数

long bytesWritten = 0L;

//总字节长度,避免多次调用contentLength()方法

long contentLength = 0L;

@Override

public void write(Buffer source, long byteCount) throws IOException {

super.write(source, byteCount);

if (contentLength == 0) {

//获得contentLength的值,后续不再调用

contentLength = contentLength();

}

//增加当前写入的字节数

bytesWritten += byteCount;

//回调

callback.onLoading(contentLength, bytesWritten);

}

};

}

}


文件下载

接口定义

文件下载请求与普通的Get和Post请求是一样的,只是他们的返回值不一样而已,文件下载请求的返回值一般定义成​​ResponseBody​


1

2

3

4



//这里只举例POST方式进行文件下载

@FormUrlEncoded

@POST("fileService")

Call<ResponseBody> downloadFile(@Field("param") String param);


发起请求


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33



RetrofitCallback<ResponseBody> callback = new RetrofitCallback<ResponseBody>() {

@Override

public void onSuccess(Call<ResponseBody> call, Response<ResponseBody> response) {

try {

InputStream is = response.body().byteStream();

String path = Util.getSdCardPath();

File file = new File(path, "download.jpg");

FileOutputStream fos = new FileOutputStream(file);

BufferedInputStream bis = new BufferedInputStream(is);

byte[] buffer = new byte[1024];

int len;

while ((len = bis.read(buffer)) != -1) {

fos.write(buffer, 0, len);

}

fos.flush();

fos.close();

bis.close();

is.close();

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public void onFailure(Call<ResponseBody> call, Throwable t) {

runOnUIThread(activity, t.getMessage());

}

};


Call<ResponseBody> call = getRetrofitService(callback).downloadFile(param);

call.enqueue(callback);


下载进度显示

下载进度显示有两种方式实现,一种是通过OkHttp设置拦截器将ResponseBody进行转换成我们扩展后的ResponseBody(稍后介绍),另外一种则是在上面的回调Callback中将ResponseBody的流写入到文件时进行进度处理,下面分别进行介绍。

扩展ResponseBody设置OkHttp拦截器


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22



private <T> RetrofitService getRetrofitService(final RetrofitCallback<T> callback) {

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();

clientBuilder.addInterceptor(new Interceptor() {

@Override

public okhttp3.Response intercept(Chain chain) throws IOException {

okhttp3.Response response = chain.proceed(chain.request());

//将ResponseBody转换成我们需要的FileResponseBody

return response.newBuilder().body(new FileResponseBody<T>(response.body(), callback)).build();

}

});

Retrofit retrofit = new Retrofit.Builder()

.baseUrl(BASE_URL)

.client(clientBuilder.build())

.addConverterFactory(GsonConverterFactory.create())

.build();

RetrofitService service = retrofit.create(RetrofitService.class);

return service ;

}

//通过上面的设置后,我们需要在回调RetrofitCallback中实现onLoading方法来进行进度的更新操作,与上传文件的方法相同


FileResponseBody


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63



/**

* 扩展OkHttp的请求体,实现上传时的进度提示

*

* @param <T>

*/

public final class FileResponseBody<T> extends ResponseBody {

/**

* 实际请求体

*/

private ResponseBody mResponseBody;

/**

* 下载回调接口

*/

private RetrofitCallback<T> mCallback;

/**

* BufferedSource

*/

private BufferedSource mBufferedSource;

public FileResponseBody(ResponseBody responseBody, RetrofitCallback<T> callback) {

super();

this.mResponseBody = responseBody;

this.mCallback = callback;

}

@Override

public BufferedSource source() {

if (mBufferedSource == null) {

mBufferedSource = Okio.buffer(source(mResponseBody.source()));

}

return mBufferedSource;

}

@Override

public long contentLength() {

return mResponseBody.contentLength();

}

@Override

public MediaType contentType() {

return mResponseBody.contentType();

}

/**

* 回调进度接口

* @param source

* @return Source

*/

private Source source(Source source) {

return new ForwardingSource(source) {

long totalBytesRead = 0L;

@Override

public long read(Buffer sink, long byteCount) throws IOException {

long bytesRead = super.read(sink, byteCount);

totalBytesRead += bytesRead != -1 ? bytesRead : 0;

mCallback.onLoading(mResponseBody.contentLength(), totalBytesRead);

return bytesRead;

}

};

}

}


直接在回调中进行进度更新

上面介绍了通过扩展ResponseBody同时设置OkHttp拦截器来实现进度条更新显示,另外也可以直接在请求回调onSuccess中将流转换成文件时实现进度更新,下面给出大致实现


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37



RetrofitCallback<ResponseBody> callback = new RetrofitCallback<ResponseBody>() {

@Override

public void onSuccess(Call<ResponseBody> call, Response<ResponseBody> response) {

try {

InputStream is = response.body().byteStream();

//获取文件总长度

long totalLength = is.available();


String path = Util.getSdCardPath();

File file = new File(path, "download.jpg");

FileOutputStream fos = new FileOutputStream(file);

BufferedInputStream bis = new BufferedInputStream(is);

byte[] buffer = new byte[1024];

int len;

while ((len = bis.read(buffer)) != -1) {

fos.write(buffer, 0, len);

//此处进行更新操作

//len即可理解为已下载的字节数

//onLoading(len, totalLength);

}

fos.flush();

fos.close();

bis.close();

is.close();

//此处就代表更新结束

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public void onFailure(Call<ResponseBody> call, Throwable t) {

runOnUIThread(activity, t.getMessage());

}

};


以上就是Retrofit中文件上传下载及其进度更新显示的实现