Chapter 12. Networked Applications

Networking—one could talk about it for hours. In the context of Android, it is primarily about web services, which are services accessed by another program (your Android app) over the HTTP (“web”) protocol. Web services come in two flavors: XML/SOAP and RESTful. XML/SOAP web services are more formal and thus have significantly more overhead, both at development time and at runtime, but offer more capabilities. RESTful services are more lighterweight, and are not tied to XML: this chapter covers using JSON (JavaScript Object Notation) and other formats with web services.

Finally, while it’s not traditionally thought of as networking, Android also offers a more general “remote procedure” (technically an inter-process Communication or IPC) mechanism layered on AIDL (the Android Interface Definition Language) that is actually used for communication among processes on the same “machine” (Android device); a recipe describing that is at the end of this chapter.

Choose your protocol wisely

While Java makes it easy to create network connections on any protocol, experience shows that HTTP (and HTTPS) is the most universal. If you use a custom protocol to talk to your own server, there are some users who will not be able to access your server. Bear in mind too that in some countries high-speed data is either not yet available or very expensive, whereas GPRS/EDGE is less expensive and more widely available. Most GPRS service providers only allow HTTP/HTTPS connections, often through a proxy. That being said, there may be things you need to do that can’t be done via HTTP—for example, because the protocol demands a different port number (e.g., SIP over port 5000). But do try to make HTTP your first choice when you can—you’ll include more customers.

Note

All recipes in this chapter require that you add the android.permission.INTERNET permission to your AndroidManifest.xml file in order to be able to open network connections:

<uses-permission
    android:name="android.permission.INTERNET"/>

12.1 Consuming a RESTful Web Service Using a URLConnection

Ian Darwin

Problem

You need to access a RESTful web service.

Solution

You can either use the “standard” Java URL and URLConnection objects, or use the Android-provided Apache HttpClient library to code at a slightly higher level or to use HTTP methods other than GET and POST.

Discussion

REST (Representational State Transfer) was originally intended as an architectural description of the early web, in which GET requests were used and the URL fully specified (represented) the state of the request. Today, RESTful web services are those that eschew the overhead of XML, SOAP, WSDL, and (usually) XML Schema, and simply send URLs that contain all the information needed to perform the request (or almost all of it, as there is often a POST body sent for some types of requests). For example, to support an Android client that allows offline editing of recipes for this book, there is a (draft) web service that lets you view the list of recipes (you send an HTTP GET request ending in /recipe/list), view the details of one recipe (using an HTTP GET ending in /recipe/NNN, where NNN is the primary key of the entry, gotten from the requested list of recipes), and later upload your revised version of the recipe using an HTTP POST to /recipe/NNN, with the POST body containing the revised recipe in the same XML document format as the “get recipe” operation downloads it.

The RESTful service used by these examples is implemented in server-side Java using the Java EE standard JAX-RS API, provided by the RestEasy implementation.

Using URL and URLConnection

Android’s developers wisely preserved a lot of the Java Standard API, including some widely used classes for networking, so as to make it easy to port existing code. The converse() method shown in Example 12-1 uses a URL and URLConnection from java.net to do a GET, and is extracted from an example in the networking chapter of my Java Cookbook, published by O’Reilly. Comments in this version show what you’d need to change to do a POST.

Example 12-1. The RESTful web service client—URLConnection version
public static String converse(String host, int port, String path)
        throws IOException {
    URL url = new URL("http", host, port, path);
    URLConnection conn = url.openConnection();
    // This does a GET; to do a POST, add conn.setDoOutput(true);
    conn.setDoInput(true);
    conn.setAllowUserInteraction(true); // Useless but harmless

    conn.connect();

    // To do a POST, you'd write to conn.getOutputStream();

    StringBuilder sb = new StringBuilder();
    BufferedReader in = new BufferedReader(
        new InputStreamReader(conn.getInputStream()));
    String line;
    while ((line = in.readLine()) != null) {
        sb.append(line);
    }
    in.close();
    return sb.toString();
}

The invocation of this method can be as simple as the following, which gets the list of recipes from this book, as long as you don’t try this on the main thread:

String host = "androidcookbook.com";
String path = "/seam/resource/rest/recipe/list";
String ret = converse(host, 80, path);

Note that the path value is expected to change to just "/rest/recipe/list" sometime in 2017.

Using HttpClient (deprecated)

Android used to support the Apache HttpClient library, but has now deprecated it (and removed it, as of Android 6, meaning you have to add it as a project dependency if you want to use it in projects compiled for Android 6 or later). HttpClient is widely used in the Java world at large for communicating at a slightly higher level than the URLConnection. I’ve used it in my PageUnit web test framework. HttpClient also lets you use other HTTP methods that are common in RESTful services, such as PUT and DELETE. Example 12-2 shows the same converse() method coded for a GET using HttpClient.

Example 12-2. The RESTful web service client—Apache HttpClient version
public static String converse(String host, int port, String path,
        String postBody) throws IOException {
    HttpHost target = new HttpHost(host, port);
    HttpClient client = new DefaultHttpClient();
    HttpGet get = new HttpGet(path);
    HttpEntity results = null;
    try {
        HttpResponse response=client.execute(target, get);
        results = response.getEntity();
        return EntityUtils.toString(results);
    } catch (Exception e) {
        throw new RuntimeException("Web Service Failure");
    } finally {
        if (results!=null)
            try {
                results.consumeContent();
            } catch (IOException e) {
                // Empty, checked exception but don't care
            }
    }
}

