Retrofit2.3使用姊妹篇——带进度下载文件










Retrofit是目前最主流的网络框架了,它对网络请求几近完美的封装,大大降低了我们开发者的研发难度,缩短了研发周期。最近项目中遇到了下载视频和图片文件的需求(还有上传视频和图片的需求,请移步这篇博客),我第一反应是用retrofit做呀,so easy!产品接着说,要带下载进度条哦!我一想,retrofit好像并没有给我们提供显示下载进度的接口,哎呀,看来还是得自己个儿整整喽!接下来,我把自己实现Retrofit带进度下载文件的流程分享给大家。

想看源码的请移步github:https://github.com/kb18519142009/DownloadFile
大家喜欢的话,就给个star^_^,有问题或者建议,可以直接提issues,也可以在博客下面给我留言。谢谢~

先看看效果图:

效果图

在demo中我分别实现了视频和图片的下载,并附带有下载进度显示,视频下载完成后运用exo播放器直接播放的,图片只是用Glide简单展示了一下。好了,我们步入正题吧!

一、添加依赖

在app的build.gradle的dependencies节点中添加以下代码:

implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.google.android.exoplayer:exoplayer:r2.5.4'
implementation 'com.github.bumptech.glide:glide:4.3.1'
  • 1
  • 2
  • 3

俗话说的好,工欲善其事必先利器!我们分别添加Retrofit、exoplayer和glide的依赖,可能有朋友要问了,“implementation ”这是什么玩意呀?添依赖不是用compile吗?ok!兄弟不要急,如果你有这个疑问,很明显你平日里吃饭的家伙什儿已经out了,赶紧去升级Android Studio3.0吧!http://www.android-studio.org/

二、添加权限和动态权限处理

在清单文件AndroidManifest中的manifest节点中添加以下代码:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • 1
  • 2

要实现将文件下载到本地,那必然需要网络权限和内存的读写权限啦!

注意:由于我们用到了写入内存的权限,所以千万要注意6.0以上动态权限的申请!我在demo里用的是自己简单封装的权限申请工具类,有兴趣的可以直接去看demo源码,代码如下:

if (KbPermissionUtils.needRequestPermission()) { //判断是否需要动态申请权限
            KbPermission.with(this)
                    .requestCode(100)
                    .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE) //需要申请的权限(支持不定长参数)
                    .callBack(new KbPermissionListener() {
                        @Override
                        public void onPermit(int requestCode, String... permission) { //允许权限的回调
                            downloadVideo(); //处理具体下载过程
                        }

                        @Override
                        public void onCancel(int requestCode, String... permission) { //拒绝权限的回调
                            KbPermissionUtils.goSetting(mContext); //跳转至当前app的权限设置界面
                        }
                    })
                    .send();
        } else {
            downloadVideo(); //处理具体下载过程
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

三、设计回调

/**
 * Description:
 * Created by kang on 2018/3/9.
 */

public interface DownloadListener {
    void onStart();

    void onProgress(int currentLength);

    void onFinish(String localPath);

    void onFailure();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

回调中包括下载开始下载进度下载结束下载失败等四个方法。其中我们在下载进度的回调中返回进度的百分比,在此可以将进度显示在控件上;在下载结束的回调中返回下载至本地的文件路径,在此可直接对下载完成的文件进行操作。如果你还有一些个性化的需求,可以自行添加。

四、网络工具类准备

/**
 * ApiHelper
 * Created by kang on 2018/3/9.
 */
public class ApiHelper {

    private static final String TAG = "ApiHelper";

    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)
                .build();
        return this;
    }

    public <T> T createService(Class<T> serviceClass) {
        return mRetrofit.create(serviceClass);
    }

}
  • 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

这里我对Retrofit进行了简单封装。

/**
 * Description:
 * Created by kang on 2018/3/9.
 */

