2.3 Services

Services

     一个Service是一个app的组件,可以提供后台的长时间的操作,而不提供用户界面。别的app组件可以开启一个service,即使用户切换到别的app,它也会继续在后台运行。另外,一个组件可以绑定一个service与其相互作用,甚至提供进程间通信。(IPC, interprocess communication)例如,一个service可以在后台控制着network transaction,播放音乐,提供文件I/O流,或者和contentprovider相互作用。

一个service本质上有两种形式:

Started

       当app组件调用startService()时,一个service就启动了,且其状态是“started”。一旦service启动了,它就在后台一直运行,即使启动这个service的组件被销毁了,该service会一直运行。通常情况下,一个开启的service表现一个独立的操作,并且不会给调用者返回结果。例如,它会通过网络下载或上传文件。当操作完成时,service也会自动停止。

Bound

       当app组件调用bindService()方法的时候,一个service就与调用者绑定在一起了,并且是“bound”状态。一个受约束的service提供客户端(Client-Server Interface)允许组件与service相互交流,发送请求,获得结果,甚至通过进程间通信(IPC)与其他进程进行交流。一个受约束的service只有在app组件与其绑定期间才运行。大多数组件可以直接绑定到service上,但是一旦解除绑定,service会被摧毁。

 

       尽管文档分别讨论了这两个service类型,你的service可以同时以这两种形式工作——它可以started(为了无限运行)同时也允许绑定。只要你简单的实现下面两个回调方法即可:onStartCommand()允许组件开启service而onBind()方法允许绑定。

       和activity一样,任何组件都可以使用service。然而你可以在manifest文件中将service定义为私有的来阻止别的app访问它。这一点会在后文Declaring the service in the manifest中讨论。

 

Caution:一个service会在持有它的进程(process)的主线程(main thread)中运行——service不会创建它自己的线程(thread),也不会在别的process中运行(除非你特别指定一个process运行这个service)。这意味着,如果你的service将要进行任何高强度CPU工作或者阻塞操作(例如播放MP3或者上网),你应该创建一个新的线程(thread)并把service放入其中工作。使用单独的线程,你可以降低错误“应用程序无应答”发生的(ANR, Application Not Responding)的风险,同时应用程序的主线程可以更专注于处理用户与activity的交流。

 

The Basics

创建一个service,你必须创建Service类的子类(或者已经存在的Service类的子类)。在你的实现中,你需要覆盖一些处理service生命周期的重要的回调方法,在需要的情况下还要提供组件与该service绑定的机制。最重要的需要覆盖的回调方法如下:

onStartCommand ()

       当别的组件,例如一个activity请求service启动(调用startService()方法)的时候,系统会调用这个方法。一旦这个方法执行了,service就启动并且在后台无限期运行。如果你执行了这个方法,在工作完成之后你必须通过调用stopSelf()或者stopService()方法停止这个service。如果你仅仅是提供绑定,那么你不需要实现这个方法

onBind ()

       当别的组件通过bindService()方法绑定该service时(例如提供RPC服务,RemoteProcedure Call Protocol,远程过程调用协议),系统会调用这个方法。对这个方法的实现中,你必须提供用户可以与该service交流的接口。(可以通过返回一个IBinder实现)这个方法你必须执行,如果你不允许该service与其他组件绑定,那么返回null即可。

onCreate ()

       当service最初被创建的时候,系统会调用这个方法来执行一次性设置程序。(在它调用onStartCommand()onBind()之前)如果service已经在运行了,那么这个方法不会被调用。

