Service实战:使用Service完成一个下载任务

来源:互联网 发布:邮政网络银行 编辑:程序博客网 时间:2024/06/05 23:00

1.新建一个项目,网络方面使用okhttp来完成。在build.gradle中添加依赖:

    compile 'com.squareup.okhttp3:okhttp:3.7.0'

2..定义一个回调接口

    /**     * 回调接口,对下载状态进行监听     * Created by lmy on 2017/4/26.     */    public interface DownloadListener {        void onProgress(int progress);//通知当前下载进度        void onSuccess();//下载成功        void onFaild();//失败        void onPaused();//暂停下载        void onCancled();//取消下载    }   

3.使用 AsyncTask来实现异步下载:

先简要介绍下asynctask的泛型。

    AsyncTask<Params, Progress, Result>

三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。是不是听着很拗口, 我一般会把它简单的理解为事件的起因,经过和结果,很好理解也好记。

在我公司的实际项目中,第一个就是我们网络请求需要的传给后台的参数,第二个参数经常用的Void,第三个参数一般为 List<>,存储请求到的数据。

这里我们泛型为String,Integer,Integer,String表示要给后台一个字符串url,即下载的地址,两个Integer分别表示使用整型来显示下载进度和反馈下载结果。(怎么样,很清晰吧)。

上代码(注释写得那是相当的详细):

/** * 异步下载任务 * Created by lmy on 2017/4/26. */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_CANCLED = 3;    private DownloadListener listener;    private boolean isPaused = false;    private boolean isCancled = false;    private int lastProgress;    //构造方法中传入我们定义的接口,待会就可以把下载的结果通过这个参数进行回调    public DownLoadTask(DownloadListener listener) {        this.listener = listener;    }    /**     * 后台任务开始执行之前调用,用于进行一些界面上的初始化操作,如显示进度条。     */    @Override    protected void onPreExecute() {        super.onPreExecute();    }    /**     * 后台任务:     * 子线程中执行耗时操作。任务完成可以用return语句来返回任务的结果。     * 如果需要更新UI,可以调用 publishProgress();     *     * @param params 这里的参数就是根据我们制指定的泛型来的     * @return     */    @Override    protected Integer doInBackground(String... params) {        InputStream inputStream = null;        RandomAccessFile savedFile = null;//RandomAccessFile 是随机访问文件(包括读/写)的类        File file = null;        try {            long downloadLength = 0;//记录已下载的文件的长度(默认为0)            String downloadUrl = params[0];            //截取下载的URL的最后一个"/"后面的内容,作为下载文件的文件名            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));            //将文件下载到sd卡的根目录下//          String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();            String directory = Environment.getExternalStorageDirectory().getAbsolutePath();            file = new File(directory + fileName);            if (file.exists()) {//判断文件是否已经存在                downloadLength = file.length();//如果文件已经存在,读取文件的字节数。(这样后面能开启断点续传)            }            long contentLength = getContentLength(downloadUrl); //获取待下载文件的总长度            if (contentLength == 0) {                return TYPE_FAILED;//待下载文件字节数为0,说明文件有问题,直接返回下载失败。            }            else if (downloadLength == contentLength) {                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) {//返回数据不为空,则使用java文件流的方式,不断把数据写入到本地                inputStream = response.body().byteStream();                savedFile = new RandomAccessFile(file, "rw");                savedFile.seek(downloadLength);//断点续传--跳过已经下载的字节                int total = 0;//记录此次下载的字节数,方便计算下载进度                byte[] b = new byte[1024];                int len;                while ((len = inputStream.read(b)) != -1) {                    //下载是一个持续过程,用户随时可能暂停下载或取消下载                    //所以把逻辑放在循环中,在整个下载过程中随时进行判断                    if (isCancled) {                        return TYPE_CANCLED;                    } else if (isPaused) {                        return TYPE_PAUSED;                    } else {                        total += len;                        savedFile.write(b, 0, len);                        //计算已经下载到的百分比                        int progress = (int) ((total + downloadLength) * 100 / contentLength);                        publishProgress(progress);                    }                }                response.body().close();                return TYPE_SUCCESS;            }        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                if (inputStream != null) {                    inputStream.close();                }                if (savedFile != null) {                    savedFile.close();                }                if (isCancled && file != null) {                    file.delete();//如果已经取消,并且文件不为空,则删掉下载的文件                }            } catch (IOException e) {                e.printStackTrace();            }        }        return TYPE_FAILED;    }    /**     * 当在后台任务中调用了publishProgress()后,onProgressUpdate很快就会被执行。     *     * @param values 参数就是在后台任务中传过来的,这个方法中可以更新UI。     */    @Override    protected void onProgressUpdate(Integer... values) {        int progress = values[0];        if (progress > lastProgress) {            listener.onProgress(progress);            lastProgress = progress;        }    }    /**     * 当后台任务执行完毕并调用return返回时,这个方法很快会被调用。返回的数据会被作为参数传到这个方法中     * 可根据返回数据更新UI。提醒任务结果,关闭进度条等。     *     * @param integer     */    @Override    protected void onPostExecute(Integer integer) {        //把下载结果通过接口回调传出去        switch (integer) {            case TYPE_SUCCESS:                listener.onSuccess();                break;            case TYPE_FAILED:                listener.onFailed();                break;            case TYPE_PAUSED:                listener.onPaused();                break;            case TYPE_CANCLED:                listener.onCanceled();                break;            default:                break;        }    }    //暂停下载    public void pausedDownload() {        isPaused = true;    }    //取消下载    public void cancledDownload() {        isCancled = true;    }    /**     * 获取待下载文件的字节数     *     * @param downloadUrl     * @return     * @throws IOException     */    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;    }}

这样,下载的功能就已经实现了。下面为了保证downloadTask能一直在后台执行,我们创建一个用来下载的Service。新建一个DownloadService,代码如下:

/** * 用于下载的Service * Created by lmy on 2017/4/27. */public class DownloadService extends Service {    @Nullable    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    private DownloadBinder mBinder=new DownloadBinder();    private DownLoadTask downLoadTask;//要通过服务来下载,当然要在服务中创建下载任务并执行。    private String downloadUrl;    //创建一个下载的监听    private DownloadListener listener = 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));        }        //下载失败        @Override        public void onFailed() {            downLoadTask = null;            //下载失败时将前台服务通知关闭,并创建一个下载成功的通知            getNotificationManager().notify(1, getNotification("下载失败!", -1));        }        //暂停下载        @Override        public void onPaused() {            downLoadTask=null;        }        //取消下载        @Override        public void onCanceled() {            downLoadTask=null;            stopForeground(true);        }    };    /**     * 代理对象:在这里面添加三个方法:     * 开始下载,暂停下载,取消下载     * 就可以在Activity中绑定Service,并控制Service来实现下载功能     */    class DownloadBinder extends Binder {        //开始下载,在Activity中提供下载的地址        public void startDownload(String url) {            if (downLoadTask == null) {                downLoadTask = new DownLoadTask(listener);                downloadUrl = url;                downLoadTask.execute(downloadUrl);                startForeground(1, getNotification("正在下载...", 0));//开启前台通知            }        }        //暂停下载        public void pausedDownload() {            if (downLoadTask != null) {                downLoadTask.pausedDownload();            }        }        //取消下载        public void cancledDownload() {            if (downLoadTask != null) {                downLoadTask.cancledDownload();            } else {                if (downloadUrl != null) {                    //取消下载时需要将下载的文件删除  并将通知关闭                    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));                    String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getParent();                    File file = new File(directory + fileName);                    if (file.exists()) {                        file.delete();                    }                    getNotificationManager().cancel(1);                    stopForeground(true);                }            }        }    }    private Notification getNotification(String title, int progress) {        Intent intent = new Intent(this, DownloadActivity.class);        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);        builder.setSmallIcon(R.drawable.liuyifei);        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.liuyifei));        builder.setContentIntent(pendingIntent);        builder.setContentTitle(title);        if (progress >= 0) {            builder.setContentText(progress + "%");            builder.setProgress(100, progress, false);//最大进度。当前进度。是否使用模糊进度        }        return builder.build();    }    //获取通知管理器    private NotificationManager getNotificationManager() {        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);    }}

然后是我们再Activity中调用startService和bindService来启动并绑定服务。
startService保证我们的Service长期在后台运行,bindService则能够让Activity和Service通信,就可以通过控制Service达到随时暂停或开始或取消下载。

Activity的布局很简单,如下:


这里写图片描述

Activity代码如下:

/** * Created by lmy on 2017/4/27. */public class DownloadActivity extends AppCompatActivity implements View.OnClickListener {    @InjectView(R.id.start_download)    Button startDownload;    @InjectView(R.id.paused_download)    Button pausedDownload;    @InjectView(R.id.cancel_download)    Button cancelDownload;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_download);        ButterKnife.inject(this);        startDownload.setOnClickListener(this);        pausedDownload.setOnClickListener(this);        cancelDownload.setOnClickListener(this);        Intent intent = new Intent(this, DownloadService.class);        startService(intent);//启动服务        bindService(intent, connection, BIND_AUTO_CREATE);//绑定服务        //运行时权限申请        if (ContextCompat.checkSelfPermission(DownloadActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)                != PackageManager.PERMISSION_GRANTED) {            ActivityCompat.requestPermissions(DownloadActivity.this,                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);        }    }    private DownloadService.DownloadBinder downloadBinder;    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            downloadBinder = (DownloadService.DownloadBinder) service;        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    public void onClick(View v) {        if (downloadBinder == null) {            return;        }        switch (v.getId()) {            case R.id.start_download:                //这里我们的下载地址是郭神提供的eclipse下载地址,致敬!                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";                downloadBinder.startDownload(url);                break;            case R.id.paused_download:                downloadBinder.pausedDownload();                break;            case R.id.cancel_download:                downloadBinder.cancledDownload();                break;            default:                break;        }    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        switch (requestCode) {            case 1:                if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {                    Toast.makeText(DownloadActivity.this, "拒绝权限将无法使用程序!", Toast.LENGTH_SHORT).show();                    finish();                }                break;            default:                break;        }    }    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(connection);    }

}
另外不要忘记添加权限:

<use-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

测试:点击开始下载:


这里写图片描述

我这个网速好慢啊!。。。
等等。。。
再等等。。。
快了。。。

这里写图片描述

你可以通过点击开始,暂停,取消,甚至断网来测试这个程序的健壮性。最终下载完成会弹出一个下载“下载成功!”的通知。(对了,由于我这个测试机是android4.2版本的,所以下载的时候没有提示运行时权限。)

终于下载好了:


这里写图片描述

打开我的手机上面的文件管理:
可以看到我们下载的文件:


这里写图片描述

以上,我们使用Service进行下载就大功告成了!结合前面我的两篇文章,对Service的解析算是比较全面了!

2 0
原创粉丝点击