public interface ApiInterface {
    /**
     * 下载视频
     *
     * @param fileUrl
     * @return
     */
    @Streaming //大文件时要加不然会OOM
    @GET
    Call<ResponseBody> downloadFile(@Url String fileUrl);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

注意:对于大文件的操作一定要加@Streaming,否则会出现OOM

五、文件下载工具类准备

/**
 * Description:下载文件工具类
 * Created by kang on 2018/3/9.
 */

public class DownloadUtil {
    private static final String TAG = "DownloadUtil";
    private static final String PATH_CHALLENGE_VIDEO = Environment.getExternalStorageDirectory() + "/DownloadFile";
    //视频下载相关
    protected ApiInterface mApi;
    private Call<ResponseBody> mCall;
    private File mFile;
    private Thread mThread;
    private String mVideoPath; //下载到本地的视频路径

    public DownloadUtil() {
        if (mApi == null) {
        //初始化网络请求接口
            mApi = ApiHelper.getInstance().buildRetrofit("https://sapi.daishumovie.com/")
                    .createService(ApiInterface.class);
        }
    }

    public void downloadFile(String url, final DownloadListener downloadListener) {
        String name = url;
        //通过Url得到文件并创建本地文件
        if (FileUtils.createOrExistsDir(PATH_CHALLENGE_VIDEO)) {
            int i = name.lastIndexOf('/');//一定是找最后一个'/'出现的位置
            if (i != -1) {
                name = name.substring(i);
                mVideoPath = PATH_CHALLENGE_VIDEO +
                        name;
            }
        }
        if (TextUtils.isEmpty(mVideoPath)) {
            Log.e(TAG, "downloadVideo: 存储路径为空了");
            return;
        }
        //建立一个文件
        mFile = new File(mVideoPath);
        if (!FileUtils.isFileExists(mFile) && FileUtils.createOrExistsFile(mFile)) {
            if (mApi == null) {
                Log.e(TAG, "downloadVideo: 下载接口为空了");
                return;
            }
            mCall = mApi.downloadFile(url);
            mCall.enqueue(new Callback<ResponseBody>() {
                @Override
                public void onResponse(@NonNull Call<ResponseBody> call, @NonNull final Response<ResponseBody> response) {
                    //下载文件放在子线程
                    mThread = new Thread() {
                        @Override
                        public void run() {
                            super.run();
                            //保存到本地
                            writeFile2Disk(response, mFile, downloadListener);
                        }
                    };
                    mThread.start();
                }

                @Override
                public void onFailure(Call<ResponseBody> call, Throwable t) {
                    downloadListener.onFailure(); //下载失败
                }
            });
        } else {
            downloadListener.onFinish(mVideoPath); //下载完成
        }
    }
    //将下载的文件写入本地存储
    private void writeFile2Disk(Response<ResponseBody> response, File file, DownloadListener downloadListener) {
        downloadListener.onStart();
        long currentLength = 0;
        OutputStream os = null;

        InputStream is = response.body().byteStream(); //获取下载输入流
        long totalLength = response.body().contentLength();

        try {
            os = new FileOutputStream(file); //输出流
            int len;
            byte[] buff = new byte[1024];
            while ((len = is.read(buff)) != -1) {
                os.write(buff, 0, len);
                currentLength += len;
                Log.e(TAG, "当前进度: " + currentLength);
                //计算当前下载百分比,并经由回调传出
                downloadListener.onProgress((int) (100 * currentLength / totalLength));
                //当百分比为100时下载结束,调用结束回调,并传出下载后的本地路径
                if ((int) (100 * currentLength / totalLength) == 100) {
                    downloadListener.onFinish(mVideoPath); //下载完成
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close(); //关闭输出流
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close(); //关闭输入流
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 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
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

这一段时我们下载文件的核心代码,我们来简单分析一下。首先我在DownloadUtil这个类的构造函数中初始化了网络请求接口,然后提供了两个方法,downloadFile和writeFile2Disk,顾名思义第一个是下载文件的方法,第二个是将文件写入SDCard的方法。

方法一:downloadFile(String url, final DownloadListener downloadListener)

两个参数:url和downloadListener
url是我们要下载的地址,downloadListener是第三步我们设计的下载回调

先截取url最后一个’/’之后的内容,得到我们将要存储到本地的文件名,然后创建该文件,通过网络请求得到Response对象,接着开启子线程,调用writeFile2Disk方法。

方法二:writeFile2Disk(Response response, File file, DownloadListener downloadListener)

三个参数:Response对象,file和downloadListener
通过Response对象我们可以获取到InputStream输入流,file是之前创建好的本地文件夹,downloadListener是第三步我们设计的下载回调

ok!到此我们要开始计算下载百分比了!
通过InputStream is = response.body().byteStream()可以获取到下载的InputStream输入流,通过long totalLength = response.body().contentLength()获取到下载的总长度;再通过file创建输出流os = new FileOutputStream(file); 此时通过输入流的read(buff)方法每次读取固定大小的buff(一般1024即可),再调用输出流的write方法将buff写入文件,这是一个while循环,直到将输入流的字节全部读取完毕,而正好在每次循环里,我们可以将读取的字节数累加,得到当前已下载的字节长度currentLength,(100*currentLength/totalLength)就是当前下载百分比啦,这个时候我们用downloadListener.onProgress回调将进度传出即可;当进度达到100时,将本地文件地址通过downloadListener.onFinish回调传出!
最后别忘记在finally中关闭输入输出流!

到此,我的Retrofit带进度下载文件的核心代码已经介绍完毕了!有木有迫不及待的想要用用试试呢!

六、最后来看看使用

private void downloadPicture() {
        mDownloadUtil = new DownloadUtil();
        mDownloadUtil.downloadFile(PICTURE_URL, new DownloadListener() {
            @Override
            public void onStart() {
                Log.e(TAG, "onStart: ");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        fl_circle_progress.setVisibility(View.VISIBLE);
                    }
                });

            }

            @Override
            public void onProgress(final int currentLength) {
                Log.e(TAG, "onLoading: " + currentLength);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        circle_progress.setProgress(currentLength);
                    }
                });

            }

            @Override
            public void onFinish(final String localPath) {
                Log.e(TAG, "onFinish: " + localPath);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        fl_circle_progress.setVisibility(View.GONE);
                        Glide.with(mContext).load(localPath).into(iv_picture);
                    }
                });
            }

            @Override
            public void onFailure() {
                Log.e(TAG, "onFailure: ");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        fl_circle_progress.setVisibility(View.GONE);
                    }
                });
            }
        });
    }
  • 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

