Service初探与异步消息处理机制

Service这部分,也算是Android中非常重要的知识了,尤其是做复杂应用的时候。至于异步消息和多线程,在功能复杂的同时用的也是越来越多,加上这几天在写Windows项目的部分里面也涉及到了异步套接字等等的异步知识,发现,进程间的通讯等等也需要重新看看。知识可能就是这样把,也许你当初把书摔在地上说没用的东西,可能这个时候你就会后悔了。

Handler的异步消息处理机制

android开发当中,经常会有一些耗时操作,他们是不能放在主线程当中的。相信学过操作系统的人都知道『阻塞』是什么意思,如果主线程中放了大量的耗时操作,那么整个程序的用户体验势必会变得很差。所以,我们很自然而然就想到了——多线程.

浅谈多线程

多线程网上有很多资料解释,在此不做赘述,只是简单的把多线程实现的几种方式说一下.

1.继承Thread类

我们可以自己定义一个类,然后继承Thread这个基类。重写类中的run方法。run方法中,放入我们想要在子线程所做的耗时操作。在类的外部,可以创建一个我们自定义的类的对象,之后调用start成员方法,就可以自动执行run方法内的代码.

2.重新实现runnable接口

3.匿名类

这种方法相对来说,比较方便,简单,还是比较好用的.

多线程不是万能的

我们可能觉得,有了多线程之后,很多耗时操作就都能够解决了,但是,假如我们现在一个activity中有一个button,一个textview,我们想通过button的点击,执行一系列的耗时操作之后,更新textview控件上面的内容。由于有了耗时操作的出现,我们很自然的想到多线程的例子,但是请注意,子线程里面是不允许对UI控件进行操作的。不信的话,你可以自己写一个子线程,然后在里面调用textview的settext方法试试,是会报错的。所以,这就用到了我们要说的异步消息处理机制。

在操作系统中,异步就意味着我们不用阻塞在那些耗时的操作上,只需要发出一个耗时操作的请求就可以做接下来的事情,这样,就不会发生进程的阻塞。等到耗时操作完成之后会用特定的方式来通知我们,已经完成。这一点在windows的异步套接字编程上也体现的很好。那么在android中,异步任务机制还有更奇妙的运用。

异步消息处理机制

异步消息的处理在android中有以下应用:当我们要执行耗时操作的同时还要对UI控件进行操作的时候,因为UI的操作不能放在子线程中运行,我们就可以把UI的操作部分传递到主线程中进行处理,把耗时操作留在子线程中进行,这样既不会阻塞进程,也完成了对UI的操作。

关于handler的异步消息处理机制的使用,上面的代码已经给出了一个比较完整的流程。想要理解Handler的异步处理机制,首先要理解几个概念.

  1. handler : 是整个异步消息处理机制中的控制着,处理者。它负责发送和处理消息
  2. message : 主要是在不同的线程之间传递消息,作为一个消息的载体,其中的what字段等等,可以携带一定的数据信息。
  3. MessageQueue: 消息队列,整个程序可能有很多异步消息需要处理,但是对于一个线程来说(主线程),只能有一个handler ,MessageQueue。所以我们需要把还没有处理的消息放入消息队列当中。
  4. looper:它相当于MessageQueue中的调度者,通过调用loop方法,进行一个无限循环,不断的从消息队列中取出消息,发送给handler进行处理。 上面的代码大家也看到了,我要做的是两件事,一个是要进行一个循环耗时操作,还有一个就是更新textview控件上面的内容。耗时操作是必须放在子线程中的,但是对UI的操作写到了一个handlemessage的函数中。 上面提到的关于handler的异步消息处理机制的几个要素当中,MessageQueue和Looper都是系统已经为我们实现好了的。所以我们首先要定义一个Hanlder的实例。并且要重写父类的handlermessage的方法,用来处理到时候发来的消息。接下来就是创建一个我们自定义的消息了.

