/* * Copyright (C) 2010 Cyril Mottier (http://www.cyrilmottier.com) * * 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 greendroid.image; import greendroid.util.Config; import greendroid.util.GDUtils; import java.net.URL; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.os.Message; import android.os.Process; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; /** * An ImageLoader asynchronously loads image from a given url. Client may be * notified from the current image loading state using the * {@link ImageLoaderCallback}. * <p> * <em><strong>Note: </strong>You normally don't need to use the {@link ImageLoader} * class directly in your application. You'll generally prefer using an * {@link ImageRequest} that takes care of the entire loading process.</em> * </p> * * @author Cyril Mottier */ public class ImageLoader { private static final String LOG_TAG = ImageLoader.class.getSimpleName(); public static interface ImageLoaderCallback { void onImageLoadingStarted(ImageLoader loader); void onImageLoadingEnded(ImageLoader loader, Bitmap bitmap); void onImageLoadingFailed(ImageLoader loader, Throwable exception); } private static final int ON_START = 0x100; private static final int ON_FAIL = 0x101; private static final int ON_END = 0x102; private static ImageCache sImageCache; private static ExecutorService sExecutor; private static BitmapFactory.Options sDefaultOptions; public ImageLoader(Context context) { if (sImageCache == null) { sImageCache = GDUtils.getImageCache(context); } if (sExecutor == null) { sExecutor = GDUtils.getExecutor(context); } if (sDefaultOptions == null) { sDefaultOptions = new BitmapFactory.Options(); sDefaultOptions.inDither = true; sDefaultOptions.inScaled = true; sDefaultOptions.inDensity = DisplayMetrics.DENSITY_MEDIUM; sDefaultOptions.inTargetDensity = context.getResources().getDisplayMetrics().densityDpi; } } public Future<?> loadImage(String url, ImageLoaderCallback callback) { return loadImage(url, callback, null); } public Future<?> loadImage(String url, ImageLoaderCallback callback, ImageProcessor bitmapProcessor) { return loadImage(url, callback, bitmapProcessor, null); } public Future<?> loadImage(String url, ImageLoaderCallback callback, ImageProcessor bitmapProcessor, BitmapFactory.Options options) { return sExecutor.submit(new ImageFetcher(url, callback, bitmapProcessor, options)); } private class ImageFetcher implements Runnable { private String mUrl; private ImageHandler mHandler; private ImageProcessor mBitmapProcessor; private BitmapFactory.Options mOptions; public ImageFetcher(String url, ImageLoaderCallback callback, ImageProcessor bitmapProcessor, BitmapFactory.Options options) { mUrl = url; mHandler = new ImageHandler(url, callback); mBitmapProcessor = bitmapProcessor; mOptions = options; } public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final Handler h = mHandler; Bitmap bitmap = null; Throwable throwable = null; h.sendMessage(Message.obtain(h, ON_START)); try { if (TextUtils.isEmpty(mUrl)) { throw new Exception("The given URL cannot be null or empty"); } // TODO Cyril: Use a AndroidHttpClient? bitmap = BitmapFactory.decodeStream(new URL(mUrl).openStream(), null, (mOptions == null) ? sDefaultOptions : mOptions); if (mBitmapProcessor != null && bitmap != null) { final Bitmap processedBitmap = mBitmapProcessor.processImage(bitmap); if (processedBitmap != null) { bitmap = processedBitmap; } } } catch (Exception e) { // An error occured while retrieving the image if (Config.GD_ERROR_LOGS_ENABLED) { Log.e(LOG_TAG, "Error while fetching image", e); } throwable = e; } if (bitmap == null) { if (throwable == null) { // Skia returned a null bitmap ... that's usually because // the given url wasn't pointing to a valid image throwable = new Exception("Skia image decoding failed"); } h.sendMessage(Message.obtain(h, ON_FAIL, throwable)); } else { h.sendMessage(Message.obtain(h, ON_END, bitmap)); } } } private class ImageHandler extends Handler { private String mUrl; private ImageLoaderCallback mCallback; private ImageHandler(String url, ImageLoaderCallback callback) { mUrl = url; mCallback = callback; } @Override public void handleMessage(Message msg) { switch (msg.what) { case ON_START: if (mCallback != null) { mCallback.onImageLoadingStarted(ImageLoader.this); } break; case ON_FAIL: if (mCallback != null) { mCallback.onImageLoadingFailed(ImageLoader.this, (Throwable) msg.obj); } break; case ON_END: final Bitmap bitmap = (Bitmap) msg.obj; sImageCache.put(mUrl, bitmap); if (mCallback != null) { mCallback.onImageLoadingEnded(ImageLoader.this, bitmap); } break; default: super.handleMessage(msg); break; } }; } }