Saturday, August 2, 2014

Simplifying Andoid Concepts

When it comes to Android there are fundamental concepts that a programmer *must* understand in order to author code for the platform or to understand code found online. You need to understand these concepts even if you are writing the application in Xamarin or some other abstraction layer.

From the Android developer web pages (http://developer.android.com/) you can find out that by default, every app runs in its own Linux process. Components are the essential building blocks of an Android app. Each component is a different point through which the system can enter your app.

There are four types of components:
Activity Screen with a user interface
Service Performs long running operations in the background
Content Provider Manages a shared set of app data
Broadcast Receiver Responds to system-wide broadcast announcements

Broadcast receiver makes perfect sense, and anyone coming from a Windows world thinks of services as performing background tasks already. If you consider data as content then of course a content provider provides data to the rest of the application. So the only one that has a slightly bizarre name is an Activity. If you think of an activity as a single focused thing that a *user* can do, then it makes sense.

Three of them (Activity, Broadcast Receiver, and Service) are all activated by an asynchronous message called an intent. Intents are confusing, but they are essentially an abstract description of an operation to be performed. Try using the mnemonic, "It is my intent to perform this operation." Another way to think of an intent is a description of an operation written down on a piece of paper (more on this later).

In Android an intent can be either explicit or implicit. In an explicit intent the fully qualified class name of an actual component that needs to run is on the piece of paper. Android makes sure that the component runs.

An implicit intent is a little more vague. Once again it is just a string, but it is global, and you don't want to collide with someone else's string. The implicit intents are generally reverse domain qualified (e.g. "com.mydomain.myapp.myintent") so that there are no naming conflicts.

When apps are *installed* Android takes all of the implicit intents specified in the manifest and adds them to its global table. If we go back to the piece of paper analogy, an implicit intent is a single-sheet newsletter. Applications that include that implicit intent in the filters section of the manifest are subscribing for the newsletter, and then when an app publishes the newsletter, Android makes sure that it gets to all of the subscribers.

There is an addtional type of intent called a *pending* intent. Things get a little more complicated with a *pending* intent. However it is easy to understand if we think of an intent (or piece of paper) that has been placed inside an envelope with some handling instructions, similar to inter-office mail or do not open until your birthday. The action written on the piece of paper is performed on behalf of the sender.

One set of handling instructions is the flags parameter. All of the static methods for creating a PendingIntent, like getActivity, getBroadcast, or getService, accept a flags parameter. Flags can be combined together and some only make sense in the context of the other flags. Here are the flags on PendingIntent and how they fit in with the envelope analogy.

FLAG_CANCEL_CURRENT This is an update, throw away the old piece of paper you were working on and replace it with this new one
FLAG_NO_CREATE (NOTE: only relevant in combination of the CURRENT flags). If you didn't already have an envelope, ignore this one also
FLAG_ONE_SHOT Burn after reading
FLAG_UPDATE_CURRENT Don't throw the first piece of paper away, instead make these revisions to the piece of paper

Here is a few snippets of code (C# code written using Xamarin Studio) that show how you would use an intent in practice. In this example we are creating a “Geofencing Intent”. A GeoFence is simply a location based circle that a user can enter or leave. When either of those events happen your application can be notified. Think of the example where the user wants to be reminded to “take the trash out when I get home”. The “when I get home” piece indicates a location based area. We can create a GeoFence for this area and be notified when the user enters this area.

Here is an example of of creating an IntentService using Xamarin C# code

using Android.App;
using Android.Content;
using Android.Support.V4.App;
using Android.Gms.Location;
using Android.Locations;
 
namespace TIG.Todo.AndroidApp
{
 [Service]
 [IntentFilter(new[] { "com.sdtig.Todo.WITHIN_PROXIMITY" })]
 public class GeofenceIntentService : IntentService
 {
  protected override void OnHandleIntent (Intent intent)
  {
   //Handle Intent
  }
 }
}

Below is a snippet that shows an example of sending a PendingIntent out (see line 43)

 [Service]
 [IntentFilter(new[] { "com.sdtig.Todo.START_LOCATION", "com.sdtig.Todo.SET_GEOFENCE" })]
 public class GeofencingHelper : Service, Android.Locations.ILocationListener
 {
  public override Android.OS.IBinder OnBind (Intent intent)
  {
   return null;
  }
 
  public override void OnStart (Intent intent, int startId)
  {
   if (intent.Action == "com.sdtig.Todo.START_LOCATION")
   {
     //Elided
   }
   if (intent.Action == "com.sdtig.Todo.SET_GEOFENCE")
   {
    SetFence();
   }
  }
   
 
  public void SetFence()
  {
   bool isWithinRadius = false;
   foreach (var fence in fences)
   {
    float[] results = new float[1];
    Location.DistanceBetween(fence.Latitude, fence.Longitude, currentLatitude, currentLongitude, results);
    float distanceInMeters = results[0];
    if (distanceInMeters < radiusInMeters)
    {
     isWithinRadius = true;
     break;
    }
   }
 
   if (!isWithinRadius)
   {
    var intent = new Intent(CustomActions.TODO_WITHIN_PROXIMITY);
    PendingIntent pendingIntent = PendingIntent.GetService(this, 0, intent, 
     PendingIntentFlags.UpdateCurrent);
    _locationManager.AddProximityAlert(currentLatitude, currentLongitude, 
     radiusInMeters, -1, pendingIntent);
   }
  }
 }

And finally here is a snippet showing the MainActivy.cs file where we start an Intent service to watching for system intent's related to Location Monitoring

using System.IO;
using Android.App;
using Android.Content;
using Android.Widget;
using Android.OS;
using TIG.Todo.Common;
using Android.Content.PM;
using TIG.Todo.Common.SQLite;
 
namespace TIG.Todo.AndroidApp
{
 [Activity (Label = "TIG.Todo.Android", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
 public class MainActivity : Activity
 {
  private TaskManager taskManager;
 
  protected override void OnCreate (Bundle bundle)
  {
   base.OnCreate (bundle);
 
   // Set our view from the "main" layout resource
   SetContentView (Resource.Layout.Main);
 
   //Intent intent = new Intent (this, typeof(GeofencingHelper));
   StartService(new Intent("com.sdtig.Todo.START_LOCATION"));
  }
 }
}

Hopefully the analogy helped to simplify some of the more abstract Android concepts.

1 comment: