服务是什么
服务是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
另外,也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程中的。也就是说,我们需要再服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
Android多线程编程
当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如果不将这类操作放在子线程里去运行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用。
线程的基本用法
Android多线程编程其实不比Java多线程编程特殊,基本都是使用相同的语法,并在里面编写耗时逻辑即可,如下:
启动线程:new出MyThread的实例,然后调用startt方法,这样run方法中的代码就会在子线程中运行了。
使用继承的方式耦合性有点高,更多时候我们都会选择使用实现Runable接口的方式来定义一个线程,如下:
使用这种写法,启动线程的方法也需要进行相应的改变,如下:
程序的耦合度是 你的子程序之间的相关联性,也就是说你的多个类的联系 是否太紧密,打个比方,你房子里边有窗子 ,那房子 和窗子 就有了关联
耦合度 是松还是紧 就看你的 关联 是强还是弱,也就是修改的代价,比如 你窗子是扣死在墙里的 那么你修改窗子 就必须修改墙
这就比较紧密了,但是如果你窗子是按照某种规格的 可以自由拆装的 那么修改的代价就小,耦合度也就低了
我们写程序的目标就是 高内聚
低耦合! 这样修改起来 就不会有太多的联系 不用 改一个地方 其他的都要修改
高内聚,一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
实现Runnable接口比继承Thread的优势有:
①适合多个相同的程序代码的线程去处理统一资源的情况
②可以避免由于java单继承特征带来的局限性
③增强开发程序的健壮性,代码可以多个线程共享,代码和数据是独立的
在子线程中更新UI
和许多其他的GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。
具体例子:
新建一个AndroidThreadTest项目,然后修改activity_main.xml中的代码,如下:
布局里定义了两个控件,一个用于显示字符串,Button用于改变textview中显示的内容,我们希望在点击Button后可以把textview中显示的字符串改成nice
to meet you.
接下来修改MainActivity,如下所示:
因为是在子线程中更新UI的,现在运行程序,崩溃了。
然后观察logcat中的错误日志,可以看出是由于在子线程中更新UI所导致的,如图:
由此证实了Android确实是不允许在子线程中进行UI操作的。但有些时候我们必须在子线程中去执行一些耗时工作,然后根据任务的执行情况来更新相应的UI控件。
对于这种情况,Android提供了一套异步消息处理机制,完美地解决了在子线程中进行UI操作的问题。先学习一下异步消息处理的方法。
修改MainActivity中的代码,如下:
Handler 是 Android 给我们提供来更新 UI
的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它来处理消息,Handler 在我们的 framework 中是非常常见的。
Android 在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新 UI 信息,就会抛出异常信息。
Message
消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
Message Queue 消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
Handler
Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。 Looper
循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message
Queue里面的Message,并交付给相应的Handler进行处理。 线程 UI thread 通常就是main
thread,而Android启动程序时会替它建立一个Message
Queue。每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。
首先定义了一个整型常量UPDATE_TEXT,用于表示更新TextView这个动作。然后新增一个Handler对象,并重写父类的handleMessage方法,在这里对具体的Message进行处理。如果发现Messsage的what字段的值等于UPDATE_TEXT,就将TextView显示的内容改成Nice to meet you 。
下面看一下button中的代码。
这次并没有在子线程中直接进行UI操作,而是创建了一个Message(android.os.Message)对象,将它的what字段的值指定为UPDATE_TEXT,然后调用Handler中的sendMessage方法将这条Message发送出去。
很快,Handler就会收到这条Message,并在handleMessage方法中对它进行处理。
注意此时的handleMessage方法中的代码就是在主线程中运行的了,所以我们可以放心地在这里进行UI操作。接下来对Message携带的what字段的值进行判断,如果等于UPDATE_TEXT,就将textview显示的内容改成nice to meet you。
解析异步消息处理机制
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue喝Looper。
其中Message和Handler我们已经接触过。
1.Message
Message是线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据,刚刚使用了Message的what字段,除此之外还可以使用arg1和arg2字段来携带一些整形数据,使用obj字段可以携带一个Object对象。
2.Handler
Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage方法中。
3.MessgaeQueue
MessgaeQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
4.Looper
Looper是每个线程中的MessgaeQueue的管家,调用Looper的loop方法后,就会进入到一个无限循环当中,然后每当发现MessgaeQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage方法中。每个线程中也只会有一个Looper对象。
异步消息处理的流程:
首先需要在主线程当中创建一个Handler对象,并重写handleMessage方法。然后当子线程中需要进行UI操作时,并重写handleMessage方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中去除待处理消息,最后分发会Handler的handleMessage方法中。由于Hnadler是主线程中创建的,所以此时handleMessage方法中的代码也会在主线程中运行,于是我们就可以在这里安心的进行Ui操作了。
整个异步消息处理机制的流程示意图:
一条Message经过这样一个流程的辗转调用后,也就从子线程进入到了主线程,从不能更新UI变成了可以更新UI,整个一异步消息处理的核心思想也就是如此。
在runOnUiThread方法其实就是一个消息处理机制的接口封装,它虽然表面上看起来用法更为简单,但其实背后的原理实现和图中描述一模一样。
使用AsyncTask
为了更方便在子线程中对UI进行操作,Andoird还提供了另外一些好用的工具,比如AsyncTask。借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单的从子线程切换到主线程。当然,AsyncTask背后的实现原理也是基于异步消息处理机制的,只是Android帮我们做好了封装。
首先看下AsyncTask的基本用法,由于AsyncTask是一个抽象类,所以我们想使用就必须创建一个子类去继承。在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:
因此一个最简单的自定义AsyncTask就可以写成如下:
这里把AsyncTask的第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台人物。第二个泛型参数指定为Integer,表示使用整形数据来作为进度显示单位。第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。
当然,目前我们自定义的DownloadTask还是一个空任务,并不能进行任何实际的操作,我们还需要去重写AsyncTask中的几个方法才能完成对人物的定制。经常需要去重写的方法有以下四个:
1.onPreExecute
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2.doInBackground(Params…)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return 语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。
3.onProgressUpdate(Progress…)
当后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate(Progress…)方法很快就会被调用,该方法中携带的参数就是从后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
4.onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就会很快被调用。返回的数据会作为此参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如提醒任务执行的结果,以及关闭掉进度条对话框等。
因此一个比较完整的自定义AsyncTask就可以写成如下:
在downloadtask中,我们在doInBackground方法里去执行具体的下载任务。这个方法里的代码都是在子线程中运行的,因而不会影响到主线程的运行。注意这里虚构了一个doDownload方法, 这个方法用于计算当前的下载进度并返回,我们假设这个方法已经存在了。在得到了当前的下载进度后,下面就该考虑如何把它显示到界面上了,由于doInBackground方法实在子线程中运行的,在这里肯定不能进行UI操作,所以我们可以调用publishProgress方法并将当前的下载进度传进来,这样onProgressUpdate方法很快就会被调用,这里就可以进行UI操作了。
当下载完成后,doInBackground方法会返回一个布尔型变量,这样onPostExecute方法将UI会很快被调用,这个妨害也是在主线程中运行的。然后在这里我们会根据下载的结果来弹出相应的Toast提示,从而完成整个DownloadTask人物。
简单的来说,使用AsyncTask的诀窍就是,在doInBackground方法中执行具体的耗时任务,在onProgressUpdate方法中进行UI操作,在onPostExecute方法中执行一些任务的收尾工作。
如果想要启动这个任务,只需要编写以下代码:
服务的基本用法
定义一个服务
新建一个ServiceTest项目,然后
现在观察MyService中的代码:
可以看到MyService是继承自Service类的,说明这是一个服务。目前MyService中可以算是空空如也,但有一个onBind方法特别醒目。这个方法是Service中唯一的一个抽象方法,所以必须要在子类里实现。
既然是定义一个服务,自然应该在服务中去处理一些事情了,那处理事情的逻辑应该写在哪里呢?这时就可以重写Service中的另一些方法,如下:
在这里重写了onCreate、onStartCommand和onDestroy这三个方法,他们是每个服务中最常用到的三个方法了。其中onCreate方法会在服务创建的时候调用,onStartCommand方法会在每次服务启动的时候调用,onDestroy方法会在服务销毁的时候调用。
通常情况下,如果我们希望服务一旦启动就里可去执行某个动作,就可以将逻辑卸载onStartCommand方法里。而当服务销毁时,我们又因该在onDestroy方法中去回收那些不再使用的资源。
另外需要注意,每一个服务都需要在AndroidMannifest.xml文件中进行注册才能生效,**这是Android四大组件共有的特点!**这里AS已经自动完成了注册。打开AndroidMannifest.xml文件,如下:
这样就已经将一个服务完全定义好了。
启动和停止服务
启动和停止的方法主要是借助Intent来实现的。
首先修改activity_main.xml中的代码,如下:
在布局文件中加入了两个按钮,分别用于启动服务和停止服务的。
然后修改MainAcitivity中的代码,如下:
startService和stopService都是定义在Context类中的,所以我们在活动里可以直接调用这两个方法。注意,这里完全是由活动来决定服务何时停止的,如果没有点击Stop Service,服务就会一直处于运行状态。
服务有没有办法可以自己停下来呢?
需要在MyService的任何一个位置调用stopSelf方法就能让这个服务停止下来了。
接下来,怎么证实服务已经成功启动或者停止了呢?
最简单的方法就是在MyService的几个方法中加入打印日志,如下:
onCreate方法是在服务第一次创建的时候调用的,而onStartCommand方法则在每次启动服务的时候都会调用,由于刚才我们是第一次点击Start Service按钮,服务此时还没有创建过,所以两个方法都会执行,之后如果再多点击几次Start Service按钮,就只有onStartCommand方法可以得到执行了。
活动和服务进行通信
刚刚的启动和停止服务的方法,虽然服务是在活动里启动的,但在启动了服务之后,活动与服务基本就没有什么关系了。这就类似于活动通知了服务一下:“你可以启动了!”然后服务就去忙自己的事情了,但活动并不知道服务到底去做了什么,以及完成的如何。
onBind方法可以让活动和服务的关系更加紧密,例如在活动中指挥服务去干什么,服务就去干什么。
比如说,我们希望在MyService里提供一个下载功能,然后再活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能尽心管理,修改MyService中的代码,如下:
这里新建了一个DownloadBinder类,并让他继承自Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。当然这只是两个模拟方法, 并没有实现真正的功能,我们在这两个方法中分别打印了一行日志。
然后再它的内部提供了开始下载以及查看下载进度的方法。当然这只是两个模拟方法,并没有实现真正的功能,我们在这两个方法中分别打印了一行日志。
接着,在MyService中创建了DownloadBinder的实例,然后在onBind方法里返回了这个实例,这样MyService的工作就全部完成了。
接下来在活动中如何去调用这些方法。首先需要在布局里新增两个按钮,修改布局代码如下:
分别是用来绑定服务和取消绑定服务的。
当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了。修改MainAcitivity中的代码,如下:
首先创了一个ServiceConnection的匿名类,然后在里面重写了onServiceConnected方法和onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除的时候调用。在onServiceConnected方法中,我们又通过向下转型得到了DownloadBinder的实例,有了这个实例,活动和服务之间的关系就变得非常紧密了。现在我们可以在活动中具体的场景来调用DownloadBinder中的任何public方法,即实现了指挥服务干什么就去干什么的功能。
这里只是一个简单测试,在onServiceConnected方法里调用了DownloadBinder的的两个方法。
当然,现在活动和服务还没有进行绑定呢,这个功能是在Bind Service按钮的点击事件里万城。
这里我们仍然是构建出了一个Intent对象,然后调用binService方法将MainAcitivity和MyService进行绑定。BindService方法接收三个参数,第一个参数就是刚构建出的Intent对象,第二个参数是前面创建出的SerciceConnection实例,第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。这回使得MyService中的onCreate方法得到执行,但onStartCommand方法不会执行。
然后如果我们想解除绑定,就需要调用unbindService方法,这也是Unbind Service按钮的点击事件里实现的功能。
注意:任何一个服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定们还可以和任何一个其他的活动进行绑定,而且在绑定完成后他们都可以获取到相同的DownloadBinder实例。
服务的生命周期
onCreate,onStartCommand,onBind,onDestroy等方法都是在服务的生命周期内可能回调的方法。
一旦在项目的任何位置调用了Context的startService方法,相应的服务就会启动起来,并回调onStartCommand方法。如果这个服务之前还没有创建过,onCreate方法回先于onStartCommand方法执行。服务启动了之后会一直保持运行状态,知道stopService或stopSelf方法被调用。注意,虽然每次调用一次startService方法,onStartCommand就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService方法,只需要调用一次stopService或stopSelf方法,服务就会停下来了。
另外,还可以调用Context的bindService来获取一个服务的持久连接,这时就会回调服务中的onBind方法,类似的,如果这个服务之前还没有创建过,onCreate方法会先于onBind方法执行。之后,调用方可以获取到onBind方法里返回的IBinder对象的实例,这样就能自由的和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
当调用了startService方法后,又去调用stopService方法,这时服务中的onDestroy方法就会执行,表示服务已经销毁了。类似的,当调用了bindService方法后,又去调用unbindService方法,onDestroy方法也会执行。
一个服务只要被启动或者被绑定之后就会一直处于运行状态。必须让以上两种条件同时不能满足时服务才能被销毁,当一个服务即调用了startService方法又调用了bindService方法时,必须同时调用stopService方法和unbindService方法,onDestroy方法才会被执行。
服务的更多技巧
使用前台服务
很多服务都是在后台进行的,但是服务的系统优先级比较低,当系统出现内存不足的情况,就很有可能被回收。如果希望服务可以一直保持运行而不会由于系统内存不足的原因被回收,就可以考虑使用前台服务
前台服务和普通服务最大区别在于,他会有一个一直运行的图标在系统的状态栏显示,下来状态栏后可以看到更详细的信息,非常类似于通知的效果。当然可能不仅仅是为了防止服务被回收才使用前台服务的,有些项目由于特殊的需求会要求必须使用前台服务,比如彩云天气app,他的服务在后台更新天气数据的同时还会再系统状态栏一直显示当前的天气信息。
创建一个前台服务
修改MyService中的代码,如图:
这里只是修改onCreate方法中的代码,这是之前学习创建通知的方法。只不过这次在创建出Notification对象后并没有使用NotificationManager来将通知显示出来,而是调用了startForeground方法。这个方法接收两个参数,第一个参数是通知的id,类似于notify方法的第一个参数,第二个参数则是构建出的Notification对象。调用startForeground方法后就会让MyService变成一个前台服务,并且在系统状态栏显示出来。
使用IntentService
服务中的代码都是默认运行在主线程中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
所以这时候需要用到Android多线程编程的技术了,我们应该在服务的每个具体方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。因此一个比较标准的服务就可以写成如下形式:
但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用stopService或者stopSelf方法才能让服务停止下来。所以,如果想要实现让一个服务再执行完毕后自动停止的功能,就可以这样写:
虽然这种写法并不复杂,但总会有些人忘记开启线程,或者忘记调用stopSelf方法。为了可以简单的创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,这个类就很好地解决了前面提到的两种尴尬。
新建一个MyIntentService类继承自IntentService,代码如下:
首先提供一个无参构造函数,并且必须在其内部调用父类的有参构造函数。然后要在子类中去实现onHandleIntent这个抽象方法,在这个方法中去处理一些具体的逻辑,而且不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。这里为了证实一下,我们在onHnadleIntent方法中打印了当前线程的id。另外根据IntentService的特性,这个服务在运行接触后应该是会自动停止的,所以我们又重写了onDestroy方法,在这里也打印了一行日志,以证实服务是不是停止掉了。
接下来修改activity_main.xml中的代码,加入一个用于启动MyIntentService这个服务的按钮,如下:
然后修改活动中的代码:
注意要在AndroidManifest.xml里注册。
服务的最佳实践——完整版的下载示例
创建一个ServiceBestPractice项目
添加依赖库
添加了OkHttp的依赖,使用它来编写网络相关功能。
接下来需要定义一个回调接口,用于对下载过程中的各种状态进行监听和回调。新建一个DownloadListener接口,代码如下:
这里定义了五个回调方法,onProgress方法用于通知当前的下载进度,onSuccess方法用于通知下载成功事件,onFailed方法用于通知下载失败事件,onPaused方法用于通知下载暂时事件,onCanceled方法用于通知下载取消事件。
回调接口定义好了之后,下面可以开始编写下载功能。这里使用刚学过的AsyncTask来进行实现,新建一个DownloadTask继承自AsyncTask,代码如下:
首先看AsyncTask中的三个泛型参数:
第一个参数表示执行AsyncTask的时候需要传入一个字符串参数给后台任务。
第二个参数表示使用整型数据来作为进度显示单位
第三个参数表示使用整形数据来反馈执行结果。
接下来定义了四个整型常量表示下载状态
接下来重写doInBackground、onProgressUpdate、onPostExecute三个方法:
doInBackground方法用于在后台执行具体的下载逻辑
onProgressUpdate方法用于在界面上更新当前的下载进度
onPostExecute方法用于通知最终的下载结果
首先看一下doInBackground方法,从参数中获取到了下载的URL地址,并根据URL地址解析出了下载的文件名,然后将指定的文件下载到Enviroment.DIRECTORY_DOWNLOADS目录下,也就是SD卡的Download目录。我们还要判断一下Download目录中是不是已经存在要下载的文件了,如果已经存在的话则读取已下载的字节数,这样就可以在后面启用断点续传的功能。接下来显示调用了getContentLength方法来获取待下载文件的总长度,如果文件长度等于0则说明文件有问题,直接返回TYPE_FALED,如果文件长度等于已经下载文件长度,则说明文件下载完毕,直接返回TYPE_SUCCESS。
接着使用OkHttp来发送一条网络请求,需要注意的是,这里再请求中添加了一个header,用于告诉服务器我们想要从哪个字节开始下载,因为已下载过的部分就不需要再重新下载了。接下来读取服务器响应的数据,并使用Java文件流方式,不断从网上读取数据,不断写入到本地,一直到文件全部下载完成为止。
在这个过程中还需要判断用户有没有触发暂停或者取消的操作,如果有的话则返回TYPE_PAUSED或TYPE_CANCELED来中断下载,如果没有的话则实时计算当前的下载进度,然后调用publishProgress方法进行通知。暂停和取消操作都是使用一个布尔型的变量来进行控制的,调用pauseDownload或cancelDownload方法即可改变变量的值。
接着看onProgressUpdate方法,这个方法就简单得多了,它首先从参数中获取到当前的下载进度,然后和上一次的下载进度进行对比,如果有变化的话就调用DownloadListener的onProgress方法来通知下载进度更新。
最后是onPostExecute方法,就是根据参数中传入的下载状态来进行回调,下载成功就是DownloadListener的onSuccess方法,下载失败就调用onFailed方法,暂停就是onPaused方法,取消就是onCanceled方法。
这样就把具体的下载功能完成了,下面为了保证DownloadTask可以一直在后台运行,还需要创建一个下载的服务。
然后修改其中的代码:
首先创建了一个DownloadListener的匿名类实例,并在匿名类中实现了onProgress、onSuccess、onFailed、onPaused和onCanceled这个五个方法。
在onProgress中调用了getNotification的方法构造了一个用于显示下载进度的通知,然后调用NotificationManager的notify方法去触发这个通知,这样就可以在下拉状态栏中实时看到当前下载的进度了。在onSuccess方法中,我们首先是将正在下载的前台通知关闭,然后创建了一个新的通知用于告诉用户下载成功了。其他几个方法也都是类似的,分别用于告诉用户下载失败、暂停和取消这几个事件。
接下来为了让DownloadService可以和活动进行通信,创建了一个DownloadBinder。
DownloadBinder中提供了startDownload、pauseDownload和cancelDownload这三个方法,分别是用于开始暂停和取消下载。
在startDownload中创建了一个DownloadTask的实例,把刚才的DownloadListener作为参数传入,然后调用execute方法开启下载,并将下载文件的URL地址传入到execute方法中。同时,为了让这个下载服务成为一个前台服务还调用了startForground方法,这样就会在系统状态栏中创建一个持续运行的通知了。接着,pauseDownload方法中代码就是调用了DownloadTask中的pauseDownload方法。cancelDownload中也基本类似,但需要注意的是取消下载的时候我们需要将正在下载的文件删除掉。
另外,DownloadService中所使用到的通知都是调用getNotification方法进行构建的,setProgress方法接收三个参数,第一个参数传入通知的最大进度,第二个参数传入统治的当前进度,第三个参数传入是否使用模糊进度条,这里传入false。
设置完后通知上就会有进度条显示出来了。
编写前端代码:
三个按钮,开始,暂停,取消。
修改活动中代码:
首先创建了一个ServiceConnection的匿名类,然后再onServiceConnected方法中获取到DownloadBinder实例,有了这个实例就可以在活动中调用服务提供的各种方法了。
注意:如果活动被销毁,一定要对服务进行解绑,不然就可能造成内存泄露,这里再onDestroy方法中完成了解绑操作。
现在对权限、服务、活动进行声明:
其中,由于使用到了网络和访问SD卡的功能,因此需要声明INTERNET和WRITE_EXTERNAL_STORAGE这两个权限。。