Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

A Sticky Intent Service, for long running tasks, with Xamarin Android

4.20/5 (5 votes)
1 Jan 2016Apache5 min read 37.3K  
A class combing the ease of use of the Intent Service, with the long running features of the Sticky services

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:

  1. 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. 
  2. 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:

  • Sticky – A sticky service will be restarted, and a null intent will be delivered to OnStartCommand 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 to OnStartCommand 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 like Sticky 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:

  1. the definition of the service as Sticky,
  2. 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
{
          
/**
 * 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);


    }
}

 

History

First Version

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0