想看源码的请移步github:github.com/kb185191420… android
你们喜欢的话,就给个star^_^,有问题或者建议,能够直接提issues,也能够在博客下面给我留言。谢谢~ git
在demo中我分别实现了视频和图片的下载,并附带有下载进度显示,视频下载完成后运用exo播放器直接播放的,图片只是用Glide简单展现了一下。好了,咱们步入正题吧! github
在app的build.gradle的dependencies节点中添加如下代码: api
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吧!www.android-studio.org/ android-studio
在清单文件AndroidManifest中的manifest节点中添加如下代码: bash
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
复制代码
要实现将文件下载到本地,那必然须要网络权限和内存的读写权限啦! 网络
注意:因为咱们用到了写入内存的权限,因此千万要注意6.0以上动态权限的申请!app
我在demo里用的是本身简单封装的权限申请工具类,有兴趣的能够直接去看demo源码。框架
/**
* Description:
* Created by kang on 2018/3/9.
*/
public interface DownloadListener {
void onStart();
void onProgress(int currentLength);
void onFinish(String localPath);
void onFailure();
}
复制代码
回调中包括下载开始、下载进度、下载结束和下载失败等四个方法。其中咱们在下载进度的回调中返回进度的百分比,在此能够将进度显示在控件上;在下载结束的回调中返回下载至本地的文件路径,在此可直接对下载完成的文件进行操做。若是你还有一些个性化的需求,能够自行添加。ide
4、网络工具类准备
/**
* 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<ResponseBody> 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带进度下载文件的核心代码已经介绍完毕了!有木有火烧眉毛的想要用用试试呢!
6、最后来看看使用
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 你们喜欢的话,就给个star^_^,有问题或者建议,能够直接提issues,也能够在博客下面给我留言。谢谢~