之前的一篇博客讲了Retrofit带进度下载文件的实现,算是Retrofit使用的“姐姐篇”,那今天我们就讲讲它的“妹妹篇“——用Retrofit实现带进度上传文件!
github地址:https://github.com/kb18519142009/UploadService.git
大家喜欢的话,就给个star^_^,有问题或者建议,可以直接提issues,也可以在博客下面给我留言。谢谢~
还是先上效果图:
这里我分别实现了图片和视频的上传,并附带有进度显示,为了更直观的展示上传效果,我写了图片选择和视频选择两个列表,将手机本地相册内的图片和视频全部展示出来(读取图片和视频的方法可以看这篇博客),有兴趣的可以直接去github下载demo查看,这里就不多说了,好了,接下来我们步入正题吧!
implementation 'com.android.support:recyclerview-v7:26.1.0' //recyclerview
implementation 'com.squareup.retrofit2:retrofit:2.3.0' //retrofit2
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' //gson解析
implementation 'com.github.bumptech.glide:glide:4.3.1' //glide加载图片
implementation 'com.jakewharton:butterknife:8.8.1' //黄油刀
implementation 'com.github.castorflex.smoothprogressbar:library-circular:1.3.0' //环形进度条
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' //黄油刀注解
在清单文件AndroidManifest中的manifest节点中添加以下代码:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
要实现将文件上传,我们需要网络权限和内存的读写权限,由于我在图片选择列表里加了拍照功能,所以这里加上了相机的权限。
**注意:由于我们用到了写入内存和相机的权限,所以千万要注意6.0以上动态权限的申请!**demo里依然用的是自己简单封装的权限申请工具类,大家可以直接去看demo里的使用!
public interface UploadCallbacks {
void onProgressUpdate(int percentage);
void onError();
void onFinish();
}
回调中包括上传进度、错误回调和结束回调等四个方法。其中我们在上传进度的回调中返回进度的百分比,在此可以将进度显示在控件上。如果你还有一些个性化的需求,可以自行添加。
对Retrofit进行简单封装。
/**
* ApiHelper 网络请求工具类
* Created by kang on 2018/3/9.
*/
public class ApiHelper {
private static final String TAG = "ApiHelper";
public static final String BASE_URL = "http://192.168.1.200:9090/";
private static ApiHelper mInstance;
private Retrofit mRetrofit;
private OkHttpClient mHttpClient;
private ApiHelper() {
this(30, 30, 30);
}
public ApiHelper(int connTimeout, int readTimeout, int writeTimeout) {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(connTimeout, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.writeTimeout(writeTimeout, TimeUnit.SECONDS);
mHttpClient = builder.build();
}
public static ApiHelper getInstance() {
if (mInstance == null) {
mInstance = new ApiHelper();
}
return mInstance;
}
public ApiHelper buildRetrofit(String baseUrl) {
mRetrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(mHttpClient)
.addConverterFactory(GsonConverterFactory.create(new Gson()))
.build();
return this;
}
public T createService(Class serviceClass) {
return mRetrofit.create(serviceClass);
}
}
上传接口,注意@Multipart注解!
/**
* Description:网络请求接口类
* Created by kang on 2018/3/9.
*/
public interface ApiInterface {
/**
* 文件整块上传
*
* @param file
* @return
*/
@Multipart
@POST("v1/upload")
Call uploadFile(@Part MultipartBody.Part file);
}
public class ProgressRequestBody extends RequestBody {
private File mFile;
private String mPath;
private String mMediaType;
private UploadCallbacks mListener;
private int mEachBufferSize = 1024;
public ProgressRequestBody(final File file, String mediaType, final UploadCallbacks listener) {
mFile = file;
mMediaType = mediaType;
mListener = listener;
}
public ProgressRequestBody(final File file, String mediaType, int eachBufferSize, final UploadCallbacks listener) {
mFile = file;
mMediaType = mediaType;
mEachBufferSize = eachBufferSize;
mListener = listener;
}
@Override
public MediaType contentType() {
// i want to upload only images
return MediaType.parse(mMediaType);
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[mEachBufferSize];
FileInputStream in = new FileInputStream(mFile);
long uploaded = 0;
try {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
// update progress on UI thread
handler.post(new ProgressUpdater(uploaded, fileLength));
uploaded += read;
sink.write(buffer, 0, read);
}
} finally {
in.close();
}
}
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
public ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
mListener.onProgressUpdate((int) (100 * mUploaded / mTotal));
}
}
}
Retrofit虽然没有直接为我们提供上传进度的接口,但是它提供了RequestBody 类,我们通过继承RequestBody类,重写writeTo方法即可获取上传进度!
1、首先我们还是看一下ProgressRequestBody 这个类的构造函数,这里我提供了两个构造:
public ProgressRequestBody(final File file, String mediaType, final UploadCallbacks listener) {
mFile = file;
mMediaType = mediaType;
mListener = listener;
}
public ProgressRequestBody(final File file, String mediaType, int eachBufferSize, final UploadCallbacks listener) {
mFile = file;
mMediaType = mediaType;
mEachBufferSize = eachBufferSize;
mListener = listener;
}
其中mediaType可以是“image/*”、“video/mp4”等文件类型(了解更多类型),eachBufferSize是将来上传时Buffer的大小。
2、接下来在重写的contentType()方法中返回文件类型mMediaType。
@Override
public MediaType contentType() {
// i want to upload only images
return MediaType.parse(mMediaType);
}
3、准备一个Runnable,在构造中传入当前已上传的文件大小uploaded和文件总长度total,然后在 run()方法中通过之前设计好的回调onProgressUpdate将进度传出。
private class ProgressUpdater implements Runnable {
private long mUploaded;
private long mTotal;
public ProgressUpdater(long uploaded, long total) {
mUploaded = uploaded;
mTotal = total;
}
@Override
public void run() {
mListener.onProgressUpdate((int) (100 * mUploaded / mTotal));
}
}
4、重写writeTo方法
@Override
public void writeTo(BufferedSink sink) throws IOException {
long fileLength = mFile.length();
byte[] buffer = new byte[mEachBufferSize];
FileInputStream in = new FileInputStream(mFile);
long uploaded = 0;
try {
int read;
Handler handler = new Handler(Looper.getMainLooper());
while ((read = in.read(buffer)) != -1) {
// update progress on UI thread
handler.post(new ProgressUpdater(uploaded, fileLength));
uploaded += read;
sink.write(buffer, 0, read);
}
} finally {
in.close();
}
}
private void uploadPicture() {
mFlCircleProgress.setVisibility(View.VISIBLE);
File file = new File(mPicPath);
//是否需要压缩
//实现上传进度监听
ProgressRequestBody requestFile = new ProgressRequestBody(file, "image/*", new ProgressRequestBody.UploadCallbacks() {
@Override
public void onProgressUpdate(int percentage) {
Log.e(TAG, "onProgressUpdate: " + percentage);
mCircleProgress.setProgress(percentage);
}
@Override
public void onError() {
}
@Override
public void onFinish() {
}
});
MultipartBody.Part body =
MultipartBody.Part.createFormData("file", file.getName(), requestFile);
mApi.uploadFile(body).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
mFlCircleProgress.setVisibility(View.GONE);
UploadVideoResp resp = response.body();
if (resp != null) {
Toast.makeText(mContext, "图片上传成功!", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call call, Throwable t) {
mFlCircleProgress.setVisibility(View.GONE);
Toast.makeText(mContext, "图片上传失败,稍后重试", Toast.LENGTH_SHORT).show();
}
});
}
1、先创建一个ProgressRequestBody对象requestFile;
2、通过MultipartBody.Part.createFormData(“file”, file.getName(), requestFile)方法创建一个MultipartBody.Part对象;
3、调用网络请求接口,出入MultipartBody.Part对象进行上传!
4、在onProgressUpdate回调中显示进度!
OK!大功告成!
demo地址github:https://github.com/kb18519142009/UploadService.git
大家喜欢的话,就给个star^_^,有问题或者建议,可以直接提issues,也可以在博客下面给我留言。谢谢~