Chapter 3

Services

In This Chapter

check1 Running code without bothering the user

check1 Running code when a device starts

check1 Starting, binding, and querying

check1 Sending messages from one process to another

Some things are of no concern to the user of an Android device. Imagine seeing a pop-up message saying, “A process on your phone is checking for email right now,” followed immediately by the message, “Nope, no new email. Sorry about the interruption. Get back to what you were doing. You’ll hear from me again in exactly one minute.” One of my phones actually did this! Such notices are intrusive and unnecessary, especially on a device with limited screen real estate.

To do something behind the scenes, you don’t want an Android activity. An activity normally has a layout file, and the user deals with the layout’s gizmos on the screen. Instead, you want the kind of component that runs quietly in the background. In other words, you want an Android service.

A Very Simple Service

I start this chapter with an embarrassingly simple example — a service that doesn’t do anything. This lazy service simply illustrates the minimum service source code requirements.

The service

Listing 3-1 contains the good-for-nothing service.

Listing 3-1: An Un-Weather Service

package com.allyourcode.p03_03_01;

 

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.widget.Toast;

 

public class MyWeatherService extends Service {

 

  @Override

  public IBinder onBind(Intent intent) {

    Toast.makeText(this, R.string.service_bound,

                          Toast.LENGTH_SHORT).show();

    return null;

    }

 

  @Override

  public int onStartCommand(Intent intent,

                            int flags, int startId) {

    Toast.makeText(this, R.string.service_started,

                         Toast.LENGTH_SHORT).show();

    return START_NOT_STICKY;

  }

 

  @Override

  public void onDestroy() {

    Toast.makeText(this, R.string.service_destroyed,

                         Toast.LENGTH_SHORT).show();

  }

}

 

In truth, the service in Listing 3-1 has more code than is absolutely necessary. As a subclass of the abstract android.app.Service class, the only required method in Listing 3-1 is onBind. Still, the listing’s onStartCommand and onDestroy methods are a bit more useful than the methods that would be inherited from the android.app.Service class.

The required onBind method in Listing 3-1 returns null. Normally, the object returned by an onBind method implements the android.os.IBinder interface, and an object that implements IBinder allows one process to exchange information with another process. That’s nice, but in this simple example, the service doesn’t exchange information.

I put the service from Listing 3-1 in its own Android Studio project, with its own package name. So this service runs as its own application on an emulator or a device. The service has no user interface (and, therefore, no layout file). The application’s AndroidManifest.xml file has no <activity> element but instead has the <service> element shown in Listing 3-2.

Listing 3-2: The Un-Weather Service’s AndroidManifest.xml File

<?xml version="1.0" encoding="utf-8"?>

<manifest

  package="com.allyourcode.p03_03_01"

  xmlns:android="http://schemas.android.com/apk/res/android">

 

  <application

    android:allowBackup="true"

    android:icon="@drawable/ic_launcher"

    android:label="@string/app_name"

    android:theme="@style/AppTheme">

    <service

      android:name=".MyWeatherService"

      android:enabled="true"

      android:exported="true">

    </service>

  </application>

 

</manifest>

 

warning In Listing 3-2, the attribute android:exported="true" is not optional. Starting with Android’s Lollipop version, an intent that starts a service must be an explicit intent. So, to start this section’s service from an activity that’s not in the com.allyourcode.p03_03_01 package, you must include android:exported="true" in the service’s AndroidManifest.xml file.

tip When you install an app with no main activity (like the app in Listings 3-1 and 3-2), Android Studio prompts you with an Edit Configuration dialog box. In this dialog box, check the Do Not Launch Activity radio button, and then press Run.

A client activity

To start the service in Listing 3-1, other components refer to the name of Java class. Listing 3-3 shows you how.

Listing 3-3: A Client for the Un-Weather Service

package com.allyourcode.p03_03_03;

 

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

 

public class ServiceConsumerActivity extends Activity {

  Intent intent = new Intent();

 

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    intent.setClassName("com.allyourcode.p03_03_01",

        "com.allyourcode.p03_03_01.MyWeatherService");

  }

 

  public void onStartClick(View view) {

    startService(intent);

  }

 

  public void onStopClick(View view) {

    stopService(intent);

  }

}

 

The activity in Listing 3-3 has two buttons — a Start button and a Stop button. (See Figure 3-1.)

image

Figure 3-1: Start and stop a service. How simple is that?

remember Again, starting with Android’s Lollipop version, an intent that starts a service must be an explicit intent. That’s why you call intent.setClassName in Listing 3-3.

remember The code in Listing 3-3 makes use of the code in Listing 3-1, and the code in these two listings belong to two different projects. Before running the code in Listing 3-3, you must install both projects on your emulator or physical device.

In creating the layout for this section’s project, I took the liberty of assigning listener method names to the two buttons:

<Button android:id="@+id/button"

  android:onClick="onStartClick"

  android:text="@string/start"

  android:layout_width="wrap_content"

  android:layout_height="wrap_content">

</Button>

<Button android:id="@+id/button2"

  android:onClick="onStopClick"

  android:text="@string/stop"

  android:layout_width="wrap_content"

  android:layout_height="wrap_content">

</Button>

 

So clicking the Start button calls startService(intent), and clicking the Stop button calls stopService(intent). In addition to starting and stopping the service, each click displays a Toast view.

crossreference For a brief treatise on Android’s Toast class, see the section “Informing the user,” later in this chapter.

A service’s primary purposes are to run in the background (independent of any obvious user interaction) and to offer help to other apps. So, if the code in Listing 3-1 represented a useful service, this code would be doing something about the weather. (For code that does something useful, see the section “Talking about the Weather,” later in this chapter.)

warning In the previous paragraph, the phrase “run in the background” means “run without displaying an activity.” It specifically does not mean “run as part of a process or thread that’s different from the main process or thread.” This subtle point has important consequences, which you can read about in this chapter’s “Getting Real Weather Data” section.

Here’s what happens when you play with the buttons in Figure 3-1:

  • Press Start and then press Stop.

    After pressing Start, you see the Service Started toast. Then, after pressing Stop, you see the Service Destroyed toast. No surprises here!

  • Press Stop twice in a row.

    If the service is running, the first call to stopService (in Listing 3-3) destroys the service. The second call to stopService doesn’t display a Service Destroyed toast because a component that’s not running can’t be destroyed.

  • Press Start twice in a row and then press Stop twice in a row.

    As a result, you see two Service Started toasts followed by only one Service Destroyed toast. Each startService call (from Listing 3-3) triggers a call to onStartCommand in Listing 3-1. But the first stopService call (again, from Listing 3-3) destroys the service. Subsequent stopService calls have no effect.

  • Press Start and then press the emulator’s Back button.

    When you press the Back button, you don’t see a Service Destroyed toast. And that’s the essence of an Android service. A service can live on after the activity that started the service has been destroyed.

    tip To confirm that the service is still running, use Android’s Recents list (also known as the Overview list) to restart the client activity. Then, in the client activity, don’t press the Start button. The service is still running, so simply press the Stop button. When you press the Stop button, the running service gets destroyed. So you see the Service Destroyed toast.

Services start, stop, and start again

A service has no user interface, and it may continue to run after you destroy the service’s starting activity. That can be a dangerous combination of traits. Services without interfaces can hang around indefinitely like Rasputin — the mad monk of czarist Russia that no one could kill. If developers don’t include code to manage their services, the services clog up the system. No one’s happy.

