背景
当我们在使用app时,经常会遇到需要下载相关资料的情况。此时,下载任务应在后台执行且不应阻塞用户操作的主进程,同时需要像用户暴露出相关操作接口:开始 / 暂停 / 继续,并使用进度条作为标识提醒。
下载功能实现part
文章图片
doInBackground方法中我们可以获取到下载的URL地址,并解析出下载文件名作为存入SD卡中的文件名。在这里需要对文件的长度进行判断(在请求中加入一个header用于告诉服务器下载开始的字节),从而获取到当前文件的下载信息。
protected Integer doInBackground(String... params) {
//在后台执行具体的下载逻辑
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try {
long downloadedLength = 0;
// 记录已下载的文件长度
String downloadUrl = params[0];
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
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){
return TYPE_FAILED;
}else if(contentLength == downloadedLength){
// 已下载的字节和文件总字节相等,说明已经下载完成了
return TYPE_SUCCESS;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//断点下载,指定从哪个字节开始下载
.addHeader("RANGE", "bytes=" + downloadedLength + "-")
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if(response != null) {
is = response.body().byteStream();
savedFile = new RandomAccessFile(file, "rw");
savedFile.seek(downloadedLength);
//跳过已下载的字节
byte[] b = new byte[1024];
int total = 0;
int len;
while((len = is.read(b)) != -1) {
if(isCanceled) {
return TYPE_CANCELED;
}else if(isPaused) {
return TYPE_PAUSED;
}else {
total += len;
savedFile.write(b, 0, len);
//计算已下载的百分比
int progress = (int) ((total + downloadedLength) * 100 / contentLength);
publishProgress(progress);
}
}
response.body().close();
return TYPE_SUCCESS;
}} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(is != null) {
is.close();
}
if(savedFile != null) {
savedFile.close();
}
if(isCanceled && file != null) {
file.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return TYPE_FAILED;
}
下载服务实现part
【前端|android学习体验之下载器demo】
文章图片
这里,我们的通知消息使用的是 getNotification方法来构建,然后使用NotificationManager中的notify方法来触发相关的通知。
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 (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//修改安卓8.1以上系统报错
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ONE_ID, CHANNEL_ONE_NAME,NotificationManager.IMPORTANCE_MIN);
notificationChannel.enableLights(false);
//如果使用中的设备支持通知灯,则说明此通知通道是否应显示灯
notificationChannel.setShowBadge(false);
//是否显示角标
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.createNotificationChannel(notificationChannel);
builder.setChannelId(CHANNEL_ONE_ID);
}
if(progress > 0){
//当progress大于等于0时才需要显示下载进度
builder.setContentText(progress + "%");
builder.setProgress(100, progress, false);
}
return builder.build();
}
使用DownloadBinder作为中间人将服务与活动进行绑定
class DownloadBinder extends Binder {
public void startDownload(String url) {
if(downloadTask == null){
downloadUrl = url;
downloadTask = new DownloadTask(listener);
downloadTask.execute(downloadUrl);
//传入URL并执行下载
//使下载服务成为一个前台服务
startForeground(1, getNotification("Downloading...", 0));
Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
}
}public void pauseDownload(){
if(downloadTask != null){
downloadTask.pauseDownload();
}
}public void cancelDownload(){
if(downloadTask != null){
downloadTask.cancelDownload();
}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, "Canceled", Toast.LENGTH_SHORT).show();
}
}
}
}
MainActivity
- 页面布局
- 绑定相关事件
- 权限声明
这里需要开启 **联网权限 / 存储读写权限 / 开启前台服务权限 **
踩坑
因参考资料所使用的Android版本较老,所以遇到了 安卓开发8.1以上系统启动服务和通知报错
android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException
解决方案可参考
此处问题持续更新…
推荐阅读
- web网页模板|如此优秀的JS轮播图,写完老师都沉默了
- 接口|axios接口报错-参数类型错误解决
- JavaScript|vue 基于axios封装request接口请求——request.js文件
- JavaScript|JavaScript — 初识数组、数组字面量和方法、forEach、数组的遍历
- JavaScript|JavaScript — call()和apply()、Date对象、Math、包装类、字符串的方法
- 前端|web前端dya07--ES6高级语法的转化&render&vue与webpack&export
- vue|Vue面试常用详细总结
- javascript|vue使用js-xlsx导出excel,可修改格子样式,例如背景颜色、字体大小、列宽等
- css|我用css精灵图拼接了自己的英文名字,不会还有人不知道精灵图技术吧()
- css|css三角的做法及其案例