通过定义一个Message的对象,并且为what属性设定好我们之前定义的常量UPDATE_TEXT,表明我们将要发送的消息附带的一些信息。通过handler调用sendmessage,我们就会把这个消息发送给MessageQueue中,之后,通过Looper的不断循环处理,在消息队列当中取出该消息之后,就会发送给handlemessage函数,进行处理。因为Handler对象的定义是在主线程中间进行的,所以在handlermessage中进行UI控件的操作,是符合规定的。该函数接受一个message的参数,由于不同种类的消息需要采取不同的处理方式,我们利用switch来进行分支,通过每个消息所携带what属性内容的不同来决定进行何种处理操作.

至此,handler异步消息处理机制就介绍完了。在android中的这种异步机制,我觉得有两个功能,一个是对耗时操作的支持,还有一个就是子线程和主线程之前的切换。它可以根据需求,把耗时操作放在子线程中,UI操作放在主线程中。

更简单的AsyncTask

handler的异步消息处理机制可能需要我们自己实现和控制具体的操作细节,android为我们提供了更方便的形式,把handler的一些处理细节进行了封装,创造了一个新的类—-AsyncTask。这是一个抽象类,我们在使用的时候,要重写定义一个子类继承于它,并且要指定三个泛型参数:传递给后台任务的参数,显示任务进度的单位,任务完成之后的结果返回值。

AsyncTask有几个核心的函数,是实现异步任务处理的关键。

  • onPreExcute()在后台任务开始之前调用,进行一些初始化的工作。
  • doInBackground()这个函数内的代码都是在子线程中进行的,可以放入耗时操作,但是不能放入UI操作.
  • publishProgress()用来进行子线程和主线程的切换,它调用之后会执行一个onProgressUpdate函数,这里面可以进行UI的更新操作,这个函数显然是在主线程中执行的。
  • onPostExcute()主要是做一些收尾工作,当任务在doInBackgroud函数中处理完之后,通过return 返回值之后,这个函数就会被调用,可以进行一些资源的回收和销毁工作,在这里同样可以进行UI操作。

整个类实现完成,AsyncTask的异步消息处理机制也就完成了,只需要定义一个这个类的对象,然后调用excute函数,这个任务就会开始执行。耗时操作放在了子线程中执行的doInBackground中,对UI的操作放在了onProgressUpdate函数中。实现了和handler一样的效果,但是使用起来方便一些。

Service初探

service是四大组件之一比较重要的一部分,它一般在后台运行,不予用户交互,主要执行一些耗时操作。当然,如果你需要的话,也可以让它变成前台运行。

服务的种类(按启动方式来分)

通过上面这张图,我们可以清晰的看到,通过启动服务的方式来分的的话,有两种服务。

  1. 通过startservice启动
  2. 通过bindservice启动

Startservice的使用与生命周期

通过这种方式启动的服务属于比较标准的一类。一般来说,从定义一个服务到使用一个服务,要遵循一下几个步骤:

  1. 自己实现一个类,该类继承service类
  2. 在清单文件中注册自己的服务(实际上,四大组件的使用,都需要在清单文件中进行注册)
  3. 调用startservice来初始化并且启动自己的服务
  4. 调用stopservice来停止我们已经运行的服务。

通过上面的图,我们也可以观察到,这种服务的生命周期是怎样的:

首先,通过startservice来启动服务,如果我们是第一次启动服务的话,需要调用oncreate函数来做一些初始化的操作,并且调用start方法来开始执行服务的核心代码(新版本中start已经被替换成onstartcommand函数)。如果我们多次调用startservice方法的话,那么oncreate也不会再被调用,重复调用的只有onstart方法,它会处理传递给startvice的intent.如果服务中有大量的耗时操作,比如在网络上下载图片等,则可以在Onstart函数里面单独开一个线程来执行这些操作,这样做可以不让我们的主线程阻塞。最后,如果我们想停止这个服务,可以调用stopservice,这个函数第一次被调用的时候,会执行ondestory函数来释放程序在oncreate函数中所创建的资源等等。当然,你也可以在服务的内部调用stopself函数来停止服务的运行。 其余的,大家看下实现代码来具体理解吧:

BindService与Binder

之前的服务,只要在活动中调用startservice之后,服务就自己去运行了,而服务具体做了什么,在活动内是不知道的,在活动内,除了调用两个API来启动和停止服务之外,就不能再对服务有所干涉了。如果我们想获取到服务执行过程中的一些情况的话,就需要服务和活动进行通信,这也就需要用到我们进程间通讯机制的一种—Binder.