这里我放上的是下载图片的代码(视频、文件等都类似),fl_circle_progress是进度条的父布局,circle_progress是环形进度条,在onStart中将fl_circle_progress显示,onProgress中给circle_progress设置进度,onFinish中将fl_circle_progress隐藏,并利用Glide将下载完成的图片显示在iv_picture中,iv_picture就是一个imageView,如果下载过程中出错那就在onFailure中将fl_circle_progress隐藏。因为对UI的处理需要在UI线程中进行,所以这些处理需要通过runOnUiThread切换线程

到这里整个下载过程就结束了,是不是很简单呀!欢迎到github下载源码:

https://github.com/kb18519142009/DownloadFile

转自:http://blog.csdn.net/k_bb_666/article/details/79500623#t5

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Retrofit 下载文件需要定义一个返回类型为 `Call<ResponseBody>` 的 Retrofit 接口,其中 `ResponseBody` 表示响应体,它可以包含文件数据。下面是一个简单的示例代码: ```java public interface FileDownloadService { @GET("file/download") Call<ResponseBody> downloadFile(@Query("fileUrl") String fileUrl); } ``` 然后,你可以在应用程序中使用这个接口来下载文件。下面是一个使用 Retrofit 下载文件的示例: ```java String fileUrl = "https://example.com/file.pdf"; Call<ResponseBody> call = fileDownloadService.downloadFile(fileUrl); call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (response.isSuccessful()) { // 获取文件数据 ResponseBody body = response.body(); try { InputStream inputStream = body.byteStream(); // 创建文件 File file = new File(Environment.getExternalStorageDirectory(), "file.pdf"); FileOutputStream outputStream = new FileOutputStream(file); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); } outputStream.flush(); outputStream.close(); inputStream.close(); // 下载完成 Log.d(TAG, "Download completed!"); } catch (IOException e) { e.printStackTrace(); } } else { Log.d(TAG, "Download failed!"); } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { Log.e(TAG, "Download failed: " + t.getMessage()); } }); ``` 在这个示例中,我们首先创建了一个 Retrofit 接口 `FileDownloadService`,然后使用这个接口下载文件。在响应成功时,我们从响应体中获取文件数据,并将其写入存储设备中的文件。在响应失败时,我们打印一条日志消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值