10

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

 3 years ago
source link: http://www.androidchina.net/9028.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Retrofit2.0使用姊妹篇——带进度下载文件 – Android开发中文站
你的位置:Android开发中文站 > 热点资讯 > Retrofit2.0使用姊妹篇——带进度下载文件

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'

俗话说的好,工欲善其事必先利器!我们分别添加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" />

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

注意:由于我们用到了写入内存的权限,所以千万要注意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(); //处理具体下载过程
        }

三、设计回调

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

public interface DownloadListener {
    void onStart();

    void onProgress(int currentLength);

    void onFinish(String localPath);

    void onFailure();
}

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

四、网络工具类准备

/**
 * 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);
    }

}

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

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

public interface ApiInterface {
    /**
     * 下载视频
     *
     * @param fileUrl
     * @return
     */
    @Streaming //大文件时要加不然会OOM
    @GET
    Call<ResponseBody> downloadFile(@Url String fileUrl);
}

注意:对于大文件的操作一定要加@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();
                }
            }
        }
    }
}

这一段时我们下载文件的核心代码,我们来简单分析一下。首先我在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);
                    }
                });
            }
        });
    }

这里我放上的是下载图片的代码(视频、文件等都类似),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

转载请注明:Android开发中文站 » Retrofit2.0使用姊妹篇——带进度下载文件


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK