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.
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.
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"
/>
Ian Darwin
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.
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
.
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.
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.
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.
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).
Ian Darwin
Using Volley, create a RequestQueue
, and submit a URL with two “callbacks”: a success listener and a failure listener.
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(); } } };
The official documentation on using Volley.
The source code for this project is in the Android Cookbook repository, in the subdirectory VolleyDemo (see “Getting and Using the Code Examples”).
Ian Darwin
Consider using Google Cloud Messaging (GCM).
GCM has just been upgraded to “Firebase Cloud Messaging,” and its documentation is now at https://firebase.google.com/docs/cloud-messaging/.
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:
Sign up with Google to use GCM.
Set up your development environment for GCM.
Configure ProGuard to preserve GCM services in your APK.
Configure your client’s AndroidManifest.xml.
Initialize GCM in your startup code.
Create a BroadcastReceiver
to handle the incoming notifications.
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.
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.
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).
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; }
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.example.gcmplay.permission.C2D_MESSAGE
). 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"/>
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.
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
.
(
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
}
}
}
}
}
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
.
(
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
}])
The official GCM documentation.
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.
Ian Darwin
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.
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.
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.
You can download the source code for this example from GitHub.
Wagied Davids
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 GET
s, 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/.
The basic steps for parsing an RSS/Atom feed with a ROME-based parser are as follows:
Modify your AndroidManifest.xml file to have INTERNET
permission to allow for Internet browsing.
Create an Android project. Set the layout file to be the contents of Example 12-4.
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.
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.
The layout is shown in Example 12-4, and the Java code in Example 12-5.
<?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>
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
(
""
);
}
}
The source code for this example is in the Android Cookbook repository, in the subdirectory AndroidRss (see “Getting and Using the Code Examples”).
Colin Wilcox
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)).
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.
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...
}
Rachee Singh
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"
);
Rachee Singh
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 WebView
s 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"
);
You can download the source code for this example from Google Docs.
Rachee Singh
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.
Rupesh Chavan
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).
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.
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.
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.
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.
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).
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.
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.
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).
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.
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()"
);
}
}
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.
The source code for this example is in the Android Cookbook repository, in the subdirectory IPCDemo (see “Getting and Using the Code Examples”).
34.231.180.210