Usage will be exactly the same as for the URLConnection-based version.

The results

In the present version of the web service, as discussed in this recipe, the return value comes back as an XML document, which you’d need to parse to display in a List. We will probably add a JSON version as well, triggering on the standard content-type header.

The output of either form should look something like the page displayed in Figure 12-1, where we access the REST URL using a browser (a common exploration technique for very simple, GET-based REST services).

See Also

Recipe 10.13.

ack2 1201
Figure 12-1. Android Cookbook contents via REST

12.2 Consuming a RESTful Web Service with Volley

Ian Darwin

Problem

You want an easy way to access a REST service and have heard that Volley might be the answer.

Solution

Using Volley, create a RequestQueue, and submit a URL with two “callbacks”: a success listener and a failure listener.

Discussion

Volley, a semi-official Google library, really does make it easy to use REST networking. We call it “semi-official” because it’s not part of the standard Android system, but is hosted on Google’s official source repository (if you wish to examine the library’s internals you can git clone https://android.googlesource.com/platform/frameworks/volley) and documented on the official Android documentation site.

To use Volley in your app, first you have to add the Volley library to your project, as it is not part of the standard Android distribution. At the time of this writing, the coordinates were com.android.volley:volley:1.0.0, though the version might have gone up by the time you read this.

With that done, you can initialize a Volley “request queue,” typically in your Activity’s onCreate() method:

// Set up the Volley queue for REST processing
queue = Volley.newRequestQueue(this);

Assuming that you want to fetch data in response to a button press or similar event, you will have a View handler to create and queue up the request. Along with the URL, the request will contain a callback handler that Volley will run on the UI thread to display the results, and a failure listener to handle errors.

In this example we use the well-known Google Suggest service, which the Chrome browser uses to make suggestions when you start typing in the browser’s search box:

public void fetchResults(View v) {

    String host = "https://suggestqueries.google.com/";
    // Amusingly, client=firefox makes the output come back in JSON
    String baseUrl = "complete/search?output=toolbar&hl=en&client=firefox&q=";
    String listUrl = mSearchBox.getText().toString();

    // Some error handling here...

    // Create a String request to get information from the provided URL
    String requestUrl = host + baseUrl + listUrl;
    JsonArrayRequest request = new JsonArrayRequest(
            requestUrl, successListener, failListener);

    // Queue the request to do the sending and receiving
    queue.add(request);
}

We ask for the data in JSON as that’s the common format for REST services. As with any JSON-based service, you need to know the format, so feel free to explore the REST responses using your favorite REST client (if you don’t have one, we suggest PostMan for Chrome or REST Client for Firefox). The results will be processed by the SuccessListener defined here (for simplicity we display the strings in a large TextView instead of a ListView; elaborating that is an obvious “exercise for the reader”):

/**
 * What we get back from this particular web service is a JSON array
 * containing:
 * 0) A JSON String containing the query string
 * 1) A JSON Array of strings with the results
 */
final Response.Listener<JSONArray> successListener =
  new Response.Listener<JSONArray>() {
    @Override
    public void onResponse(JSONArray response) {
        try {
            String query = response.getString(0);
            mTextView.append("Original query: " + query + "
");
            JSONArray rest = response.getJSONArray(1);
            mTextView.setText("We got " + rest.length() + " results:
");
            for (int i = 0; i < rest.length(); i++) {
                mTextView.append(rest.getString(i) + "
");
            }
        } catch (JSONException e) {
            mTextView.append("
);");
            mTextView.append("FAIL: " + e);
            e.printStackTrace();
        }
    }
};

See Also

The official documentation on using Volley.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory VolleyDemo (see “Getting and Using the Code Examples”).

12.3 Notifying Your App with Google Cloud Messaging “Push Messaging”

Ian Darwin

Problem

You want to get “push” notifications sent asynchronously from a server, without setting up your own complex infrastructure. This can be used to send short data (up to about 4 KB), or to send a “ping” notification that will cause the app to download new data from your server.

Solution

Consider using Google Cloud Messaging (GCM).

Note

GCM has just been upgraded to “Firebase Cloud Messaging,” and its documentation is now at https://firebase.google.com/docs/cloud-messaging/.

Discussion

GCM is a free service offered to Android developers to deliver small messages direct to your application running on an Android device. This avoids your application having to poll a server, which would be either not very responsive, or very bad for battery life. The basic operation of GCM is:

particular user’s device (e.g., new or changed data available). .You send a message to the GCM server. .The GCM server sends the message to the user’s device, where it is passed to a BroadcastReceiver in your app. .You do something with the information.

There are other solutions, such as intercepting incoming SMS messages (see Recipe 11.5). GCM has the advantage that it’s free, and the disadvantage that it takes a bit longer to set up than other solutions. Note that prior to API 4.0.4, the user was required to have a Google sign-in in order to receive GCM push messages.

The basic steps in building a GCM application are:

  1. Sign up with Google to use GCM.

  2. Set up your development environment for GCM.

  3. Configure ProGuard to preserve GCM services in your APK.

  4. Configure your client’s AndroidManifest.xml.

  5. Initialize GCM in your startup code.

  6. Create a BroadcastReceiver to handle the incoming notifications.

  7. Configure your backend server to notify the GCM server when it has data to send (or to send a notice to tell the client to download new data, a form of distributed MVC).

The following sections elaborate on these steps.

Sign up with Google to use GCM

Assuming that you have a Google Play Developer account (if not, see “Signing up” in Recipe 21.2), go to your Developer Console. If this is your first time here, or you need to make a new project, click Create Project; otherwise, select the project. In either case, jot down the Project Number, which appears in the URL and at the top of the page. This number is used as your GCM Sender ID.

At the left of the page, select APIs. Then set “Google Cloud Messaging for Android” to ON. You have to accept a license. The API will disappear from the list and reappear at the top, with the status set to ON.

Back at the left, under “APIs & auth,” click Credentials, then “Create new Key” (not “Create new Client ID”). Then select “Server key” (not “Android key”). Click Create, and put in your server’s IP address (you can enter as many as you need). Click OK.

The reason you need a server key is that your app server will be the one contacting GCM, not your client app.

Save the API key that is generated; you will need it in your server. This is further described in the GCM documentation.

Set up your development environment for GCM

Ensure you have the Google Play Services SDK installed (use the Android SDK Manager in your IDE, or the android sdk command-line tool). Then select Google Play Services, under Extras.

If this is your first use of Google Play Services, you’ll have to install the library project from /extras/google/google_play_services/libproject/google-play-services_lib/ to a source folder. If you’re using Eclipse, then import it using File → Import → Android → Existing Android Code. If, like me, you prefer to keep everything in your workspace, you can point the Import at the library project path above, but be sure to check the “Copy files into Workspace” checkbox.

Then you need to make your client app project depend upon this library using Project → Properties → Android → Library → Add (it is a common mistake to use Project → Build Path → Add Library → Project instead; this does not work).

Configure ProGuard to preserve GCM Services in your APK

If you’re using ProGuard (see Recipe 21.5), add the following to your proguard-project.txt file:

-keep class * extends java.util.ListResourceBundle {
protected Object[][] getContents();
}

-keep public class
com.google.android.gms.common.internal.safeparcel.SafeParcelable {
public static final *** NULL;
}

-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
@com.google.android.gms.common.annotation.KeepName *;
}

-keepnames class * implements android.os.Parcelable {
public static final ** CREATOR;
}

Configure your client’s AndroidManifest.xml

There are several pieces to go in your AndroidManifest.xml file. First, add the permissions android.permission.INTERNET and com.google.android.c2dm.permission.RECEIVE. Also, add android.permission.WAKE_LOCK if you want to keep the device from sleeping between receipt of a message and its processing. And add android.permission.GET_ACCOUNTS if the device API is lower than 4.0.4.

You also have to build your own permission, in order to prevent other apps from stealing your messages. Create and give yourself the permission applicationPackage.permission.C2D_MESSAGE (for example, com.exa⁠mple.⁠gcmplay​.permi⁠ssion.C2D_MES⁠SAGE). You must use this exact name for your custom permission. This might look like the following:

<permission android:name="com.example.gcmplay.permission.C2D_MESSAGE"
            android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcmplay.permission.C2D_MESSAGE" />

Inside the application element, add:

<meta-data android:name="com.google.android.gms.version"
           android:value="@integer/google_play_services_version" />

Configure a BroadcastReceiver to receive the GCM Intent, protected by the GCM permission. This might look like the following:

<receiver android:name=".GcmplayBroadcastReceiver"
          android:permission="com.google.android.c2dm.permission.SEND" >
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <category android:name="com.example.gcm" />
    </intent-filter>
</receiver>

Last but not least, you’ll probably want an IntentService to receive the messages from the receiver and get them into the app:

<service android:name=".GcmIntentService"/>

Initialize GCM in your startup code

In your app’s startup code (e.g., in onCreate() or onResume()), check to see that Google Play Services are available using the static method GooglePlayservicesUtil.isGooglePlayServicesAvailable(Context ctx). For example:

boolean checkForGcm() {
    int ret = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
    if (ConnectionResult.SUCCESS == ret) {
        return true;
    } else {
        if (GooglePlayServicesUtil.isUserRecoverableError(ret)) {
            GooglePlayServicesUtil.getErrorDialog(ret, this,
                PLAY_SERVICES_RESOLUTION_REQUEST).show();
        } else {
            Toast.makeText(this,
                "Google Message Not Supported on this device",
                Toast.LENGTH_LONG).show();
        }
    return false;
    }
}

At this point you have to decide whether you are going to use HTTP or XMPP to communicate from the server to your client. XMPP (the Extensible Messaging and Presence Protocol, a chat protocol now used by Google Talk) allows bidirectional messages, whereas HTTP is simpler to set up but is only one-way. While the official documentation uses XMPP, we’ll use HTTP because it is simpler; you can later refer to the official documentation if you want to use XMPP.

Create a BroadcastReceiver to handle the incoming notification

The BroadcastReceiver gets the message via an Intent, and hands it off to another class (the IntentService) to handle it. The only change it makes to the incoming Intent is to change it to explicitly have the class name of the Service in order to pass it along. Reusing the Intent this way causes the Intent extra—which contains the actual data from the server—to be passed along.

The use of WakefulBroadcastReceiver (shown in the following code) is optional; if you don’t care about the device possibly going to sleep before the Service has finished, you can just use a plain BroadcastReceiver (and remove the call to completeWakefulIntent() in the Service):

public class GcmReceiver extends WakefulBroadcastReceiver {
    /**
    * Called when a message is received from GCM for this app
    */
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(GcmMainActivity.TAG, "GcmReceiver.onReceive()");
        // Recycle "intent" into an explicit Intent for the handler.
        ComponentName comp =
                new ComponentName(context.getPackageName(),
                                  GcmService.class.getName());
        intent.setComponent(comp);

        // Pass control to the handler. Using startWakefulService() will keep the
        // device awake so the user has a good chance of seeing the message;
        // the WakeLock is released at the end of the handler. Reusing
        // the incoming Intent this way lets us pass along Intent extras, etc.
startWakefulService(context, intent);

        // If we didn't throw an exception yet, life is good.
setResultCode(Activity.RESULT_OK);
    }
}

The last bit of client code is the IntentService, also known as the “do what you want with the result” section. This trivial example merely displays the result in the LogCat output, but that is enough to show that our “Hello, World” application is receiving messages and handling them (a better example would show the result in a notification, and an even better example would update the GUI in the main Activity; these are left as an exercise for the reader):

/**
 * A very simple program which pretends to be a "server" in that it sends
 * a notification to the Google Cloud Messaging Server to cause it to send
 * a message to our GCM Client.
 * @author Ian Darwin, http://androidcookbook.com/
 */
public class GcmMockServer {
    /** Confidential Server API key gotten from the Google Dev Console
     * -> Credentials -> Create new Key -> Server key */
    final static String AUTH_KEY;

    final static String POST_URL = "https://android.googleapis.com/gcm/send";

    public static void main(String[] args) throws Exception {

        final  String[][]  MESSAGE_HEADERS = {
            {"Content-Type", "application/json"},
            {"Authorization", "key=" + AUTH_KEY}
        };

        String regIdFromClientApp = "Paste GCM Client App Google ID here";
        String jsonMessage =
                "{
" +
                "    "registration_ids" : [""+ regIdFromClientApp + ""],
" +
                "    "data" : {
" +
                "        "message": "See your doctor ASAP!"
" +
                "    }
" +
                "}
";

        // Dump out the HTTP send for debugging
        for (String[] hed : MESSAGE_HEADERS) {
            System.out.println(hed[0] + "=>" + hed[1]);
        }
        System.out.println(jsonMessage);

        // Actually send it
        sendMessage(POST_URL, MESSAGE_HEADERS, jsonMessage);
    }

    private static void sendMessage(String postUrl, String[][] messageHeaders,
            String jsonMessage) throws IOException {
        HttpURLConnection conn =
                (HttpURLConnection) new URL(postUrl).openConnection();
        for (String[] h : messageHeaders) {
            conn.setRequestProperty(h[0], h[1]);
        }
        System.out.println("Connected to " + postUrl);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setUseCaches(false);       // Ensure response always from server

        PrintWriter pw = new PrintWriter(
                new OutputStreamWriter(conn.getOutputStream()));

        pw.print(jsonMessage);
        pw.close();

        System.out.println("Connection status code " + conn.getResponseCode());
    }

    /** Static initializer, just load API key so it doesn't appear
     * in the commit history */
    static {
            InputStream is = null;
            try {

                is = GcmMockServer.class.getResourceAsStream("keys.properties");
                if (is == null) {
                    throw new RuntimeException("could not open keys files");
                    "maybe copy keys.properties.sample "
                    "to keys.properties in resource?");
                }
                Properties p = new Properties();
                p.load(is);
                AUTH_KEY = p.getProperty("GCM_API_KEY");
                if (AUTH_KEY == null) {
                    String message = "Could not find GCM_API_KEY in props";
                    throw new ExceptionInInitializerError(message);
                }

            } catch (Exception e) {
                String message = "Error loading properties: " + e;
                throw new ExceptionInInitializerError(message);
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        // What a useless exception
                    }
                }
            }
    }
}