Of course, Android can kill services in order to reclaim needed memory. The http://developer.android.com/reference/android/app/Service.html page lists all the situations in which Android kills or doesn’t kill a service, and it doesn’t make for light reading.

One insight about the lifetime of a service comes from the onStartCommand method in Listing 3-1. The onStartCommand method takes an Intent parameter. The parameter’s value points to whatever Intent object the startService method sends. (See Listing 3-3.) The onStartCommand method returns an int value. In Listing 3-1, the int value is START_NOT_STICKY (a constant in the android.app.Service class). This constant value tells Android how to restart the service at a certain time interval after killing it. The alternative int values are as follows:

  • START_STICKY: If Android kills the service, Android waits for a certain time interval and then restarts the service. Upon restart, Android feeds the service the intent from whatever startService call is next in the queue of such commands. If no startService calls are waiting to start this particular service, Android feeds null to the onStartCommand method’s Intent parameter.
  • START_REDELIVER_INTENT: If Android kills the service, Android waits for a certain time interval and then restarts the service. Upon restart, Android feeds the service the intent that came as a parameter in the current call to onStartCommand.
  • START_NOT_STICKY: If Android kills the service, Android doesn’t automatically restart the service. Android restarts the service if and when the next startService call queues up to start this particular service.
  • START_STICKY_COMPATIBILITY: If Android kills the service, Android tries to restart the service the way START_STICKY restarts services. But Android doesn’t promise to restart the service.

The bottom line is, be proactive in starting and stopping your own service. Don’t be a memory hog by relying on the system to clean up after you. Be aware of your service’s lifespan, and destroy your service when it’s no longer needed. Add a stopService call to your activity’s onPause or onDestroy method if it makes sense to do so. And, if a service knows that it’s no longer useful, have the service call its own stopSelf method.

crossreference Android calls an activity’s onDestroy method whenever the user turns the device (from portrait to landscape, for example). If you put a stopService call in an activity’s onDestroy method, you must deal with all possible situations in which the service halts. For details, see this chapter’s “Talking about the Weather” section.

Running a Service at Boot Time

How important is your service? Does your service start on rare occasions when the user presses a certain button? Or does your service start when the device powers up?

If Android users can’t survive without running your service, you can start the service at boot time. To do so, you create another kind of component — a broadcast receiver.

A broadcast receiver responds to intents that you fling into the air using the sendBroadcast or sendOrderedBroadcast method. Android provides special treatment for an intent sent with either of these methods.

  • When you call startActivity or startService, Android looks for one component to satisfy the intent.

    If the system finds more than one suitable activity (two installed web browsers, for example), Android displays a dialog box prompting the user to choose among the alternatives.

  • When you call sendBroadcast or sendOrderedBroadcast, Android fires up all the receivers whose filters satisfy the intent.

    With sendOrderedBroadcast, Android runs receivers one after the other. Each receiver can pass the intent on to the next receiver in line or can break the chain by calling its abortBroadcast method.

    With sendBroadcast, Android may interleave the running of several receivers. In this scenario, having a receiver abort a broadcast doesn’t make sense.

For an example, consider the Weather service in Listing 3-1. In the same application, create a Java class with the code from Listing 3-4.

Listing 3-4: A Simple Broadcast Receiver

package com.allyourcode.p03_03_01;

 

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

 

public class MyBootReceiver extends BroadcastReceiver {

 

  @Override

  public void onReceive(Context context, Intent intent) {

    Intent serviceIntent = new Intent();

    serviceIntent.setClassName(

        "com.allyourcode.p03_03_01",

        "com.allyourcode.p03_03_01.MyWeatherService");

 

    context.startService(serviceIntent);

  }

}

 

When MyBootReceiver runs, it starts an instance of the MyWeatherService class. The not-too-difficult trick is to make MyBootReceiver run when the emulator or device starts.

Listing 3-5 shows you the mechanics of launching the receiver in Listing 3-4.

Listing 3-5: Manifest for the Receiver in Listing 3-4

<?xml version="1.0" encoding="utf-8"?>

<manifest

  package="com.allyourcode.p03_03_01"

  xmlns:android="http://schemas.android.com/apk/res/android">

 

  <uses-permission android:name=

    "android.permission.RECEIVE_BOOT_COMPLETED"/>

 

  <application

    android:allowBackup="true"

    android:icon="@drawable/ic_launcher"

    android:label="@string/app_name"

    android:theme="@style/AppTheme">

    <service

      android:name=".MyWeatherService"

      android:enabled="true"

      android:exported="true">

    </service>

    <receiver

      android:name=".MyBootReceiver"

      android:enabled="true"

      android:exported="true" >

      <intent-filter>

        <action android:name=

            "android.intent.action.BOOT_COMPLETED" />

        <category android:name=

            "android.intent.category.HOME" />

      </intent-filter>

    </receiver>

 

  </application>

 

</manifest>

 

The <uses-permission> element in Listing 3-5 grants this app permission to receive "android.intent.action.BOOT_COMPLETED" broadcasts. And, in the receiver’s <action> element, the android:name attribute says, “Wake me up if anyone hollers android.intent.action.BOOT_COMPLETED".

remember The Java constant ACTION_BOOT_COMPLETED in the Intent class has String value "android.intent.action.BOOT_COMPLETED".

When you launch your emulator or you turn on your device, Android runs through its normal boot sequence and then sends an intent containing ACTION_BOOT_COMPLETED. At that point, Android finds the receiver in Listing 3-4 and calls the receiver’s onReceive method. In turn, the onReceive method in Listing 3-4 gooses the Weather service in Listing 3-1.

technicalstuff A broadcast receiver lives long enough to run its onReceive method, and then the receiver stops running. A receiver doesn’t have any onCreate or onDestroy methods, or any of the lifecycle methods belonging to other kinds of components. A broadcast receiver does its work and then hides in the shadows until the next relevant broadcast comes along.

tip To add a broadcast receiver to an existing project, right-click (or on a Mac, Ctrl-click) the package name in the Project tool window. Then, in the context menu that appears, select New ⇒ Other ⇒ Broadcast Receiver.

Testing the boot-time service

You can download this section’s example from the book’s website. To test the code, install the app on an emulator. Then shut down the emulator and restart the emulator. Ay, there’s the rub! Starting an emulator once is annoying enough. Starting it several times (because you got some detail wrong the first few times) is a pain in the class.

Your code can’t test Listing 3-4 by creating an ACTION_BOOT_COMPLETED intent. Android reserves ACTION_BOOT_COMPLETED for system-level code only. By using the Android Debug Bridge, though, you can launch an intent as a Linux shell superuser. When you do, your emulator or device does what looks like a partial reboot, and this saves a bit of time. Here’s what you do:

  1. Install this section’s code onto an emulator.
  2. Launch a command window by clicking the Terminal tool button at the bottom of Android Studio’s main screen.
  3. In the Terminal window, issue the cd command to make the ANDROID_HOME/platform-tools directory your working directory.

    For examples of the use of the cd command, and for help finding your ANDROID_HOME/platform-tools directory, see Book I, Chapter 2.

  4. Type the following command (but type it all on one line):

    adb shell am broadcast

            -a android.intent.action.BOOT_COMPLETED

     

    Using Android’s am command, you can call startActivity, startService, and sendBroadcast as if you were Android itself (or himself, or herself, or whomever). When you issue the command in Step 4, Android behaves as if the system is just finishing its boot sequence.

  5. Using Run ⇒ Run ‘app' or the Recents button on the emulator, restart the client app (from Listing 3-3).

    When the app starts running, don’t touch the Start button. You’re trying to find out if the service is already running.

  6. In the client app’s activity, press the Stop button.

    Hooray! The Service Destroyed message appears on your emulator’s screen.