在之前实现service类的时候,我们可以看到有一个抽象方法被重新实现—onBind()。service和activity的通信大致是通过这样的一种过程来实现的:

  1. 首先在service的类中,实现一个类,这个类继承于Binder,这个类中你可以定义一些你自己的方法,这些方法就是以后要在服务中执行的方法,这个类是服务类的子类。然后,创建一个这个binder类的实例,在Onbind函数中返回这个binder对象。这个对象就像是一个开着的接口,向外提供了子类内部将要执行的操作。
  2. 服务对外通信的接口做好之后,想让活动和服务建立连接的话,就要在两者之间创建一个连接点。这个连接点的工作由ServiceConnection类的对象来实现,我们要在activity中创建一个ServiceConnection的匿名类,类中要重写两个方法: onServiceDisconnected,onServiceConnected。之后在创建一个这个类的对象,到此为止,连接点也做好了,那么如何让服务和活动通过这个连接点连接起来进行通讯工作呢?
  3. 因为在实现服务的时候,给我们返回了一个Binder的对象作为接口,可以看见在重写onServiceConnected方法的时候,里面有一个参数就是Binder类型的参数,通过把这个Binder对象强制类型转换成我们在服务中自己实现binder类的那个对象的类类型之后,就可以通过它来调用我们服务内部的一些操作。这就为活动来指挥服务内部的运行,提供了良好的接口和条件。
  4. 而对于acitivity来说的话,我们可以通过bindservice这个启动服务的函数,在参数中把要启动服务的intent和创建的连接点ServiceConnection对象传递过去,如此一来,在绑定成功之后就可以创建服务并且连接在一起,然后自动调用连接点中onServiceconnected方法,在这个方法的内部就可以通过参数中的binder对象来指挥服务。在服务和活动解除绑定的时候,自然也会调用连接点处的onServiceDisconnected方法。
  5. 我们在活动中,现在通过Binder对象所能执行的是service类的子类所提供的方法,如果想执行service类内部的方法,可以在这个接口中再定义一个函数,返回服务类的一个对象,便可以执行服务类中的成员方法了。

那么BinderService的生命周期就由以下几个部分构成:

onbindservice方法来创建一个持久的连接,它会在第一次调用的时候,调用oncreate来做一些服务的初始化的操作。之后自动调用onbind函数,向服务的外部返回Binder对象,这算是一个服务面向外部的接口。之后,通过serviceconnection对象的作用,活动和服务便能够成功绑定并且可以执行相应的操作,如果想要停止服务,可以调用unbindservice函数来解除之前建立的连接,这会直接调用服务内部的ondestory函数。

优秀的结合—-IntentService

服务的运行一般来说关心两个问题:

  1. 如果处理服务中的耗时操作
  2. 如果在服务中的逻辑运行完毕之后,自动关闭该服务。对于第一个问题来说,我们可以在服务的逻辑方法中新建一个子线程,让它来完成耗时操作。 对于第二个问题来说,我们可以在服务的运行逻辑的末尾,加上stopself以便让它在逻辑完成时,结束服务。

那么能否有一种service可以不用我们自己手动单独开辟线程,自动的来完成我们的耗时操作呢,并且在服务的逻辑处理完成之后,可以自动的帮我们结束服务,不用我们自己再调用stopself。这种服务就是intentservice。

intentservice的使用方法和普通的service是一样的,但是在intentservice的实现中,我们需要继承自intentservice的类,并且要为这个类重载一个没有参数的构造方法。具体的实现步骤有以下几个比较重要的:

  1. 继承Intentservice并提供一个无参数的构造函数,在方法内部调用父类的构造函数。
  2. 重写父类当中onhandleintent这个方法,把我们要在服务当中处理的逻辑过程都放在这个方法内完成
  3. 启动这个service。 这个服务的特殊之处就在于onhandleintent这个方法,这个方法能够代替我们手动进行的两个工作:这个方法内部的代码都是在子线程中运行的,并且在这个方法执行到末尾的时候,会自动的帮我们关闭service。这样一来,就不用我们再手动的开子线程和关闭服务了。其实在intentservice中也是通过开启子线程来完成服务内的逻辑过程的执行的。