Configure your backend server to notify the GCM server when it has data

Instead of writing a full server, here we just show a standalone main program containing the code that your server would use to send a message to the client. It just uses a Java HttpUrlConnection to talk to the Google server.

In real life your app would need to send its registration ID string to your server, which would use it as a token to identify the client to receive this particular message. The registration ID is a unique identifier for a version of your app installed at a particular instant on a particular device; uninstall and reinstall the same app and you get a different client ID.

Your server also needs the API key we generated near the outset of this recipe in order to authenticate itself. Keep this key confidential, as it will allow anybody who finds it to send messages to your clients.

Here’s the code from my GcmMockServer application:

/**
* A very simple Java SE program which pretends to be a "server" in that it sends
* a notification to the Google Cloud Messaging Server to cause it to send
* a message to our GCM Client.
* @author Ian Darwin, http://androidcookbook.com/
*/
public class GcmMockServer {

    /** Confidential Server API key gotten from the Google Dev Console ->
     * Credentials -> Create new Key -> Server key */
    final static String AUTH_KEY; // Set in a static initializer, not shown

    final static String POST_URL = "https://android.googleapis.com/gcm/send";

    public static void main(String[] args) throws Exception {

        final String[][] MESSAGE_HEADERS = {
            {"Content-Type", "application/json"},
            {"Authorization", "key=" + AUTH_KEY}
            };

        String regIdFromClientApp = null; // Has to be set somehow!
        String jsonMessage =
            "{
" +
            " "registration_ids" : [""+ regIdFromClientApp + ""],
" +
            " "data" : {
" +
            " "message": "See your doctor ASAP!"
" + // THE ACTUAL MESSAGE
            " }
" +
            "}
";

        // Dump out the HTTP send for debugging
        for (String[] hed : MESSAGE_HEADERS) {
            System.out.println(hed[0] + "=>" + hed[1]);
        }
        System.out.println(jsonMessage);

        // Actually send it
        sendMessage(POST_URL, MESSAGE_HEADERS, jsonMessage);
    }

