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 the OnHandleIntent
method, etc. - The
Sticky
backgrounding behavior, available only to the Service
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, the IntentService
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 the IntentService
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:
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:
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
{
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);
}
}
public StickyIntentService(String name):base()
{
mName = name;
}
public void setIntentRedelivery(bool enabled) {
mRedelivery = enabled;
}
public override void OnCreate()
{
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);
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
OnStart(intent, startId);
return StartCommandResult.Sticky;
}
public override void OnDestroy() {
mServiceLooper.Quit();
}
public override IBinder OnBind(Intent intent) {
return null;
}
protected abstract void OnHandleIntent(Intent intent);
}
}
History
First Version