上一讲学习了很多关于服务的使用技巧,但是当在真正的项目里需要用到服务的时候,可能还会有一些棘手的问题让你不知所措。接下来就来综合运用一下,尝试实现一下在服务中经常会使用到的功能——下载。

  在这一讲我们将要编写一个完整版的下载示例,其中会涉及到前面的许多内容,算是目前综合程度最高的一个例子了。

1、创建一个ServiceBestPractice项目

2、首先将项目中会使用到的依赖库添加好,编辑app/build.gradle文件,在dependencies闭包中添加如下内容,这里只需要添加一个OkHttp的依赖就行了,待会儿在编写网络相关的功能时,将使用OkHttp来进行实现。

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

  可以看到,这里我们一共定义了5个回调方法:

  onProgress():用于通知当前的下载进度

  onSuccess():用于通知下载成功事件

  onFailed():用于通知下载失败事件

  onPaused():用于通知下载暂停事件

  onCanceled():用于通知下载取消事件

4、回调接口定义好了后,下面开始编写下载功能了,这里用学过的AsyncTask来进行实现,新建一个DownloadTask继承自AsyncTask:

代码分析:

  这段代码比较长,需要一步一步分析,首先看一下AsyncTask中的3个泛型参数:第一个泛型参数指定为String,表示在执行AsyncTask的时候需要传入一个字符串参数给后台任务;第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位;第三个泛型参数指定为Integer,则表示使用整型数据来反馈执行结果。

  接下来我们定义了4个整型常量用于表示下载的状态,TYPE_SUCCESS:表示下载成功;TYPE_FAILED:表示下载失败;TYPE_PAUSED:表示下载暂停;TYPE_CANCELED表示取消下载。然后在DownloadTask的构造函数中要求传入一个刚刚定义的DownloadListener参数,我们待会就会将下载的状态通过这个参数进行回调。

  接着就是要重写doInBackground()、onProgressUpdate()、onPostExecute()这3个方法了,我们之前学过这3个方法各自的作用,因此在这里它们各自所负责的任务也是明确的:  

  doInBackground():用于在后台执行具体的下载逻辑

  onProgressUpdate():用于在界面上更新当前的下载进度

  onPostExecute():用于通知最终的下载结果

  那么先来看一下doInBackground()方法,首先我们从参数中获取到了下载的URL地址,并根据URL地址解析出了下载的文件名,然后指定将文件下载到:Environment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录。我们还要判断一下Download目录中是不是已经存在要下载的文件了,如果已经存在的话则读取已下载的字节数,这样就可以在后面启用断点续传的功能。接下来先是调用了getContentLength()方法来获取待下载的总长度,如果文件长度等于0则说明文件有问题,直接返回TYPE_FAILED,如果文件长度等于已下载文件长度,那么就说明文件已经下载完了,直接返回TYPE_SUCCESS即可。紧接着使用OkHttp来发送一条网络请求,需要注意的是,这里在请求中添加了一个header,用于告诉服务器我们想要从哪个字节开始下载,因为已下载过的部分就不需要在重新下载了。接下来读取服务器响应的数据,并使用Java的文件流方式,不断从网络上读取数据,不断写入到本地,一直到文件全部下载完成为止。在这个过程中,我们还要判断用户有没有触发暂停或者取消的操作,如果有的话则返回TYPE_PAUSED或TYPE_CANCELED来中断下载,如果没有的话则实时计算当前的下载进度,然后调用publishProgress()方法进行通知。暂停和取消操作都是使用一个布尔型的变量来进行控制的,调用pausedDownload()或cancelDownload()方法即可更改变量的值。

  接下来看一下onProgressUpdate()方法,这个方法就简单得多了,它首先从参数中获取到当前的下载进度,然后和上一次的下载进度进行对比,如果有变化的话则调用DownloadListener的onProgress()方法来通知下载进度更新。

  最后是onPostExecute()方法,也非常简单,就是根据参数中传入的下载状态来进行回调。下载成功就调用DownloadListener的onSuccess()方法,下载失败就调用onFailed()方法,暂停下载就调用onPaused()方法,取消下载就调用onCanceled()方法。

  这样我们就把具体的下载功能完成了。

5、为了保证DownloadTask可以一直在后台运行,我们还需要创建一个下载的服务。新建一个DownloadService

