前端|android学习体验之下载器demo

背景
当我们在使用app时,经常会遇到需要下载相关资料的情况。此时,下载任务应在后台执行且不应阻塞用户操作的主进程,同时需要像用户暴露出相关操作接口:开始 / 暂停 / 继续,并使用进度条作为标识提醒。
下载功能实现part
前端|android学习体验之下载器demo
文章图片

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】前端|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
解决方案可参考
此处问题持续更新…

    推荐阅读