onDestroy ()

       当service不再使用且将要被销毁的时候,系统会调用这个方法。你的service应该执行这个方法来清除类似线程(thread),注册监听器(register listener),接收器(receiver)等资源。这是service最后接收到的调用。

 

       如果一个组件通过startService()启动service(这将导致调用onStartCommand()),那么service会一直运行,直到它调用stopSelf()停止自己,或者别的组件调用stopService()停止该service。

       如果一个组件调用了bindService()创建一个service(同时onStartCommand()没有调用),那么该service只在绑定到组件期间运行,一旦service与组件解除绑定,系统会销毁该service。

       Android系统只有在内存低的时候会强制停止一个service,因为它要回收系统资源给用户当前关注的activity使用。如果service正好绑定在获得用户焦点的当前activity,那么它不太可能会被系统停止,如果一个service被声明在前台运行(后面会讨论到),那么它将永远不可能被停止。另外,如果service已经被启动并且长时间运行,那么随着时间的流逝,系统会降低它在后台task中的位置,该service就越有可能被关闭——如果你的service启动了,那么你必须将它设计为可以被系统重启的模式。如果系统关闭了你的service,当资源再次可用的时候它会很快重启该service(尽管这也取决于你返回给onStartCommand()的值,稍后讨论)。更多关于系统可能摧毁service的文档,请参照Processes and Threading

       下面是如何创建不同种类的service以及如何使用它们。

 

 

Declaring a service in the manifest

像是activity(以及其他租组件),你必须在app的manifest文件中声明所有的service。

在<application>标签下添加<service>子标签即可声明。如下所示:

<manifest …>

       … …

       <application…>

              <serviceandroid: name=”.ExampleService”/>

              ……

       </application>

</manifest>

 