StartService和BindService的区别

  1. 如果是使用startservice的方式来启动服务的话,即使启动这个服务的activity可能处于destory状态,但是服务本身还是在继续运行的,除非这个服务所在进程被销毁,那么这个服务也会停止。
  2. 如果是使用bindservice来启动服务的话,一旦启动这个服务的activity变为destory的状态的时候,那么这个服务也随之会销毁,因为这个服务是和特定的activity进行绑定的。
  3. startservice一旦启动之后,activity就不能再插手服务运行过程中的事情,比如我想了解服务运行的状态等等,都不能够实现,也就是说,activity只能是给服务发一个信号来启动他,但是并不能和它进行交互。
  4. bindservice就不一样了,它是依靠binder进程间通讯的机制,在服务本身内部向外提供接口,以便让activity能够和服务进行交互操作,这样一来,activity就可以在服务运行的过程当中来查看服务运行的情况。
  5. bindservice虽然向外部提供了一个binder的对象,通过这个对象可以调用service内部的一些方法,但是在activity中,通过binder调用的函数并不是直接一步到位调用了服务中的相关成员函数,而只是得到一个类似指针和映射的东西,通过这些再去找在服务内部真正存在的方法。总的来说把,bindservice向外部提供的,只是内部信息的一个映射,并不是真正的把内部的函数地址等等信息提供出去。
  6. 如果有一个service和多个activity同时绑定的话,那么即使一个activity解除了service的绑定,但是并不会回调unbind和ondestory的方法,只不过在绑定多个actiity的时候要注意绑定的顺序。> 其实bind和start两种服务最本质的区别就是,Bind是一种服务器/客户端模型,能够让服务的启动者和服务的执行者交互,但是start不行。

Service到底是什么?进程 ?线程?

这个问题也是自己刚刚遇到的,一开始有点模糊不清,包括不明白service为什么和调用他的activity是在同一个主线程中的。网上的资料,大家还是最好有一点甄别能力,尤其是论坛上面的,有些明显的错误来误导提问者。当然,我写的东西可能也是有错的,如果有不同意见,大家可以一起讨论。 我对这个问题的理解是这样的:<br> 首先,service不是一线程,也不是一个进程,它是一个组件。它的生存运行,和线程的生命周期没有一点关系。也就是说,线程的死活和它是没有关系的。但是,service的运行,毕竟需要一些条件,比如内存,CPU这些资源。然而,这些资源,操作系统只会分配给进程,这也就是为什么,不管我们用什么种类的service,只要我们把这个进程干掉,服务神马的也就都是浮云了。所以,服务虽然是一个组件,但是它的运行还是要依赖于进程。

服务分为远程服务和本地服务,本地服务就是在我们这个应用进程之内启动并运行的。一般来说,服务的运行并不会新开一个线程,无论是哪一种service他们都是运行在进程中的主线程中。这也就是为什么不能在服务中直接执行大量的耗时操作,要单独新开一个子线程完成的原因。

还有一个问题:startservice在启动之后,其实activity销毁了,但是它还是继续在运行,直到我们显式的调用stopservice或者stopself,他才会停止。而且,即使我们在服务运行的过程中stopservice,但是服务还是会完成这一次的逻辑,也就是说,我们在一个服务执行的过程中,不能中断本次执行的这个过程。这一点对于Bindservice和startservice都是一样。

但是bindeservice在activity销毁之后,它自己也会跟着销毁,我们可能会觉得这是因为进程或者线程的生命周期的关系,其实,这是不正确的一个是,按下back键使activity销毁,进程还有被杀死,还有一个就是,线程的生命周期和服务压根一点关系没有。之所以bindservice被销毁了,是因为它绑定的是activity,而不是什线程,activity销毁了,那么绑定它上面的service也就会销毁了。startservice没有销毁是因为它没有和人和activity绑定。

5 收藏 3 评论

关于作者:fengzi

一个普通大学的CS本科生,伪ACmer,喜欢操作系统,喜欢linux,讨厌复制粘贴,喜欢看书,喜欢一切经过思考和积累才能够获得成果的知识。欢迎大家来我的博客逛逛(新浪微博@徐疯子)(Blog疯子徐\'s blog) 个人主页 · 我的文章 · 2

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部