代码分析:

  这段代码同样也比较长,还是来耐心慢慢看吧。首先这里创建了一个DownloadListener的匿名类实例,并在匿名类中实现了onProgress()、onSuccess()、onFailed()、onPaused()、onCanceled()这5个方法。在onProgress()方法中,我们调用getNotification()方法构建了一个用于显示下载进度的通知,然后调用NotificationManager的notify方法去触发这个通知,这样就可以在下拉状态栏中实时看到当前下载的进度了。在onSuccess()方法中,我们首先是将正在下载的前台通知关闭,然后创建了一个新的通知用于告诉用户下载成功了。其他几个方法也都是类似的,分别告诉用户下载失败、暂停和取消这几个事件。

  接下来为了要让DownloadService可以和活动进行通信,我们又创建了一个DownloadBinder,DownloadBinder中提供了startDownload()、pauseDownload()和cancelDownload()这3个方法,那么顾名思义,它们分别是用于开始下载、暂停下载、和取消下载的。在startDownload()方法中,我们创建了一个DownloadTask的实例,把刚才的DownloadListener作为参数传入,然后调用execute()方法开启下载,并将下载文件的URL地址传入到execute()方法中。同时,为了让这个下载服务成为一个前台服务,我们还调用了startForeground()方法,这样就会在系统状态栏中创建一个持续运行的通知了。接着往下看,pausedDownload()方法中的代码非常简单了,就是简单的调用了一下DownloadTask中的pauseDownload()方法。cancelDownload()方法中的逻辑也基本类似,但是要注意,取消下载的时候我们需要将正在下载的文件删除掉,这一点和暂停下载是不同的。

  另外,DownloadService类中所有使用到的通知都是调用getNotification()方法进行构建的,这个方法中的代码我们之前基本都是学过的,只有一个setProgress()方法没有见过。setProgress()方法接收3个参数,第一个参数传入通知的最大进度,第二个参数传入通知的当前进度,第三个参数表示是否使用模糊进度条,这里传入false。设置完setProgress()方法,通知上就会有进度条显示出来了。

  现在下载的服务也已经成功实现了,后端的工作基本都完成了。

6、接下来开始编写前端部分,修改activity_main.xml中的代码,如下所示:设置了开始下载、暂停下载、取消下载这3个按钮。

7、修改MainActivity中的代码:

代码分析:

  可以看到,这里我们首先创建了一个ServiceConnection的匿名类,然后在onServiceConnected()方法中获取到DownloadBinder的实例,有了这个实例。我们就可以在活动中调用服务提供的各种方法了。

  接下来看一下onCreate()方法,在这里我们对各个按钮都进行了初始化操作并设置了点击事件,然后分别调用了startService()和bindService()方法来启动和绑定服务。这一点至关重要,因为启动服务可以保证DownloadService一直在后台运行,绑定服务则可以让MainActivity和DownloadService进行通信,因此两个方法调用都必不可少。在onCreate()方法的最后,我们还进行了:WRITE_EXTERNAL_STORAGE的运行时权限申请,因为下载文件是要下载到SD卡的Download目录下的,如果没有这个权限的话,我们这个程序都无法正常工作。

  接下来的代码就非常简单了,在onClick()方法中我们对点击事件进行了判断,如果点击了开始按钮就调用DownloadBinder的startDownload()方法,如果点击了暂停按钮就调用pauseDownload()方法,如果点击了取消按钮就调用cancelDownload()方法。startDownload()方法中你可以传入任意的下载地址,这里我们使用了一个Eclipse的下载地址。

  另外还需要注意,如果活动被销毁了,那么一定要记得对服务进行解绑,不然就有可能会造成内存泄漏。这里我们在onDestroy()方法中完成了解绑操作。

8、现在还只有最后一步,我们还需要在AndroidManifest.xml文件中声明使用到的权限。当然除了权限外,MainActivity和DownloadService也是需要声明的,只不过Android Studio在创建的时候已经帮我们声明好了。

  其中,由于我们的程序使用到了网络和访问SD卡的功能,因此需要声明:INTERNET和WRITE_EXTERNAL_STORAGE这两个权限。

9、运行程序,程序一旦启动立刻就会有申请访问SD卡的权限,这里我们点击ALLOW,然后点击Start Download开始下载,下拉状态栏可以看到下载进度。

  

  同时,我们还可以点击Pause Download或Cancel Download,甚至断网操作来测试这个下载程序的健壮性。最终下载完成后会弹出一个Download Success的通知,然后我们可以通过任意一个文件浏览器来查看一下SD卡的Download目录,如图所示:

  

  可以看到文件下载成功了,当然我们还可以做一些更加丰富的操作,比如说再次点击Start Download按钮,你会发现程序会立刻弹出一个Download Success的提示,因为它检测到文件已经下载完成了,因而不会再重新去下载一遍,如果我们点击Cancel Download按钮先将下载文件删除掉,然后再点击Start Download按钮,你会发现程序又会开始重新下载了。

  总体来说,这个下载实例的稳定性还是挺不错的,而且综合性很强。

