- 1. Use non-blocking I/O, which is generally clumsy and unintuitive for most Java engineers.
- 2. Cancel blocking I/O threads by simply setting a stop flag and discarding the reference to the thread. The thread will clean itself up after an arbitrary length of time up to its transmit or connect timeout.
- 3. Close the socket owning the input stream on which the blocking thread is waiting.
So, to get started we need to create our
StoppableDownloadThread
class which allows us to encapsulate our interrupt logic.public class StoppableDownloadThread extends ThreadThis simply outlines our basic strategy for stopping and synchronization. A simple volatile boolean flag and a monitor lock to share the HttpGet handle should do just fine. Now let's continue with the implementation of the run() method:
{
private String mURL;
private HttpGet mMethod = null;
/* Volatile stop flag used to coordinate state between the two
* threads involved in this example. */
protected volatile boolean mStopped = false;
/* Synchronizes access to mMethod to prevent an unlikely race
* condition when stopDownload() is called before mMethod has
been committed. */
private Object lock = new Object();
public StoppableDownloadThread(String url)
{
mURL = url;
}
}
public void run()This is a pretty standard example of an HTTP GET using HttpClient4, however do note that we have strategically placed checks against our stop flag to avoid leaving the download thread in an inconsistent state when it's being cancelled. We're not done yet though as we still need to implement the stop part of the interface so that our main thread (or any other thread) can abort the download thread:
{
HttpClient cli = new DefaultHttpClient();
HttpGet method;
try {
method = new HttpGet(mURL);
} catch (URISyntaxException e) {
e.printStackTrace();
return;
}
/* It's important that we pause here to check if we've been stopped
* already. Otherwise, we would happily progress, seemingly ignoring
* the stop request. */
if (mStopped == true)
return;
synchronized(lock) {
mMethod = method;
}
HttpResponse resp = null;
HttpEntity ent = null;
InputStream in = null;
try {
resp = cli.execute(mMethod);
if (mStopped == true)
return;
StatusLine status = resp.getStatusLine();
if ((ent = resp.getEntity()) != null)
{
long len;
if ((len = ent.getContentLength()) >= 0)
mHandler.sendSetLength(len);
in = ent.getContent();
byte[] b = new byte[2048];
int n;
long bytes = 0;
/* Note that for most applications, sending a handler message
* after each read() would be unnecessary. Instead, a timed
* approach should be utilized to send a message at most every
* x seconds. */
while ((n = in.read(b)) >= 0)
{
bytes += n;
System.out.println("Read " + bytes + " bytes...");
}
}
} catch (Exception e) {
/* We expect a SocketException on cancellation. Any other type of
* exception that occurs during cancellation is ignored regardless
* as there would be no need to handle it. */
if (mStopped == false)
e.printStackTrace();
} finally {
if (in != null)
try { in.close(); } catch (IOException e) {}
synchronized(lock) {
mMethod = null;
}
/* Close the socket (if it's still open) and cleanup. */
cli.getConnectionManager().shutdown();
}
}
This completes our basic interface, but we still don't have a usable example here. There's no communication between our download thread back to the user in any meaningful way as would be required for an Android application. For that I have modified the above code slightly and introduced an Android layer in the form of a working demo. Source code for the full example: CancelHttpGet.tar.gz.public void stopDownload()
{if (mStopped == true)
return;
/* Flag to instruct the downloading thread to halt at the next
* opportunity. */
mStopped = true;
/* Interrupt the blocking thread. This won't break out of a blocking
* I/O request, but will break out of a wait or sleep call. While in
* this case we know that no such condition is possible, it is always a
* good idea to include an interrupt to avoid assumptions about the
* thread in question. */
interrupt();
/* A synchronized lock is necessary to avoid catching mMethod in
* an uncommitted state from the download thread. */
synchronized(lock) {
/* This closes the socket handling our blocking I/O, which will
* interrupt the request immediately. This is not the same as
* closing the InputStream yieled by HttpEntity#getContent, as the
* stream is synchronized in such a way that would starve our main
* thread. */
if (mMethod != null)
mMethod.abort();
}
}