Android 学习笔记之——服务中的下载功能

之前博客《 Android学习笔记之——Service 》和《Android 学习笔记之——service进一步探索 》已经详细介绍了服务的各种用法。本博文尝试实现一个在服务中经常会使用到的功能——下载

 

 

 

首先创建一个ServiceBestPractice项目。

首先我们需要将项目中会使用到的依赖库添加好,编辑app/build.gradle文件,在dependencies闭包中添加如下内容:

额外新增的是

implementation 'com.squareup.okhttp3:okhttp:3.4.1'

接下来需要定义一个回调接口,用于对下载过程中的各种状态进行监听和回调。新建一个DownloadListener 接口,代码如下所示:

package com.example.servicebestpractice;

public interface DownloadListener {
    //在接口中定义一系列抽象函数
    void onProgress(int progress);//法用于通知当前的下载进度
    void onSuccess();//用于通知下载成功事件
    void onFailed();//法用于通知下载失败事件
    void onPaused();//用于通知下载暂停事件
    void onCanceled();//用于通知下载取消事件
}

回调接口定义好之后,开始编写下载功能。采用之前博客《 Android学习笔记之——Service 》介绍过的AsyncTask来进行实现

package com.example.servicebestpractice;

import android.os.AsyncTask;
import android.os.Environment;

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> {
    //首先在AsyncTask中的三个泛型参数
    //第一个泛型参数指定为String ,表示在执行AsyncTask的时候需要传入一个字符串参数给后台任务
    //第二个泛型参数指定为Integer ,表示使用整型数据来作为进度显示单位
    //第二个泛型参数指定为Integer ,表示使用整型数据来作为进度显示单位

    //接下来我们定义了4个整型常量用于表示下载的状态
    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 isCanceled = false;
    private boolean isPaused = false;
    private int lastProgress;


    //然后在DownloadTask 的构造函数中要求传入一个刚刚定义的DownloadListener 参数
    // 我们待会就会将下载的状态通过这个参数进行回调。
    public DownloadTask (DownloadListener listener){
        this.listener=listener;
    }


    //这个方法中的所有代码都会在子线程中运行,
    // 我们应该在这里去处理所有的耗时任务。
    // 任务一旦完成就可以通过return 语句来将任务的执行结果返回,
    // 如果AsyncTask的第三个泛型参数指定的是Void ,就可以不返回任务执行结果。(但是此处是Integer)
    @Override
    protected Integer doInBackground(String... params) {//用于在后台执行具体的下载逻辑
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;

        try {
            long downloadedLength = 0; // 记录已下载的文件长度
            String downloadUrl = params[0];//从参数中获取到下载的URL地址

            //指定将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录。
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));//根据URL地址解析出了下载的文件名。
            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();//要下载的目录名
            file = new File(directory + fileName);
            //判断一下Download目录中是不是已经存在要下载的文件了
            if (file.exists()) {//如果已经存在的话则读取已下载的字节数,这样就可以在后面启用断点续传的功能
                downloadedLength = file.length();
            }
            //调用了getContentLength() 方法来获取待下载文件的总长度
            long contentLength = getContentLength(downloadUrl);
            if (contentLength == 0) {//如果文件长度等于0则说明文件有问题
                return TYPE_FAILED;
            } else if (contentLength == downloadedLength) {
                // 已下载字节和文件总字节相等,说明已经下载完成了
                return TYPE_SUCCESS;
            }
            //紧接着使用OkHttp来发送一条网络请求
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
            // 断点下载,指定从哪个字节开始下载(已下载过的部分就不需要下载了)
                    .addHeader("RANGE", "bytes=" + downloadedLength + "-")//在请求中添加了一个header,用于告诉服务器我们想要从哪个字节开始下载
                    .url(downloadUrl)
                    .build();
            //接下来读取服务器响应的数据。,并使用Java的文件流方式,
            // 不断从网络上读取数据,不断写入到本地,一直到文件全部下载完成为止。
            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() 方法进行通知
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled && file != null) {
                    file.delete();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;

    }

    //定义getContentLength() 方法来获取待下载文件的总长度
    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        Response response = client.newCall(request).execute();
        if (response != null && response.isSuccessful()) {
            long contentLength = response.body().contentLength();
            response.body().close();
            return contentLength;
        }
        return 0;
    }



    //用于在界面上更新当前的下载进度
    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];//首先从参数中获取到当前的下载进度
        if (progress > lastProgress) {//和上一次的下载进度进行对比
            //如果有变化的话则调用DownloadListener的onProgress() 方法来通知下载进度更新。
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }


    //用于通知最终的下载结果
    @Override
    protected void onPostExecute(Integer status) {//根据参数中传入的下载状态来进行回调
        switch (status) {//对应不同状态就调用不同的方法
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            default:
                break;
        }
    }

    //暂停和取消操作都是使用一个布尔型的变量来进行控制的,
    // 调用pauseDownload()或cancelDownload() 方法即可更改变量的值。
    public void pauseDownload() {
        isPaused = true;
    }
    public void cancelDownload() {
        isCanceled = true;
    }

}

把具体的下载功能完成了之后,为了保证DownloadTask可以一直在后台运行,我们还需要创建一个下载的服务。右击com.example.servicebestpractice→New→Service→Service,新建DownloadService,然后修改其中的代码,如下所示:

package com.example.servicebestpractice;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
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;

    //首先这里创建了一个DownloadListener的匿名类实例,
    //并在匿名类中实现了5个方法
    private DownloadListener listener = new DownloadListener() {

        @Override
        public void onProgress(int progress) {
            //调用getNotification() 方法构建了一个用于显示下载进度的通知
            //该方法返回NotificationManager类型的数据
            //然后调用NotificationManager的notify() 方法去触发这个通知,
            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();
        }

        //类似于onSuccess(),这里是告诉用户下载失败
        @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();
        }

        //类似于onSuccess(),这里是告诉用户下载暂停
        @Override
        public void onPaused() {
            downloadTask = null;
            Toast.makeText(DownloadService.this, "Paused", Toast.LENGTH_SHORT).show();
        }

        //类似于onSuccess(),这里是告诉用户下载取消
        @Override
        public void onCanceled() {
            downloadTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
        }
    };

    //为了让DownloadService可以和活动进行通信。
    //创建了一个DownloadBinder
    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;//下载文件的URL地址
                downloadTask = new DownloadTask(listener);
                downloadTask.execute(downloadUrl);//调用execute() 方法开启下载

                //为了让这个下载成为一个前台服务。调用了startForeground() 方法
                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();
                }
            }
        }
    }


    //定义一个函数getNotification() ,用于显示下载进度的通知
    private NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

    //DownloadService 类中所有使用到的通知都是调用getNotification() 方法进行构建的
    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时才需显示下载进度
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();
    }
}

接下来修改activity_main.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/start_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Download" />
    <Button
        android:id="@+id/pause_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pause Download" />
    <Button
        android:id="@+id/cancel_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Cancel Download" />
</LinearLayout>

然后修改MainActivity中的代码,如下所示:

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.servicebestpractice">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        android:usesCleartextTraffic="true"
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".DownloadService"
            android:enabled="true"
            android:exported="true" />
    </application>
</manifest>

 

运行结果

这是由于Android9.0对未加密的流量不在信任,添加了新的限制。

在Android 的mainfest.xml中的application添加一句配置

android:usesCleartextTraffic="true"

但是仍然是运行不了。。。看来就是之前遇到的前台服务没有解决而遗留下来的问题了。。。。

 

 

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值