本文讲述Android服务,是Android运行在后台的组件。并解释服务可用于不同的场景,以及如何实现长时间运行后台任务、为远程调用提供接口。
This article covers Android services, which are Androidcomponents that allow work to be done in the background. It explains thedifferent scenarios that services are suited for and shows how to implementthem both for performing long-running background tasks as well as for providingan interface for remote procedure calls.
概述
使用服务可使业务在后台执行。同Activity一样,他们具有自己的生命周期,调用者关闭后还可以继续执行。另外可与调用者在同一个进程,也可在独立进程中。
Servicesenable tasks to be performed in the background. They run in their ownlifecycles, so they can continue to operate even after the caller, such as anActivity, exits. Additionally, they can run locally, in the same process as thecaller, as well as in separate processes.
有两种方式使用服务:
l 可用于与调用者无交互的执行长时间运行的任务。例如在后台下载信息的服务。这种服务叫做启动服务(Started Services)。
l 提供程序接口与调用者交互,例如系统包含的定位服务,提供各种方法给应用程序获取当前位置信息。这种服务叫做绑定服务( Bound Services)
他们不是独立存在的。有的服务即是启动服务(Started Services)也是绑定服务(Bound Services),一直运行到被显式的停止而且没有客户端与之绑定,或者系统由于内存紧缺将他关闭。
Thereare two fundamental ways that services can be used:
· They can be used to perform somelong running task without any need for interaction with a caller other than therequest that some task be performed. An example of this would be a service thatdownloads some information in the background. These services are referred toas Started Services.
· They can provide a programmaticinterface for callers to interact with, such as a location service, which thesystem already includes, that provides a variety of methods that applicationscan use to glean information about the current location. These services arereferred to as Bound Services.
Thesearen’t mutually exclusive. The same service can be both a Started Service and aBound Service and will run until it is explicitly stopped and no clients arebound to it, or the system shuts it down under memory pressure.
本文讨论这如何开发两种类型服务。对于自启动服务(Started Services)讲述:
l 服务生命周期
l 线程
l 控制由于系统内存不足而关闭的服务重启
l 使用IntentService类简化服务开发
l 通知用户
对于绑定服务(Bound Services)讲述:
l 创建绑定服务(Bound Service)
l 与自启动服务(Started Service)生命周期的差别
l 创建一个进程内,同时也是跨进程的服务
本文同时演示如果显示设备上运行服务的信息。最后,用股票的例子作为结尾来阐述上面的概念,以及服务开发中有用的模式。
Thisarticle discusses how to develop services of either type. For Started Servicesit explains:
· The service lifecycle.
· Threading.
· Controlling service restarts dueto service shutdown by the system under memory pressure.
· Simplifying service developmentusing the IntentService class.
· Notifying users.
ForBound Services it examines:
· Creating a bound service.
· How the lifecycle differs fromStarted Services.
· Creating in-process, as well asinter-process services.
Thearticle also looks at how to view information about running services on thedevice. Finally, it concludes by examining an implementation of a stock servicethat demonstrates several of the conceptual topics listed above, as well assome additional patterns useful in service development.
章节
结论
本文讲述XamarinAndroid的服务。讲述如何使用StartedService在后台执行长时间任务,以及BoundService给调用者提供接口。首先讲解与调用者同进程的本地服务的各种实现机制,在讲解远程服务如何使用Messenger类与客户端通信。最后,详细的股票服务展示如何实现同时为Started Service和Bound Service的服务。另外,范例还演示了使用警告来调度服务更新,以及使用通知和广播与用户通信。
This article covered using services inXamarin.Android. It discussed how Started Services can be used to do long runningwork in the background, as well as how Bound Services can be used to provide aservice interface to callers. After examining the various mechanisms forimplementing services locally within the process of the caller, remote serviceswere examined, showing how to use the Messenger classto communicate from a client. Finally, a detailed stock service example waspresented that showed how to implement a service as both a Started Service aswell as a Bound Service. In addition, the example covered how to use alarms toschedule service updates, as well as how to communicate to the user usingnotifications and broadcasts.
第一节 - Started Services
StartedService生命周期
服务的生命周期与调用它的组件如Activity是独立的。这种自制机制使服务在启动它的组件停止后还能继续执行。下图阐述了Started Service的生命周期,在总结小计中讲述每个方法的实现方式。
Services havea lifecycle that is separate from that of the component, such as an Activity,that starts them. This autonomy allows a service to continue running even afterthe starting component is no longer alive. The following figure illustrates thelifecycle of a started service, followed by a summary section that outlineswhat type of code would go in each method:
Service类包含了一个必须实现的纯抽象OnBind方法。在StartedService中可以简单的返回null,而在Bound Service中,必须返回IBinder实例用于客户端调用服务。将在下面解释。
The Service
class also includes a method called OnBind
that must be implemented because it is abstract. Itshould simply return null for a started service that is not also a boundservice. OnBind
is used by bound services to return an IBinder
instance that clients use to call a service. Thisprocedure will be examined later in this article.
回调方法的总结
上图是服务各个生命周期调用的各种方法。下面分别描述他们:
l OnCreate—服务第一次启动时被调用。用于编写初始化代码。
l OnStartCommand—启动服务的时候被调用,可调用StartService方法启动服务或系统自动重启服务。在这里启动长时间运行任务。方法返回StartCommmandResult
类型的值,指示系统在内存缺少时服务被关闭,是否需要自动服务重启。这个调用在主进程中执行。
l OnDestroy—服务调用StopSelf或StopService时调用。这里可执行服务的结束过程。
The figureabove shows a variety of methods that are called at different points in thelifecycle of a service. The following list describes each of them:
· OnCreate
– Called one time when theservice is first started. This is where initialization code should beimplemented.
· OnStartCommand
– Called for each request tostart the service, either as a result of a call to StartService or a restart by the system. Thisis where the service can begin any long-running task. The method returns aStartCommmandResult
value that indicates how or ifthe system should handle restarting the service after a shutdown due to lowmemory. This call takes place on the main thread.
· OnDestroy
– Called after the servicereceives a StopSelf or StopService call. This is where service-widecleanup can be performed.
Sticky(粘性)和非Sticky服务
当系统内存紧张时,Android可能会停止所有运行的服务。例外的情况是服务可显示的在前台启动。
当服务被系统停止,Android将根据OnStartCommand方法返回的值确定服务是否需要重启。StartCommandResult
值如下:
l Sticky—Sticky服务会重启,重启时OnStartCommand的intent参数为null。用于连续不断执行长时间运行的操作,如更新股票信息。
l RedeliverIntent
—服务会重启,服务暂停前最后一个传递给OnStartCommand的intent将传递进来。用于继续执行目录,如上传大文件。
l NotSticky
– 服务不会自动重启。
l StickyCompatibility
– 在API5及以后版本,效果同Sticky。但可用于API5之前版本。
When thesystem is under memory pressure, Android may stop any running services. Theexceptions to this rule are services explicitly started in the foreground,which are discussed later in this article.
When a service is stopped by the system, Android willuse the value returned from OnStartCommand
to determine how or if the service should berestarted. This value is of type StartCommandResult
, which can be any of the following:
· Sticky
– A sticky service will berestarted, and a null intent will be delivered
· to OnStartCommand
at restart. Used when theservice is continuously performing a long-running operation, such as updating astock feed.
· RedeliverIntent
– The service is restarted, andthe last intent that was delivered to OnStartCommand
before the service was stoppedby the system is redelivered. Used to continue a long-running command, such asthe completion of a large file upload.
· NotSticky
– The service is notautomatically restarted.
· StickyCompatibility
– Restart will behave like Sticky
on API level 5 or greater, butwill downgrade to pre-level 5 behavior on earlier versions.
Service类
概述
Service类是任意服务的基类。要创建服务,应用程序必须做如下过程:
l 必须创建一个Service的子类,重新前面讨论的声明周期方法实现服务。
l 类必须在AndroidManifest.xml中进行注册,在Xamarin Android中可以使用ServiceAttribute来修饰Service的子类。
另外,系统提供了Service子类IntentService,已经简单的实现了服务,但不能处理模拟器情况。本文将讨论IntentService类。
The Service
class is the base class used for any service. Tocreate a service, an application must do the following:
· Aclass must be created that inherits from Service
, overriding the variouslifecycle methods described earlier that implement the service.
· Theclass must be registered with the system by using the AndroidManifest.xml
, which in Xamarin.Android canbe achieved by decorating the Service
subclass with the ServiceAttribute
.
Additionally, the system provides a Service
subclass called IntentService,
which simplifies service development at the cost ofnot being able to handle simultaneous requests. IntentService
is discussed in detail later in this article.
实现服务
要实现服务,必须创建Service的子类,并使用自定义属性ServiceAttribute来修饰,如下所示:
To implement a service, a class must be created thatinherits from Service
, and is adorned with theServiceAttribute
custom attribute, as shown below:
[Service]
public class DemoService : Service
{
…
}
这个属性将使服务在应用程序生成的时候注册到AndroidManifest.xml中。例如,将在AndroidManifest.xml中生成如下XML节点:
The attribute will result in the service beingregistered in the AndroidManifest.xml
that is packaged with the application at build time.For example, an XML element like the following could also be added directly toanAndroidManifest.xml
:
<service android:name="demoservice.DemoService"></service>
更多关于 AndroidManifest.xml
的信息见 Working with AndroidManifest.xml文档。
注意服务运行在主线程中,因此长时间运行的工作应该开启独立线程。将在线程章节讨论。
从Context类继承的其他组件如Activity,可以在需要的时候调用StartService方法启动服务。(对于调用远程服务方法,需要使用Bound服务,将在后续章节讲解)。例如,Activity中的如下代码将启动服务:
For more about the AndroidManifest.xml,
see the Working with AndroidManifest.xml document.
It isimportant to note that the service will run on the main thread, so anylong-running work must be kept on a separate thread. This is demonstrated inthe threading section later in this article.
Another component that inherits from Context
, such as an Activity
, will start the service in the started servicescenario described here by calling the StartService
method. (When performing remote procedure calls to aservice, a bound service is used. This process is described later in thisarticle.) For example, the following code in an Activity would start thisservice:
StartService (new Intent (this, typeof(DemoService)));
调用StartService将执行服务的OnStartCommand生命周期函数。实现OnStartCommand时必须返回一个StartCommandResult
类型值,当由于内存紧缺导致服务停止时,这个值决定操作系统是否/如何重启服务。例如,返回StartResultCommand.Sticky
,指示服务会自动重启,并传递intent参数为null。
Calling StartService
results in the OnStartCommand
lifecycle method being called on the service. Theimplementation of OnStartCommand
must return a StartCommandResult
, which determines if the service will be restartedby the system, if it is shut down in a low memory situation. For example, thefollowing code returnsStartResultCommand.Sticky
, which would result in the service being restartedautomatically with a null intent:
public override StartCommandResultOnStartCommand (Android.Content.Intent intent, StartCommandFlags flags, intstartId)
{
Log.Debug ("DemoService", "DemoService started");
...
returnStartCommandResult.Sticky;
}
为了节省系统资源,Started服务在完成长时间运行任务后需要调用StopSelf方法。这很重要,因为服务独立于启动他的组件运行。因此,即使启动他的组件如Activity已经释放,服务仍然可能运行。
例如,如下代码在执行完任务后调用StopSelf方法:
In order to conserve system resources, a startedservice implementation calls StopSelf
after any long-running work is done. This isimportant because the service will run independently of the component thatcalledStartService
. Therefore, the service will keep running even after thestarting component, such as an Activity
, is destroyed.
For example, the following code calls the StopSelf
method after it completes its work:
public void DoWork ()
{
var t = new Thread (() => {
Log.Debug ("DemoService", "Doing work");
Thread.Sleep (5000);
Log.Debug ("DemoService", "Work complete");
StopSelf ();
}
);
t.Start ();
}
另外,服务启动者可调用StopService方法停止服务:
Additionally, the caller can request that the servicebe stopped by calling the StopService
method, as shown below:
StopService (new Intent (this, typeof(DemoService)));
Android在没有客户端绑定到服务的时候停止服务。绑定服务见后续文档。
Android willstop the service if there are no clients bound to it. Bound services arediscussed later in this article.
使用startId停止服务
服务可有多个调用者启动。如果有未完成的请求,可通过OnStartCommand传递的startId参数避免服务过早停止。startID与最近一次调用StartService相对应,每次调用都加1.因此,如果随后的请求还没有执行完OnStartCommand,服务可以调用StopSelfResult,传递最后一次接收到的startId的值(替换简单调用StopSelf)。如果StartService对应的OnStartCommand还没有执行完,系统就不会停止这个服务,因为用于调用StopSelf的startId参数并非最后调用StartService对应的值。
Multiple callers can request that a service bestarted. If there is an outstanding start request, the service can use thestartId
that is passed into OnStartCommand
to prevent the service from being stoppedprematurely. ThestartId
will correspond to the latest call to StartService
, and will be incremented each time it is called.Therefore, if a subsequent request to StartService
has not yet resulted in a call to OnStartCommand
, the service can call StopSelfResult
, passing it the latest value of startId
it has received (instead of simply callingStopSelf
). If a call to StartService
has not yet resulted in a corresponding call to OnStartCommand
, the system will not stop the service, because the startId
used in the StopSelf
call will not correspond to the latestStartService
call.
线程
服务运行在主线程,长时间运行的任务将阻塞主线程,导致应用程序反应缓慢。因此,服务中的一些代码应该在单独线程中执行。在Xamarin Android中,可使用System.Threading命名空间中的Thread类。例如,长时间运行的代码可运行在新的Thread类型中,如下所示:
A service runs on the main thread, so anylong-running task would block the main thread, making the applicationunresponsive. Therefore, such code should be implemented on a separate threadin the service. With Xamarin.Android, the threading classes from the System.Threading
namespace are available for this scenario. Forexample, the long-running code can be run on a new Thread
like this:
Thread t = new Thread (() => {
// long running code ...
});
t.Start();
处理服务析构
服务停止时将执行OnDestroy方法。在这里可清理服务占用的资源。需要简单的重写OnDestroy方法,如下所示:
When a service is stopped, the OnDestroy
method will be called on the service. This is thepart of the process where any cleanup of service-wide resources can be done. Toprovide an implementation, simply overrideOnDestroy
as follows:
public override void OnDestroy ()
{
base.OnDestroy ();
// cleanup code
}
使用IntentFilter启动服务
到目前为止,已经讲解了通过调用StartService(显式传递一个Intent和Service子类)来启动服务。这对于调用同一个应用程序内的服务是OK的。然而,服务还必须让其他应用程序来调用。无论调用本地或远处服务,都可使用Intent filter。
同服务一样,Intent filter也注册在AndroidManifest.xml
中。这个注册也可通过Xamarin Android的属性来完成。要为服务注册intent filter,服务类可通过IntentFilterAttribute
来修饰。例如,如下代码中给服务添加叫做com.xamarin.DemoService
的intent filter。
The discussion to this point has been about servicesthat are started by calling StartService
with an explicit intent for the type of the Service
subclass. This call is all that is needed if aservice is only to be used from within a single application. However, a servicecan also be made available in such a way that other applications can start it.To call a service in either the local or remote scenario, use intent filters.
Like the service itself, intent filters areregistered in the AndroidManifest.xml
. This registration can also be accomplished withXamarin.Android by using attributes. To register an intent filter for aservice, the Service
class is decorated with an IntentFilterAttribute
. For example, the following code adds an intentfilter with an associated action of com.xamarin.DemoService
:
[Service]
[IntentFilter(new String[]{"com.xamarin.DemoService"})]
public class DemoService : Service
同样,这个属性使AndroidManifest.xml
文件添加了一个节点,随应用程序一起打包,内容如下:
Again, specifying this attribute results in an entrybeing included in the AndroidManifest.xml
file, an entry that is packaged with the applicationin a way analogous to the following example:
<service android:name="demoservice.DemoService">
<intent-filter>
<action android:name="com.xamarin.DemoService" />
</intent-filter>
</service>
添加属性后,从Context继承的子类中可以启动服务,例如,Activity使用如下代码,传递intent filter:
With this attribute in place, the service can bestarted from any class that inherits from Context
, For example,Activity
uses the following code, and passes the action to theintent:
StartService (new Intent ("com.xamarin.DemoService"));
同样,在Activity中如下代码可停止服务:
Likewise, the following code would stop the servicefrom an Activity
:
StopService (new Intent ("com.xamarin.DemoService"));
通知用户
使用通知(Notification)
Startedservice用于长时间运行任务,但又不需要与用户交互的情况,运行在调用组件的生命周期之内。然而,使用通知可以在服务中与用户通信。Started service中的通知可用于长时间运行任务的情形,如大文件传输、完成,或提示用户特定事件。
如下代码将通知显示给用户,在通知窗口中显示信息:
Startedservices are used in scenarios where some long-running task is required thatboth does not need any user interaction and that needs to run beyond thelifetime of the calling component. However, by using notifications, informationfrom the service can be communicated to the user. Notifications on startedservices would be used in scenarios such as when a long-running task—like alarge file transfer—completes, or any case where an alert of some future eventneeds to be communicated.
For example,the following code displays a notification to the user, presenting a message inthe ticker and the notification screen:
varnMgr = (NotificationManager)GetSystemService (NotificationService);
var notification = new Notification (Resource.Drawable.Icon, "Message from demo service");
varpendingIntent = PendingIntent.GetActivity (this, 0, new Intent (this, typeof(DemoActivity)), 0);
notification.SetLatestEventInfo (this, "Demo Service Notification", "Message from demo service", pendingIntent);
nMgr.Notify (0, notification);
效果如下:
This coderesults in the following notification ticker:
当用户将通知窗口从上往下滑动,界面如下:
When the userslides down the notification screen from the top, the full notification isdisplayed:
通知中包含一个PendingIntent
成员
,封装与之相关的Intent。如果用户选择通知,PendingIntent
中的Activity将被置于前台。
通知同时包含一个图标,是的Notification
类
构造函数的第一个参数。可用于通知的图标大小件Status Bar Icons。
The notification includes a PendingIntent
, which is used to encapsulate an intent along withan action. If the user selects the notification, the Activity
returned from the PendingIntent
will be brought to the foreground.
The notification also includes an icon, which can bespecified in the first argument to the constructor of theNotification
class. For details about the sizes that can be usedfor the notification icon see Status Bar Icons.
使用 Toast
通知机制适合于在发生重要的变更或需要输入时警告用户。这时因为通知向状态栏发生了临时信息,同时还有其他相关联信息。然而对于临时的被动的(不响应用户动作)消息,使用Toast。Toast类提供了一个临时弹出消息,如下所示:
Notifications are the preferred mechanism foralerting the user of an important change or the need for input. This is becausenotifications provide both a transient message in the status bar, as well asmore persistent information that can be both accessed and acted upon later, viathe notification screen. However, for messages that are both transient andpassive (“passive” meaning no user action can be taken on them), a Toast canalso be used. TheToast
class provides a temporary pop-up containing amessage, as the following code illustrates:
Toast.MakeText (this, "The demo service has started", ToastLength.Long).Show();
界面如下所示:
This codeproduces the Toast in the screenshot shown below:
由于服务运行于主线程,可在OnStartCommand中直接调用Toast。然而,与Activity不同,服务没有RunOnUIThread
方法。在服务中可使用Handler实现相同效果。执行在独立线程中的服务代码可直接通过Handler的Post方法更新UI界面:
Since the service is running on the main thread, theToast can be displayed directly in the OnStartCommand
method. However, unlike Activities, services don’thave a RunOnUIThread
method. From services, a Handler
can be used to achieve the same result. Any UIupdates that are made directly from code in a service that is running on aseparate thread can be performed in the Post
method of a handler as follows:
varmyHandler = new Handler ();
...
myHandler.Post(() => {
Toast.MakeText (this, "Message from demo service", ToastLength.Long).Show();
});
除了Handle,还有其他可选的方法,包括Android.App.Application.SynchronizationContext.Post()
和System.Threading.SynchronizationContext.Current.Post()
。
In addition to a Handler
, other options that may be used includeAndroid.App.Application.SynchronizationContext.Post()
andSystem.Threading.SynchronizationContext.Current.Post()
.
接收广播
在服务中简单的通知用户发生的事件,可使用上面介绍的通知。而在Start Service中更新界面,可使用广播消息,Activity可使用广播接收器接收更新通知。
服务使用SendBroadcast
方法广播,传递与动作相关的Intent。Activity实例化BroadcastReceiver
的子类,将接收器注册为与服务广播动作相同的IntentFilter,在OnReceive方法中接收广播。使用BroadcastReceiver
的范例见本文后面的股票范例。
For simply notifying the user that something hashappened in the service, use notifications as shown earlier. For more elaborateUI updates from a started service, intents can be used to broadcastinformation, and an Activity can use a BroadcastReceiver to receiveupdates.
The service would send a broadcast by calling the SendBroadcast
method, passing it an intent with an action. TheActivity would create an instance of a class that inherits from BroadcastReceiver
, registering the receiver with anIntentFilter
for the same action that the service used when itsent the broadcast. The receiver would then listen for the broadcast, receivingit in its OnReceive
method. An example using a BroadcastReceiver
is presented in the stock service example later inthis article.
前台服务
本文已经讨论了重启相关话题。这个行为可由StartCommandResult
控制。必须恰当的处理由于内存压力导致的服务重启。然而,也可以创建在正常情况下不会释放的服务。这种服务叫做前台服务(foreground services)。用户能够感觉到前台服务的存在。例如网络广播服务就可以作为前台服务,因为用户通过声频播放就能感觉到服务的存在。
同时,不同于其他服务通知时可选的,前台服务需要通知来告诉用户服务在运行中。下面的代码运行在Service的OnStartCommand中,将在其他一个前台服务:
Various restart behaviors were discussed earlier inthis article. These behaviors are controllable through theStartCommandResult
. It’s important to handle service restarts properlywhen those restarts are triggered while the system is under memory pressure.However, it’s possible to create some services that will not be destroyed bythe system under normal circumstances. Services of this kind are known as foreground services. Users areactively aware of the presence of foreground services. For example, an Internetradio service is a good candidate to be created as a foreground service becausethe user is aware of its existence through the audio it plays back.
Also, unlike other services, where notifications areoptional, foreground services require notifications so that users will be awarethat the services are running. The following example, which would run insidethe Service
(in theOnStartCommand
method, for instance), shows how to request startinga service in the foreground:
var ongoing = new Notification (Resource.Drawable.Icon, "DemoService in foreground");
varpendingIntent = PendingIntent.GetActivity (this, 0, new Intent (this, typeof(DemoActivity)), 0);
ongoing.SetLatestEventInfo (this, "DemoService", "DemoService is running in the foreground", pendingIntent);
StartForeground ((int)NotificationFlags.ForegroundService, ongoing);
上面的代码中创建一个通知警告用户服务是运行在前台的。通知对象和NotificationFlags.ForegroundService
标志一起传递给StartForeground
方法。
要将服务从前台移除,简单的调用StopForeground
方法,传递一个Boolean值指示是否同时将通知也移除掉。StopForeground
方法将服务从前台移除以便于在内存紧张的时候可以停止服务,但这个函数不会直接停止服务。
当服务停止时,如果通知还在显示,也会被移除。
In the above code, a notification is created thatwill alert the user that the service is running in the foreground. Thisnotification is then passed to the StartForeground
method, along with theNotificationFlags.ForegroundService
flag.
To remove the service from the foreground, simplycall StopForeground
, passing a Boolean that indicates whether to removethe notification as well. StopForeground
removes the service from the foreground so that thesystem will be able to stop it under memory pressure, but it does not stop theservice.
When theservice is stopped, if the notification is still present, it will be removed.
IntentService类
有些服务不需要并发调用。这种情况下,可使用IntentService
类。IntentService
是的Service子类,简单的实现了服务的各种生命周期方法。从IntentService
类继承只需要实现OnHandleIntent
方法。
IntentService
将所有的Intent请求加入到工作者队列中等待处理。每个Intent都会在一个独立的线程中按顺序处理,因此不会阻塞主线程,会在OnHandleIntent
方法中传递Intent参数。当所有Intent处理完毕,IntentService
服务会调用StopSelf
方法结束自己。
Some services do not require multiple, simultaneouscallers. In such scenarios, the IntentService
class can be used. IntentService
is a Service
subclass that simplifies service development byimplementing various lifecycle methods internally. Implementing an IntentService
requires only that the OnHandleIntent
method be implemented.
The IntentService
works by sending all intents to a worker queue forprocessing. This queue processes each intent serially on a separate thread, soas to not block the main thread, passing the intent to the OnHandleIntent
method. When all the intents have been processed, the IntentService
stops itself by calling StopSelf
internally.
实现IntentService.OnHandleIntent
下面代码中实现了IntentService
类中的OnHandleIntent
方法:
The following code shows an IntentService
implementation with the overridden OnHandleIntent
method:
[Service]
[IntentFilter(new String[]{"com.xamarin.DemoIntentService"})]
public class DemoIntentService: IntentService
{
publicDemoIntentService () : base("DemoIntentService")
{
}
protected override void OnHandleIntent (Android.Content.Intent intent)
{
Console.WriteLine ("perform some long running work");
...
Console.WriteLine ("work complete");
}
}
除了OnHandleIntent
方法,还需要在构造函数中向基类中传递一个字符串。这个字符串用来标识内部的工作线程;上面的代码提供的字符串是“DemoIntentService
”。这个代码与其他服务一样使用属性注册IntentService服务。
另外,调用代码与上面所述的一样调用StartService方法,传递一个intent参数。每次调用StartService
,都会将其intent参数放在队列中,按顺序由OnHandleIntent方法处理。
OnHandleIntent方法中的代码不会阻塞主线程,因为会在独立线程中调用。
IntentService类可以极大简化服务开发,可用于无需多用户、并发的情况下。
Other than the OnHandleIntent
method, the only additional requirement is that theconstructor needs to pass a string to the base class. This string provides aname that is used to identify the worker thread internally; the code abovesupplies the string “DemoIntentService.”
The code to register the IntentService
uses the same attributes as any other service.
Additionally, the calling code would call StartService
, passing an intent as shown earlier in the article.Each timeStartService
is called, the intent would be queued and forwarded to OnHandleIntent
in the order received.
The implementation in OnHandleIntent
does not block because it is called on a separatethread from the main thread.
The IntentService
class greatly simplifies service development andshould be used in any scenario where multiple, simultaneous service calls arenot required.
第二部分- Bound Services
除了后台运行,服务也可以提供客户端-服务器接口,让客户与之交互。这样的服务叫做Bound Services。BoundService可以在本地进程中创建,也可以为多个应用程序提供服务而创建在远程进程中。本地服务为应用程序提供后台运行特定函数的能力。而远程服务提供了系统级别的跨进程处理的能力。
In addition to running indefinitely in thebackground, services can also provide a client-server interface that a clientcan interact with. Such services are termed BoundServices. Bound Services can be created either locally in aparticular application process, or in a remote process that can servicemultiple applications. Local services would be used to provide backgroundworker capability within an application process for particular method calls.Remote services, however, would be used across process boundaries to provide somesystem-wide capability.
Bound Service生命周期
Bound Service的生命周期与StartedService不同。Bound Service启动很有规律。当一个客户端请求连接的时候会创建服务,所有客户端断开连接时释放。然而,Bound Service也可以与StartedService一样,实现为Service的子类,如果由startService启动,则所有客户端断开连接后也不会释放。同样,如果还存在客户端连接,在调用了StopService或StopSelf后,也不会释放。
下图阐述了Bound Service生命周期:
The lifecycle of a Bound Service is different fromthat of a Started Service. Unlike Started Services, Bound Services do not runindefinitely. Instead, they are created when a client connects to them and aredestroyed after all bound clients have disconnected. However, a Bound Servicecan be implemented in the same Service
subclass as a Started Service, so if it has beenstarted with a StartService
call, it will not be destroyed even after all boundclients have disconnected. Likewise a StartedService
will not be destroyed after a call to StopService
orStopSelf
, if it still has bound clients connected.
The followingdiagram illustrates the lifecycle of a Bound Service:
l OnBind
– 这个方法会返回IBinder实例,客户端可从中获取服务实例,调用服务的方法。
l OnUnbind
– 当所有客户端都断开连接时会被调用。将返回值设置为true,当有客户端在连接过来的时候,会使用其intent参数做为参数调用OnRebind方法。如果当服务以及没有客户端绑定的情况下还要继续运行,就需要这么做。这将使在未调用StopService或StopSelf时,无客户端绑定的服务变为Started Service。这时可在OnRebind方法中获取intent参数。默认返回false。
· OnBind
– This method is used to returnan instance on an IBinder
that the client uses to obtain aservice instance that can, in turn, call methods on the service.
· OnUnbind
– This method is called when allbound clients have unbound. By returning true
from this method, the servicewill later call OnRebind
with the intent passed to OnUnbind
when new clients bind to it. Youwould do this when a service continues running after it has been unbound. Thiswould happen if the recently unbound service were also a started service, and StopService
or StopSelf
hadn’t been called. In such ascenario,OnRebind
allows the intent to beretrieved. The default returns false
, which does nothing.
实现本地Bound Service
实现本地Bound Service需要如下步骤:
1. 创建返回服务实例的Binder子类,Binder实现了IBinder。
2. 实现返回Binder子类实例的OnBind方法。
Binder子类负责向客户端返回服务实例,以便于调用服务方法。OnBind方法返回Binder实例。客户端要连接到服务的时候会调用OnBind方法。
例如,下面代码实现了Binder子类DemoServiceBinder
:
Implementing aLocal Bound Service requires the following:
1. A subclass of the Binder
class that returns the serviceinstance, where Binder
is the default implementation of IBinder
.
2. Animplementation of the OnBind
method that returns an instanceof the Binder
subclass.
The Binder
subclass is responsible for returning the serviceinstance to clients so that they can make method calls against it. The OnBind
method returns an instance of the Binder
. OnBind
is called by Android the first time any clientattempts to connect to a service.
For example, the following code shows animplementation of a Binder
subclass named DemoServiceBinder
:
1 |
publicclassDemoServiceBinder : Binder {
DemoService service;
publicDemoServiceBinder (DemoService service) {
this.service= service; }
publicDemoServiceGetDemoService () {
return service; }
}
|
客户端可使用DemoServiceBinder
类获取DemoService
,这样客户端就可以调用服务方法了。在DemoService类的OnBind方法中需要返回DemoServiceBinder类的实例。下面代码是DemoServiceBinder
类的OnBind方法的实现:
Clients can use the DemoServiceBinder
class to obtain a reference to the DemoService
itself, which the client can then use to call theservice’s methods. The mechanism that the DemoService
uses to return the instance ofDemoServiceBinder
is the OnBind
lifecycle method. The following code shows theimplementation of OnBind
that returns a DemoServiceBinder
:
1 |
publicoverrideIBinderOnBind (Intent intent) {
binder =newDemoServiceBinder (this); return binder; }
|
binder实例变量中有对DemoService的引用。由于OnBind尽在客户端第一次连接到服务的时候调用,后续会重用这个binder实例。
The binder
variable is an instance variable for which the DemoService
keeps a reference. Since the OnBind
method is only called on the first connection to theservice, additional calls will reuse the same binder instance.
客户端服务绑定
客户端调用BindService方法,传递一个intent和一个ServiceConnect实例,连接到服务上。ServiceConnection是一个在客户端和服务间提供调用接口的类。例如,如下代码在Activity的OnStart方法中绑定一个intent filter为com.xamarin.DemoService
的服务:
A client binds to a service by calling BindService
with an intent and an instance of a ServiceConnection
. AServiceConnection
is a class that provides a calling interface betweenthe client and the service. For example, the following code in the OnStart
of an Activity binds a service that has an intentfilter with the actioncom.xamarin.DemoService
:
1 |
Protected override void OnStart () {
base.OnStart ();
var demoServiceIntent = newIntent ("com.xamarin.DemoService"); demoServiceConnection = new DemoServiceConnection (this); BindService (demoServiceIntent, demoServiceConnection, Bind.AutoCreate); }
|
上面的代码中在BindService函数中传递了Bind.AutoCreate
参数值,如果绑定成功将自动创建服务。BindService是异步调用的,如果不存在可绑定的服务则返回false,或连接成功的时候触发ServiceConnection
类的OnServiceConnected
函数。
上面代码还创建了DemoServiceConnection
实例。这是ServiceConnection
的子类,并重写了OnServiceConnected
方法获取IBinder的引用。本例中IBinder实例是DemoServiceBinder
类型的。DemoServiceBinder
实例可获取DemoService实例,这样客户端就可以调用服务的方法了。
DemoServiceConnection
类定义如下:
The above code uses the Bind.AutoCreate
value in the BindService
call to automatically create the service, if thebinding exists. BindService
itself is an asynchronous call that will eitherreturn false
if there is no service to bind to, or cause acallback to be sent to the OnServiceConnected
method of a ServiceConnection
class when the connection is made.
The code also creates an instance of DemoServiceConnection
. This is a class that inherits fromServiceConnection
and overrides OnServiceConnected
to get a reference to the particular IBinder
for the service. In this case, IBinder
is an instance of a DemoServiceBinder
. The DemoServiceBinder
is used to get a reference to the DemoService
itself, so that the client can call any methods thatthe service defines.
The following code shows the DemoServiceConnection
class:
1 |
classDemoServiceConnection : Java.Lang.Object, IServiceConnection {
DemoActivity activity;
publicDemoServiceConnection (DemoActivity activity) {
this.activity= activity; }
publicvoidOnServiceConnected (ComponentName name, IBinder service) {
vardemoServiceBinder= service asDemoServiceBinder; if (demoServiceBinder!=null) { activity.binder=demoServiceBinder; activity.isBound=true; }
}
publicvoidOnServiceDisconnected (ComponentName name) {
activity.isBound=false; }
}
|
注意构造函数中传递了一个Activity实例参数,以便于在OnServiceConnected回调方法中获取Activity实例,并设置Activity的binder属性。这是特意安排的流程,使在Activity中使用其binder属性就可以获取到服务的引用,以便于调用服务方法。
Notice that an instance of the Activity
is passed into the constructor so that the binderobtained in theOnServiceConnected
callback can be set on the Activity
itself. This is the order of the process because it’stheActivity
that wants to use the binder to get a reference tothe service so that the Activity can call the service’s methods.
调用服务方法
例如假设服务定义了一个GetText方法。点那个OnServiceConnected被调用而且Activity设置了binder属性,Activity就可以使用binder获取服务实例并调用GetText方法了:
For example, say the service defines a method called GetText
. Once OnServiceConnected
has been called and theActivity
has the binder, the Activity can use the binder toget the service reference and subsequently callGetText
as follows:
1 |
binder.GetDemoService ().GetText (); |
解除服务绑定
客户端如Activity在使用完服务后必须解除绑定。这样可使服务在无请求的时候释放自己。当断开服务连接,ServiceConnect类的OnServiceDisconnected方法将被调用。
要解除位于服务的绑定,客户端可调用UnbindService,以绑定时的ServiceConnection
实例作为参数。例如,下面代码展示Activity在OnDestroy方法中解除与服务的绑定:
Clients such as the Activity
shown here also must unbind from the service whenthey are finished using it. This allows the service to shut down when it is notin use. After the service disconnects, the OnServiceDisconnected
method of ServiceConnection
class will be called.
To unbind from a service, a client calls UnbindService
, passing the ServiceConnection
instance it used in the binding. For example, thefollowing code shows an Activity unbinding from a service in the OnDestroy
method of the Activity:
1 |
protectedoverridevoidOnDestroy () {
base.OnDestroy ();
if (isBound) { UnbindService (demoServiceConnection); isBound=false; }
}
|
如果没有未返回的StartService调用,也没有客户端绑定到服务上,Android将会关闭服务。然而,当配置变更时(如旋转设备)会调用OnStop方法,这时上面的代码将解除服务绑定。如何保持对服务的绑定将在下节讨论。
If there are no outstanding calls to StartService
, and no other clients are bound to the service,Android will shut down the service. However, since OnStop
is called during a configuration change (such as arotation change), the above code would also unbind from the service in thatscenario. How to preserve the binding to the service in this scenario isdiscussed next.
处理配置变更
当配置变更(如旋转设备)时,所有Activity生命周期函数中(如OnPause或OnStop)与服务的绑定和解除绑定代码都会执行。如果绑定到服务是很费时的,可以保存ServiceConnection
。为绑定到服务的客户端在配置变更后保存ServiceConnection
:
l 应该在ApplicationContext
中调用BindService,而不是在Activity中
l 在nRetainNonConfigurationInstance
方法中返回SerivceConnection
实例
l nRetainNonConfigurationInstance
方法应该设置一个标志位,当配置变更并没有导致服务停止,就不需要绑定服务。
When a configuration change such as a device rotationoccurs, any binding and unbinding code that is bound to an Activity and placedin a lifecycle method (such as OnPause
or OnStop
) will be run. If binding to a particular service isexpensive, you may want to preserve the ServiceConnection
. For a client bound to a service to preserve theServiceConnection
across configuration changes:
· The BindService
method should be called from the ApplicationContext
rather than from the Activity.
· The SerivceConnection
instance should be returned from OnRetainNonConfigurationInstance
.
· The OnRetainNonConfigurationInstance
method should set a flag thatwill only be used to unbind the service when the service is not stopped due toa configuration change.
上面代码应改为:
For the code above, the BindService
call would become:
1 |
ApplicationContext.BindService (demoServiceIntent, demoServiceConnection, Bind.AutoCreate); |
当配置变更时DemoServiceConnection
应该保存IBinder 实例的引用:
Then the DemoServiceConnection
would keep a reference to the IBinder
for retrieval after the configuration change:
1 |
classDemoServiceConnection : Java.Lang.Object, IServiceConnection {
DemoServiceBinder binder;
publicDemoServiceBinderBinder { get { return binder; }
}
...
publicvoidOnServiceConnected (ComponentName name, IBinder service) {
vardemoServiceBinder= service asDemoServiceBinder; if (demoServiceBinder!=null) { ...
// keep instance for preservation across configuration changes
this.binder= (DemoServiceBinder)service; }
}
|
接着OnRetainNonConfiguration
方法应该设置一个标志位,在Activity中将其初始化为false。接下来将阻止由于配置变更而释放绑定,并返回DemoServiceConnection
实例:
Next, the OnRetainNonConfiguration
method would set a flag, initialized to false
when declared in the Activity. This method would thenbe used to prevent unbinding due to a configuration change, and would returntheDemoServiceConnection
instance:
1 |
Bool isConfigurationChange=false; ...
// return the service connection if there is a configuration change
publicoverrideJava.Lang.ObjectOnRetainNonConfigurationInstance () {
base.OnRetainNonConfigurationInstance ();
isConfigurationChange=true;
returndemoServiceConnection; }
protectedoverridevoidOnDestroy () {
base.OnDestroy ();
if (!isConfigurationChange) { if (isBound) { UnbindService (demoServiceConnection); isBound=false; }
}
}
|
最后,在Activity的OnCreate方法中,从LastNonConfigurationInstance
中获取DemoServiceConnection
和DemoServiceBinder
的实例:
Finally, in the OnCreate
method of the Activity, the DemoServiceConnection
instance and theDemoServiceBinder
would be retrieved from the LastNonConfigurationInstance
:
1 |
// restore from connection there was a configuration change, such as a device rotation
demoServiceConnection=LastNonConfigurationInstanceasDemoServiceConnection;
if(demoServiceConnection!=null) binder =demoServiceConnection.Binder; |
更多与旋转Activity相关内容见Handling Rotation。
For more information about how to work with rotationin Activities, see Handling Rotation.
服务的进程间通信
除了本地服务(与调用者在同一个进程),服务也可以在其自己的进程内创建。要在Android中执行于服务的进程间通信(IPC:inter-process communication),可使用Messenger
类以客户端服务器的方式发生消息。与服务进行IPC通信的更高级技术(AndroidInterface Definition Language (AIDL)),将在XamarinAndroid的后续发布版中提供。
In addition to local services, which run in the sameprocess as the caller, services can also be created in their own processes. Toperform inter-processcommunication (IPC) toa service on Android, a Messenger
class can be used to send messages in a client-serverfashion. A more advanced technique that can be employed in order to perform IPCwith services is to use AndroidInterface Definition Language (AIDL), which Xamarin.Android willsupport in a future release.
使用Messager类
Android提供了Messager类无需AIDL支持就可以与服务进行IPC通信。当使用Messager,客户端发生的消息将放置在服务的队列中,依次进行处理。同样,Messager也不会向客户端暴漏服务的接口。客户端发现Message对象,服务使用Handler类进行处理。
Android provides the Messenger
class to enable IPC for services without using AIDL.When using Messenger
, messages sent from clients are queued in theservice and processed one at a time. Also, the Messenger
does not expose a service interface to clients.Instead, clients send Message
objects, which the service handles in a Handler
class.
实现Messenger-BoundService
要实现使用Messager技术的服务,需要在服务中创建一个Messager。服务端应该做两件事:
1. 创建一个Handler子类
2. 使用Handler实例作为参数,创建Messager实例
例如,如下代码中的服务,使用DemoHandler类的实例创建命名为demoMessenger
的Messager:
Implementing a service that uses a Messenger
involves creating a Messenger
in the service that returns a binder to the client.Two things need to be in place in the service for this:
1. A class that inherits from Handler
.
2. A Messenger
instance that is created with aninstance of the Handler
.
For example, the following code shows a service thatuses a Messenger
instance named demoMessenger
that, in turn, takes an instance of a class called DemoHandler
:
1 |
[Service] [IntentFilter(newString[]{"com.xamarin.DemoMessengerService"})] publicclassDemoMessengerService : Service {
Messenger demoMessenger;
publicDemoMessengerService () {
demoMessenger=newMessenger (new DemoHandler ()); }
publicoverrideIBinderOnBind (Intent intent) {
returndemoMessenger.Binder; }
classDemoHandler : Handler {
publicoverridevoidHandleMessage (Messagemsg) {
...
}
}
}
|
当客户端绑定到服务,并向服务发生含有Message对象的消息时调用HandleMessage方法。
为了简单,这里的Message对象包含两个整数属性,命名为Arg1和Arg2,还有一个命名为What的属性用来区分其他消息调用。例如,基于上面的两个属性,客户端可以发送不同的What值,使HandleMessag的实现执行不同的动作。
要传递更多类型数据,Message有一个Android.OS.Bundle类型的Data属性。这个属性可以使用键值对传递更多的值。例如,如果要在Data中包含一个叫做key为InputText的值,可在服务的HandleMessage中按如下方式获取:
The HandleMessage
method is called when a client binds to the serviceand sends a message to the service that is contained in a Message
object.
For simple messages, the Message
object can contain two integer properties, named Arg1
and Arg2
respectively, as well as a property called What
that can be used to distinguish the particularmessage call. For instance, based upon the values of these two properties, theclient could send different values of What
and the HandleMessage
implementation and so take different actions.
To send more types of data than integers, Message
includes a Data
property that is an Andorid.OS.Bundle
. This property can include other values that can beretrieved by key. For example, if the client included a string with the key 'InputText'
in the Data
, this string could be retrieved in the HandleMessage
method in the service as shown below:
1 |
classStockHandler : Handler {
publicoverridevoidHandleMessage (Messagemsg) {
Log.Debug ("DemoMessengerService", "What = "+msg.What.ToString()); string text =msg.Data.GetString ("InputText"); Log.Debug ("DemoMessengerService", "InputText = "+ text); }
}
|
要使用Messager调用服务,客户端需要创建Message对象,然后调用Messager的Send方法。客户端需要:
1. 实现创建Messager的IServiceConnection
接口
2. 创建Message对象并添加数据
3. 调用Messager的Send方法
To use a Messenger
to call a service, a client needs to create a Message
object, and then call the Send
method of the Messenger
class. The client needs to:
1. Implement an IServiceConnection
that creates a Messenger
.
2. Createa Message
object and add data to it.
3. Callthe Send
method of the Messenger
.
在客户端创建Messenger
客户端在连接到服务的时候(在IServiceConnection
的实现类中完成,客户端调用BindService后)创建Messager。例如,下面代码中客户端连接到服务,然后创建Messager实例:
The client creates the Messenger
when it connects to the service. In the IServiceConnection
implementation, this occurs after the client calls BindService
. For example, the following code shows a clientconnecting to a service, and then creating a Messenger
instance.
1 |
protectedoverridevoidOnStart () {
base.OnStart ();
vardemoServiceIntent=newIntent ("com.xamarin.DemoMessengerService"); demoServiceConnection=newDemoServiceConnection(this); BindService (demoServiceIntent, demoServiceConnection, Bind.AutoCreate); }
protectedoverridevoidOnStop () {
base.OnStop ();
if (isBound) { UnbindService (demoServiceConnection); isBound=false; }
}
classDemoServiceConnection : Java.Lang.Object, IServiceConnection {
DemoMessengerActivity activity;
publicDemoServiceConnection (Activity1 activity) {
this.activity= activity; }
publicvoidOnServiceConnected (ComponentName name, IBinder service) {
activity.demoMessenger=newMessenger (service); activity.isBound=true; }
publicvoidOnServiceDisconnected (ComponentName name) {
activity.demoMessenger.Dispose (); activity.demoMessenger=null; activity.isBound=false; }
}
|
这个过程与上面展示的本地Bound Service很相似。主要的不同在于在OnServiceConnected
方法中使用IBinder创建Messager实例。
This procedure is very similar to the local boundservice example shown earlier. The main difference is that the code here, in OnServiceConnected
, creates a Messenger
from the IBinder
, which it can then use to call the service.
创建消息(Message)
使用Message.Obtain方法创建消息。Message.Data属性是Android.OS.Bundle类的,可设置大量信息。例如,下面代码创建一个Message并包含一个键为InputText的值:
To create a Message
, use the Message.Obtain
method. The Message.Data
property allows anAndroid.OS.Bundle
to be sent in the Message
, as mentioned earlier. The bundle can be used toinclude additional data in the Message
. For example, the following code creates a Message
and includes a string with the key“InputText”
to a bundle:
1 |
Messagemessage=Message.Obtain (); Bundle b =newBundle (); b.PutString ("InputText", "text from client"); message.Data= b; |
将Message发送到服务
使用在IBinder的 OnServiceConnected
方法中创建的Messager实例向服务端发送消息。如下所示:
To send the Message
to the service, the Messenger
instance created from the IBinder
in OnServiceConnected
is used. Simply call the Send
method of the Messenger
, as shown below:
1 |
demoMessenger.Send (message); |
服务将在Handler类中接收到Message,并提取数据。
The server will receive the Message
in the Handler
, where it can extract the data, as shown earlier.