本篇主要介绍的是利用服务来进行文件下载功能,其中支持:开始下载,暂停下载,取消下载,断点续传等功能。
步骤如下:
一、添加远程依赖库okhttp3,这是目前最好用的一款网络服务框架。
/*okHttp网络请求*/
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
二、写一个接口,用于对下载过程中的各种状态进行监听和回调。
/**
* 下载状态的监听接口
*/
public interface DownLoadListener {
void onProgress(int progress);
void onSuccess();
void onFailed();
void onPaused();
void onCanceled();
}
三、下面开始编写下载功能,这里我这边用的是异步任务AsyncTask来进行实现。
/**
* 下载的异步任务
*/
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 boolean isPaused = false;
private boolean isCanceled = false;
private int lastProgress;
private DownLoadListener downLoadListener;
public DownLoadTask(DownLoadListener downLoadListener) {
this.downLoadListener = downLoadListener;
}
public void setDownLoadListener(DownLoadListener downLoadListener) {
this.downLoadListener = downLoadListener;
}
@Override
protected Integer doInBackground(String... strings) {
InputStream inputStream = null;
RandomAccessFile randomAccessFile = null;
File file = null;
try {
long downLoadLength = 0;//记录已下载的文件长度
String downLoadUrl = strings[0];
//获取文件名称
String fileName = downLoadUrl.substring(downLoadUrl.lastIndexOf("/"));
//外部存储的公共文件夹目录
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
file = new File(directory, fileName);
if (file.exists()) {
downLoadLength = file.length();
}
long contentLength = getContentLength(downLoadUrl);
if (contentLength == 0) {
return TYPE_FAILED;
} else if (contentLength == downLoadLength) {
//如果已下载的字节和文件总字节相等,说明已经下载完成了
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.addHeader("RANGE", "bytes=" + downLoadLength + "-") //断点继续下载
.url(downLoadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null) {
inputStream = response.body().byteStream();
randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(downLoadLength);//跳过已下载的字节
byte[] bytes = new byte[1024];
int total = 0;
int len;
//inputStream.read(bytes)--读取多个字节写到bytes中
while ((len = inputStream.read(bytes)) != -1) {
if (isCanceled) {
return TYPE_CANCELED;
} else if (isPaused) {
return TYPE_PAUSED;
} else {
total += len;
randomAccessFile.write(bytes, 0, len);
int progress = (int)((total + downLoadLength) * 100 / contentLength);
publishProgress(progress);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
if (isCanceled && file != null) {
file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return TYPE_FAILED;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
downLoadListener.onProgress(progress);
lastProgress = progress;
}
}
@Override
protected void onPostExecute(Integer integer) {
switch (integer) {
case TYPE_SUCCESS: {
downLoadListener.onSuccess();
break;
}
case TYPE_FAILED: {
downLoadListener.onFailed();
break;
}
case TYPE_PAUSED: {
downLoadListener.onPaused();
break;
}
case TYPE_CANCELED: {
downLoadListener.onCanceled();
break;
}
default:break;
}
}
/**
* 暂停下载
*/
public void pauseDownLoad() {
this.isPaused = true;
}
/**
* 取消下载
*/
public void cancelDownLoad() {
this.isCanceled = true;
}
/**
* 获取文件长度
*
* @param downLoadUrl
* @return
*/
private long getContentLength(String downLoadUrl) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(downLoadUrl).build();
try {
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()) {
long contentLength = response.body().contentLength();
response.body().close();
return contentLength;
}
} catch (IOException e) {
e.printStackTrace();
return 0;
}
return 0;
}
}
·四、开始写服务,这是为了让下载的异步任务可以一直运行在后台。(比如,当前Activity关闭,当时下载服务不受影响)
备注:(1)因为服务本身就是运行在后台的,不与活动生命周期相关,只与当前应用程序的生命周期相关!
(2)因为还要控制下载的开始、暂停与取消,所以这里要用BindService绑定服务。
/**
* 后台下载服务
*/
public class DownLoadService extends Service {
private DownLoadTask downLoadTask;
private String downLoadUrl;
private DownLoadListener downLoadListener = new DownLoadListener() {
@Override
public void onProgress(int progress) {
getNotificationManager().notify(1, getNotification("正在下载", progress));
}
@Override
public void onSuccess() {
downLoadTask = null;
stopForeground(true);
getNotificationManager().notify(1, getNotification("下载完成", -1));
Toast.makeText(DownLoadService.this, "下载完成!", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailed() {
downLoadTask = null;
stopForeground(true);
getNotificationManager().notify(1, getNotification("下载失败", -1));
Toast.makeText(DownLoadService.this, "下载失败!", Toast.LENGTH_SHORT).show();
}
@Override
public void onPaused() {
downLoadTask = null;
Toast.makeText(DownLoadService.this, "下载暂停!", Toast.LENGTH_SHORT).show();
}
@Override
public void onCanceled() {
downLoadTask = null;
stopForeground(true);
Toast.makeText(DownLoadService.this, "下载取消!", Toast.LENGTH_SHORT).show();
}
};
public DownLoadService() {
}
/**
* 自定义Binder类
*/
class DownLoadBinder extends Binder {
/**
* 开始下载
*
* @param url
*/
public void startDownLoad(String url) {
if (downLoadTask == null) {
downLoadUrl = url;
downLoadTask = new DownLoadTask(downLoadListener);
downLoadTask.execute(downLoadUrl);
startForeground(1, getNotification("开始下载", 0));
}
}
/**
* 暂停下载
*/
public void pauseDownLoad() {
if (downLoadTask != null) {
downLoadTask.pauseDownLoad();
}
}
/**
* 取消下载
*/
public void cancelDownLoad() {
if (downLoadTask != null) {
downLoadTask.cancelDownLoad();
}
//文件删除
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, "下载取消!", Toast.LENGTH_SHORT).show();
}
}
}
private DownLoadBinder downLoadBinder = new DownLoadBinder();
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return downLoadBinder;
}
private NotificationManager getNotificationManager() {
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
/**
* 显示通知
*
* @param title
* @param progress
* @return
*/
private Notification getNotification(String title, int progress) {
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//创建渠道
String id = "my_channel_01";
String name="channelName";
NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
getNotificationManager().createNotificationChannel(mChannel);
//设置图片,通知标题,发送时间,提示方式等属性
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, id);
builder.setContentTitle(title) //标题
.setWhen(System.currentTimeMillis()) //系统显示时间
.setSmallIcon(R.mipmap.ic_launcher) //收到信息后状态栏显示的小图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//大图标
.setAutoCancel(true); //设置点击后取消Notification
builder.setContentIntent(pendingIntent); //绑定PendingIntent对象
if (progress >= 0) {
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
} else {
//设置图片,通知标题,发送时间,提示方式等属性
Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle(title) //标题
.setWhen(System.currentTimeMillis()) //系统显示时间
.setSmallIcon(R.mipmap.ic_launcher) //收到信息后状态栏显示的小图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//大图标
.setAutoCancel(true); //设置点击后取消Notification
builder.setContentIntent(pendingIntent); //绑定PendingIntent对象
if (progress >= 0) {
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
}
}
}
五、下载服务已创建完成,接下来就要开始使用了。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final int WRITE_PERMISSION_CODE = 1000;
//文件下载链接
private String url = "http://XXX/50016582633.mp4";
private Context mContext;
private Button btnStartDownLoad, btnPauseDownLoad, btnCancelDownLoad;
private DownLoadService.DownLoadBinder downLoadBinder;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downLoadBinder = (DownLoadService.DownLoadBinder) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
baseDataInit();
bindViews();
viewsAddListener();
viewsDataInit();
}
private void baseDataInit() {
mContext = this;
}
private void bindViews() {
btnStartDownLoad = findViewById(R.id.Main_btnStartDownLoad);
btnPauseDownLoad = findViewById(R.id.Main_btnPauseDownLoad);
btnCancelDownLoad = findViewById(R.id.Main_btnCancelDownLoad);
}
private void viewsAddListener() {
btnStartDownLoad.setOnClickListener(this);
btnPauseDownLoad.setOnClickListener(this);
btnCancelDownLoad.setOnClickListener(this);
}
private void viewsDataInit() {
checkUserPermission();
Intent intent = new Intent(mContext, DownLoadService.class);
startService(intent);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
@Override
public void onClick(View view) {
if (downLoadBinder == null) {
return;
}
switch (view.getId()) {
case R.id.Main_btnStartDownLoad: {
downLoadBinder.startDownLoad(url);
break;
}
case R.id.Main_btnPauseDownLoad: {
downLoadBinder.pauseDownLoad();
break;
}
case R.id.Main_btnCancelDownLoad: {
downLoadBinder.cancelDownLoad();
break;
}
default:break;
}
}
/**
* 检查用户权限
*/
private void checkUserPermission() {
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_PERMISSION_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case WRITE_PERMISSION_CODE: {
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(mContext, "拒绝权限将无法开启下载服务", Toast.LENGTH_SHORT).show();
}
break;
}
default:break;
}
}
}
我这边activity_main.xml布局就是用了三个按钮,你们自己添加即可,就不贴代码了。
另外,别忘了在清单文件里添加用户权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
相应的demo地址:https://download.csdn.net/download/lpcrazyboy/11055398