    private static void sendMessage(String postUrl, String[][] messageHeaders,
            String jsonMessage) throws IOException {
            HttpURLConnection conn =
            (HttpURLConnection) new URL(postUrl).openConnection();
        for (String[] h : messageHeaders) {
            conn.setRequestProperty(h[0], h[1]);
        }
        System.out.println("Connected to " + postUrl);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setUseCaches(false); // Ensure response always from server

        PrintWriter pw = new PrintWriter(
                new OutputStreamWriter(conn.getOutputStream()));

        pw.print(jsonMessage);
        pw.close();

        System.out.println("Connection status code " + conn.getResponseCode());
    }
}

So, does it all work? If everything has been set up just so, and you run the client in a device (or maybe it will work in an emulator), and then you copy the RegistrationId string into the server (logcat is your friend here!), and then you run the GcmMockServer as a Java application, and the winds are blowing from the south, then you will see the following, or something very like it, in the logcat output:

D/com.darwinsys.gcmdemo( 7496): GcmReceiver.onReceive()
D/com.darwinsys.gcmdemo( 7496): Got a message of type gcm
D/com.darwinsys.gcmdemo( 7496): MESSAGE = 'See your doctor ASAP!'
    (Bundle[{from=117558675814, message=See your doctor ASAP!,
     android.support.content.wakelockid=2,
      collapse_key=do_not_collapse}])

