/** * * Remove a url from the cache * * @param url * @return The bitmap removed, if any. */ public static Bitmap remove(String url) { new File(getFilenameForUrl(url)).delete(); Drawable drawable = mLiveCache.remove(url); if (drawable instanceof ZombieDrawable) { ZombieDrawable zombie = (ZombieDrawable) drawable; Bitmap ret = zombie.getBitmap(); zombie.headshot(); return ret; } return null; }
/** * Download and shrink an Image located at a specified URL, and display it in the provided {@link * ImageView}. * * @param context A {@link Context} to allow setUrlDrawable to load and save files. * @param imageView The {@link ImageView} to display the image to after it is loaded. * @param url The URL of the image that should be loaded. * @param defaultDrawable A {@link Drawable} that should be displayed in {@code imageView} while * the image has not been loaded. This image will also be displayed if the image fails to * load. This can be set to {@code null}. * @param cacheDurationMs The length of time, in milliseconds, that this image should be cached * locally. * @param callback An instance of {@link UrlImageViewCallback} that is called when the image * successfully finishes loading. This value can be null. */ private static void setUrlDrawable( final Context context, final ImageView imageView, final String url, final Drawable defaultDrawable, final long cacheDurationMs, final UrlImageViewCallback callback) { cleanup(context); // disassociate this ImageView from any pending downloads if (isNullOrEmpty(url)) { if (imageView != null) { imageView.setImageDrawable(defaultDrawable); } return; } final int tw; final int th; if (mMetrics == null) prepareResources(context); tw = mMetrics.widthPixels; th = mMetrics.heightPixels; final String filename = context.getFileStreamPath(getFilenameForUrl(url)).getAbsolutePath(); final File file = new File(filename); if (mDeadCache == null) { mDeadCache = new UrlLruCache(getHeapSize(context) / 8); } Drawable drawable; final BitmapDrawable bd = mDeadCache.remove(url); if (bd != null) { // this drawable was resurrected, it should not be in the live cache clog("zombie load: " + url); Assert.assertTrue(url, !mAllCache.contains(bd)); drawable = new ZombieDrawable(url, bd); } else { drawable = mLiveCache.get(url); } if (drawable != null) { clog("Cache hit on: " + url); // if the file age is older than the cache duration, force a refresh. // note that the file must exist, otherwise it is using a default. // not checking for file existence would do a network call on every // 404 or failed load. if (file.exists() && !checkCacheDuration(file, cacheDurationMs)) { clog("Cache hit, but file is stale. Forcing reload: " + url); if (drawable instanceof ZombieDrawable) ((ZombieDrawable) drawable).headshot(); drawable = null; } else { clog("Using cached: " + url); } } if (drawable != null) { if (imageView != null) { imageView.setImageDrawable(drawable); } if (callback != null) { callback.onLoaded(imageView, drawable, url, true); } return; } // oh noes, at this point we definitely do not have the file available in memory // let's prepare for an asynchronous load of the image. // null it while it is downloading if (imageView != null) { imageView.setImageDrawable(defaultDrawable); } // since listviews reuse their views, we need to // take note of which url this view is waiting for. // This may change rapidly as the list scrolls or is filtered, etc. clog("Waiting for " + url); if (imageView != null) { mPendingViews.put(imageView, url); } final ArrayList<ImageView> currentDownload = mPendingDownloads.get(url); if (currentDownload != null) { // Also, multiple vies may be waiting for this url. // So, let's maintain a list of these views. // When the url is downloaded, it sets the imagedrawable for // every view in the list. It needs to also validate that // the imageview is still waiting for this url. if (imageView != null) { currentDownload.add(imageView); } return; } final ArrayList<ImageView> downloads = new ArrayList<ImageView>(); if (imageView != null) { downloads.add(imageView); } mPendingDownloads.put(url, downloads); final int targetWidth = tw <= 0 ? Integer.MAX_VALUE : tw; final int targetHeight = th <= 0 ? Integer.MAX_VALUE : th; final Loader loader = new Loader() { @Override public void run() { try { result = loadDrawableFromStream(context, url, filename, targetWidth, targetHeight); } catch (final Exception ex) { } } }; final Runnable completion = new Runnable() { @Override public void run() { Assert.assertEquals(Looper.myLooper(), Looper.getMainLooper()); Drawable usableResult = loader.result; if (usableResult == null) { usableResult = defaultDrawable; } mPendingDownloads.remove(url); mLiveCache.put(url, usableResult); if (callback != null && imageView == null) callback.onLoaded(null, loader.result, url, false); int waitingCount = 0; for (final ImageView iv : downloads) { // validate the url it is waiting for final String pendingUrl = mPendingViews.get(iv); if (!url.equals(pendingUrl)) { clog("Ignoring out of date request to update view for " + url); continue; } waitingCount++; mPendingViews.remove(iv); if (usableResult != null) { // System.out.println(String.format("imageView: %dx%d, // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(), // imageView.getWidth(), imageView.getHeight())); iv.setImageDrawable(usableResult); // System.out.println(String.format("imageView: %dx%d, // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(), // imageView.getWidth(), imageView.getHeight())); if (callback != null && iv == imageView) callback.onLoaded(iv, loader.result, url, false); } } clog("Populated: " + waitingCount); } }; if (file.exists()) { try { if (checkCacheDuration(file, cacheDurationMs)) { clog( "File Cache hit on: " + url + ". " + (System.currentTimeMillis() - file.lastModified()) + "ms old."); final AsyncTask<Void, Void, Void> fileloader = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(final Void... params) { loader.run(); return null; } @Override protected void onPostExecute(final Void result) { completion.run(); } }; executeTask(fileloader); return; } else { clog("File cache has expired. Refreshing."); } } catch (final Exception ex) { } } mDownloader.download(context, url, filename, loader, completion); }
/** * Download and shrink an Image located at a specified URL, and display it in the provided {@link * ImageView}. * * @param context A {@link Context} to allow setUrlDrawable to load and save files. * @param imageView The {@link ImageView} to display the image to after it is loaded. * @param url The URL of the image that should be loaded. * @param defaultDrawable A {@link Drawable} that should be displayed in {@code imageView} while * the image has not been loaded. This image will also be displayed if the image fails to * load. This can be set to {@code null}. * @param cacheDurationMs The length of time, in milliseconds, that this image should be cached * locally. * @param callback An instance of {@link UrlImageViewCallback} that is called when the image * successfully finishes loading. This value can be null. */ private static void setUrlDrawable( final Context context, final ImageView imageView, final String url, final Drawable defaultDrawable, final long cacheDurationMs, final UrlImageViewCallback callback) { assert (Looper.getMainLooper().getThread() == Thread.currentThread()) : "setUrlDrawable and loadUrlDrawable should only be called from the main thread."; cleanup(context); // disassociate this ImageView from any pending downloads if (isNullOrEmpty(url)) { if (imageView != null) { mPendingViews.remove(imageView); imageView.setImageDrawable(defaultDrawable); } return; } final int tw; final int th; if (mMetrics == null) prepareResources(context); tw = mMetrics.widthPixels; th = mMetrics.heightPixels; final String filename = context.getFileStreamPath(getFilenameForUrl(url)).getAbsolutePath(); final File file = new File(filename); // check the dead and live cache to see if we can find this url's bitmap if (mDeadCache == null) { mDeadCache = new LruBitmapCache(getHeapSize(context) / 8); } Drawable drawable = null; Bitmap bitmap = mDeadCache.remove(url); if (bitmap != null) { clog("zombie load: " + url); } else { drawable = mLiveCache.get(url); } // if something was found, verify it was fresh. if (drawable != null || bitmap != null) { clog("Cache hit on: " + url); // if the file age is older than the cache duration, force a refresh. // note that the file must exist, otherwise it is using a default. // not checking for file existence would do a network call on every // 404 or failed load. if (file.exists() && !checkCacheDuration(file, cacheDurationMs)) { clog("Cache hit, but file is stale. Forcing reload: " + url); if (drawable != null && drawable instanceof ZombieDrawable) ((ZombieDrawable) drawable).headshot(); drawable = null; bitmap = null; } else { clog("Using cached: " + url); } } // if the bitmap is fresh, set the imageview if (drawable != null || bitmap != null) { if (imageView != null) { mPendingViews.remove(imageView); if (drawable instanceof ZombieDrawable) drawable = ((ZombieDrawable) drawable).clone(mResources); else if (bitmap != null) drawable = new ZombieDrawable(url, mResources, bitmap); imageView.setImageDrawable(drawable); } // invoke any bitmap callbacks if (callback != null) { // when invoking the callback from cache, check to see if this was // a drawable that was successfully loaded from the filesystem or url. // this will be indicated by it being a ZombieDrawable (ie, something we are managing). // The default drawables will be BitmapDrawables (or whatever else the user passed in). if (bitmap == null && drawable instanceof ZombieDrawable) bitmap = ((ZombieDrawable) drawable).getBitmap(); callback.onLoaded(imageView, bitmap, url, true); } return; } // oh noes, at this point we definitely do not have the file available in memory // let's prepare for an asynchronous load of the image. // null it while it is downloading // since listviews reuse their views, we need to // take note of which url this view is waiting for. // This may change rapidly as the list scrolls or is filtered, etc. clog("Waiting for " + url + " " + imageView); if (imageView != null) { imageView.setImageDrawable(defaultDrawable); mPendingViews.put(imageView, url); } final ArrayList<ImageView> currentDownload = mPendingDownloads.get(url); if (currentDownload != null && currentDownload.size() != 0) { // Also, multiple vies may be waiting for this url. // So, let's maintain a list of these views. // When the url is downloaded, it sets the imagedrawable for // every view in the list. It needs to also validate that // the imageview is still waiting for this url. if (imageView != null) { currentDownload.add(imageView); } return; } final ArrayList<ImageView> downloads = new ArrayList<ImageView>(); if (imageView != null) { downloads.add(imageView); } mPendingDownloads.put(url, downloads); final int targetWidth = tw <= 0 ? Integer.MAX_VALUE : tw; final int targetHeight = th <= 0 ? Integer.MAX_VALUE : th; final Loader loader = new Loader() { @Override public void onDownloadComplete( UrlDownloader downloader, InputStream in, String existingFilename) { try { assert (in == null || existingFilename == null); if (in == null && existingFilename == null) return; String targetFilename = filename; if (in != null) { in = new BufferedInputStream(in, 8192); OutputStream fout = new BufferedOutputStream(new FileOutputStream(filename), 8192); copyStream(in, fout); fout.close(); } else { targetFilename = existingFilename; } result = loadBitmapFromStream(context, url, targetFilename, targetWidth, targetHeight); } catch (final Exception ex) { // always delete busted files when we throw. new File(filename).delete(); if (Constants.LOG_ENABLED) Log.e(Constants.LOGTAG, "Error loading " + url, ex); } finally { // if we're not supposed to cache this thing, delete the temp file. if (downloader != null && !downloader.allowCache()) new File(filename).delete(); } } }; final Runnable completion = new Runnable() { @Override public void run() { assert (Looper.myLooper().equals(Looper.getMainLooper())); Bitmap bitmap = loader.result; Drawable usableResult = null; if (bitmap != null) { usableResult = new ZombieDrawable(url, mResources, bitmap); } if (usableResult == null) { clog("No usable result, defaulting " + url); usableResult = defaultDrawable; mLiveCache.put(url, usableResult); } mPendingDownloads.remove(url); // mLiveCache.put(url, usableResult); if (callback != null && imageView == null) callback.onLoaded(null, loader.result, url, false); int waitingCount = 0; for (final ImageView iv : downloads) { // validate the url it is waiting for final String pendingUrl = mPendingViews.get(iv); if (!url.equals(pendingUrl)) { clog( "Ignoring out of date request to update view for " + url + " " + pendingUrl + " " + iv); continue; } waitingCount++; mPendingViews.remove(iv); if (usableResult != null) { // System.out.println(String.format("imageView: %dx%d, // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(), // imageView.getWidth(), imageView.getHeight())); iv.setImageDrawable(usableResult); // System.out.println(String.format("imageView: %dx%d, // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(), // imageView.getWidth(), imageView.getHeight())); // onLoaded is called with the loader's result (not what is actually used). null // indicates failure. } if (callback != null && iv == imageView) callback.onLoaded(iv, loader.result, url, false); } clog("Populated: " + waitingCount); } }; if (file.exists()) { try { if (checkCacheDuration(file, cacheDurationMs)) { clog( "File Cache hit on: " + url + ". " + (System.currentTimeMillis() - file.lastModified()) + "ms old."); final AsyncTask<Void, Void, Void> fileloader = new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(final Void... params) { loader.onDownloadComplete(null, null, filename); return null; } @Override protected void onPostExecute(final Void result) { completion.run(); } }; executeTask(fileloader); return; } else { clog("File cache has expired. Refreshing."); } } catch (final Exception ex) { } } for (UrlDownloader downloader : mDownloaders) { if (downloader.canDownloadUrl(url)) { try { downloader.download(context, url, filename, loader, completion); } catch (Exception e) { clog("Can't download from url: " + url + " Exception: " + e.getMessage()); mPendingDownloads.remove(url); if (imageView != null) { mPendingViews.remove(imageView); } } return; } } imageView.setImageDrawable(defaultDrawable); }