Android学习之基础知识十三 — 四大组件之服务详解第二讲(完整版的下载示例)的更多相关文章

  1. Android学习之基础知识十三 — 四大组件之服务详解第一讲

    一.服务是什么 服务(Service)是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务.服务的运行不依赖于任何用户界面,即使程序被切换到后台, ...

  2. Android学习之基础知识二(build.gradle文件详解)

    一.详解build.gradle文件 1.Android Studio是采用Gradle来创建项目的,Gradle是非常先进的构建的项目的工具,基于Groovy领域特定的语言(DSL)来声明项目配置, ...

  3. Android 短信模块分析(二) MMS中四大组件核心功能详解

    接下来的分析先从MMS中四大组件(Activity ,BroadCastReceiver,Service,ContentProvider),也是MMS中最核心的部分入手: 一. Activity  1 ...

  4. Android基础知识、四大组件(转)

    Android应用程序使用java语言编写的.Android SDK工具将所有的数据和资源文件以及代码进行编译,打包称为一个apk文件.一个apk文件中的所有代码被认为是一个应用,android系统的 ...

  5. Android学习之基础知识十四 — Android特色开发之基于位置的服务

    一.基于位置的服务简介 LBS:基于位置的服务.随着移动互联网的兴起,这个技术在最近的几年里十分火爆.其实它本身并不是什么时髦的技术,主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动 ...

  6. Android学习之基础知识十—内容提供器(Content Provider)

    一.跨程序共享数据——内容提供器简介 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能 ...

  7. Android学习之基础知识一

    一.Android的系统架构: 1.Linux内核层:提供Android硬件的各种驱动(显示驱动,音频驱动,蓝牙驱动,WiFi驱动等等) 2.系统运行库层:提供各种特性支持(数据库支持,绘图支持,浏览 ...

  8. Android学习之基础知识五—ListView控件(最常用和最难用的控件)

    ListView控件允许用户通过上下滑动来将屏幕外的数据拉到屏幕内,把屏幕内的数据拉到屏幕外. 一.ListView的简单用法第一步:先创建一个ListViewTest项目,在activity_mia ...

  9. Android四大组件之一 -- Service详解

    相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的Android程序员如果连Service都没听说过的话,那确实也太逊了.Service作为Android四大组件之一,在每一个应用程序 ...

随机推荐

  1. MVC中使用JQuery方式进行异步请求和使用自带方式进行异步请求

    在MCV中使用异步请求可以很很高效地进行前台和后台的数据传递,在这里,笔者为初学者介绍两种在MVC中常用的异步请求处理方式. 在这里,我们通过在一个页面中放置一个按钮来异步获取当前服务器端的系统时间为 ...

  2. inheritPrototypal.js

    // 原型式继承 // 其基本思路是借助原型可以基于已有的对象创建新的对象 function object(o){ function F(){} F.prototype = o; return new ...

  3. [HTML/CSS]有一种节点叫做文本节点

    HTML可以看成是由节点(node)组成的树结构 我们一般都是在<p>节点里面写字符串. 在上图中,<p>节点和字符串之间有一个text, 这个text就是文本节点. 我们可以 ...

  4. mysql插入表数据中文乱码问题解决方案

    一.问题 开发中遇到将其它数据库数据插入到mysql数据库表中一直会报类似如下错误: Incorrect string value: '\xE6\x88\x91' for column 'name' ...

  5. ActiveReports公开课开启报名,学习如何解决中国式复杂报表难题

    ActiveReports实战教学 90分钟解决中国式复杂报表六大需求 [开课时间]4月19日 [主讲老师]葡萄城资深报表专家 [培训方式]网络在线公开课 报名地址

  6. (其他)最常用的15大Eclipse开发快捷键技巧

    转自CSDNJava我人生(陈磊兴)   原文出处 引言 做java开发的,经常会用Eclipse或者MyEclise集成开发环境,一些实用的Eclipse快捷键和使用技巧,可以在平常开发中节约出很多 ...

  7. 【HANA系列】SAP HANA XS的JavaScript安全事项

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA XS使用Jav ...

  8. tkinter学习系列之(六)Radiobutton控件

    目录 目录 前言 (一)基本属性 (二)在Frame里布局: 目录 前言 Radiobutton单选框,在一组选框中,只能选中一个. (一)基本属性 (1)特有属性: value 按钮的值 varia ...

  9. 第3章 Git使用人门

    [初识Github] 首先让我们大家一起喊一句“Hello Github”.YEAH!就是这样. Git是一个分布式的版本控制系统,最初由Linus Torvalds编写,用作Linux内核代码的管理 ...

  10. MySQL sql_mode=only_full_group_by错误

    今天在测试服务器上突然出现了这么一个MySQL的问题,同样的代码正式服没有问题,那肯定就是出在了配置上,查了一下原因才明白原来是数据库版本为5.7以上的版本, 默认是开启了 only_full_gro ...