See Also

The official GCM documentation.

Source Download URL

The source code for the client part of project is in the Android Cookbook repository, in the subdirectory GcmClient (see “Getting and Using the Code Examples”). The mock server is in the subdirectory GcmMockServer.

12.4 Extracting Information from Unstructured Text Using Regular Expressions

Ian Darwin

Problem

You want to get information from another organization, but the organization doesn’t make it available as information, only as a viewable web page.

Solution

Use java.net to download the HTML page, and use regular expressions to extract the information from the page.

Discussion

If you aren’t already a big fan of regular expressions, well, you should be. Maybe this recipe will help interest you in learning regex technology.

Suppose that I, as a published author, want to track how my book is selling in comparison to others. I can obtain this information for free just by clicking the page for my book on any of the major bookseller sites, reading the sales rank number off the screen, and typing the number into a file—but that’s too tedious. As I wrote in one of my earlier books, “computers get paid to extract relevant information from files; people should not have to do such mundane tasks.”

The program shown in Example 12-3 uses the Regular Expressions API and, in particular, newline matching to extract a value from an HTML page on the Amazon.com website. It also reads from a URL object (see Recipe 12.1). The pattern to look for is something like this (bear in mind that the HTML may change at any time, so I want to keep the pattern fairly general):

(bookstore name here) Sales Rank:
# 26,252

As the pattern may extend over more than one line, I read the entire web page from the URL into a single long string using a private convenience routine, readerToString(), instead of the more traditional line-at-a-time paradigm. The value is extracted from the regular expression, converted to an integer value, and returned. The longer version of this code in Java Cookbook would also plot a graph using an external program. The complete program is shown in Example 12-3.

Example 12-3. Part of class BookRank
public static int getBookRank(String isbn) throws IOException {
    // The RE pattern - digits and commas allowed.
    final String pattern = "Rank:</b> #([\d,]+)";
    final Pattern r = Pattern.compile(pattern);

    // The url -- must have the "isbn=" at the very end, or otherwise
    // be amenable to being appended to.
    final String url = "http://www.amazon.com/exec/obidos/ASIN/" + isbn;

    // Open the URL and get a Reader from it.
    final BufferedReader is = new BufferedReader(new InputStreamReader(
        new URL(url).openStream()));
    // Read the URL looking for the rank information, as
    // a single long string, so can match RE across multiple lines.
    final String input = readerToString(is);

    // If found, append to sales data file.
    Matcher m = r.matcher(input);
    if (m.find()) {
        // Group 1 is digits (and maybe ','s) that matched; remove comma
        return Integer.parseInt(m.group(1).replace(",",""));
    } else {
        throw new RuntimeException(
            "Pattern not matched in `" + url + "'!");
    }
}

It should be noted that in general you cannot parse arbitrary HTML using regular expressions; the reasons have to do with complexity and have been well covered online, both seriously and humorously.

See Also

As mentioned, using the regex API is vital to being able to deal with semistructured data that you will meet in real life. Chapter 4 of Java Cookbook, written by me and published by O’Reilly, is all about regular expressions, as is Jeffrey Friedl’s comprehensive Mastering Regular Expressions, also published by O’Reilly.