tip You can see all the am command’s options by typing adb shell am in the Terminal window.

crossreference For more information about the Android Debug Bridge, see Book I, Chapter 2. For more information about broadcast receivers, see Chapter 4 in this minibook.

Starting and Binding

You can do two kinds of things with a service:

  • You can start and stop a service.

    You do this by calling the Context class’s startService and stopService methods. Also, a service can take the bull by the horns and call its own stopSelf or stopSelfResult method.

    When you call startService, you create only a momentary relationship with the service. Android creates an instance of the service if no instances are already running. In addition, Android calls the service’s onStartCommand method. (See Listing 3-1.)

    Calls to startService don’t pile up. To illustrate the point, consider this sequence of method calls, along with their resulting Android responses:

    Activity A calls startService to start MyService.

        Android instantiates MyService and

            calls the instance’s onStartCommand method.

     

    Activity B calls startService to start MyService.

        Android calls the existing instance’s

            onStartCommand method.

     

    Activity A calls stopService to stop MyService.

        Android destroys the MyService instance.

     

    Activity B calls stopService to stop MyService.

        Android says "The joke’s on you." There’s no

            instance of MyService to stop.

  • You can bind to and unbind from a service.

    You do this by calling the Context class’s bindService and unbindService methods. Between binding and unbinding, you have an ongoing connection with the service. Through this connection, you can send messages to the service and receive messages from the service. That’s useful!

    When you call bindService, Android creates an instance of the service if no instances are running already. In addition, Android calls the service’s onBind method. (For an example, skip ahead to Listings 3-6 and 3-7.)

    remember When you call bindService, Android doesn’t call the service’s onStartCommand method.

    Calls to bindService pile up. A service can have many bindings at once, each to a different activity. Your service’s code can keep track of all this hubbub by maintaining a collection of binding objects (an ArrayList, or whatever). When you call unbindService, you don’t destroy the service instance. Android keeps the service alive as long as any activities are bound to the service.

    technicalstuff Services can receive Start requests and Bind requests all at the same time. When all bound activities unbind themselves from a particular service, the system checks whether anybody started the service this time around. If so, the system waits for somebody to call stopService before destroying the service.

    remember Android can terminate activities to reclaim memory. If I were a service and Android terminated the activities that were bound to me, I’d be afraid for my own survival. Test your apps for unwanted results from the untimely termination of activities and their services. If, in testing, you experience any unexpected behavior due to the early termination of a service, please fix the code.

The previous sections' examples started and stopped a service. The rest of this chapter binds and unbinds with a service.

Talking about the Weather

Every Android book needs a Weather Service example, and this book is no exception. In this section, your activity binds to a service, which in turn reaches out for weather information over the Internet.

A service

I build the example in stages. The first stage is essence de service. An activity binds to the service, gets back some fake responses to nonsense queries, and then unbinds. Listing 3-6 contains the service.

Listing 3-6: A Weather Service with a Fear of Commitment

package com.allyourcode.p03_03_06;

 

import android.app.Service;

import android.content.Intent;

import android.os.Bundle;

import android.os.Handler;

import android.os.IBinder;

import android.os.Message;

import android.os.Messenger;

import android.os.RemoteException;

import android.widget.Toast;

 

public class MyWeatherService extends Service {

 

  Messenger messengerToClient = null;

 

  MyIncomingHandler myIncomingHandler =

      new MyIncomingHandler();

  Messenger messengerToService =

      new Messenger(myIncomingHandler);

 

  @Override

  public IBinder onBind(Intent intent) {

    doToast(R.string.service_bound);

    return messengerToService.getBinder();

  }

 

  class MyIncomingHandler extends Handler {

 

    @Override

    public void handleMessage(Message incomingMessage) {

       messengerToClient = incomingMessage.replyTo;

 

       Bundle reply = new Bundle();

       reply.putString("weather", "It’s dark at night.");

       Message replyMessage = Message.obtain();

       replyMessage.setData(reply);

       try {

         messengerToClient.send(replyMessage);

       } catch (RemoteException e) {

         e.printStackTrace();

       }

         doToast(R.string.message_handled);

       }

    }

 

    @Override

    public boolean onUnbind(Intent intent) {

      doToast(R.string.service_stopped_itself);

      stopSelf();

      return false;

    }

 

    @Override

    public void onDestroy() {

      myIncomingHandler = null;

      doToast(R.string.service_destroyed);

    }

 

    void doToast(int resource) {

      Toast.makeText(this, resource,

          Toast.LENGTH_SHORT).show();

    }

}

 

The flow of control in Listing 3-6 isn’t simple, so I created Figure 3-2 to help you understand what’s going on. The first thing to notice in Figure 3-2 is that the service doesn’t interact directly with a client application. Instead, the service gets calls indirectly through the Android operating system.

image

Figure 3-2: Binding and messaging.

Like many other communication regimens, the talk between a client and a service has two phases:

  • The first phase (the binding phase) establishes a line of communication.
  • In the second phase, the client and the service exchange useful information via messages. In general, the client sends a request for information and the service sends a reply.

