下载一个大文件
从最基础分析
要用到
- 后台服务
Android学习整理 - 6 -Service - 接口定义
- 网络请求
- okhttp
- 异步处理机制
Android学习整理 -11 -异步处理机制
功能分析
- 开始下载
- 暂停下载
- 取消下载
- 下载完成
- 下载出错
状态监听
- 下载的进度条更新()
- 成功时
- 失败时
- 暂停时
- 取消时
这里可以用一个接口定义5个状态,使用的时候重写监听方法,写入具体逻辑
实现
- 下载需要后台,需要服务(需要和Activity互相传递消息,需要绑定服务)
- 需要异步处理(网络请求和主线程中更新UI),不然会造成ANR
状态接口
public interface DownLoadListener {
void onProgress(int progress);
void onSuccess();
void onFailed();
void onPaused();
void onCanceled();
}
异步下载任务
使用AsyncTask
传入参数是url,int数据处理进度,返回int类型的状态标记
public class DownLoadTask extends AsyncTask<String, Integer, Integer> {
//定义四个下载状态标记
private static final int TYPE_SUCXCSS = 0;
private static final int TYPE_FAILED = 1;
private static final int TYPE_PAUSED = 2;
private static final int TYPE_CANCELED = 3;
//这个监听在服务中重写,因为在服务中启动任务,可以顺便初始化DownLoadTask(构造函数)
private DownLoadListener mListener;
private boolean isPaused = false;//下载是否暂停标记
private int lastProgress;//进度条上次更新时的大小
private boolean isCancelled = false;
public DownLoadTask(DownLoadListener listener) {
mListener = listener;
}
}
重写doInBackground()
@Override
protected Integer doInBackground(String... params) {
InputStream is = null;//输入流
RandomAccessFile savedFile = null;//用来访问那些保存数据记录的文件的,可以用seek( )方法来访问记录
File file = null;//待会要下载的文件地址引用
long downloadedLength = 0;//已下载的文件长度
String downloadUrl = params[0];//传入参数的第一个就是url
//subString(int)代表从该字符串的第int个开始截取(向右,下标第一个是0),lastIndexof(String)是String那里的字符数量(从左到右)
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
//外置卡的共享目录路径,这里要得是下载目录,平时手机里的Download文件夹
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
//文件路径加文件名
file = new File(directory + fileName);
if (file.exists()) {
//如果文件已经存在,则查看已下载文件长度
downloadedLength = file.length();
}
long contentLength = getContentLength(downloadUrl);//获取要文件的大小长度
if (contentLength == 0) {
//如果文件长度是0,说明文件有问题,返回错误标记
return TYPE_FAILED;
} else if (contentLength == downloadedLength) {
//如果文件长度是downloadedLength,说明文件下完了,返回完成标记
return TYPE_SUCXCSS;
}
OkHttpClient client = new OkHttpClient();//下载客户端
Request request = new Request.Builder()//构建下载请求
.addHeader("RANGE", "bytes=" + downloadedLength + "-")
.url(downloadUrl).build();
try {
Response response = client.newCall(request).execute();//发出请求并接受
if (response != null) {
//收到响应,则返回response读到的输入流给本地输入流is
is = response.body().byteStream();
savedFile = new RandomAccessFile(file, "rw");//随机读写模式为读写,写入file中
//跳过已下载的字节
savedFile.seek(downloadedLength);
byte[] b = new byte[1024];//常规读写文件形式,缓冲buffer
int total = 0;//读到的总下载长度,此处用他们来计算出进度条大小
int len;//读到的文件长度
while ((len = is.read(b)) != -1) {
//is.read(d)是len(buffer)的长度,等于-1代表到达终点
if (isCancelled) {
//如果取消了则返回取消标记
return TYPE_CANCELED;
} else if (isPaused) {
return TYPE_PAUSED;
} else {
total += len;
savedFile.write(b, 0, len);//写len长度b数组的byte到文件中
int progress = (int) ((total + downloadedLength) * 100 / contentLength);
publishProgress(progress);//发出进度条
}
}
//关闭response响应
response.body().close();
return TYPE_SUCXCSS;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
//关输入流
is.close();
}
if (savedFile != null) {
//关闭随机读取
savedFile.close();
}
if (isCancelled() && file != null) {
file.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return TYPE_FAILED;
}
获取内容长度的方法
/**
* 如果请求不为空并且响应成功接收到(就是以前的响应码200),就把文件的长度返回
* 如果请求为空或者响应没接收到,返回1
*/
private long getContentLength(String url) {
OkHttpClient client = new OkHttpClient();//实例化客户端
Request request = new Request.Builder()
.url(url)
.build();//构造请求体
try {
Response response = client.newCall(request).execute();//发出请求
if (request != null && response.isSuccessful()) {
//如果请求不为空并且响应成功接收到(就是以前的响应码200),就把文件的长度返回
long contentLength = response.body().contentLength();
response.close();
return contentLength;
}
} catch (IOException e) {
e.printStackTrace();
}
//如果请求为空或者响应没接收到,返回0
return 0;
}
剩下的是(最后加的)。。。。。
public void pauseDownload() {
//只需修改状态标记
isPaused = true;
}
public void cancelDownload() {
//只需修改状态标记
isCancelled = true;
}
onProgressUpdate()方法
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
mListener.onProgress(progress);
lastProgress = progress;
}
}
onPostExecute()方法
@Override
protected void onPostExecute(Integer status) {
switch (status) {
case TYPE_SUCXCSS:
mListener.onSuccess();
break;
case TYPE_FAILED:
mListener.onFailed();
break;
case TYPE_PAUSED:
mListener.onPaused();
break;
case TYPE_CANCELED:
mListener.onCanceled();
break;
}
}
后台服务
新建DownloadService服务
public class DownloadService extends Service {
private static final String TAG = "DownloadService";
private DownLoadTask mDownLoadTask;
private String downloadUrl;
private DownloadBinder mBinder = new DownloadBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* 获取系统状态栏信息服务
*/
private NotificationManager getNotificationManager() {
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
/**
* 显示进度封装
* */
private Notification getNotification(String title, int progress) {
Intent intent = new Intent(this, MainActivity.class);//上下文
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
//设置notification信息
builder.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi)
.setContentTitle(title);
if (progress >= 0) {
//当Progress大于等于0时才显示进度
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
}
}
内部类DownloadBinder
public class DownloadBinder extends Binder {
/**
* 开始下载
* */
public void startDownLoad(String url) {
if (mDownLoadTask == null) {
downloadUrl = url;
mDownLoadTask = new DownLoadTask(listener);
mDownLoadTask.execute(downloadUrl);//开始异步任务,传入url
startForeground(1, getNotification("下载中...", 0));
Log.d(TAG, "开始下载的服务");
}
}
public void pauseDownLoad(){
if (mDownLoadTask != null){
mDownLoadTask.pauseDownload();
}
}
public void cancelDownLoad(){
if (mDownLoadTask != null){
mDownLoadTask.cancelDownload();
}else{
if(downloadUrl != null){
//下面三句是为了获取文件名字,然后对比手机存储内的,删除
String filename = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String derectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
File file = new File(derectory + filename);
if(file.exists()){
file.delete();
}
getNotificationManager().cancel(1);//关闭1号通知
stopForeground(true);
Log.d(TAG, "取消了");
}
}
}
重写监听方法
private DownLoadListener listener = new DownLoadListener() {
@Override
public void onProgress(int progress) {
//设置进度条
getNotificationManager().notify(1, getNotification("下载中....", progress));
}
@Override
public void onSuccess() {
mDownLoadTask = null;
//关闭前台服务,并且创建下载成功通知
stopForeground(true);
getNotificationManager().notify(1, getNotification("下好了亲", -1));
Log.d(TAG, "下载wan");
}
@Override
public void onFailed() {
mDownLoadTask = null;
stopForeground(true);
getNotificationManager().notify(1, getNotification("xiazai出错", -1));
Log.d(TAG, "下载出错");
}
@Override
public void onPaused() {
mDownLoadTask = null;
getNotificationManager().notify(1, getNotification("xiazai暂停", -1));
Log.d(TAG, "下载暂停");
}
@Override
public void onCanceled() {
mDownLoadTask = null;
stopForeground(true);
getNotificationManager().notify(1, getNotification("xiazai取消了", -1));
Log.d(TAG, "下载取消了");
}
};
控制的活动MainActivity
纽带ServiceConnection
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinder = (DownloadService.DownloadBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
onCreate中
initView();
Intent intent = new Intent(this, DownloadService.class);
startService(intent);
bindService(intent, mConnection, BIND_AUTO_CREATE);
//判断权限够不够,不够就给
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
} else {
//权限够了这里处理逻辑
Log.d(TAG, "权限够了");
}
```
Activity中获取权限的回调
```
//获取到权限回调方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//权限够了处理逻辑
Log.d(TAG, "权限够了,逻辑");
} else {
Toast.makeText(this, "权限不够,程序将退出", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
```
按钮的控制
```
@Override
public void onClick(View v) {
if (mBinder == null) {
//没纽带,控制个毛,直接GG
return;
}
switch (v.getId()) {
case R.id.button:
String url = "http://down.360safe.com/se/360se8.1.1.250.exe";
mBinder.startDownLoad(url);
break;
case R.id.button2:
mBinder.pauseDownLoad();
break;
case R.id.button3:
mBinder.cancelDownLoad();
break;
default:
break;
}
}
```
![布局](https://img.haomeiwen.com/i3515789/80b1777e35111901.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
注册服务与权限
```
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```
```
<service android:name=".service.DownloadService"/>
```
---
Github源代码**[DownLoadService](https://github.com/minminaya/DownLoadService)**
参考自郭霖《第一行代码》
网友评论