android学习体验之下载器demo

背景

当我们在使用app时,经常会遇到需要下载相关资料的情况。此时,下载任务应在后台执行且不应阻塞用户操作的主进程,同时需要像用户暴露出相关操作接口:开始 / 暂停 / 继续,并使用进度条作为标识提醒。

下载功能实现part

android学习体验之下载器demo_第1张图片
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_第2张图片
这里,我们的通知消息使用的是 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

  • 页面布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ... >

   <Button
   		...
       android:text="Start Download"
       />

    <Button
        ...
        android:text="Pause Download"
        />

    <Button
        ...
        android:text="Cancel Download"
        />

LinearLayout>
  • 绑定相关事件
  • 权限声明
    这里需要开启 **联网权限 / 存储读写权限 / 开启前台服务权限 **
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

踩坑

因参考资料所使用的Android版本较老,所以遇到了 安卓开发8.1以上系统启动服务和通知报错
android.app.RemoteServiceException: Bad notification for startForeground: java.lang.RuntimeException
解决方案可参考
此处问题持续更新…

你可能感兴趣的:(前端,计算机)