Source Download URL

You can download the source code for this example from GitHub.

12.5 Parsing RSS/Atom Feeds Using ROME

Wagied Davids

Problem

You want to parse RSS/Atom feeds, which are commonly used to provide an updated list of news articles on websites and often identified by the “news” icon:

images/anck_13in01.png

Solution

This recipe shows an RSS/Atom feed parser based on ROME, a Java-based RSS syndication feed parser. It has some useful features such as HTTP conditional GETs, ETags, and Gzip compression. It also covers a wide range of formats, including RSS 0.90, RSS 2.0, and Atom 0.3 and 1.0. The web site for the Rome project is http://rometools.github.io/rome/.

Discussion

The basic steps for parsing an RSS/Atom feed with a ROME-based parser are as follows:

  1. Modify your AndroidManifest.xml file to have INTERNET permission to allow for Internet browsing.

  2. Create an Android project. Set the layout file to be the contents of Example 12-4.

  3. Add the dependency rome:rome:1.0 in your build file. Or, manually download the rome-1.0.jar and jdom-1.0.jar files and add them to your project.

  4. Create the Activity shown in Example 12-5. In particular, the getRSS() method demonstrates the use of the ROME API to parse the XML RSS feed and display the results.

When run with the given feed URL, the output should look like Figure 12-2, except with newer news items.

ack2 1202
Figure 12-2. RSS feed in ListView

The layout is shown in Example 12-4, and the Java code in Example 12-5.

Example 12-4. main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <TableLayout
        android:id="@+id/table"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:stretchColumns="0">
        <TableRow
            android:id="@+id/top_add_entry_row"
            android:layout_height="wrap_content"
            android:layout_width="fill_parent">

            <EditText
                android:id="@+id/rssURL"
                android:hint="Enter RSS URL"
                android:singleLine="true"
                android:maxLines="1"
                android:maxWidth="220dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
            </EditText>
            <Button
                android:id="@+id/goButton"
                android:text="Go"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content">
            </Button>
        </TableRow>
    </TableLayout>

    <!--  Mid Panel -->
    <ListView
        android:id="@+id/ListView"
        android:layout_weight="1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
    </ListView>

    <Button
        android:id="@+id/clearButton"
        android:text="Clear"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
    </Button>
</LinearLayout>
Example 12-5. AndroidRss.java
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;

public class AndroidRss extends Activity {
    private static final String tag="AndroidRss ";
    private int selectedItemIndex = 0;
    private final ArrayList list = new ArrayList();
    private EditText text;
    private ListView listView;
    private Button goButton;
    private Button clearButton;
    private ArrayAdapter adapter = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

            text = (EditText) this.findViewById(R.id.rssURL);
            goButton = (Button) this.findViewById(R.id.goButton);
            goButton.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                            String rss = text.getText().toString().trim();
                            getRSS(rss);
                        }
                });

            clearButton = (Button) this.findViewById(R.id.clearButton);
            clearButton.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                            adapter.clear();
                            adapter.notifyDataSetChanged();
                        }
                });

            listView = (ListView) this.findViewById(R.id.ListView);
            listView.setOnItemClickListener(new OnItemClickListener() {
                    @Override
                    public void onItemClick(AdapterView parent, View view,
                    int position, long duration) {
                            selectedItemIndex = position;
                            Toast.makeText(getApplicationContext(),
                                "Selected " + adapter.getItem(position) +
                                " @ " + position, Toast.LENGTH_SHORT).show();
                        }
                });

            adapter = new ArrayAdapter(this, R.layout.dataview, R.id.ListItemView);
            listView.setAdapter(adapter);

        }

    private void getRSS(String rss) {

            URL feedUrl;
            try {
                    Log.d("DEBUG", "Entered:" + rss);
                    feedUrl = new URL(rss);

                    SyndFeedInput input = new SyndFeedInput();
                    SyndFeed feed = input.build(new XmlReader(feedUrl));
                    List entries = feed.getEntries();
                    Toast.makeText(this,
                         "#Feeds retrieved: " + entries.size(),
                         Toast.LENGTH_SHORT).show();

                    Iterator iterator = entries.listIterator();
                    while (iterator.hasNext()) {
                            SyndEntry ent = (SyndEntry) iterator.next();
                            String title = ent.getTitle();
                            adapter.add(title);
                        }
                    adapter.notifyDataSetChanged();

            }
            catch (Exception e) {
                    e.printStackTrace();
            }
        }

    private void clearTextFields() {
            Log.d(tag, "clearTextFields()");
            this.text.setText("");
    }
}

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory AndroidRss (see “Getting and Using the Code Examples”).

12.6 Using MD5 to Digest Clear Text

Colin Wilcox

Problem

Sometimes you need to convert clear text to a nonreadable form before saving or transmitting it.

Solution

Android provides a standard Java MD5 class to allow plain text to be replaced with an MD5 digest of the original text. This is a one-way digest that is not believed to be easily reversible (if you need that, use Java Cryptography (O’Reilly)).

Discussion

Example 12-6 is a simple function that takes a clear-text string and digests it using MD5, returning the encrypted string as a return value.

