Retrofit2.0使用PC蛋蛋源码搭建姊妹篇——带进度下载文件

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

注意:对于大文件的操作一定要加@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() {
br/>@Override
public void onStart() {
Log.e(TAG, "onStart: ");
runOnUiThread(new Runnable() {
br/>@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);
                }
            });
        }
    });
}

这里PC蛋蛋源码搭建 dsluntan.com 我放上的是下载图片的代码(视频、文件等都类似),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切换线程!

Retrofit2.0使用PC蛋蛋源码搭建姊妹篇——带进度下载文件

原文地址:http://blog.51cto.com/13930863/2161867

时间: 2024-10-06 14:14:35

Retrofit2.0使用PC蛋蛋源码搭建姊妹篇——带进度下载文件的相关文章

游戏差异更新—PC蛋蛋源码下载BSDiff算法解析

PC蛋蛋源码下载联系方式:QQ:2747044651 网址请添加链接描述----pi2502差异更新即在软件更新时只更新差异化的部分,以达到用最小的下载量完成软件的更新需求.该思想由来已久,从刚接触电脑时的操作系统.应用软件快速更新功能或填补漏洞,到迭代更加频繁的移动应用时代更多了节省下载流量费用的需求.尤其在移动游戏领域,随着手机性能的提升和玩家对游戏体验的追求,安装包亦是越来越大,并且会频繁的更新以不断给玩家带来更新的玩法和更为优化的体验.然而,这种频繁的更新也同样会带来负面的影响:更新包太

c/c++如何解决PC蛋蛋源码下载 大数存储问题(100的阶乘)

PC蛋蛋源码下载 联系方式:QQ:2747044651 网址http://zhengtuwl.com 首先在编程中会遇到一些很大的数,由于已经给定的数据类型存储范围有限,所以我们应该掌握,如何处理这样的问题. 我这里通过一个计算100!的例子给出思路. 想要存储100的阶乘,已有的数据类型肯定无法存储,我们可以使用字符串或者数组来解决,通过模拟基本计算过程,将 得到的数据存储到数组或字符串中保存下来.不多说先给出C语言解法! #include<stdio.h>#define max 1000i

基于用户PC蛋蛋源码下载画像大数据的电商防刷架构

PC蛋蛋源码下载联系方式:QQ:2747044651 网址请添加链接描述最近1~2年电商行业飞速发展,各种创业公司犹如雨后春笋大量涌现,商家通过各种活动形式的补贴来获取用户.培养用户的消费习惯. 但任何一件事情都具有两面性,高额的补贴.优惠同时了也催生了"羊毛党". "羊毛党"的行为距离欺诈只有一步之遥,他们的存在严重破环了活动的目的,侵占了活动的资源,使得正常的用户享受不到活动的直接好处. 今天主要分享下腾讯自己是如何通过大数据.用户画像.建模来防止被刷.恶意撞库

布局、SD路径、单元测试、SQLitePC蛋蛋源码出租与ListView

相对PC蛋蛋源码出租 dsluntan.com Q:3393756370 VX:17061863513布局 RelativeLayout组件默认左对齐.顶部对齐设置组件在指定组件的右边 android:layout_toRightOf="@id/tv1" 设置在指定组件的下边 android:layout_below="@id/tv1" 设置右对齐父元素 android:layout_alignParentRight="true" 设置与指定组件

利用ServerlessPC蛋蛋源码下载,让COS中文件变更自动刷新CDN

第二种是使用字段判断逻辑PC蛋蛋源码下载联系方式:QQ:2747044651 网址http://zhengtuwl.com .也就是服务器进程,会在每次读取缓存前,根据一些特征数据,快速的判断内存中的缓存和源数据内容,是否有不一致(是否脏)的地方,如果有不一致的地方,就自动清理这条数据的缓存.这种做法会消耗一部分CPU,但是就不需要人工去处理清理缓存的事情,自动化程度很高.现在我们的浏览器和WEB服务器之间,就有用这种机制:检查文件MD5:或者检查文件最后更新时间.具体的做法,就是每次浏览器发起

如何避免DockerPC蛋蛋源码下载 容器启动脚本运行后自动退出—

docker build DocPC蛋蛋源码下载 联系方式:QQ:2747044651 网址http://zhengtuwl.com kerfile后,采用docker run --name xxx -d 运行容器,发现start.sh执行后容器就退出了,根本无法启动定时任务,网上各种办法有说用nohup,有死循环,还有说用信号,发现都不靠谱.分析了一下docker的机制,一个docker容器同时只能管理一个进程,这个进程退出后,容器也就退出了.这并不意味着一个容器里只能同时运行一个进程(那样太

源码搭建LAMP环境

源码搭建LAMP环境 一,LAMP环境概述: LAMP指的Linux(操作系统).ApacheHTTP 服务器,MySQL(有时也指MariaDB,数据库软件)和PHP(有时也是指Perl或Python) 的第一个字母,一般用来建立web 服务器.是一组常用来搭建动态网站或者服务器的开源软件,本身都是各自独立的程序,但是因为常被放在一起使用,拥有了越来越高的兼容度,共同组成了一个强大的Web应用程序平台.随着开源潮流的蓬勃发展,开放源代码的LAMP已经与J2EE和.Net商业软件形成三足鼎立之势

源码搭建LNMP

源码安装LNMP 作者:尹正杰 前言:非常简单的一个平台LNMP,在生产实际环节中我们也经常用到! 二话不说,开始享受我们的搭建过程吧! 一.源码安装nginx 1.安装依赖包 [[email protected] yinzhengjie]# yum -y install gcc gcc-c++ autoconf automake zlib zlib-devel openssl openssl-devel pcre* make gd-devel libjpeg-devel libpng-deve

linux下源码搭建php环境之mysql(一)

现在已经大半夜了,五一劳动节挺无聊的. 折腾一下吧,实在是睡不着.于是乎在电脑上安装个虚拟机,然后呢,在虚拟机上搭建一个php环境. 首先我得安装MYSQL吧.发现遇到的问题真多..待我娓娓道来. 主要介绍linux操作系统下MySQL源码进行纯手动安装,如果你对linux操作系统下MySQL源码进行纯手动安装的实际操作流程感兴趣的话,你不妨浏览以下的文章. 1.实现linux下纯手动MySQL源码安装,首先要下载MySQL的源码,我下载的是MySQL-5.0.87.tar.gz 2.解压tar