Android四大组件之Service(一)
Android四大组件之Service(一)
一个服务是一个能在后台执行长时间运行操作的程序组件,但是这个组件并没有提供一个用户界面(用户接口)。另一个程序组件能启动一个服务,即使用户切换道另一个程序去,但是这个服务将继续在后台运行。一个组件能够绑定到一个服务从而和它进行交互,甚至能够执行进程内通讯(IPC)。例如,一个服务也许可以处理网络事务,播放音乐,执行文件I/O,或者和ContentProvider进行交互,所有这些都是在后台进行的。
0.概述
一个服务本质上来讲有两种形式:
0.1.Started
一个服务是started当一个程序组件(例如一个Activity)通过调用startService()的方式启动一个服务的时候。
一旦被启动了,一个服务能在后台对独立的运行,即使启动它的组件被销毁了。通常来讲,一个started的服务执行的是单一的操作,且不会有返回结果给调用者的。例如,这个服务也许通过网络上传下载文件。当操作被完成以后,服务就应该停下来。
0.2.Bound
一个服务是bound当一个程序组件通过调用bindService()绑定到这个服务的时候。一个被绑定的服务提供一个client-server的接口,这个接口运行组件和服务交互,允许组件发送请求,获取结果,甚至能够跨进程执行上述操作(IPC).只要另一个程序组件绑定到这个服务,那么这个bound的服务就会运行起来。多个组件能够绑定到同一个服务上面,但是当所有的组件都和服务解绑的时候,这个服务就被销毁了。
尽管这篇文当分开的单独的讨论了这两种类型的服务,但是你的服务能够采用这两种方式一起使用。它可以是started,(独立的运行)也可以运行绑定。这两种方式的仅仅是你是否实现了onStartCommand()来允许程序start它,和 是否实现了onBind()来允许是否这个服务可以绑定。
暂时不考虑是否你的程序的服务是started还是bound的或者两者都是,任何程序组件(甚至是跨程序组件)使用服务的方式都和组件使用一个activity的方式是一样的,那就是通过一个Intent来启动它。然而,你可以在manifest文件中声明这个服务是私有的,阻止其他程序的访问。关于这个更多的讨论在下面的某一个部分。
注意:一个服务运行在托管它的进程中的主线程中,这个服务并没有创建属于他自己的线程,并且没有运行在一个单独的进程中(除非你做了另外的申明).这就是说,如果你的服务将要做一些CPU密集的工作或者一些阻塞操作(例如MP3的播放或者网络操作),那么你应该在这个服务里面创建一个额外的线程来进行这些操作。通过使用这个单独的线程,你将减少ANR错误发生的风险,同时你的UI线程能够保持对用户交互的持续响应,改善用户体验。
1.基础
为了构建一个服务,你必须实现一个Service的子类或者存在的Service的子类的子类,在你的实现中,你需要重写能够处理服务关键生命周期的回调方法,如果合适的话,并且应该为组件提供一个绑定到这个服务的机制。你应该重写的最重要的回调方法是:
onStartCommand()
当另一个组件例如activity通过调用startService()的方式来请求这个服务被启动的时候,系统会调用这个方法。一旦这个方法被调用,那么这个服务就能够独立的在后台执行了。如果你实现这个方法,那么当服务完成了工作以后来停止服务(通过调用stopSelf()、stopService())就是你的责任了。如果你仅仅想提供绑定的方式,那么你就没有必要实现这个方法了。
onBind()
当一个组件通过调用bindService()想绑定到这个服务的时候(例如执行RPC Remote Process Call),系统便会调用这个方法。在你的实现中,你必须通过返回一个IBinder来提供一个客户端和服务交互的接口。你必须实现始终都要实现这个方法,但是如果你不想这个服务的提供绑定到功能,那么你应该返回null.
onCreate()
当一个服务首次被创建来执行一次性的设置程序(初始化)的时候,(先于服务调用它的onStartCommand()或者onBind()).如果这个服务正在运行,这个方法是不会被调用的。
onDestroy()
当服务不再被使用并且正在被销毁的时候,系统会调用这个方法。你的服务应该实现这个方法来清理任何占用的资源例如线程,注册的监听器、广播接受者等等。这个是服务最后收到的方法调用。
.如果组件是通过调用startService()方法来启动服务的(这个将导致方法onStartCommand()被调用),然后这服务就将保持一直运行直到服务调用stopSelf()来停止自己活着另一个组件通过调用stopService()来停止这个服务。
.如果组件是通过调用bindService()来创建服务的(这不会导致onStartCommand()被调用),然后这个服务能够开始运行只要有组件绑定到这个服务上。一旦这个服务没有和任何组件绑定在一起,那么系统就会销毁这个服务。
当内存不足的时候且Android系统必须为了有用户焦点的activity回收系统资源的时候,Android系统将强制停止服务。如果一个服务被绑定到一个拥有用户焦点的activity上面,那么这个服务是不太可能被kill掉的。如果这个服务被声明运行在前台,那么它将几乎不被kill。然而,如果这个服务被开启了并且长时间运行,那么系统将会降低这个服务在后台任务列表中的位置,这个服务就会变得更加可能被系统kill掉,因此,如果你的服务被开启了,那么你就必须设计它能够优雅的处理服务重启的情况。如果这个系统kill掉你的服务,系统将重启你的服务只要资源变得充足的时候。(这个依赖于你的onStartCommand()方法的返回值,这个后面会讲解到)。
在下面的部分,你将看见如何创建每一种类型的服务,和怎么在其他程序组件中使用这些服务。
在manifest文件中声明一个服务
像activity和其他组件一样,你必须在你的manifest文件中申明所有的服务。为了申明你的服务,增加一个<Service>元素作为<application>元素的子元素。例如
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
在service元素里面,有很多属性你可以去定义例如启动这个服务所需要的权限和这个服务启动后运行在哪个进程中。这个android:name属性是这个唯一必填的属性---它指定了这个服务的类的名字。一旦你发布了你的程序,那么你不应该去修改这个名字了,因为如果你这么做了,你就有可能会由于你的前期代码依赖于显式intent来启动started或者bound的服务,但是你修改了service的类名,导致破坏了你原有的代码。
为了确保你的app是安全的,始终要使用一个显式的intent当你开始或者绑定到你的服务的时候,并且不要申明intent filter为了你的服务。如果你想允许一定程度的模糊性来开始你的服务的话,那么你可以运用intent filter在你的服务中,然后在intent中包含服务组件的名字,但是你必须为这个intent用setPackage()方法设置这个intent的包名,这种方式提供了足够的确定性来开启你的目标服务,(而不是开启了别的程序的服务。)
另外,你可以通过包含android:exported属性并且设置其为false来确保你的服务仅仅是对你的程序可用的。这个有效的组织了别的程序来启动你的服务,即使它使用了一个显示的intent。
2.创建一个被开始的服务(Creating a Started Service)
一个被开始的服务是另一个组件通过调用startService()方法来启动的一个服务,这个将导致服务的onStartCommand()方法被调用。
当一个服务被开始的时候,他有一个独立于开启它的组件的生命周期,同时这个服务能够独立在后台运行,即使开启它的组件被销毁。例如,这个服务在任务做完以后应该通过调用stopSelf()来停止自己或者另一个组件通过调用stopService()来停止这个服务。
一个程序组件例如activity能够通过调用startService()方法来启动服务,同时传递一个指定了服务的且包含了这个服务将要使用的数据的intent.这个服务在onStartCommand()方法中收到这个intent.
例如,假设一个activity需要保存一些数据到一个在线数据库中(通常是你的服务器上的数据库).这个activity就可以开始一个协助它的服务同时将通过传递一个intent给startService()方法,来将要保存的数据分发给服务。这个服务在startCommand()中收到这个intent,接着连上网络来执行数据库的事务操作。当数据库的事务操作完成以后,这个服务就停止自己,然后被销毁。
注意:服务是运行在申明它的程序所运行的进程中的,并且默认是在这个程序的主线程中。因此,如果你的服务执行耗时或者阻塞式的操作,如果同时,用户和这个程序的activity进行交互的话,这个服务会降低activity 的响应速度的。为了避免影响程序的响应,你应该在服务中启动一个新的线程。
传统的,你可以继承自两种类来创建一个被开始的服务。
.Service
这个是所有服务的基类,当你继承这个类的时候,你在你自己的服务中创建一个新线程来做所有的服务的工作是很重要的,因为这个继承自Service的服务默认是使用你的主线程的,这个将降低你程序正在运行的任何activity的响应速度的。
.IntentService
这个是Service的一个子类,它使用一个工作线程来处理所有的开启服务的请求,一次处理一个。如果你不需要再同时处理多个请求的话这个是最好的选择。你所需要做的就是实现onHandleIntent(),这个方法接收每一个开始请求的intent,这样你就可以做这个后台工作了。
下面的部分描述了你怎么样使用上诉的介绍的类去实现你的服务。
2.1继承IntentService类
因为大多数被开始的服务不需要同时处理多个请求的(这个有时候实际上是一个危险的多线程情景),因此使用IntentService类来实现你的服务可能是最好的选择。
这个IntentService做如下的操作:
- 创建一个一个默认的工作线程来执行了所有的被分发到startCommand()的Intent,这个线程与你的程序的主线程分离。
- 创建一个工作队列,这个队列每一次传递一个intent到你的服务的onHandleIntent()方法的实现里面,因此你用不着担心多线程的问题。
- 在所有的服务启动请求都已经被处理后,将停止这个服务,因此你根本不用调用stopSelf()。
- 提供一个返回值为null的onBind()的方法的实现。
- 提供一个默认的onStartCommand()的实现,这个实现发送intent到工作队列,然后到你的onHandleIntent()的实现里面。
所有的一切加起来就是,你需要做的就是实现onHandleIntent()来做有客户端请求的任务。(不过,你还需要提供一个小的构造函数为这个服务)
下面是一个IntentService的实现的例子:
<pre name="code" class="java"> public class HelloIntentService extends IntentService { /** * A constructor is required, and must call the super IntentService(String) * constructor with a name for the worker thread. */ public HelloIntentService() { super("HelloIntentService"); } /** * The IntentService calls this method from the default worker thread with * the intent that started the service. When this method returns, IntentService * stops the service, as appropriate. */ @Override protected void onHandleIntent(Intent intent) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } } }
这就是所有你需要做的:一个构造函数和一个onHandleIntent()的实现。
如果你决定重写回调方法例如onCreate(),onStartCommand()或者onDestroy(),请确保要调用父类的实现,这样的话,IntentService类才能够恰当的处理这个工作线程的生命周期。
@Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); return super.onStartCommand(intent,flags,startId); }
例如:onStartCommand()必须返回一个默认的实现(这个实现就是这个intent怎么被分发onHandleIntent()的)
除了onHandleIntent()方法以外,这个剩下的唯一的你不需要调用父类的就是onBind(),(但是如果你想你的服务允许绑定,那么你就需要去实现这个为客户端提供一个接口)
在下面的部分,你将看见继承自基类Service的相同的服务是怎么实现的,这种继承自基类Service的方式我们需要写的代码量稍微多一些,但是如果你想在同时处理多个服务启动的请求的时候,这个也许是适合你的一种方式。
总结:
1.Service的使用形式本质上来讲有started 和 bound两种,其中started的服务是通过组件调用startService()的方法来启动服务的,而bound的服务则是组件通过调用bindService()方法来启动服务的。
2.started形式的Service在启动的时候不会调用onBind()方法,会调用onStartCommand()方法,而bound形式的Service相反。
3.service默认是允许在UI线程中的,所以在Service里面耗时的操作就需要单独的开一个线程。
4.服务的实先也有两种方式,继承自Service基类和继承IntentService类,两种形式的主要区别是前者的服务默认是在启动服务的线程中运行(通常是在UI线程),而后者的服务则是在一个另外的工作线程中运行,对于每一个启动服务请求,都将请求入队列,然后在onHandleIntent()方法中取出请求,
5.在manifest中申明Service的时候,可以指定其属性,例如exported来指定该服务是否能够被其他程序的组件所启动等等