Example 12-6. MD5 hash
public static String md5(String s) {
        try {
            // Create MD5 hasher
            MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
            digest.update(s.getBytes());
            byte messageDigest[] = digest.digest();
            // Create hex string
            StringBuffer hexString = new StringBuffer();
            for (int i = 0; i < messageDigest.length; i++) {
                hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
            }
            return hexString.toString();
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";    // Or give the user an Exception...
    }

12.7 Converting Text into Hyperlinks

Rachee Singh

Problem

You need to turn web page URLs into hyperlinks in a TextView of your Android app.

Solution

Use the autoLink property for a TextView.

Discussion

Say you are setting the URL www.google.com as part of the text in a TextView, but you want this text to be a hyperlink so that the user can open the web page in a browser by clicking it. To achieve this, add the autoLink property to the TextView:

android:autoLink = "all"

Now, in the Activity’s code, you can set any text to the TextView and all the URLs will be converted to hyperlinks! See Figure 12-3.

linkText = (TextView)findViewById(R.id.link);
linkText.setText("The link is: www.google.com");
ack2 12in01
Figure 12-3. TextView with links auto-converted

12.8 Accessing a Web Page Using a WebView

Rachee Singh

Problem

You want to download and display a web page within your application.

Solution

Embed the standard WebView component in the layout and invoke its loadUrl() method to load and display the web page.

Discussion

WebView is a View component that can be placed in an Activity. Its primary use is, as its name implies, to handle web pages for you. Since WebViews usually need to access remote web page(s), don’t forget to add the INTERNET permission into the manifest file:

<uses-permission android:name="android.permission.INTERNET" />

Then you can add the WebView to your XML layout:

<WebView
    android:id="@+id/webview"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"/>

In the Java code for the Activity that displays the web page, we obtain a handle onto the WebView using the findViewById() method. On the WebView we use the loadUrl() method to provide it the URL of the website we wish to open in the application:

WebView webview = (WebView)findViewById(R.id.webview);
webview.loadUrl("http://google.com");

Source Download URL

You can download the source code for this example from Google Docs.

12.9 Customizing a WebView

Rachee Singh

Problem

You need to customize the WebView opened by your application.

Solution

Use the WebSettings class to access built-in functions for customizing the browser.

Discussion

As discussed in Recipe 12.8, to open a web page in an Android application, we use a WebView component. Then, to load a URL in the WebView, we use, for example:

webview.loadUrl("http://www.google.com/");

We can do many things to customize the browser to suit users’ needs. To customize the view, we need an instance of the WebSettings class, which we can get from the WebView component:

WebSettings webSettings = webView.getSettings();

Here are some of the things we can do using WebSettings:

  • Tell the WebView to block network images:

    webSettings.setBlockNetworkImage(true);
  • Set the default font size in the browser:

    webSettings.setDefaultFontSize(25);
  • Control whether the WebView supports zoom:

    webSettings.setSupportZoom(true);
  • Tell the WebView to enable JavaScript execution:

    webSettings.setJavaScriptEnabled(true);
  • Control whether the WebView will save passwords:

    webSettings.setSavePassword(false);
  • Control whether the WebView will save form data:

    webSettings.setSaveFormData(false);

Many more methods of this kind are available. For more information, see the developer documentation on the WebView class.

12.10 Writing an Inter-Process Communication Service

Rupesh Chavan

Problem

You want to know how to write an IPC service and access it from another application.

Solution

Android provides an AIDL-based programming interface that both the client and the service agree upon in order to communicate with each other using inter-process communication (IPC).

Discussion

IPC is a key feature of the Android programming model. It provides the following two mechanisms:

  • Intent-based communication

  • Remote service–based communication

In this recipe we will concentrate on the remote service–based communication approach. This Android feature allows you to make method calls that look “local” but are executed in another process. This is somewhat similar to standard Java’s Remote Method Invocation (RMI), and involves use of the Android Interface Definition Language (AIDL). The service has to declare a service interface in an AIDL files and then the AIDL tool will automatically create a Java interface corresponding to the AIDL file. The AIDL tool also generates a stub class that provides an abstract implementation of the service interface methods. You have to provide a Service class, which will extend this stub class to provide the real implementation of the methods exposed through the interface.

The service clients will invoke the onBind() method of the Service in order to connect to the service. The onBind() method returns an object of the stub class to the client. Example 12-7 shows the code-related snippets.

Example 12-7. IMyRemoteService.aidl
package com.demoapp.service;

interface IMyRemoteService {
  String getMessage();
}

Note that the AIDL file in Example 12-7 looks like Java code but must be stored with a filename extension of .aidl in order to be processed correctly. Either Eclipse or Android Studio will automatically generate (in the generated sources directory, since you don’t need to modify it) the remote interface corresponding to your AIDL file; the generated interface will also provide an abstract member class named Stub, which must be implemented by the RemoteService class. The stub class implementation within the service class is shown in Example 12-8.

Example 12-8. Remote service stub
public class MyService extends Service {
    private IMyRemoteService.Stub myRemoteServiceStub = new IMyRemoteService.Stub() {
        public int getMessage() throws RemoteException {
            return "Hello World!";
        }
    };
    // The onBind() method in the service class:
    public IBinder onBind(Intent arg0) {
        Log.d(getClass().getSimpleName(), "onBind()");
        return myRemoteServiceStub;
    }

Now, let’s quickly look at the meat of the service class before we move on to how the client connects to this class. Our MyService class consists of one method, which just returns a string. Example 12-9 shows the overridden onCreate(), onStart(), and onDestroy() methods. The onCreate() method of the service will be called only once in a service life cycle. The onStart() method will be called every time the service is started. Note that the resources are all released in the onDestroy() method (see Example 12-9). Since these just call super() and log their presence, they could be omitted from the service class.

Example 12-9. Service class onCreate(), onStart(), and onDestroy() methods
    public void onCreate() {
        super.onCreate();
        Log.d(getClass().getSimpleName(),"onCreate()");
    }
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.d(getClass().getSimpleName(), "onStart()");
    }
    public void onDestroy() {
       super.onDestroy();
       Log.d(getClass().getSimpleName(),"onDestroy()");
    }

Let’s discuss the client class. For simplicity, I placed the start, stop, bind, release, and invoke methods all in the same client, and will discuss each method in turn. In reality, though, one client may start and another can bind to the already started service.

There are five buttons, one each for the start, stop, bind, release, and invoke actions, each with an obvious listener method to invoke one of the five corresponding methods.

A client needs to bind to a service before it can invoke any method on the service, so we begin with Example 12-10, which shows the start method. In our simplified example, the code from Example 12-10 (which starts the service) to the end of this Recipe is all in the main Activity class; in a “real” application, the service would be started in a separate application.

Example 12-10. The startService() method
    private void startService() {
        if (started) {
            Toast.makeText(RemoteServiceClient.this, "Service already started",
                Toast.LENGTH_SHORT).show();
        } else {
             Intent i = new Intent(this, MyRemoteService.class);
             startService(i);
             started = true;
             updateServiceStatus();
             Log.d( getClass().getSimpleName(), "startService()" );
        }
    }

An explicit Intent is created and the service is started with the Context.startService(i) method. The rest of the code updates some status on the UI. There is nothing specific to a remote service invocation here; it is in the bindService() method that we see the difference from a local service (see Example 12-11).

Example 12-11. The bindService() method
    private void bindService() {
        if(conn == null) {
            conn = new RemoteServiceConnection();
            Intent i = new Intent(this, MyRemoteService.class);
            bindService(i, conn, Context.BIND_AUTO_CREATE);
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "bindService()" );
        } else {
              Toast.makeText(RemoteServiceClient.this,
                  "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

Here we get a connection to the remote service through the RemoteServiceConnection class, which implements the ServiceConnection interface. The connection object is required by the bindService() method—an intent, a connection object, and the type of binding are to be specified. So, how do we create a connection to the RemoteService? Example 12-12 shows the implementation.

Example 12-12. The ServiceConnection implementation
    class RemoteServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className,
                IBinder boundService ) {
            remoteService = IMyRemoteService.Stub.asInterface((IBinder)boundService);
            Log.d( getClass().getSimpleName(), "onServiceConnected()" );
        }

        public void onServiceDisconnected(ComponentName className) {
            remoteService = null;
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "onServiceDisconnected" );
        }
    };

