基于AsyncTask进行异步下载(service后台服务执行并可查看下载进度)
本文章介绍使用AsyncTask异步下载,为了保证任务不被回收,使用service后台服务去执行下载任务,并用通知的方式查看下载进进度。
1.下载任务:
首先我们使用AsyncTask来完成下载任务。在doInBackground方法中编写逻辑。
我们首先确定保存路径,并查看是否存在需要下载的文件,如果存在了确定文件的长度。然后获取需要下载的文件长度,如果需要下载的文件长度是0,那么文件有问题返回错误,如果需要下载的文件长度和已经下载的长度一致说明已经存在,返回成功。然后是下载操作:需要注意的是添加了:.addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)方法,意思是告诉服务器从哪里还是下载,截止到哪里。这是断点续传的关键一步。但是服务器需要配合,不然怎么操作都行从头开始。在后面就是保存下载的数据了,并计算进度,通过publishProgress(progress);方法把进度传出去。
在onProgressUpdate方法中,通过接口毁掉将更新进度。
在onPostExecute中将,通过接口毁掉跟新状态。
package com.example.mydownload;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class DownloadTask extends AsyncTask<String, Integer, Integer> {
public static final int TYPE_SUCCESS = 0;
public static final int TYPE_FAILED = 1;//失败
public static final int TYPE_PAUSED = 2;
public static final int TYPE_CANCELED = 3;
private DownloadListener listener;
private boolean isCancel = false;
private boolean isPaused = false;
private int lastProgress;
public DownloadTask(DownloadListener listener) {
this.listener = listener;
}
@Override
protected Integer doInBackground(String... params) {
InputStream is = null;
File file = null;
RandomAccessFile saveFile = null;
try {
//记录下载文件长度
long downloadLength = 0;
String dowmloadUrl = params[0];
String fileName = dowmloadUrl.substring(dowmloadUrl.lastIndexOf("/"));
//下载路径
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
//如果文件存在 记录文件的长度
file = new File(directory + fileName);
if (file.exists()) {
downloadLength = file.length();
}
//获取文件长度
long contentLength = getContentLength(dowmloadUrl);
if (contentLength == 0) {
//文件总长度是0 说明有问题直接返回错误哦
return TYPE_FAILED;
} else if (contentLength <= downloadLength) {
//已下载字节和文件总字节相同,说明下载完成。
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//断点下载,从哪个字节开始下载 range
// .addHeader("RANGE", "bytes=" + downloadLength + "-")
.addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)
.url(dowmloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null) {
is = response.body().byteStream();
saveFile = new RandomAccessFile(file, "rw");
saveFile.seek(downloadLength);//跳过已下载字节
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1) {
if (isCancel) {//取消加载任务 //中途有人点击取消
return TYPE_CANCELED;
} else if (isPaused) { //中途有人点击暂停
return TYPE_PAUSED;
} else {
total += len;
saveFile.write(b, 0, len);
//计算下载的百分比
int progress = (int) ((total + downloadLength) * 100 / contentLength);
publishProgress(progress);
}
}
response.body().close();
return TYPE_SUCCESS;
}
} catch (Exception e) {
} finally {
try {
if (is != null)
is.close();
if (saveFile != null)
saveFile.close();
if (isCancel && file != null)
file.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
return TYPE_FAILED;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
listener.onProgess(progress);
lastProgress = progress;
}
}
@Override
protected void onPostExecute(Integer integer) {
switch (integer) {
case TYPE_SUCCESS:
listener.onSuccess();
break;
case TYPE_FAILED:
listener.onFailed();
break;
case TYPE_PAUSED:
listener.onPaused();
break;
case TYPE_CANCELED:
listener.onCanceled();
break;
}
}
public void pauseDownload() {
isPaused = true;
}
public void cancelDownlag() {
isCancel = true;
}
private long getContentLength(String dowmloadUrl) throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(dowmloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()) {
long contentLength = response.body().contentLength();
response.close();
return contentLength;
}
return 0;
}
}
2.开启服务
为了避免下载任务在执行中被回收,我们采用使用服务区下载。通过DownloadBinder区开启异步下载。并在service中实现接口回调。
package com.example.mydownload;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.database.sqlite.SQLiteDoneException;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import java.io.File;
public class DownloadService extends Service {
private DownloadTask downloadTask;
private String downloadUrl;
private DownloadListener listener = new DownloadListener() {
@Override
public void onProgess(int progress) {
getNotificationManager().notify(1, getNotification("Downloading...", progress));
}
@Override
public void onSuccess() {
downloadTask = null;
//下载成功通知前台关闭通知,并创建下一个通知
stopForeground(true);
getNotificationManager().notify(1, getNotification("Download Success", -1));
Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed() {
downloadTask = null;
//下载失败时将前台服务关闭,并创建下一个失败通知
stopForeground(true);
getNotificationManager().notify(1, getNotification("Download Failed", -1));
Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();
}
@Override
public void onPaused() {
downloadTask = null;
Toast.makeText(DownloadService.this, "Download Paused", Toast.LENGTH_SHORT).show();
}
@Override
public void onCanceled() {
downloadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this, "Download Canceled", Toast.LENGTH_SHORT).show();
}
};
public DownloadService() {
}
private DownloadBinder mBinder = new DownloadBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
class DownloadBinder extends Binder {
public void startDownload(String url) {
if (downloadTask == null) {
downloadUrl = url;
downloadTask = new DownloadTask(listener);
downloadTask.execute(downloadUrl);
startForeground(1, getNotification("下载...", 0));
Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
}
}
public void pausedDownload() {
if (downloadTask != null) {
downloadTask.pauseDownload();
}
}
public void cancelDownload() {
if (downloadTask != null) {
downloadTask.cancelDownlag();
} else {
if (downloadUrl != null) {
//取消下载时 需要将文件删除,并将通知关闭
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
//下载路径
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
File file = new File(directory + fileName);
//如果文件存在 记录文件的长度
if (file.exists()) {
file.delete();
}
getNotificationManager().cancel(1);
stopForeground(true);
Toast.makeText(DownloadService.this, "Cancel", Toast.LENGTH_SHORT).show();
}
}
}
}
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);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
builder.setContentIntent(pi);
builder.setContentTitle(title);
if (progress > 0) {
//当progress >0 或 =0 的时候才需要显示现在进度
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
}
}
3.接口
public interface DownloadListener {
void onProgess(int progress);
void onSuccess();
void onFailed();
void onPaused();
void onCanceled();
}
4.调用:
package com.example.mydownload;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.health.PackageHealthStats;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private DownloadService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (DownloadService.DownloadBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button start = findViewById(R.id.start);
Button pause = findViewById(R.id.pause);
Button cancle = findViewById(R.id.cancle);
intent = new Intent(this, DownloadService.class);
startService(intent);//启动服务
bindService(intent, connection, BIND_EXTERNAL_SERVICE);//绑定服务
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);
}
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String s = "http://mirrors.neusoft.edu.cn/eclipse/oomph/epp/2020-06/R/eclipse-inst-win64.exe";
downloadBinder.startDownload(s);
}
});
pause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downloadBinder.pausedDownload();
}
});
cancle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
downloadBinder.cancelDownload();
}
});
}
@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) {
Toast.makeText(this, "请开启权限", Toast.LENGTH_SHORT).show();
finish();
}
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopService(intent);
unbindService(connection);
}
}
5.对应的xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载" />
<Button
android:id="@+id/pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂停" />
<Button
android:id="@+id/cancle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消" />
</LinearLayout>
6.权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
转发表明出处:https://blog.csdn.net/qq_35698774/article/details/107729677
android互助群:
感谢:郭霖的《第一行代码 第二版》
本文地址:https://blog.csdn.net/qq_35698774/article/details/107729677