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

之前的一篇博客讲了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> T createService(Class<T> 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<UploadVideoResp> 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<UploadVideoResp>() {
            @Override
            public void onResponse(Call<UploadVideoResp> call, Response<UploadVideoResp> response) {
                mFlCircleProgress.setVisibility(View.GONE);
                UploadVideoResp resp = response.body();
                if (resp != null) {
                    Toast.makeText(mContext, "图片上传成功!", Toast.LENGTH_SHORT).show();
                }
            }
            @Override
            public void onFailure(Call<UploadVideoResp> 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机动车(JsAndroidClub)

原文发表时间:2018-03-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏知识分享

Android 多线程-----AsyncTask详解

本篇随笔将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信。

1143
来自专栏求索之路

四大组件以及Application和Context的全面理解

1.概述 ? Context抽象结构 2.用处 1.Context的实现类有很多,但是ContextImpl(后称CI)是唯一做具体工作的,其他实现都是对CI做...

4515
来自专栏alexqdjay

Springboot 随笔(1) -- 自动引入配置与启动机制

38213
来自专栏肖蕾的博客

OKHttp3(支持Retrofit)的网络数据缓存Interceptor拦截器

3553
来自专栏向治洪

android性能优化1

一、在使用Gallery控件时,如果载入的图片过多,过大,就很容易出现OutOfMemoryError异常,就是内存溢出。这是因为Android默认分配的内存只...

2196
来自专栏Android干货

安卓开发_浅谈AsyncTask

3097
来自专栏向治洪

Support Annotation Library使用详解

概述 Support Annotation Library是在Android Support Library19.1版本开始引入的一个全新的函数包,它包含了诸多...

1998
来自专栏开发之途

Android 实现无网络传输文件(2)

3288
来自专栏Android 开发学习

data-binding 踩坑记

2004
来自专栏Java与Android技术栈

用kotlin打印出漂亮的android日志写在最后

Kotlin号称是Android版本的swift,距离它1.0正式版本的推出快一年了。它像swift一样,可以写客户端也可以写服务端。由于公司项目比较繁忙,我一...

1272

扫码关注云+社区

领取腾讯云代金券