Introduction
Android services, are Android components that allow work to be done in the background. While, as the name implies, services can be used for building long running persistent background tasks, this is not their default behavior. Android services come into two main flavours:
- the
Service
class, and, - the derived
IntentService
class.
While the
IntentService
class is very easy to use, and suitable for most cases, it fails to support cases where a persistent TCP connection, e.g. a XMPP connection, or a long running waiting background service is required. In order to do so, the Sticky
service behavior is required, available only to the base Service
class. In this article, the new StickyIntentService
class is presented, for Xamarin Android. This class combines:- The ease of use and built in features of the
IntentService
, i.e. operations running in a separate background thread, necessity to implement only theOnHandleIntent
method, etc. - The
Sticky
backgrounding behavior, available only to theService
class, which enables the service to be restarted, if it is stopped by Android.
StickyIntentService
is suitable for including a reference to a long running background listening TCP connection, such as the one required by XMPP libraries, e.g. Sharp.Xmpp. The class features an IntentService
like interface to use, but with the missing IntentService "sticky" behavior.Background
Android services are provided by the
Service
class. By default, Service
object's operations run on the main thread, so a background thread must be constructed for such operations. Thus, Service
class can be complicated to use. As an additional option Android provides the IntentService
class. The IntentService
works by sending all intents to a worker queue for processing. This queue processes each intent serially on a separate thread, passing the intent to the OnHandleIntent
method. When all the intents have been processed, the IntentService
stops itself by calling StopSelf
internally.
This last point is crucial for the purpose of having long running TCP connections, or other background tasks, such as the one required e.g. for XMPP, in an
IntentService
object. When the supplied Intent is processed, theIntentService
stops itself and the IntentService is available for Android to destroy. Any references to objects or long running connections, e.g. TCP connections, can now be destroyed by the OS. Furthermore, since theIntentService
can not be made Sticky
, the service is not restarted later on. Thus, while IntentService is suitable for a wide range of applications, it is not suitable as a long running background service, or for supporting a long running TCP connection, e.g. a XMPP connection with Sharp.Xmpp.
It should be noted that, when the system is under memory pressure, Android may stop any running services. However, for
Service
objects, a Sticky
or RedeliverIntent
Intent
could be delivered, in order to restart the background service. Copying from Xamarin Guides:
"When a service is stopped by the system, Android will use the value returned from
OnStartCommand
to determine how or if the service should be restarted. This value is of type StartCommandResult
, which can be any of the following:Sticky
– A sticky service will be restarted, and a null intent will be delivered toOnStartCommand
at restart. Used when the service is continuously performing a long-running operation, such as updating a stock feed.RedeliverIntent
– The service is restarted, and the last intent that was delivered toOnStartCommand
before the service was stopped by the system is redelivered. Used to continue a long-running command, such as the completion of a large file upload.NotSticky
– The service is not automatically restarted.StickyCompatibility
– Restart will behave likeSticky
on API level 5 or greater, but will downgrade to pre-level 5 behavior on earlier versions"
For a more detailed explanation of Services see the relevant section in Xamarin Android Guides.
Thus the purpose of this article is to constuct a Class that combines the ease of use of the
IntentService
class, and the Sticky
, behavior, making this class suitable for running inside a long running TCP connection, such as required by Xmpp libraries, e.g. Sharp.Xmpp.Points of Interest
The
StickyIntentService
class is derived from the Service
class. We are requiring all the features and behavior of the IntentService
class, except:- the definition of the service as
Sticky
, - the service is not stopping when the supplied Intent processing is completed.
Luckily the Android
IntentService
code is available thus the Java Android code can be migrated to C# and Xamarin, and modified, in order to display the required behavior. The IntentService is essentially a Service:public abstract class StickyIntentService : Service
which features a Looper objet:
private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; private bool mRedelivery;
The Class migrates to C#, from Java, the functionality and code existing in the initial Java file except the
OnStartCommand
and the HandleMessage methods. There the initial Java code:@Override public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; }
is migrated, to C#, and modified, in
StickyIntentService
as:public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId){ OnStart(intent, startId); return StartCommandResult.Sticky; }
Thus, the
OnStartCommand
method now returns a Sticky
value. Please note that the RedeliverIntent
, as defined in the original IntentService code is not suitable, since it will redeliver the last delivered Intent again in the service. If this is a message already processed and completed this might give erroneous results.
Moreover, after the Intent is handled, in
HanldeMessage
, the service is not stopped, thus preserving the background running Loopers. In greater detail the initial java IntentService
code:@Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); }
is migrated, to C#, and modified, in
StickyIntentService
as:public override void HandleMessage(Message msg) { sis.OnHandleIntent((Intent)msg.Obj); }
Using the Code
You can use the IntentServiceClass in exaclty the same way you are using an IntentService class. For more information refer to Xamarin's Service Guide. The whole
StickyIntentService
class code, in C# is available below. Please note that the initial Android source code comments are preserved:
y Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; namespace Pgstath.Utils { /** * IntentService is a base class for {@link Service}s that handle asynchronous * requests (expressed as {@link Intent}s) on demand. Clients send requests * through {@link android.content.Context#startService(Intent)} calls; the * service is started as needed, handles each Intent in turn using a worker * thread, and stops itself when it runs out of work. * * <p>This "work queue processor" pattern is commonly used to offload tasks * from an application's main thread. The IntentService class exists to * simplify this pattern and take care of the mechanics. To use it, extend * IntentService and implement {@link #onHandleIntent(Intent)}. IntentService * will receive the Intents, launch a worker thread, and stop the service as * appropriate. * * <p>All requests are handled on a single worker thread -- they may take as * long as necessary (and will not block the application's main loop), but * only one request will be processed at a time. * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For a detailed discussion about how to create services, read the * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> developer guide.</p> * </div> * * @see android.os.AsyncTask */ public abstract class StickyIntentService : Service { private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; private bool mRedelivery; private sealed class ServiceHandler : Handler { private StickyIntentService sis; public ServiceHandler(Looper looper, StickyIntentService sis): base(looper) { this.sis = sis; } public override void HandleMessage(Message msg) { sis.OnHandleIntent((Intent)msg.Obj); } } /** * Creates an IntentService. Invoked by your subclass's constructor. * * @param name Used to name the worker thread, important only for debugging. */ public StickyIntentService(String name):base() { mName = name; } /** * Sets intent redelivery preferences. Usually called from the constructor * with your preferred semantics. * * <p>If enabled is true, * {@link #onStartCommand(Intent, int, int)} will return * {@link Service#START_REDELIVER_INTENT}, so if this process dies before * {@link #onHandleIntent(Intent)} returns, the process will be restarted * and the intent redelivered. If multiple Intents have been sent, only * the most recent one is guaranteed to be redelivered. * * <p>If enabled is false (the default), * {@link #onStartCommand(Intent, int, int)} will return * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent * dies along with it. */ public void setIntentRedelivery(bool enabled) { mRedelivery = enabled; } public override void OnCreate() { // TODO: It would be nice to have an option to hold a partial wakelock // during processing, and to have a static startService(Context, Intent) // method that would launch the service & hand off a wakelock. base.OnCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.Start(); mServiceLooper = thread.Looper; mServiceHandler = new ServiceHandler(mServiceLooper,this); } public override void OnStart(Intent intent, int startId) { Message msg = mServiceHandler.ObtainMessage(); msg.Arg1 = startId; msg.Obj = intent; mServiceHandler.SendMessage(msg); } /** * You should not override this method for your IntentService. Instead, * override {@link #onHandleIntent}, which the system calls when the IntentService * receives a start request. * @see android.app.Service#onStartCommand */ public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) { OnStart(intent, startId); return StartCommandResult.Sticky; } public override void OnDestroy() { mServiceLooper.Quit(); } /** * Unless you provide binding for your service, you don't need to implement this * method, because the default implementation returns null. * @see android.app.Service#onBind */ public override IBinder OnBind(Intent intent) { return null; } /** * This method is invoked on the worker thread with a request to process. * Only one Intent is processed at a time, but the processing happens on a * worker thread that runs independently from other application logic. * So, if this code takes a long time, it will hold up other requests to * the same IntentService, but it will not hold up anything else. * When all requests have been handled, the IntentService stops itself, * so you should not call {@link #stopSelf}. * * @param intent The value passed to {@link * android.content.Context#startService(Intent)}. */ protected abstract void OnHandleIntent(Intent intent); } }
No comments:
Post a Comment