To bind to a service, a client sends an intent. Android hands this intent to the service’s onBind method. In response, the onBind method returns a binder — an object that implements the IBinder interface. (Refer to Listing 3-6 and Figure 3-2.) The binder is like a business card. By returning a binder, the service says, “Android, tell the client application that it can reach me at this address.” That’s why, in Listing 3-6, the service creates the binder from an instance of MyIncomingHandler. (It’s the same as printing a business card from an instance of “my answering machine’s phone number.")

Android delivers the binder to the client app. Eventually, the client app queries the service. The query contains a request for specific information. But the query also contains a replyTo field. The service’s inner class (in this example, MyIncomingHandler) uses the replyTo information to send an answer back to the client app. In Listing 3-6, I keep things simple by replying It’s dark at night no matter what query the client sends. (A weather report like this is always correct.)

A client

Listing 3-7 contains a client for the service in Listing 3-6. The first several lines of Listing 3-7 are parallel to the code in Listing 3-6. Like the service in Listing 3-6, the client in Listing 3-7 has messengers and an incoming handler class. But unlike the service in Listing 3-6, the client in Listing 3-7 spans three printed pages. So, in this section, I describe Listing 3-7 one piece at a time.

Listing 3-7: A Client for the Service in Listing 3-6

package com.allyourcode.p03_03_07;

 

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.content.SharedPreferences;

import android.os.Bundle;

import android.os.Handler;

import android.os.IBinder;

import android.os.Message;

import android.os.Messenger;

import android.os.RemoteException;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.TextView;

import android.widget.Toast;

 

public class ServiceConsumerActivity extends Activity {

 

  Messenger messengerToService = null;

 

  MyIncomingHandler myIncomingHandler =

      new MyIncomingHandler();

  Messenger messengerFromService =

      new Messenger(myIncomingHandler);

 

  Serviceconnection connection =

      new MyServiceConnection();

  SharedPreferences prefs;

  boolean isBound = false;

 

  void bind() {

    Intent intent = new Intent();

    intent.setClassName("com.allyourcode.p03_03_06",

          "com.allyourcode.p03_03_06.MyWeatherService");

    isBound =

        bindService(intent, connection,

            Context.BIND_AUTO_CREATE);

  }

 

  public void queryService() {

    if (isBound) {

      Bundle bundle = new Bundle();

      bundle.putString("location", "19122");

        // 19122 is the zip code of the neighborhood

        // where I grew up. Philadelphia’s El train

        // passed right by my bedroom window.

 

      Message message = Message.obtain();

      message.replyTo = messengerFromService;

      message.setData(bundle);

      try {

        messengerToService.send(message);

      } catch (RemoteException e) {

        e.printStackTrace();

      }

    } else {

      textView.setText(R.string.report_appears_here);

      doToast(R.string.service_not_bound);

    }

  }

 

  class MyIncomingHandler extends Handler {

    @Override

    public void handleMessage(Message msg) {

      Bundle bundle = msg.getData();

      textView.setText(bundle.getString("weather"));

    }

  }

 

  void unbind() {

    if (isBound) {

      unbindService(connection);

      isBound = false;

    }

  }

 

  class MyServiceConnection implements ServiceConnection {

    public void onServiceConnected(

        ComponentName className, IBinder binder) {

      messengerToService = new Messenger(binder);

      doToast(R.string.service_connected);

    }

 

    public void onServiceDisconnected(ComponentName n) {

      messengerToService = null;

      doToast(R.string.service_crashed);

    }

  }

 

  void doToast(int resource) {

    Toast.makeText(this, resource,

        Toast.LENGTH_SHORT).show();

  }

 

  @Override

  public void onDestroy() {

  super.onDestroy();

 

    prefs = getSharedPreferences("PREFS", MODE_PRIVATE);

    SharedPreferences.Editor editor = prefs.edit();

    editor.putBoolean("isBound", isBound);

    editor.putString("report", textView.getText()

        .toString());

    editor.commit();

 

 

    unbind();

  }

 

  @Override

  public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_service_consumer);

    prefs = getSharedPreferences("PREFS", MODE_PRIVATE);

    if (prefs != null) {

      textView = (TextView) findViewById(R.id.textView);

 

      textView.setText(prefs.getString("report",

        getString(R.string.report_appears_here)));

      if (prefs.getBoolean("isBound", false)) {

        bind();

      }

    }

 

    bindButton = (Button) findViewById(R.id.buttonBind);

    locationText =

        (EditText) findViewById(R.id.editText);

    queryButton = (Button) findViewById(R.id.buttonQuery);

    unbindButton =

        (Button) findViewById(R.id.buttonUnbind);

  }

 

  public static TextView textView;

  Button bindButton, queryButton, unbindButton;

  EditText locationText;

 

  public void onButtonClick(View view) {

    if (view == bindButton) {

      bind();

    } else if (view == queryButton) {

      queryService();

    } else if (view == unbindButton) {

      unbind();

    }

  }

}

 

Figure 3-3 shows a layout that I created for the activity in Listing 3-7. The bind, queryService, and unbind methods in Listing 3-7 handle the button clicks in Figure 3-3. (This section’s service doesn’t care what city you’re in, so, in Figure 3-3, the EditText view set up for a United States postal code doesn’t serve any purpose. It’s a placeholder for user input in subsequent examples.)

image

Figure 3-3: A user interface for the activity in Listing 3-7.

In the activity’s layout file, I set each button’s onClick attribute to onButtonClick. Then, in the Java code (Listing 3-7), the onButtonClick method tests to find out which of the three buttons experienced the click event.

Informing the user

Near the bottom of Figure 3-3, there’s a capsule-shaped pop-up containing the words Message handled. The rectangle illustrates the use of Android’s Toast class. A toast is an unobtrusive little view that displays some useful information for a brief period of time. A Toast view pops up on the screen, the way a hot piece of bread pops up from a toaster. (Rumor has it that the Android class name Toast comes from this goofy analogy.)

technicalstuff A Toast view typically displays a message for the user to read. So Android developers often talk about toast messages. In principle, there’s nothing wrong with the term toast message. But much of this chapter deals with instances of the android.os.Message class — messages sent between a service and its client. And near the bottom of Figure 3-3, the words Message handled refer to a message between a service and its client, not to a toast message. So in Figure 3-3, a toast message informs the user about a completely different kind of message. What’s an author to do? In this chapter, I use the word message to refer to communication between a service and its client. In other chapters, I throw around the words toast message without worrying about the problem.