你可以在<service>元素中包含别的属性定义service属性,像是定义权限许可(permission required)启动service以及定义service在那个process中运行等。属性android:name是唯一要求的定义的属性——它指定service的类名。当你发布你的app时,你不应该修改这个名字,如果你修改了这个名字,那么你会冒着破坏依赖显式intent启动或绑定service代码的风险。(可以参考文档Things That Cannot Change

为了确保你的app是安全的,可以使用显式intent开启或者绑定你的service,且不为这个service声明intent filter。如果你需要允许一些模糊service启动,你可以给你的service提供intent filter同时将组件名称从Intent中排除(exclude the component name from the Intent),但是你仍然需要使用setPackage()为intent设置package,它为目标service提供足够的消除歧义服务。

       另外,通过设置android: exported标签为false可以限制你的service只为你的app所用。这举措有效的阻止别的app启动你的service,即使是使用显式intent也无法启动。

 

Shouldyou use a service or a thread?

       Service是一个简单的可以在后台运行的组件,并不需要用户与你的app进行交互。如果你需要在主线程之外执行某些工作,但是又需要在你的app运行期间执行,那么与其创建一个service,你更应该使用thread。例如,如果你想在你的activity运行期间播放音乐,你可以在onCreate()中创建一个thread,在onStart()方法中启动该thread,最后在onStop()方法中停止它。你也可以考虑使用AsynTask或者HandlerThread替代传统的Thread类。可以查看Processes and Threading文档获取更多内容。

       记住如果你使用了一个service,它在默认情况下运行在你的app的主线程中(mainthread),如果你的service进行集中或阻塞操作,你应该将它放入一个新创建的线程中。

 

Creating a Started Service

       别的app组件调用startService(),导致调用service的onStartCommand()来启动一个service。

       当一个service启动时,它会有一个生命周期,并且这个生命周期依赖于启动它的组件的生命周期,即使启动它的组件已经被销毁,它也会在后台无限运行下去。只有通过它自己调用stopSelf()或者别的组件调用stopService()时,该service才会停止。

       一个app组件例如activity调用startService()启动service,并传递一个Intent声明该service,并在Intent中包含一些该service可能需要的数据。通过onStartCommand(),service获得这个intent。

       例如,假设一个activity保存一些数据到在线数据库上。这个activity可以启动一个伴随service(companion service),通过方法startService()传递需要保存的数据给service。该service通过onStartCommand()获得传递过来的intent,连接网络并执行数据库事务。当事务结束后,service会停止并销毁。

 

Caution: 默认情况下service与app在同一个process下运行(即app的mainthread中)。所以,如果当用户在与同一个线程中app下的一个activity进行交互时,而你的service需要进行集中或阻塞操作,service会降低activity的性能。为了避免这种情况,你应该为service开启一个新的thread。

 

       通常情况下,你可以继承下面两者之一来创建一个started service:

Service

       这是所有service的基本类。当你继承这个类的时候,最重要的是你必须创建一个新的thread,在这个thread中执行所有的service。

IntentService

       这是Service类的子类,使用一个worker thread处理所有的启动请求,一次一个。如果你不要求你的service同时处理多请求,那么这个类是最好的选择。你需要做的只是执行onHandleIntent()方法,该方法可以从每个启动请求中获得intent以便于你进行后台任务。

       接下来的部分描述如何使用上述类实现你的service。

 

Extending the IntentService class

       因为大部分的started service不需要同时处理多请求(这是很危险的多线程场景),所以IntentService可能是最佳实现你的service的类。

IntentService包括:

●       创建一个默认的worker thread,从你的app主程序中分别发送所有的intent给方法onStartCommand()

●       创建一个工作队列,每次传递一个intent给你的实现方法onHandleIntent(),这样你将永远不必担心多线程的问题

●       当所有的开启请求被处理以后,停止service,你永远不需要调用stopSelf()方法

●       提供默认实现onBind()并返回null

●       提供默认实现onStartCommand()并传递intent到工作队列(work queue)中,然后再发送到onHandleIntent()方法中。

综上所述,你需要做的仅仅是提供onHandleIntent()方法的实现(尽管你也需要提供service的构造器)

下面就是IntentService实现的例子:

public class HelloIntentService extendsIntentService{

       /**

       * Aconstructor is required, and must call the super IntentService (String)

       *constructor with a name for the worker thread

       */

       publicHelloIntentService(){

       super(“HelloIntentService”);

}

 

/**

*TheIntentService calls this method from the default worker thread with the *intentthat started the service. When this method returns, IntentService stops *theservice, as appropriate.

*/

@Override

protectedvoid onHandleIntent(Intent intent){

       //Normally we would do some work here,like download a file.

       //For our sample, we just sleep for 5seconds.

       longendTime=System.currentTimeMillis()+5*1000;

       while(System.currentTimeMillis()<endTime){

       synchronized(this){

       try{

              wait(endTime-System.currentTimeMillis());

}catch (Exception e){}

}

}

}

}

 

这就是你需要的所有代码:一个构造器和onHandleIntent()的实现。

       如果你也想覆盖别的回调方法,像是onCreate()onStartCommand()或者是onDestroy(),确定调用其父类的实现,然后IntentService就可以适当的操作worker thread的生命周期。

例如,onStartCommand()必须返回默认的实现(该实现传递intent到方法onHandleIntent()

@Override

public int onStartCommand(Intent intent, intflags, int startId){

       Toast.makeText(this, “service starting”, Toast.LENGTH_SHORT).show();

       returnsuper.onStartCommand(intent, flags, startId);

}

 

除了onHandleIntent()方法,另外一个唯一不需要调用其父类方法的是onBind()

       下一部分你将看到继承Service类的相同类型的service是如何实现的,它们允许处理并发的启动请求。

 

Extending the Service class

       就像你在先前部分看到的,使用IntentService使得实现一个started service非常简单。然而,如果你要求你的service可以提供多线程服务(而不是通过一个工作队列分别实现启动请求),那么你可以继承Service类处理每个intent。

       为了方便对比,下面的代码和上面的继承了IntentService的类一样,实现相同的功能。即为每个启动请求,它都使用一个worker thread执行,每次只在线程中实现一个请求。

public class HelloService extends Service {

       privateLooper mServiceLooper;

       privateServiceHandler mServiceHandler;

       //Handlerthat receives message from the thread

       privatefinal class ServiceHandler extends Handler{

              publicServiceHandler (Looper looper){

       super(looper);

}

 

@Override

public voidhandleMessage (Message msg){

       //Normally we would do some work here,like download a file

       //For our sample, we just sleep for 5seconds

       longendTime=System.currentTimeMillis()+5*1000;

       while(System.currentTimeMillis()<endTime) {

              synchronize(this) {

       try{

       wait(endTime-System.currentTimeMillis());

}catch (Exception e){}

}

}

}

//Stop theservice using the startId, so that we don’t stop

//the servicein the middle of handling another job

stopSelf(msg.arg1);

}

       @Override

       publicvoid onCreate (){

// Start upthe thread running the service. Note that we create a //separate thread becausethe service normally runs in the process’s //main thread, which we don’t wantto block. We also make it //background priority so CPU-intensive work will notdisrupt our UI.

HandlerThreadthread= new HandlerThread(“ServiceStartArguments”,

Process.THREAD_PRIORITY_BACKGROUND);

              thread.start();

              //Getthe HandlerThread’s Looper and use it for our Handler

              mServiceLooper=thread.getLooper();

              mServiceHandler=newServiceHandler(mServiceLooper);

}

 

@Override

public intonStartCommand(Intent intent, int flags, int startId) {

       Toast.makeText(this, “service starting”,Toast.LENGTH_SHORT).show();

//For eachstart request, send a message to start a job and deliver the //start ID so weknow which request we’re stopping when we finish the job

Messagemsg=mServiceHandler.obtainMessage();

msg.arg1=startId;

mServiceHandler.sentMessage(msg);

//if we getkilled, after returning from here, restart

returnSTART_STICKY;

}

 

@Override

publicIBinder onBind (Intent intent) {

       // we don’t provide binding, so returnnull

       return null;

}

 

@Override

public voidonDestroy(){

       Toast.makeText (this, “service done”,Toast.LENGTH_SHORT).show();

}

}

 

对比之下可以知道,这比IntentService多出了很多工作。

       然而,因为你自己在onStartCommand()中处理每一个调用,你可以同时处理多个请求。当然例子中没有示范,但是只要你想,你可以为每一个请求创建一个thread并马上运行它们(而不是等待前一个请求结束)

       注意onStartCommand()方法必须返回一个整数。这个整数描述系统应该如何在event中继续执行service以及关闭它。从onStartCommand()中返回的值一定是一下的其中之一:

START_NOT_STICKY

       如果系统在onStartCommand()返回后关闭service,那么除非有intent传递过来时,否则不要重新创建service。这是避免在没必要的时候,或者是你的app可以简单的重启任何没有完成的任务的时候运行的service的最安全的方法。

START_STICKY

       如果系统在onStartCommand()方法返回后关闭service,重新创建service且调用onStartCommand(),但是不重新发送最后一个intent。取而代之的是,除非他们发送一个intent来启动该service,否则系统会以一个null intent调用onStartCommand()。这个方法使用于类似播放器的service,他们不执行命令,但是可以无限运行并且可以暂时中断。

START_REDELIVER_INTENT

       如果系统在onStartCommand()返回后关闭service,重新创建service且使用最后一个intent重新调用onStartCommand()方法。任何传递进来的intent都会按顺序排列。这个方法适用于那些需要积极执行任务并且需要立即重启的service,例如下载文件等。

 

       更多信息,请查看它们的连接

 

Starting a Service

       你可以从一个activity或其他app组件通过传递一个Intent(声明启动service)给startService()来启动一个service。Android系统会调用onStartCommand()方法并传递intent。(你永远不需要自己手动调用onStartCommand()方法)。

例如,一个activity启动示例service可以使用显式intent传递给startService():

 

Intent intent=new Intent(this,HelloService.class);

startService(intent);

 

startService()方法会立即返回然后android系统调用service的onStartCommand()方法。如果service没有运行,那么系统会首先调用onCreate()然后再调用onStartCommand()

       如果service不提供绑定,传递给startService()的intent是只允许app组件与service交流的模式。然而,如果你希望service返回一个值,那么可以创建一个PendingIntent广播(使用getBroadcast()),将它放在intent中传递给service并开启它。Service可以使用该广播传递结果。

 

Stopping a service

       一个started service必须管理它自己的生命周期。就是说,系统不会停止或销毁service除非它必须恢复系统内存,那么service会在onStartCommand()返回后继续运行。所以,在这个情况下service必须调用stopSelf()停止其自身或者其他app调用stopService()停止该service。

       一旦调用stopSelf()或者stopService(),系统会尽快销毁service。

       然而,如果你的service在onStartCommand()并发处理多个请求,那么当你处理完一个启动请求时也不应该停止当前service,因为你可能已经收到一个新的启动请求(在第一个请求结束的时候停止service可能会终止第二个请求的继续)。为了避免这种事情的发生,你可以使用stopSelf(int)保证你的请求终止service总是依赖于最近的启动请求。(you can use stopSelf(int) to ensure that yourrequest to stop the service is always based on the most recent start request.)就是说,当你调用stopSelf(int)的时候,将启动请求的ID传递给与之相关联的停止请求。(startId传递给onStartCommand())那么在你调用stopSelf(int)之前,service收到一个新的启动请求,那么ID不匹配service也不会停止。

 

Caution:当工作完成时,app停止service非常重要,这样避免浪费系统资源以及导致电池电量低下。如果有需要的话,别的app可以调用stopService()停止service。即使你启用绑定service,如果service没有收到onStartCommand()调用,你也必须自己的停止该service。

       更多信息请参见Managing the Lifecycle of a Service.

Creating a Bound Service

       一个bound service是允许app组件调用bindService()绑定的service,这样可以创建一个long-standing connection(且通常不允许组件通过startService()启动它)。

       当你想和来自app中的activity或其他组件的service交流时,或通过IPC(进程间通信)给其他app显示你的app的一些功能的时候,你可以创建一个boundservice。

       创建一个bound service,你必须实现onBind()方法,返回一个IBinder定义与service交互的接口。别的app组件也可以调用bindService()方法获得该接口,调用service中的方法。该service只存在于与组件的绑定期间,如果没有任何组件与该service绑定,系统会销毁它。(你不需要像停止通过onStartCommand()启动的service那样停止绑定的service)

       创建一个绑定的service,你最先要做的事情是定义用户如何与service交互的接口。这个存在于用户和service之间的接口必须是IBinder类的实现,并且IBinder类也是你从onBind()方法中返回的结果。一旦用户收到IBinder,那么它立即通过这个接口与service交互。

       大部分client(组件)可以立即与service绑定。当一个用户结束与service的交互,它会调用unbindService()解除绑定。一旦没有用户与service绑定,系统会销毁service。

       有多种方法实现bound service且实现方法也比实现started service要复杂,实现bound service的具体实施可以参照文档Bound Services.

 

Sending Notifications to the User

       一旦运行,一个service可以使用Toast NotificationStatus Bar Notification通知事件的用户。

       一个toast notification是显示在当前屏幕的一条信息,通常存在一会儿就消失;而bar notification在status bar中提供带有信息(message)的图标,用户可以点击它执行一个action。(例如启动一个activity)

       通常情况下,当一些后台工作结束时使用status bar notification是最好的选择(例如文件下载完成),用户可以马上操作该文件。当用户从界面选择该notification时,这个notification会启动一个activity。(例如查看下载好的文件)

       可以查看文档Toast NotificationStatus Bar Notification获得更多信息

 

Running a Service in the Foreground

       一个foreground service是一个用户主动注意到的东西且不会再系统内存低时被销毁。一个前台service必须为status bar提供notification,放在“正在运行(Ongoing)”标题下,这意味着除非service停止或者从前台上被移除,通知不会消失。

       例如,一个利用service播放音乐的播放器必须设置在前端运行,因为用户需要明确清楚他的操作。在status bar上的notification(通知)可以显示当前播放的音乐,允许用户launch一个activity与当前音乐播放器交互。

       调用startForeground()可以定义一个service在前端运行。这个方法有两个参数:一个整数定义特定的notification以及在status bar上显示的notification对象,例如:

 

Notification notification=new Notification

(R.drawable.icon,getText(R.string.ticker_text), System.currentTimeMillis());

Intent notificationIntent=new Intent(this,ExampleActivity.class);

PendingIntent pendingIntent=PendingIntent.getActivity(this,0, notificationIntent,

0);

notification.setLatesEventInfo(this,getText(R.string.notification_title),

getText(R.string.notification_message),pendingIntent);

startForeground(ONGOING_NOTIFICATION_ID , notification);

 

Caution:你传递给startForeground()的ID一定不能是0。

 

调用stopForeground()可以将前台运行的service移除。该方法带有一个boolean参数表明是否将status bar一起移除。该方法不会停止service。然而,如果在service仍在前台运行的时候停止它,那么notification也会移除。

更多信息,参见Creating Status Bar Notification

 

Managing the Lifecycle of a Service

       一个service的生命周期比activity的简单得多。然而,你更应该关注的是你service如何被创建和销毁,因为service可以在后台不被用户注意的运行。

       Service生命周期——从它被创建到它被销毁——可能遵从下面两条路径:

●       A started service

其他组件调用startService()创建这一类型service。该类型service无限运行并且必须通过其自身调用stopSelf()停止。其他组件也可以调用stopService()停止它。当它停止时,系统会摧毁该service。

●       A bound service

当别的app组件(或client)调用bindService()时创建。Client利用IBinder接口与service交互。当调用unbindService()时该service立即关闭连接。多个client可以与同一个service绑定,当他们全部解除绑定的时候,系统才会销毁该service。(该;类型service不需要手动停止)

 

       这两种路径并不完全分离。意思是,你可以将一个已经通过startService()启动的service与其他的app组件绑定。例如,一个在后台运行的音乐播放器service可以由一个声明了播放某一首音乐的Intent传递到startService()来启动。然后,如果用户想要控制该播放器或想要获取当前播放的歌曲的信息,可以让一个activity与service绑定(通过bindService())。像这个例子,stopService()stopSelf()并不能完全停止service,除非client已经与service解除绑定。

 

Implementing the lifecycle callbacks

       和activity相似,service也有生命周期回调方法,你可以实现这些回调方法监控service的状态,以及在适当的时间执行相应的工作。下面的框架说明生命周期中的不同方法:

public class ExampleService extends Service{

       intmStartMode; //indicates how to behave ifthe service is killed

       IBindermBinder;        //interface for clientsthat bind

       booleanmAllowRebind;              //indicateswhether onRebind should be used

       @Override

       publicvoid onCreate(){

              //Theservice is being create

}

      

       @Override

       publicint onStartCommand(Intent intent, int flags, int startId){

       //The service is starting, due to a callto startService()

       return mStartMode;

}

      

       @Override

       publicIBinder onBind(Intent intent){

       //A client is binding to the service withbindService()

       return mBinder;

}

      

       @Override

       publicboolean onUnbind(Intent intent){

       //All clients have unbound with unbindService()

      

}

      

       @Override

       publicvoid onRebind(Intent intent){

              //Aclient is binding to the service with bindService()

              //afteronUnbind() has already been called

}

      

       @Override

       publicvoid onDestroy(){

       //The service is no longer used and isbeing destroyed

}

}

 

Note: 与activity的生命周期回调方法不同,你不需要调用service的父类回调方法。

 

 

 

左图是service的生命周期。靠左边的图标说明的是通过startService()创建的created service生命周期而靠右图表说明通过bindService()创建的bind service生命周期。

 

通过这些方法的实施,你可以监控service生命周期的两个嵌套循环(nested loops):

●       entirelifetime

service的完整生命周期是从方法onCreate()的调用到onDestroy()的调用返回为止。和activity一样,service也在onCreate()中初始化其设置,释放所有保存在onDestroy()中的资源。例如,一个音乐播放service可以在onCreate()中创建thread,这样音乐可以在onCreate()中播放,而在onDestroy()中停止该thread。

●       activelifetime

service的有效时间指service调用onStartCommand()onBind()时。这两个方法都处理从startService()bindService()传递过来的intent。

 

       如果service是started类型的,那么activelifetime与entirelifetime同时间结束(即使onStartCommand()返回后,service依旧active),如果service是bound类型,那么active lifetime会在onUnbind()返回后结束。

 

Note:尽管startedservice会被stopSelf()或者stopService()停止,但是没有相关的service回调方法(没有onStop()回调方法)所以除非service与client绑定,否则系统会在service停止的时候摧毁它,且onDestroy()是唯一能够接受到这个结果的回调方法。

 

       图二说明了service的经典回调方法。尽管started和bound类型的service被不同的方法开启(startService()/bindService()),但是每个service都可能潜在的被绑定,即是说通过onStartCommand()的service也可以收到方法onBind()的调用。

更多信息可以参考Bound Service文档,它说明更多关于onRebind()方法的使用以及Managing the Lifecycle of a BoundService

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值