Retrofit2.0使用姊妹篇——带进度上传文件

之前的一篇博客讲了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);
}

五、继承RequestBody 类

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 这个类的构造函数,这里我提供了两个构造:

  • 1、传入要上传的文件对象file、文件类型mediaType和上传回调。
 public ProgressRequestBody(final File file, String mediaType, final UploadCallbacks listener) {
        mFile = file;
        mMediaType = mediaType;
        mListener = listener;
    }
  • 2、传入要上传的文件对象file、文件类型mediaType、上传buffer大小和上传回调。
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();
        }
    }
  • 1、在writeTo中我们拿到文件的总长度,输入流,创建byte数组;
  • 2、创建Handler对象,注意创建时传入Looper.getMainLooper()主线程的Looper对象,这样就可以将线程切换到主线程,也就是说在进度回调中便可以直接将进度显示到控件上啦;
  • 3、循环将输入流写入buffer,然后调用handler.post(new ProgressUpdater(uploaded,fileLength)),将当前已上传的长度和总长度传出;
  • 4、将每次循环写入的长度累加到uploaded 上;
  • 5、通过BufferedSink对象的write方法将buffer里的内容写入缓存,这是上传最重要的一步!
  • 6、别忘记在finally中关闭输入流。

六、具体使用

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,也可以在博客下面给我留言。谢谢~

你可能感兴趣的:(android)