sample.multithreading.ImageDownloaderThread.java Source code

Java tutorial

Introduction

Here is the source code for sample.multithreading.ImageDownloaderThread.java

Source

package sample.multithreading;

/*
 * Copyright (C) 2012 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.
 */

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.util.Log;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Downloads images. We have to be careful here, because we take advantage of
 * static class members to implement our ThreadPool. We implement a number of
 * optimizations based upon the number of active cores that may not be
 * appropriate to all circumstances, as that gives us only a small idea of the
 * available hardware resources present.
 * <p>
 * We use a semaphore to limit the number of simultaneous image decodes to the
 * number of available processor cores.
 * <p>
 * We try to handle running out of memory in the most graceful way possible.
 * That doesn't mean that we always run out of memory gracefully.
 */
public class ImageDownloaderThread implements Runnable {
    private static final int DOWNLOAD_FAILED = -1;
    private static final int DOWNLOAD_STARTED = 1;
    private static final int DECODE_QUEUED = 2;
    private static final int DECODE_STARTED = 3;
    private static final int TASK_COMPLETE = 4;

    private static final int IMAGE_CACHE_SIZE = 1024 * 1024 * 4;
    private static final int KEEP_ALIVE_TIME = 1;
    private static final TimeUnit KEEP_ALIVE_TIME_UNIT;
    private static final String LOG_TAG = "IDT";
    private static final int CORE_POOL_SIZE = 8;
    private static final int MAXIMUM_POOL_SIZE = 8;
    private static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    private static final int READ_SIZE = 1024 * 2;
    private static final LruCache<URL, byte[]> sCache;
    private static final Semaphore sCoreAvailable;
    private static final BlockingQueue<Runnable> sPoolWorkQueue;
    private static final ThreadPoolExecutor sThreadPool;
    private static final int NUMBER_OF_DECODE_TRIES = 2;

    final boolean mCache;
    Handler mHandler;
    final WeakReference<ImageDownloaderView> mIVRef;
    final URL mLocation;
    final int mTargetHeight;
    final int mTargetWidth;
    volatile Thread mThread;

    static {
        KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
        sPoolWorkQueue = new LinkedBlockingQueue<Runnable>();
        sThreadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
                KEEP_ALIVE_TIME_UNIT, sPoolWorkQueue);
        sCoreAvailable = new Semaphore(NUMBER_OF_CORES, true);
        sCache = new LruCache<URL, byte[]>(IMAGE_CACHE_SIZE) {
            protected int sizeOf(URL paramURL, byte[] paramArrayOfByte) {
                return paramArrayOfByte.length;
            }
        };
    }

    private ImageDownloaderThread(ImageDownloaderView paramImageDownloaderView, boolean paramBoolean) {
        this.mLocation = paramImageDownloaderView.getLocation();
        this.mIVRef = new WeakReference<ImageDownloaderView>(paramImageDownloaderView);
        this.mCache = paramBoolean;
        this.mTargetWidth = paramImageDownloaderView.getWidth();
        this.mTargetHeight = paramImageDownloaderView.getHeight();
        this.mHandler = new Handler() {
            public void handleMessage(Message paramMessage) {
                ImageDownloaderView localImageDownloaderView = (ImageDownloaderView) ImageDownloaderThread.this.mIVRef
                        .get();
                if (localImageDownloaderView != null) {
                    URL localURL = localImageDownloaderView.getLocation();

                    // Intentionally doing a cheap object compare here.  Only update the bitmap
                    // if this ImageDownloaderThread is supposed to be servicing this ImageDownloaderView.                    
                    if (ImageDownloaderThread.this.mLocation == localURL)
                        switch (paramMessage.what) {
                        case DOWNLOAD_STARTED:
                            localImageDownloaderView.setStatusResource(R.drawable.imagedownloading);
                            break;
                        case DECODE_QUEUED:
                            localImageDownloaderView.setStatusResource(R.drawable.decodequeued);
                            break;
                        case DECODE_STARTED:
                            localImageDownloaderView.setStatusResource(R.drawable.decodedecoding);
                            break;
                        case TASK_COMPLETE:
                            localImageDownloaderView.setImageBitmap((Bitmap) paramMessage.obj);
                            ImageDownloaderThread.this.mIVRef.clear();
                            ImageDownloaderThread.this.mHandler = null;
                            break;
                        case DOWNLOAD_FAILED:
                            localImageDownloaderView.setStatusResource(R.drawable.imagedownloadfailed);
                            ImageDownloaderThread.this.mIVRef.clear();
                            ImageDownloaderThread.this.mHandler = null;
                            break;
                        default:
                            super.handleMessage(paramMessage);
                        }
                }
            }
        };
    }

    public static void cancelAll() {
        ImageDownloaderThread[] arrayOfImageDownloaderThread = new ImageDownloaderThread[sPoolWorkQueue.size()];
        sPoolWorkQueue.toArray(arrayOfImageDownloaderThread);
        int len = arrayOfImageDownloaderThread.length;
        for (int j = 0; j < len; j++) {
            Thread t = arrayOfImageDownloaderThread[j].mThread;
            if (null != t) {
                t.interrupt();
            }
        }
    }

    public static void removeDownload(ImageDownloaderThread paramImageDownloaderThread) {
        if (paramImageDownloaderThread != null) {
            Thread t = paramImageDownloaderThread.mThread;
            if (null != t)
                t.interrupt();
            sThreadPool.remove(paramImageDownloaderThread);
        }
    }

    public static ImageDownloaderThread startDownload(ImageDownloaderView paramImageDownloaderView,
            boolean paramBoolean) {
        ImageDownloaderThread localImageDownloaderThread = new ImageDownloaderThread(paramImageDownloaderView,
                paramBoolean);
        sThreadPool.execute(localImageDownloaderThread);
        paramImageDownloaderView.setStatusResource(R.drawable.imagequeued);
        return localImageDownloaderThread;
    }

    public void run() {
        mThread = Thread.currentThread();
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
        Bitmap bmReturn = null;
        try {
            if (Thread.interrupted())
                return;
            byte[] imageBuf = sCache.get(mLocation);
            if (null == imageBuf) {
                mHandler.sendEmptyMessage(DOWNLOAD_STARTED);
                InputStream is = null;
                try {
                    HttpURLConnection httpConn = (HttpURLConnection) mLocation.openConnection();
                    httpConn.setRequestProperty("User-Agent", NetworkDownloadService.USER_AGENT);
                    if (Thread.interrupted())
                        return;
                    is = httpConn.getInputStream();
                    if (Thread.interrupted())
                        return;
                    int contentLength = httpConn.getContentLength();

                    /*
                     * We take advantage of the content length if it is returned
                     * to preallocate our buffer. If it is not returned, we end
                     * up thrashing memory a bit more.
                     */
                    if (-1 == contentLength) {
                        byte[] buf = new byte[READ_SIZE];
                        int bufferLeft = buf.length;
                        int offset = 0;
                        int result = 0;
                        outer: do {
                            while (bufferLeft > 0) {
                                result = is.read(buf, offset, bufferLeft);
                                if (result < 0) {
                                    // we're done
                                    break outer;
                                }
                                offset += result;
                                bufferLeft -= result;
                                if (Thread.interrupted())
                                    return;
                            }
                            // resize
                            bufferLeft = READ_SIZE;
                            int newSize = buf.length + READ_SIZE;
                            byte[] newBuf = new byte[newSize];
                            System.arraycopy(buf, 0, newBuf, 0, buf.length);
                            buf = newBuf;
                        } while (true);
                        imageBuf = new byte[offset];
                        System.arraycopy(buf, 0, imageBuf, 0, offset);
                    } else {
                        imageBuf = new byte[contentLength];
                        int length = contentLength;
                        int offset = 0;
                        while (length > 0) {
                            int result = is.read(imageBuf, offset, length);
                            if (result < 0) {
                                throw new EOFException();
                            }
                            offset += result;
                            length -= result;
                            if (Thread.interrupted())
                                return;
                        }
                    }
                    if (Thread.interrupted())
                        return;
                } catch (IOException e) {
                    return;
                } finally {
                    if (null != is) {
                        try {
                            is.close();
                        } catch (Exception e) {

                        }
                    }
                }
            }
            mHandler.sendEmptyMessage(DECODE_QUEUED);
            try {
                sCoreAvailable.acquire();
                mHandler.sendEmptyMessage(DECODE_STARTED);
                BitmapFactory.Options bfo = new BitmapFactory.Options();
                int targetWidth = mTargetWidth;
                int targetHeight = mTargetHeight;
                if (Thread.interrupted())
                    return;
                bfo.inJustDecodeBounds = true;
                BitmapFactory.decodeByteArray(imageBuf, 0, imageBuf.length, bfo);
                int hScale = bfo.outHeight / targetHeight;
                int wScale = bfo.outWidth / targetWidth;
                int sampleSize = Math.max(hScale, wScale);
                if (sampleSize > 1) {
                    bfo.inSampleSize = sampleSize;
                }
                if (Thread.interrupted())
                    return;
                bfo.inJustDecodeBounds = false;
                // oom handling in decode stage
                for (int i = 0; i < NUMBER_OF_DECODE_TRIES; i++) {
                    try {
                        bmReturn = BitmapFactory.decodeByteArray(imageBuf, 0, imageBuf.length, bfo);
                        // break out of OOM loop
                        break;
                    } catch (Throwable e) {
                        Log.e(LOG_TAG, "Out of memory in decode stage. Throttling.");
                        java.lang.System.gc();
                        if (Thread.interrupted())
                            return;
                        try {
                            Thread.sleep(0xfa);
                        } catch (java.lang.InterruptedException ix) {

                        }
                    }
                }
                if (mCache) {
                    sCache.put(mLocation, imageBuf);
                }
            } catch (java.lang.InterruptedException x) {
                x.printStackTrace();
            } finally {
                sCoreAvailable.release();
            }
        } finally {
            mThread = null;
            if (null == bmReturn) {
                mHandler.sendEmptyMessage(DOWNLOAD_FAILED);
            } else {
                Message completeMessage = mHandler.obtainMessage(TASK_COMPLETE, bmReturn);
                completeMessage.sendToTarget();
            }
            // clear interrupt flag
            Thread.interrupted();
        }
    }
}