The Context.BIND_AUTO_CREATE ensures that a service is created if one did not exist, although the onStart() method will be called only on explicit start of the service.

Once the client is bound to the service and the service has already started, we can invoke any of the methods that are exposed by the service. In our interface and its implementation (see Example 12-7 and Example 12-8), there is only one method, getMessage(). In this example, the invocation is done by clicking the Invoke button. That will return the text message and update it below the button. Example 12-13 shows the invoke method.

Example 12-13. The invokeService() method
    private void invokeService() {
        if(conn == null) {
            Toast.makeText(RemoteServiceClient.this,
                "Cannot invoke - service not bound", Toast.LENGTH_SHORT).show();
         } else {
               try {
                   String message = remoteService.getMessage();
                   TextView t = (TextView)findViewById(R.id.R.id.output);
                   t.setText( "Message: "+message );
                   Log.d( getClass().getSimpleName(), "invokeService()" );
               } catch (RemoteException re) {
                     Log.e( getClass().getSimpleName(), "RemoteException" );
               }
           }
    }

Once we use the service methods, we can release the service. This is done as shown in Example 12-14 (by clicking the Release button).

Example 12-14. The releaseService() method
    private void releaseService() {
        if(conn != null) {
            unbindService(conn);
            conn = null;
            updateServiceStatus();
            Log.d( getClass().getSimpleName(), "releaseService()" );
        } else {
              Toast.makeText(RemoteServiceClient.this,
                  "Cannot unbind - service not bound",
                  Toast.LENGTH_SHORT).show();
        }
    }

Finally, we can stop the service by clicking the Stop button. After this point, no client can invoke this service. Example 12-15 shows the relevant code.

Example 12-15. The stopService() method
    private void stopService() {
        if (!started) {
            Toast.makeText(RemoteServiceClient.this, "Service not yet started",
                Toast.LENGTH_SHORT).show();
        } else {
              Intent i = new Intent(this, MyRemoteService.class);
              stopService(i);
              started = false;
              updateServiceStatus();
              Log.d( TAG, "stopService()" );
        }
    }
Note

If the client and the service are using different package structures, then the client has to include the AIDL file along with the package structure, just like the service does.

These are the basics of working with a remote service on the Android platform.

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory IPCDemo (see “Getting and Using the Code Examples”).

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

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