August 28, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Handling Lengthy Operations with Android App Widgets

  • October 30, 2009
  • By Shane Conder
  • Send Email »
  • More Articles »

Introduction

Android home screens enjoy the benefits of the App Widget framework, from displaying pictures to indicating the weather. In our previous articles on the App Widgets (listed in the reference section), we illustrated how to create a simple App Widget which allows for user interaction. Specifically, we built an App Widget that displayed a slideshow of images, where the slideshow controls were made available to the user. In these examples, the slideshow images were sourced locally from the device, but this is not terribly realistic for a real App Widget. Many developers will wish to have their App Widgets display information that is retrieved remotely, for example, from an RSS. Therefore, we now turn our attention to handling some "background processing" in conjunction with an App Widget. In this article, you will learn how to add background downloading of the images by using a Service object. To do this, we will build upon our previous App Widget example.

The following tasks need to be performed to add background processing to our existing App Widget:

  1. Create a new Service
  2. Modify the App Widget to call in to the Service, as needed
  3. Add code to the Service to handle downloading of images
  4. Add threading to the Service for each on-screen widget

That's it in a nutshell. Each App Widget update (as handled in the onUpdate() method of the AppWidgetProvider) sends a message to the Service, starting it, as if it wasn't already started. This message must contain all the information needed to control a specific instance of the App Widget. The Service may already know about the App Widget, in which case it just updates it. Otherwise, the Service must launch a new thread to handle background downloading of the images for this particular App Widget instance. The instance is identified by the App Widget's appWidgetId value.

The Configuration screen has also been updated (see Figure 1). It now includes an entry for a URL to a compatible image feed. For this example, we use an Atom XML feed with image enclosures.


Figure 1: Updated Configuration screen with image feed setting.

Working with Android Services

Android services are loosely defined as background processes or executables that can be accessed from other applications. They can be started, to run in the background, or they can be directly connected to by means of a remote interface. However, an App Widget can't directly connect to a Service, no Broadcast receiver can. If you attempt to connect to a Service with an App Widget, an exception is thrown:

 

  android.content.ReceiverCallNotAllowedException: 
     IntentReceiver components are not allowed to bind to services

 

To keep things simple, the primary interface to our Service will be via a mechanism we're already using: the SharedPreferences object. A Service can be started any number of times. However, only a single stop request is needed to shut it down. When starting the Service, we'll use Intent data to communicate what appWidgetId (App Widget instance) is starting up. This way, we can track all displayed widgets using just a single Service.

Starting the Service--or sending an update to it--is as easy as creating and posting an Intent. The following code demonstrates how to do this, including passing some control information to the Service:

 

  Intent intent = new Intent(context, WidgetService.class);
  
  intent.putExtra(WidgetService.EXTRA_FLAG_REQUEST_STOP, requestStop);
  intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
  intent.putExtra(WidgetService.EXTRA_FLAG_UPDATE_IMAGE, updateImage);
  
  context.startService(intent);

 

Although the method call to send this Intent is called startService(), it only starts the Service if it's not already running. There is no internal reference counting. A single call to stop the Service will stop it, regardless of how many times the startService() method is called. Since we may have multiple App Widgets using the Service, we've added a requestStop flag to specifically stop the thread for a particular instance of our App Widget.

On the Service-side of the startService() call is our onStart() handler. In short, the handler checks to see if a thread is already running for the specific App Widget instance. If not, one is started. If one is already running, the handler either stops it (if requestStop is true) or updates the image (if updateImage is true). Additionally, it updates the image in case the UI state has changed. The App Widget image updates proceed as they did in the previous examples, with the exception that the images are being pushed out from the Service. Here's some pseudo-code (edited for clarity; see code download for details) for this process:

 

  public void onStart(Intent intent, int startId) {
  
     super.onStart(intent, startId);
     int appWidgetId = intent.getIntExtra(
        AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
  
     boolean updateImage = intent.getBooleanExtra(
        WidgetService.EXTRA_FLAG_UPDATE_IMAGE, false);
     boolean stopRequested = intent.getBooleanExtra(
        WidgetService.EXTRA_FLAG_REQUEST_STOP, false);
  
     if (threadPool.containsKey(appWidgetId)) {
        UpdateThread thread = threadPool.get(appWidgetId);
  
        imageUrl = getNextImageUrl();
        updateWidget(this, appWidgetId, imageUrl);
  
     } else {
        UpdateThread thread = new UpdateThread(appWidgetId);
        threadPool.put(appWidgetId, thread);
        thread.start();
        /* wait for first image to download */
        imageUrl = getNextImgeUrl();
        updateWidget(this, appWidgetId, imageUrl);
     }
  }

 

Although the details have been removed, the flow is relatively straightforward. The threadPool is simply a Hashtable to keep track of the thread for each App Widget instance. If the thread exists, we use it. If not, we start one and add it to the threadPool.





Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel