| /* |
| * Copyright (C) ${year} The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.threadsample; |
| |
| import com.example.android.threadsample.PhotoDecodeRunnable.TaskRunnableDecodeMethods; |
| |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| |
| /** |
| * This task downloads bytes from a resource addressed by a URL. When the task |
| * has finished, it calls handleState to report its results. |
| * |
| * Objects of this class are instantiated and managed by instances of PhotoTask, which |
| * implements the methods of {@link TaskRunnableDecodeMethods}. PhotoTask objects call |
| * {@link #PhotoDownloadRunnable(TaskRunnableDownloadMethods) PhotoDownloadRunnable()} with |
| * themselves as the argument. In effect, an PhotoTask object and a |
| * PhotoDownloadRunnable object communicate through the fields of the PhotoTask. |
| */ |
| class PhotoDownloadRunnable implements Runnable { |
| // Sets the size for each read action (bytes) |
| private static final int READ_SIZE = 1024 * 2; |
| |
| // Sets a tag for this class |
| @SuppressWarnings("unused") |
| private static final String LOG_TAG = "PhotoDownloadRunnable"; |
| |
| // Constants for indicating the state of the download |
| static final int HTTP_STATE_FAILED = -1; |
| static final int HTTP_STATE_STARTED = 0; |
| static final int HTTP_STATE_COMPLETED = 1; |
| |
| // Defines a field that contains the calling object of type PhotoTask. |
| final TaskRunnableDownloadMethods mPhotoTask; |
| |
| /** |
| * |
| * An interface that defines methods that PhotoTask implements. An instance of |
| * PhotoTask passes itself to an PhotoDownloadRunnable instance through the |
| * PhotoDownloadRunnable constructor, after which the two instances can access each other's |
| * variables. |
| */ |
| interface TaskRunnableDownloadMethods { |
| |
| /** |
| * Sets the Thread that this instance is running on |
| * @param currentThread the current Thread |
| */ |
| void setDownloadThread(Thread currentThread); |
| |
| /** |
| * Returns the current contents of the download buffer |
| * @return The byte array downloaded from the URL in the last read |
| */ |
| byte[] getByteBuffer(); |
| |
| /** |
| * Sets the current contents of the download buffer |
| * @param buffer The bytes that were just read |
| */ |
| void setByteBuffer(byte[] buffer); |
| |
| /** |
| * Defines the actions for each state of the PhotoTask instance. |
| * @param state The current state of the task |
| */ |
| void handleDownloadState(int state); |
| |
| /** |
| * Gets the URL for the image being downloaded |
| * @return The image URL |
| */ |
| URL getImageURL(); |
| } |
| |
| /** |
| * This constructor creates an instance of PhotoDownloadRunnable and stores in it a reference |
| * to the PhotoTask instance that instantiated it. |
| * |
| * @param photoTask The PhotoTask, which implements TaskRunnableDecodeMethods |
| */ |
| PhotoDownloadRunnable(TaskRunnableDownloadMethods photoTask) { |
| mPhotoTask = photoTask; |
| } |
| |
| /* |
| * Defines this object's task, which is a set of instructions designed to be run on a Thread. |
| */ |
| @SuppressWarnings("resource") |
| @Override |
| public void run() { |
| |
| /* |
| * Stores the current Thread in the the PhotoTask instance, so that the instance |
| * can interrupt the Thread. |
| */ |
| mPhotoTask.setDownloadThread(Thread.currentThread()); |
| |
| // Moves the current Thread into the background |
| android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); |
| |
| /* |
| * Gets the image cache buffer object from the PhotoTask instance. This makes the |
| * to both PhotoDownloadRunnable and PhotoTask. |
| */ |
| byte[] byteBuffer = mPhotoTask.getByteBuffer(); |
| |
| /* |
| * A try block that downloads a Picasa image from a URL. The URL value is in the field |
| * PhotoTask.mImageURL |
| */ |
| // Tries to download the picture from Picasa |
| try { |
| // Before continuing, checks to see that the Thread hasn't been |
| // interrupted |
| if (Thread.interrupted()) { |
| |
| throw new InterruptedException(); |
| } |
| |
| // If there's no cache buffer for this image |
| if (null == byteBuffer) { |
| |
| /* |
| * Calls the PhotoTask implementation of {@link #handleDownloadState} to |
| * set the state of the download |
| */ |
| mPhotoTask.handleDownloadState(HTTP_STATE_STARTED); |
| |
| // Defines a handle for the byte download stream |
| InputStream byteStream = null; |
| |
| // Downloads the image and catches IO errors |
| try { |
| |
| // Opens an HTTP connection to the image's URL |
| HttpURLConnection httpConn = |
| (HttpURLConnection) mPhotoTask.getImageURL().openConnection(); |
| |
| // Sets the user agent to report to the server |
| httpConn.setRequestProperty("User-Agent", Constants.USER_AGENT); |
| |
| // Before continuing, checks to see that the Thread |
| // hasn't been interrupted |
| if (Thread.interrupted()) { |
| |
| throw new InterruptedException(); |
| } |
| // Gets the input stream containing the image |
| byteStream = httpConn.getInputStream(); |
| |
| if (Thread.interrupted()) { |
| |
| throw new InterruptedException(); |
| } |
| /* |
| * Gets the size of the file being downloaded. This |
| * may or may not be returned. |
| */ |
| int contentSize = httpConn.getContentLength(); |
| |
| /* |
| * If the size of the image isn't available |
| */ |
| if (-1 == contentSize) { |
| |
| // Allocates a temporary buffer |
| byte[] tempBuffer = new byte[READ_SIZE]; |
| |
| // Records the initial amount of available space |
| int bufferLeft = tempBuffer.length; |
| |
| /* |
| * Defines the initial offset of the next available |
| * byte in the buffer, and the initial result of |
| * reading the binary |
| */ |
| int bufferOffset = 0; |
| int readResult = 0; |
| |
| /* |
| * The "outer" loop continues until all the bytes |
| * have been downloaded. The inner loop continues |
| * until the temporary buffer is full, and then |
| * allocates more buffer space. |
| */ |
| outer: do { |
| while (bufferLeft > 0) { |
| |
| /* |
| * Reads from the URL location into |
| * the temporary buffer, starting at the |
| * next available free byte and reading as |
| * many bytes as are available in the |
| * buffer. |
| */ |
| readResult = byteStream.read(tempBuffer, bufferOffset, |
| bufferLeft); |
| |
| /* |
| * InputStream.read() returns zero when the |
| * file has been completely read. |
| */ |
| if (readResult < 0) { |
| // The read is finished, so this breaks |
| // the to "outer" loop |
| break outer; |
| } |
| |
| /* |
| * The read isn't finished. This sets the |
| * next available open position in the |
| * buffer (the buffer index is 0-based). |
| */ |
| bufferOffset += readResult; |
| |
| // Subtracts the number of bytes read from |
| // the amount of buffer left |
| bufferLeft -= readResult; |
| |
| if (Thread.interrupted()) { |
| |
| throw new InterruptedException(); |
| } |
| } |
| /* |
| * The temporary buffer is full, so the |
| * following code creates a new buffer that can |
| * contain the existing contents plus the next |
| * read cycle. |
| */ |
| |
| // Resets the amount of buffer left to be the |
| // max buffer size |
| bufferLeft = READ_SIZE; |
| |
| /* |
| * Sets a new size that can contain the existing |
| * buffer's contents plus space for the next |
| * read cycle. |
| */ |
| int newSize = tempBuffer.length + READ_SIZE; |
| |
| /* |
| * Creates a new temporary buffer, moves the |
| * contents of the old temporary buffer into it, |
| * and then points the temporary buffer variable |
| * to the new buffer. |
| */ |
| byte[] expandedBuffer = new byte[newSize]; |
| System.arraycopy(tempBuffer, 0, expandedBuffer, 0, |
| tempBuffer.length); |
| tempBuffer = expandedBuffer; |
| } while (true); |
| |
| /* |
| * When the entire image has been read, this creates |
| * a permanent byte buffer with the same size as |
| * the number of used bytes in the temporary buffer |
| * (equal to the next open byte, because tempBuffer |
| * is 0=based). |
| */ |
| byteBuffer = new byte[bufferOffset]; |
| |
| // Copies the temporary buffer to the image buffer |
| System.arraycopy(tempBuffer, 0, byteBuffer, 0, bufferOffset); |
| |
| /* |
| * The download size is available, so this creates a |
| * permanent buffer of that length. |
| */ |
| } else { |
| byteBuffer = new byte[contentSize]; |
| |
| // How much of the buffer still remains empty |
| int remainingLength = contentSize; |
| |
| // The next open space in the buffer |
| int bufferOffset = 0; |
| |
| /* |
| * Reads into the buffer until the number of bytes |
| * equal to the length of the buffer (the size of |
| * the image) have been read. |
| */ |
| while (remainingLength > 0) { |
| int readResult = byteStream.read( |
| byteBuffer, |
| bufferOffset, |
| remainingLength); |
| /* |
| * EOF should not occur, because the loop should |
| * read the exact # of bytes in the image |
| */ |
| if (readResult < 0) { |
| |
| // Throws an EOF Exception |
| throw new EOFException(); |
| } |
| |
| // Moves the buffer offset to the next open byte |
| bufferOffset += readResult; |
| |
| // Subtracts the # of bytes read from the |
| // remaining length |
| remainingLength -= readResult; |
| |
| if (Thread.interrupted()) { |
| |
| throw new InterruptedException(); |
| } |
| } |
| } |
| |
| if (Thread.interrupted()) { |
| |
| throw new InterruptedException(); |
| } |
| |
| // If an IO error occurs, returns immediately |
| } catch (IOException e) { |
| e.printStackTrace(); |
| return; |
| |
| /* |
| * If the input stream is still open, close it |
| */ |
| } finally { |
| if (null != byteStream) { |
| try { |
| byteStream.close(); |
| } catch (Exception e) { |
| |
| } |
| } |
| } |
| } |
| |
| /* |
| * Stores the downloaded bytes in the byte buffer in the PhotoTask instance. |
| */ |
| mPhotoTask.setByteBuffer(byteBuffer); |
| |
| /* |
| * Sets the status message in the PhotoTask instance. This sets the |
| * ImageView background to indicate that the image is being |
| * decoded. |
| */ |
| mPhotoTask.handleDownloadState(HTTP_STATE_COMPLETED); |
| |
| // Catches exceptions thrown in response to a queued interrupt |
| } catch (InterruptedException e1) { |
| |
| // Does nothing |
| |
| // In all cases, handle the results |
| } finally { |
| |
| // If the byteBuffer is null, reports that the download failed. |
| if (null == byteBuffer) { |
| mPhotoTask.handleDownloadState(HTTP_STATE_FAILED); |
| } |
| |
| /* |
| * The implementation of setHTTPDownloadThread() in PhotoTask calls |
| * PhotoTask.setCurrentThread(), which then locks on the static ThreadPool |
| * object and returns the current thread. Locking keeps all references to Thread |
| * objects the same until the reference to the current Thread is deleted. |
| */ |
| |
| // Sets the reference to the current Thread to null, releasing its storage |
| mPhotoTask.setDownloadThread(null); |
| |
| // Clears the Thread's interrupt flag |
| Thread.interrupted(); |
| } |
| } |
| } |
| |