The Toast class has two extremely useful methods: makeText and show.

  • The static Toast.makeText method creates an instance of the Toast class.

    The makeText method has three parameters:

    • The first parameter is a context (the word this in Listing 3-7).
    • The second parameter is either a resource or a sequence of characters (a String, for example).

      If you call makeText with a String, the user sees the String when Android displays the toast. If you call makeText with a resource, Android looks for the resource in your app’s res directory. In Listing 3-7, the code calls makeText twice — once with resource R.string.service_connected and once with R.string.service_crashed.

      remember If you use an int value (42, for example) for the second parameter of the makeText method, Android doesn’t display the characters 42 in the Toast view. Instead, Android looks for a resource whose value in R.java is 42. Your R.java file probably doesn’t contain the number 42. So, instead of a Toast view, you get a ResourceNotFound exception. Your app crashes, and you groan in dismay.

    • The makeText method’s third parameter is either Toast.LENGTH_LONG or Toast.LENGTH_SHORT. With LENGTH_LONG, the Toast view appears for about four seconds. With LENGTH_SHORT, the Toast view appears for approximately two seconds.
  • The show method tells Android to display the Toast view.

    In Listing 3-7, notice that I call both makeText and show in one Java statement. If you forget to call the show method, the Toast view doesn’t appear. You stare in disbelief wondering why you don’t see the Toast view. ("Who stole my toast?” you ask.) When you finally figure out that you forgot to call the show method, you feel foolish. (At least that’s the way I felt when I forgot earlier today.)

Binding to the service

In Listing 3-7, the call to bindService takes three parameters — an intent, a service connection, and an int value representing flags.

  • The intent helps determine which service to invoke.

    In Listing 3-7, the intent has class name com.allyourcode.p03_03_06.MyWeatherService. That points to the code in Listing 3-6.

  • The connection is the virtual rope between the client and the service.

    The connection parameter in Listing 3-7 implements the android.content.ServiceConnection interface. I declare the MyServiceConnection class later in Listing 3-7.

    Notice that in one of the MyServiceConnection class’s methods, Android hands the service’s business card (the binder) to the client. This is a bit different from the code in Listing 3-6, where the service gets replyTo information from each incoming message. The difference stems from the way the client and the service talk to each other. The client initiates communications, and the service twiddles its virtual thumbs waiting for communications.

    Another thing to notice about MyServiceConnection is the peculiar role of the onServiceDisconnected method. As the toast implies, Android doesn’t call onServiceDisconnected unless the service takes a dive prematurely.

  • The flags provide additional information about the run of the service.

    When Android needs more memory, Android terminates processes. In Listing 3-7, the flag BIND_AUTO_CREATE tells Android to avoid terminating the service’s process while your activity runs. An alternative, BIND_NOT_FOREGROUND, tells Android not to consider your activity’s needs when deciding whether to terminate the service’s process.

Querying the service

In Listing 3-7, the queryService method asks the service for the answer to a question. Here’s what the queryService method does:

  1. The queryService method obtains a blank message from the android.os.Message class.
  2. The queryService method adds a question (the bundle) to the message.
  3. The queryService method tells a messenger to send the message to the service.

A bundle (an instance of android.os.Bundle) is something that a process can write to and that another process can read from. You see a bundle in every activity’s onCreate method. In the world of data communications, sending a message is likened to writing data. So, in Listing 3-7, the code juggles bundles.

  • The queryService method puts a bundle on a message and then “writes” the message to an Android message queue.
  • The handleMessage method in the MyIncomingHandler class “reads” a message from an Android message queue and then gets the message’s bundle for display on the device’s screen.

Using shared preferences to restart a connection

Listing 3-7 contains an important lesson about the life of a service. A service that’s bound to another component (an activity, for example) tends to stay alive. If developers don’t explicitly unbind from services, the services build up and start clogging Android’s pipes. So a good citizen does the housekeeping to unbind services.

So, in Listing 3-7, the onDestroy method unbinds the service. So far, so good. But what happens when the user turns the device sideways? Chapter 1 of this minibook reminds you what happens when the user reorients the device — Android destroys the current activity and re-creates the activity in the new orientation. So, if you’re not careful, the user loses the service just by turning the device sideways. That’s probably not what you want.

To defend against this problem, use shared preferences. With shared preferences, you can store information. Later, your app (or, if you want, someone else’s app) can retrieve the information.

Here’s how you wield a set of shared preferences:

  • To create shared preferences, call the android.content.Context class’s getSharedPreferences method.

    For parameters, feed a name and a mode to the method call. In Listing 3-7, the name is the string "PREFS", and the mode is the int value android.content.Context.MODE_PRIVATE. The alternatives are

    • MODE_PRIVATE: No other process can read from or write to these preferences.
    • MODE_WORLD_READABLE: Other processes can read from these preferences.
    • MODE_WORLD_WRITEABLE: Other processes can write to these preferences.
    • MODE_MULTI_PROCESS: Other processes can write to these preferences even while a process is in the middle of a read operation. Weird things can happen with this much concurrency. So watch out!

    You can combine modes with Java’s bitwise or operator. So a call such as

    getSharedPreferences("PREFS",

        MODE_WORLD_READABLE | MODE_WORLD_WRITEABLE);

     

    makes your preferences both readable and writable for all other processes.

  • To add values to a set of shared preferences, use an instance of the android.content.SharedPreferences.Editor class.

    In Listing 3-7, the onDestroy method creates a new editor object. Then the code uses the editor to put information for safe keeping into the shared preferences. Each piece of information is a ( name , value ) pair, such as ("isBound", isBound) or ("report", textView.getText().toString()). For putting information into shared preferences, the Editor class has methods such as putInt, putString, putStringSet, and so on.

  • To make Android remember the information in shared preferences, call the editor’s commit method.

    Again, see Listing 3-7.

  • To retrieve an existing set of shared preferences, call getSharedPreferences, using the same name as the name you used to create the preferences. Can you guess which listing contains an example of this code? Yes! Listing 3-7. Look at the listing’s onCreate method.
  • To read values from an existing set of shared preferences, call getBoolean, getInt, getFloat, or one of the other get methods belonging to the SharedPreferences class.

    In Listing 3-7, the call to getBoolean takes two parameters. The first parameter is the name in whatever ( name , value ) pair you’re trying to get. The second parameter is a default value. So, when you call prefs.getBoolean("isBound", false), if prefs has no pair with name "isBound", the method call returns false.

In Listing 3-7, the onDestroy method saves the value of isBound. Then, when Android revives the activity, the onCreate method retrieves the isBound value. In effect, the onCreate method “finds out” whether the service was bound before the activity was destroyed. If the service was bound, the code renews its connection, making another call to the bindService method.

tip The SharedPreferences code in Listing 3-7 saves the weather report and the isBound value no matter how the activity is destroyed. This includes times when the user reorients the device, but it also includes times when the user presses Android’s Back button. The trouble is, a press of the Back button probably means “I’m finished with this activity. The next time I use this activity, I will have forgotten all about this moment’s weather conditions.” So, when the user presses the Back button, you might not want to save the current weather report. What you need is a way to distinguish between Back-button onDestroy calls and other kinds of onDestroy calls. To do this, use the Activity class’s isFinishing method. For details, see Chapter 1 in this minibook.

crossreference Using attributes in an app’s AndroidManifest.xml document, you can keep Android from destroying an activity when the user reorients the device. But the Android docs recommend against doing this. For information, see Chapter 1 of this minibook.

Getting Real Weather Data

In this section, you supplement the code in Listings 3-6 and 3-7 so that your app retrieves real weather data. Here’s what you do:

  • In the client app, get the user’s input from the EditText widget and send this input to the service.

    That is, change the statement

    bundle.putString("location", "19122");

     

    in Listing 3-7 to a statement such as

    bundle.putString("location",

    locationText.getText().toString().trim());

  • In the service, send the incoming message’s text to the Weather Underground server.

    That is, change the statement

    reply.putString("weather", "It’s dark at night.");

     

    in Listing 3-6 to a statement such as

    reply.putString("weather", actualWeatherString);

     

    The Weather Underground server takes a city name or a U.S. postal code (a zip code) and returns a JSON document describing the weather at that location. (To keep the example simple, I use only U.S. postal codes in this section’s listings.)

    Listing 3-8 contains an abridged version of a response from the Weather Underground server.

  • In the service, sift information from the Weather Underground server’s response.

    This section’s code ferrets out the Fahrenheit temperature and the weather condition from the Weather Underground’s response. (I apologize in advance to non-U.S., non-Belize readers for the use of the Fahrenheit scale. Bad habits are difficult to break.)

  • As a final step, your service gets the data obtained from the Weather Underground server and forwards that information to the client app.

Listing 3-8: JSON Response from the Weather Underground

    {

      "response": {

        "version":"0.1",

        "features": {

          "conditions": 1

        }

      }

      ,

      "current_observation": {

        "weather":"Overcast",

        "temperature_string":"62.4 F (16.9 C)",

        "temp_f":62.4,

        "temp_c":16.9,

        "relative_humidity":"88%",

        "wind_dir":"SSE",

        "wind_degrees":150,

        "wind_mph":6.0,

      }

    }

 

You must have a Weather Underground account in order to run this section’s code. But don’t fret. For up to 500 queries per day, Weather Underground’s service is free. You can get an account by visiting www.wunderground.com/weather/api/.

technicalstuff In the old days, before JSON or XML were commonly used, your app would be screen scraping. Screen scraping refers to the practice of fishing for data in an ordinary web page. In the era before JSON or XML, your code had to eliminate the page’s colors, font tags, advertisements, and any other irrelevant material. Then your code had to search the page for the current Fahrenheit temperature, making every effort to avoid grabbing next week’s forecast or the cost of a subscription to Weather and Wine Weekly. Then you hoped that any future changes in the website’s layout didn’t spoil the correctness of your code. Undoubtedly, reading JSON or XML is more reliable.

Dealing with JSON

This section presents an alternative to the fake weather service in Listing 3-6. As you might expect, a real service is more complicated than a fake service. So it helps to digest this section’s code in pieces. The first piece is in Listing 3-9.

Listing 3-9: A Real Weather Service: Part I

package com.allyourcode.p03_03_09;

 

import android.app.Service;

import android.content.Intent;

import android.os.AsyncTask;

import android.os.Bundle;

import android.os.Handler;

import android.os.IBinder;

import android.os.Message;

import android.os.Messenger;

import android.os.RemoteException;

import android.widget.Toast;

 

import org.json.JSONException;

import org.json.JSONObject;

 

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.net.HttpURLConnection;

import java.net.URL;

 

public class MyWeatherService extends Service {

 

  Messenger messengerToClient = null;

 

  MyIncomingHandler myIncomingHandler =

      new MyIncomingHandler();

  Messenger messengerToService =

      new Messenger(myIncomingHandler);

 

  @Override

  public IBinder onBind(Intent intent) {

    doToast(R.string.service_bound);

    return messengerToService.getBinder();

  }

 

  class MyIncomingHandler extends Handler {

 

    @Override

    public void handleMessage(Message incomingMessage) {

      messengerToClient = incomingMessage.replyTo;

      new MyAsyncTask().execute

          (incomingMessage.getData().getString("location"));

    }

  }

 

  @Override

  public boolean onUnbind(Intent intent) {

    doToast(R.string.service_stopped_itself);

    stopSelf();

    return false;

  }

 

  @Override

  public void onDestroy() {

    myIncomingHandler = null;

    doToast(R.string.service_destroyed);

  }

 

  void doToast(int resource) {

    Toast.makeText(this, resource, Toast.LENGTH_SHORT).show();

  }

 

// … More to come in Listings 3-10 and 3-11

 

Looking at Listing 3-9, you may have a distinct sense of déjà vu. In fact, Listing 3-9 is almost identical to Listing 3-6. The only difference is the body of the handleMessage method. (Come to think of it, that’s not too surprising. Both listings need the plumbing for responding to binding requests and queries. The only difference is the way the two listings get their weather data.)

In Listing 3-6, the handleMessage method composes a fake reply and sends the reply back to the client. But in Listing 3-9, handleMessage delegates the work to a mysterious-looking thing called an AsyncTask. Listing 3-10 contains your weather service’s AsyncTask code.

Listing 3-10: A Real Weather Service: Part II

// … Continued from Listing 3-9

 

class MyAsyncTask extends AsyncTask<String, Void, String> {

 

  @Override

  protected String doInBackground(String… locations) {

    return getWeatherFromInternet(locations[0]);

  }

 

  @Override

  protected void onPostExecute(String actualWeatherString) {

    sendWeatherToClient(actualWeatherString);

    doToast(R.string.message_handled);

  }

 

}

 

// … More to come in Listing 3-11

 

The class in Listing 3-10 is an inner class of the MyWeatherService class. I have a lot more to say about the AsyncTask construct in Book IV, Chapter 3. In this chapter, I make only two points:

  • An AsyncTask runs in a thread of its own.

    That’s important because a service runs in the same thread as the client that invokes the service. Your service has to reach out along the Internet to Weather Underground’s servers. That takes time, time that the client’s busy user interface code might not have.

    Imagine putting a request to Weather Underground right inside the handleMessage method. The handleMessage method (and everything else inside the service’s code) runs inside the same thread as the client activity. So the client activity, with its EditText field and buttons, can do nothing while the handleMessage method waits to get information back from Weather Underground. If the user presses a button, nothing happens. The button’s shade doesn’t even change to show that it’s been pressed. That’s bad.

    Before Honeycomb, a developer was free to issue Internet requests inside an activity’s thread. It was bad programming practice, but Android would let you do it. Starting with Honeycomb, requests of this kind are strictly forbidden. A call for network data from the service’s regular code throws a NetworkOnMainThreadException, and the app’s request is not fulfilled.

    To fix this problem, Listing 3-10 executes an AsyncTask. An AsyncTask operates outside of its originator’s thread, so an AsyncTask can make Internet requests on its originator’s behalf.

  • The execution of an AsyncTask proceeds in distinct phases.

    The code in Listing 3-10 has two methods — namely, doInBackground and onPostExecute.

    • In an AsyncTask, the doInBackground method is where all the heavy lifting occurs.

      In Listing 3-10, the doInBackground method reaches out to the Internet (via method calls) and actually waits for the response from Weather Underground. This step can be time-consuming, but because the AsyncTask isn’t blocking the client activity’s responses, the wait is tolerable.

      At the end of its execution, the doInBackground method returns a value. In Listing 3-10, this value is a String such as "31.7° F Overcast" — a value obtained through a request sent over the Internet.

    • In an AsyncTask, the onPostExecute method starts running only after the doInBackground method has finished whatever it has to do.

      The onPostExecute method’s parameter is whatever value the doInBackgroud method returned. So, in Listing 3-10, the value of actualWeatherString is something like "31.7° F Overcast". The job of the onPostExecute method is to bundle this information into a reply and send that reply back to the client.

The rest of this section’s code makes good on the promises made inside Listing 3-10 — namely, to implement the getWeatherFromInternet and sendWeatherToClient methods.

Listing 3-11: A Real Weather Service: Part III

// … Continued from Listing 3-10

 

  private static final String WEATHER_UNDERGROUND_URL =

     "http://api.wunderground.com/" +

        "api/your_key_id/conditions/q/";

  private static final String CONDITION = "weather";

  private static final String TEMP_F = "temp_f";

 

    String getWeatherFromInternet(String location) {

      String temperature = "", condition = "", weatherString;

 

      URL url;

 

      if (location != null && !location.equals("")) {

         try {

         url = new URL

           (WEATHER_UNDERGROUND_URL + location + ".json");

 

         HttpURLConnection connection =

            (HttpURLConnection) url.openConnection();

         connection.setDoInput(true);

         connection.connect();

 

         InputStream input = connection.getInputStream();

         BufferedReader reader = new BufferedReader

                          (new InputStreamReader(input));

 

         String oneLineFromInternet;

         String wholeReplyFromInternet = "";

         while ((oneLineFromInternet = reader.readLine())

                                               != null) {

         wholeReplyFromInternet +=

                             oneLineFromInternet + " ";

      }

 

       JSONObject jsonObject =

                new JSONObject(wholeReplyFromInternet);

       JSONObject current_observation =

        jsonObject.getJSONObject("current_observation");

       temperature = current_observation.getString(TEMP_F);

       condition = current_observation.getString(CONDITION);

 

      } catch (JSONException | IOException e) {

        e.printStackTrace();

      }

 

      weatherString = temperature

         + (char) 0x00B0 + "F " + condition;

    } else {

      weatherString = "It’s dark at night.";

    }

    return weatherString;

  }

 

  void sendWeatherToClient(String actualWeatherString) {

    Bundle reply = new Bundle();

    reply.putString("weather", actualWeatherString);

    Message replyMessage = Message.obtain();

    replyMessage.setData(reply);

    try {

      messengerToClient.send(replyMessage);

    } catch (RemoteException e) {

      e.printStackTrace();

    }

  }

 

}

 

Listing 3-11 contains a lot of code, but most of it either comes straight from Listing 3-6 or is boilerplate Java code (not Android-specific code).

  • The stuff about HttpURLConnection, InputStream, and BufferedReader is the way Java sends a URL to an Internet server.

    The while loop in Listing 3-11 gets the server’s response, line by line, and appends this response to its own wholeReplyFromInternet string. When all is said and done, that string looks something like the JSON code in Listing 3-8. (Of course, in real life, that response string is quite a bit longer.)

  • The business about JSONObject is Java’s way of sifting values from a JSON string.

    Notice the nesting in Listing 3-8. The whole listing has two parts: a "response" part and a "current_observation" part. And the "current_observation" part has several parts of its own, two of which are the "weather" and "temp_f" parts.

    (As far as I’m concerned, the name "weather" is an unfortunate choice. The whole thing is about weather. The pair "weather":"Overcast" in Listing 3-8 is only a small piece of the overall weather picture. But "weather":"Overcast" is what the Weather Underground server deals to me, so "weather":"Overcast" is what I must use. In Listing 3-11, I create an alias CONDITION for this strange use of the name "weather".)

    In Listing 3-11, I extract the "weather" and "temp_f" values from the code in Listing 3-8. Because of the nesting in Listing 3-8, I perform the extraction in three steps:

    1. Using the JSONObject constructor, I get something that I can analyze with Java’s JSON methods.

      I put this thing in the jsonObject variable.

    2. I call the jsonObject variable’s getJSONObject method to sift the "current_observation" part from the server’s response.

      I put this "current_observation" part into the observation variable.

    3. I call the observation variable’s getString method to sift the "temp_f" and "weather" values out of the "current_observation" part.

      I put these values into the temperature and condition variables.

    As a final coup de grâce, I combine the temperature and condition strings into one weatherString. I return that weatherString so that, down the line in the code, the weatherString can be returned to the client code and displayed on the user’s screen.

In Listing 3-11, the only other excitement comes from the (char) 0x00B0 value. The hex value B0 (decimal value 176) is the Unicode representation for the degree symbol. (See the text view in Figure 3-4.)

image

Figure 3-4: Displaying weather information.

remember If an app’s code fetches data from the Internet (as this section’s service does), then the app’s AndroidManifest.xml file must have the <uses-permission-android:name="android.permission.INTERNET"/> element.

Dealing with XML

In your Android-related travels, you might not always get JSON back from an Internet server. Another popular format for data interchange is XML. Because Weather Underground offers both JSON and XML, I can illustrate the use of XML by translating the previous section’s example.

crossreference For some tips on deciphering the contents of XML documents, see Book II, Chapter 5.

To get weather data from Weather Underground, you send a URL. If the URL ends with .json, you get a response like the stuff in Listing 3-8. But if the URL ends with .xml, you get something like the response in Listing 3-12.

Listing 3-12: XML Response from the Weather Underground

<response>

  <version>0.1</version>

  <features>

    <feature>conditions</feature>

  </features>

  <current_observation>

    <weather>Mostly Cloudy</weather>

    <temperature_string>65.7 F (18.7 C)</temperature_string>

    <temp_f>65.7</temp_f>

    <temp_c>18.7</temp_c>

    <relative_humidity>71%</relative_humidity>

    <wind_dir>South</wind_dir>

    <wind_degrees>177</wind_degrees>

    <wind_mph>15.2</wind_mph>

  </current_observation>

</response>

 

There are two popular ways to get information from an XML document — DOM and SAX:

  • DOM (Document Object Model) picks apart an entire XML document, loads all this information into memory, and then lets you query the parser for values anywhere in the document. “What are the characters inside the <temp_f> element in the <current_conditions> element?” you ask. The DOM parser answers, but only after analyzing the entire document.

    DOM works much like the code in Listing 3-11, but tends to consume lots and lots of memory.

  • SAX (Simple API for XML) scans an XML document one piece at a time, keeping only the current piece in memory. At every step, a SAX parser offers to report its findings. “I found a start tag” or “I found some characters between start and end tags,” says the parser. This section’s SAX code monitors parser findings for relevant data and adds any useful data to an instance of the Weather class.

    SAX isn’t as memory-intensive as DOM.

The SAX code to monitor the response from Weather Underground is in Listing 3-13.

Listing 3-13: Your Very Own SAX Handler

package com.allyourcode.p03_03_13;

 

import org.xml.sax.Attributes;

import org.xml.sax.SAXException;

import org.xml.sax.helpers.DefaultHandler;

 

public class MySaxHandler extends DefaultHandler {

 

  private static final String CONDITION = "weather";

  private static final String TEMP_F = "temp_f";

 

  private Weather weather = new Weather();

 

  private boolean isInTemp_f = false;

  private boolean isInCondition = false;

 

 

  public Weather getWeather() {

    return weather;

}

 

  @Override

  public void startElement(String namespaceURI,

                           String localName,

                           String qName,

                           Attributes attributes)

                                  throws SAXException {

    if (localName.equals(TEMP_F)) {

      isInTemp_f = true;

    } else if (localName.equals(CONDITION)) {

      isInCondition = true;

    }

  }

 

  @Override

  public void characters(char[] ch,

                         int start,

                         int length) {

    if (isInTemp_f) {

      weather.temperature =

                         new String(ch, start, length);

    } else if (isInCondition) {

      weather.condition = new String(ch, start, length);

    }

  }

 

  @Override

  public void endElement(String namespaceURI,

                           String localName,

                           String qName)

                                    throws SAXException {

    if (localName.equals(TEMP_F)) {

      isInTemp_f = false;

    } else if (localName.equals(CONDITION)) {

      isInCondition = false;

    }

  }

}

 

The SAX handler makes use of a tiny Weather class shown in Listing 3-14.

Listing 3-14: (Not much to see, here!)

package com.allyourcode.p03_03_13;

 

public class Weather {

 

  String temperature = "0.0";

  String condition = "";

 

}

 

The code in Listing 3-13 has more to do with XML than with Android, so I don’t go into detail about the code in Listing 3-13.

Listing 3-15 contains the XML/SAX alternative to the getWeatherFromInternet method in Listing 3-11.

Listing 3-15: Getting Weather the XML/SAX Way

String getWeatherFromInternet(String location) {

  Weather weather = null;

  String weatherString;

 

  URL url;

 

  if (location != null && !location.equals("")) {

    try {

      url = new URL

        (WEATHER_UNDERGROUND_URL + location + ".xml");

 

       SAXParser parser =

          SAXParserFactory.newInstance().newSAXParser();

      XMLReader reader = parser.getXMLReader();

      MySaxHandler saxHandler = new MySaxHandler();

      reader.setContentHandler(saxHandler);

 

      reader.parse(new InputSource(url.openStream()));

 

      weather = saxHandler.getWeather();

 

    } catch (SAXException |

             ParserConfigurationException |

             IOException e) {

      e.printStackTrace();

    }

 

    weatherString = weather.temperature

        + (char) 0x00B0 + "F " + weather.condition;

  } else {

    weatherString = "It’s dark at night.";

  }

  return weatherString;

}

 

The method in Listing 3-15 does what Java programs do when they get a response from a web server and submit the response to a SAX parser. I review the steps briefly because the code is mostly boilerplate. You can paste it into your own app with barely any changes.

  1. Create a URL pointing to Weather Underground’s weather server.
  2. Create a SAXParser instance and then use the parser to get an XMLReader instance (whatever an XMLReader instance is).
  3. Create a MySaxHandler instance (refer to Listing 3-10) and feed the MySaxHandler instance to the XMLReader instance.
  4. Connect to Weather Underground by creating newInputSource(url.openStream()).
  5. Call the reader’s parse method, feeding Weather Underground’s response to the reader.
  6. Get a Weather instance from the SAX handler.

Whew!

Talking to a Service as if You’re Right Next Door

Where I come from, you’re thought to be sophisticated if you’re multilingual. Do you speak a foreign language? If so, you’re cool.

Learning a second language is easy. Just find something whose acronym ends with the letter L, and learn how to use it. In this section, you read about AIDL — the Android Interface Definition Language.

Aside from being a language, AIDL is a programming idiom. AIDL is a way of rewriting some of your Java code to make it more natural and more straightforward.

In Listings 3-6 and 3-7, a service and its client pass messages back and forth. The message-passing paradigm is nice, but wouldn’t life be simpler if the client could simply call one of the service’s methods? That’s exactly what AIDL does for your code.

Using AIDL

Here’s how AIDL works:

  1. Create two projects.

    One project has the service; the other project has the client. The service project has no activity; the client project starts with a blank activity.

    In this section’s listings, the service’s package name is com.allyourcode.aidlservice, and the client’s package name is com.allyourcode.aidlclient.

  2. In the Project tool window of the service project, right-click (or on a Mac, Ctrl-click) the app branch.
  3. In the context menu that appears, select New ⇒ AIDL ⇒ AIDL File.

    A dialog box appears. The dialog box has an Interface Name field. In this set of instructions, I accept the default name IMyAidlInterface.

  4. Click Finish.

    As a result, the Project tool window has a new branch — an app/aidl branch containing an IMyAidlInterface.aidl item. In the editor, you see the text inside the new IMyAidlInterface.aidl file.

  5. Modify the IMyAidlInterface.aidl file’s text, as shown in Listing 3-16.

    The .aidl file describes the kind of information to be passed between the service and the client. The file is almost a plain old Java source file. The big difference is the use of the non-Java keyword in. This in keyword tells the world that the service’s code receives a String value (instead of sending out a String value).

  6. In the service project’s main menu, choose Build ⇒ Make Project.

    This forces Android Studio to create some Java code. The new Java code implements some of the things that are described in your AIDL file.

  7. Find the Java code that was generated in Step 6.

    To do so, switch the Project tool window from the Android view to the Project view. Then expand the app/build/generated/source/aidl/debug/com.allyourcode.aidlservice branch. Inside this branch, you see an IMyAidlInterface branch.

    The IMyAidlInterface.java file is long and complicated. You can look at the IMyAidlInterface.java file by double-clicking the Project view’s IMyAidlInterface branch, but you’ll be just as happy if you don’t.

    Both the service and the client must have copies of the AIDL information. So your next step is to put a copy of the IMyAidlInterface stuff in the client project.

  8. Go to the Android view in the Project tool window in the client project.
  9. Right-click (or on a Mac, Ctrl-click) the app/java branch. In the resulting context menu, select New ⇒ Package.

    A Choose Destination Directory dialog box appears. Most likely, the dialog box wants you to choose between main and androidTest.

  10. In the dialog box, select main, and then click OK.

    A New Package dialog box appears.

  11. In the dialog box’s Enter New Package Name field, type the name of the service app’s package. Then click OK.

    If you’re following these instructions to the letter, the service app’s package name is com.allyourcode.aidlservice.

    Now, in addition to its aidlclient branch, the client’s Project tool window contains an aidlservice branch. The aidlservice branch is a sub-branch of the app/java/com.allyourcode branch.

  12. Right-click (or on a Mac, Ctrl-click) the aidlservice branch. Then, in the resulting context menu, choose New ⇒ AIDL ⇒ AIDL File.

    As in Step 3, a dialog box appears. For the Interface Name, type the same name that you typed in Step 3.

  13. Click Finish.

    As in Step 4, the Project tool window has a new app/aidl branch containing an IMyAidlInterface.aidl item. In the editor, you see the text inside the new IMyAidlInterface.aidl file.

  14. Modify the IMyAidlInterface.aidl file’s text, as shown in Listing 3-16.

    Now both the service and the client have IMyAidlInterface.aidl files, and both of these files belong to the com.allyourcode.aidlservice package.

  15. In the client project’s main menu, choose Build ⇒ Make Project.

    As a result, both the service and the client have IMyAidlInterface.java files.

    Now it’s time to add some regular Java code (along with layout files and strings.xml files) to your two projects. The Java code in this section is very much like the service and client classes in Listings 3-6 and 3-7. But in this section’s code, you don’t worry about messengers and binders. The automatically generated IMyAidlInterface.java code takes care of that messy stuff on your behalf.

  16. In the service app’s Project tool window, right-click the com.allyourcode.service branch.

    As usual, Ctrl-click if you’re using a Mac.

  17. In the resulting context menu, select New ⇒ Service ⇒ Service.

    A dialog box appears. The dialog box has a Class Name field and two check boxes. The default class name is MyService. The Exported and Enabled check boxes are checked.

  18. Click Finish to accept the defaults.

    When the dust settles, you see a new MyService class in Android Studio’s editor.

  19. Modify the MyService class’s code, as shown in the upcoming Listing 3-17.

    The code in Listing 3-17 is quite a bit simpler than the code in Listing 3-6. The WeatherFetcher class in Listing 3-17 takes the place of the messengers and the handler in Listing 3-6.

  20. In the client project, modify the ActivityMain class’s code, as shown momentarily in Listing 3-18.

    Notice the call to reporter.fetchWeather in Listing 3-18. Back in Listing 3-7, the client code passes a message to the service. But with the code in Listing 3-18, the client uses an ordinary method call to get data from the service. That’s much simpler than message passing.

  21. Add layout and strings.xml files to the client project.

    You can reuse the files you used for Listing 3-7.

  22. Install the service and client apps on an emulator or a real device.
  23. Run the client app.

    The client communicates with the service through the AIDL mechanisms. The weather report appears on your screen, and the experiment is a success!

Listing 3-16: An AIDL file

package com.allyourcode.aidlservice;

 

interface IMyAidlInterface {

 

    String fetchWeather(in String location);

 

}

 

The code in Listing 3-6 has a messenger and an incoming message handler. The message handler sends a message to be delivered to the client. In contrast, the code in Listing 3-12 has a fetchWeather method, which simply returns a String value. The class in Listing 3-12 can’t shoot the messenger because the class doesn’t even see the messenger.

In Listings 3-17 and 3-18, the developer is free of the messy messaging business when one process communicates with another. So the developer — that’s you! — can concentrate instead on the underlying application logic. Nice stuff!

Listing 3-17: The Service Code Using AIDL

package com.allyourcode.aidlservice;

 

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

 

public class MyService extends Service {

 

  @Override

  public IBinder onBind(Intent intent) {

    return new WeatherFetcher();

  }

 

  class WeatherFetcher extends IMyAidlInterface.Stub {

    public String fetchWeather(String city) {

      String weatherString = null;

      if (city != null) {

        weatherString = "It’s dark at night.";

      }

      return weatherString;

    }

  }

 

  @Override

  public boolean onUnbind(Intent intent) {

    stopSelf();

    return false;

  }

}

Listing 3-18: The Client Code Using AIDL

package com.allyourcode.aidlclient;

 

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.content.SharedPreferences;

import android.os.Bundle;

import android.os.IBinder;

import android.os.RemoteException;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.TextView;

 

import com.allyourcode.aidlservice.IMyAidlInterface;

 

public class MainActivity extends Activity {

 

  IMyAidlInterface reporter;

  ServiceConnection connection = new MyServiceConnection();

  SharedPreferences prefs;

  boolean isBound = false;

 

  void bind() {

    Intent intent = new Intent();

    intent.setClassName("com.allyourcode.aidlservice",

        "com.allyourcode.aidlservice.MyService");

    isBound = bindService(intent, connection,

      Context.BIND_AUTO_CREATE);

  }

 

  public void queryService() {

    if (isBound) {

      try {

        String report = reporter

           .fetchWeather(locationText.getText()

           .toString());

        textView.setText(report);

      } catch (RemoteException e) {

        e.printStackTrace();

      }

    } else {

      textView.setText(R.string.service_not_bound);

    }

  }

 

  void unbind() {

    if (isBound) {

      unbindService(connection);

      isBound = false;

    }

  }

 

  class MyServiceConnection implements ServiceConnection {

    public void onServiceConnected(

       ComponentName className, IBinder binder) {

      reporter = IMyAidlInterface.Stub

         .asInterface(binder);

    }

 

    public void onServiceDisconnected(ComponentName n) {

    }

  }

 

// For rest of this listing, copy the code from

// Listing 3-7 (from the onDestroy method,

// downward).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.4.181