I try to understand this sample to match with mine:(DisplayingBitmaps)
http://developer.android.com/downloads/samples/DisplayingBitmaps.zip
This code uses the class ImageCache that manages the caching of bitmaps:
public class ImageCache {
private static final String TAG = "ImageCache";
// Default memory cache size in kilobytes
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB
// Default disk cache size in bytes
private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
// Compression settings when writing images to disk cache
private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
private static final int DEFAULT_COMPRESS_QUALITY = 70;
private static final int DISK_CACHE_INDEX = 0;
// Constants to easily toggle various caches
private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
private DiskLruCache mDiskLruCache;
private LruCache<String, BitmapDrawable> mMemoryCache;
public static ImageCacheParams mCacheParams;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private Set<SoftReference<Bitmap>> mReusableBitmaps;
/**
* Create a new ImageCache object using the specified parameters. This should not be
* called directly by other classes, instead use
* {#link ImageCache#getInstance(FragmentManager, ImageCacheParams)} to fetch an ImageCache
* instance.
*
* #param cacheParams The cache parameters to use to initialize the cache
*/
public ImageCache(ImageCacheParams cacheParams) {
init(cacheParams);
}
/**
* Return an {#link ImageCache} instance. A {#link RetainFragment} is used to retain the
* ImageCache object across configuration changes such as a change in device orientation.
*
* #param fragmentManager The fragment manager to use when dealing with the retained fragment.
* #param cacheParams The cache parameters to use if the ImageCache needs instantiation.
* #return An existing retained ImageCache object or a new one if one did not exist
*/
public static ImageCache getInstance(
FragmentManager fragmentManager, ImageCacheParams cacheParams) {
// Search for, or create an instance of the non-UI RetainFragment
final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
// See if we already have an ImageCache stored in RetainFragment
ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
// No existing ImageCache, create one and store it in RetainFragment
if (imageCache == null) {
imageCache = new ImageCache(cacheParams);
mRetainFragment.setObject(imageCache);
}
return imageCache;
}
/**
* Initialize the cache, providing all parameters.
*
* #param cacheParams The cache parameters to initialize the cache
*/
public void init(ImageCacheParams cacheParams) {
mCacheParams = cacheParams;
//BEGIN_INCLUDE(init_memory_cache)
// Set up memory cache
if (mCacheParams.memoryCacheEnabled) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
}
// If we're running on Honeycomb or newer, create a set of reusable bitmaps that can be
// populated into the inBitmap field of BitmapFactory.Options. Note that the set is
// of SoftReferences which will actually not be very effective due to the garbage
// collector being aggressive clearing Soft/WeakReferences. A better approach
// would be to use a strongly references bitmaps, however this would require some
// balancing of memory usage between this set and the bitmap LruCache. It would also
// require knowledge of the expected size of the bitmaps. From Honeycomb to JellyBean
// the size would need to be precise, from KitKat onward the size would just need to
// be the upper bound (due to changes in how inBitmap can re-use bitmaps).
if (Utils.hasHoneycomb()) {
mReusableBitmaps =
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
/**
* Notify the removed entry that is no longer being cached
*/
#Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later
mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
/**
* Measure item size in kilobytes rather than units which is more practical
* for a bitmap cache
*/
#Override
protected int sizeOf(String key, BitmapDrawable value) {
final int bitmapSize = getBitmapSize(value) / 1024;
return bitmapSize == 0 ? 1 : bitmapSize;
}
};
}
//END_INCLUDE(init_memory_cache)
// By default the disk cache is not initialized here as it should be initialized
// on a separate thread due to disk access.
if (cacheParams.initDiskCacheOnCreate) {
// Set up disk cache
initDiskCache();
}
}
/**
* Initializes the disk cache. Note that this includes disk access so this should not be
* executed on the main/UI thread. By default an ImageCache does not initialize the disk
* cache when it is created, instead you should call initDiskCache() to initialize it on a
* background thread.
*/
public void initDiskCache() {
// Set up disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
File diskCacheDir = mCacheParams.diskCacheDir;
if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {
try {
mDiskLruCache = DiskLruCache.open(
diskCacheDir, 1, 1, mCacheParams.diskCacheSize);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache initialized");
}
} catch (final IOException e) {
mCacheParams.diskCacheDir = null;
Log.e(TAG, "initDiskCache - " + e);
}
}
}
}
mDiskCacheStarting = false;
mDiskCacheLock.notifyAll();
}
}
public void addBitmapToCache(String data, BitmapDrawable value) {
//BEGIN_INCLUDE(add_bitmap_to_cache)
if (data == null || value == null) {
return;
}
// Add to memory cache
if (mMemoryCache != null) {
if (RecyclingBitmapDrawable.class.isInstance(value)) {
// The removed entry is a recycling drawable, so notify it
// that it has been added into the memory cache
((RecyclingBitmapDrawable) value).setIsCached(true);
}
mMemoryCache.put(data, value);
}
synchronized (mDiskCacheLock) {
// Add to disk cache
if (mDiskLruCache != null) {
final String key = hashKeyForDisk(data);
OutputStream out = null;
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot == null) {
final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
out = editor.newOutputStream(DISK_CACHE_INDEX);
value.getBitmap().compress(
mCacheParams.compressFormat, mCacheParams.compressQuality, out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (final IOException e) {
Log.e(TAG, "addBitmapToCache - " + e);
} catch (Exception e) {
Log.e(TAG, "addBitmapToCache - " + e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {}
}
}
}
//END_INCLUDE(add_bitmap_to_cache)
}
public BitmapDrawable getBitmapFromMemCache(String data) {
//BEGIN_INCLUDE(get_bitmap_from_mem_cache)
BitmapDrawable memValue = null;
if (mMemoryCache != null) {
memValue = mMemoryCache.get(data);
}
if (BuildConfig.DEBUG && memValue != null) {
Log.d(TAG, "Memory cache hit");
}
return memValue;
//END_INCLUDE(get_bitmap_from_mem_cache)
}
public Bitmap getBitmapFromDiskCache(String data) {
//BEGIN_INCLUDE(get_bitmap_from_disk_cache)
final String key = hashKeyForDisk(data);
Bitmap bitmap = null;
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
InputStream inputStream = null;
try {
final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache hit");
}
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (inputStream != null) {
FileDescriptor fd = ((FileInputStream) inputStream).getFD();
// Decode bitmap, but we don't want to sample so give
// MAX_VALUE as the target dimensions
bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
}
}
} catch (final IOException e) {
Log.e(TAG, "getBitmapFromDiskCache - " + e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {}
}
}
return bitmap;
}
//END_INCLUDE(get_bitmap_from_disk_cache)
}
/**
* #param options - BitmapFactory.Options with out* options populated
* #return Bitmap that case be used for inBitmap
*/
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
//BEGIN_INCLUDE(get_bitmap_from_reusable_set)
Bitmap bitmap = null;
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
synchronized (mReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (null != item && item.isMutable()) {
// Check to see it the item can be used for inBitmap
if (canUseForInBitmap(item, options)) {
bitmap = item;
// Remove from reusable set so it can't be used again
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
}
return bitmap;
//END_INCLUDE(get_bitmap_from_reusable_set)
}
/**
* Clears both the memory and disk cache associated with this ImageCache object. Note that
* this includes disk access so this should not be executed on the main/UI thread.
*/
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Memory cache cleared");
}
}
synchronized (mDiskCacheLock) {
mDiskCacheStarting = true;
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.delete();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache cleared");
}
} catch (IOException e) {
Log.e(TAG, "clearCache - " + e);
}
mDiskLruCache = null;
initDiskCache();
}
}
}
/**
* Flushes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void flush() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache flushed");
}
} catch (IOException e) {
Log.e(TAG, "flush - " + e);
}
}
}
}
/**
* Closes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void close() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
if (!mDiskLruCache.isClosed()) {
mDiskLruCache.close();
mDiskLruCache = null;
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache closed");
}
}
} catch (IOException e) {
Log.e(TAG, "close - " + e);
}
}
}
}
/**
* A holder class that contains cache parameters.
*/
public static class ImageCacheParams {
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
public File diskCacheDir;
public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
public int compressQuality = DEFAULT_COMPRESS_QUALITY;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
/**
* Create a set of image cache parameters that can be provided to
* {#link ImageCache#getInstance(FragmentManager, ImageCacheParams)} or
* {#link ImageWorker#addImageCache(FragmentManager, ImageCacheParams)}.
* #param context A context to use.
* #param diskCacheDirectoryName A unique subdirectory name that will be appended to the
* application cache directory. Usually "cache" or "images"
* is sufficient.
*/
public ImageCacheParams(Context context, String diskCacheDirectoryName) {
diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);
}
/**
* Sets the memory cache size based on a percentage of the max available VM memory.
* Eg. setting percent to 0.2 would set the memory cache to one fifth of the available
* memory. Throws {#link IllegalArgumentException} if percent is < 0.01 or > .8.
* memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed
* to construct a LruCache which takes an int in its constructor.
*
* This value should be chosen carefully based on a number of factors
* Refer to the corresponding Android Training class for more discussion:
* http://developer.android.com/training/displaying-bitmaps/
*
* #param percent Percent of available app memory to use to size memory cache
*/
public void setMemCacheSizePercent(float percent) {
if (percent < 0.01f || percent > 0.8f) {
throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+ "between 0.01 and 0.8 (inclusive)");
}
memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);
}
}
/**
* #param candidate - Bitmap to check
* #param targetOptions - Options that have the out* value populated
* #return true if <code>candidate</code> can be used for inBitmap re-use with
* <code>targetOptions</code>
*/
#TargetApi(VERSION_CODES.KITKAT)
private static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
//BEGIN_INCLUDE(can_use_for_inbitmap)
if (!Utils.hasKitKat()) {
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
// From Android 4.4 (KitKat) onward we can re-use if the byte size of the new bitmap
// is smaller than the reusable bitmap candidate allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
//END_INCLUDE(can_use_for_inbitmap)
}
/**
* Return the byte usage per pixel of a bitmap based on its configuration.
* #param config The bitmap configuration.
* #return The byte usage per pixel.
*/
private static int getBytesPerPixel(Config config) {
if (config == Config.ARGB_8888) {
return 4;
} else if (config == Config.RGB_565) {
return 2;
} else if (config == Config.ARGB_4444) {
return 2;
} else if (config == Config.ALPHA_8) {
return 1;
}
return 1;
}
/**
* Get a usable cache directory (external if available, internal otherwise).
*
* #param context The context to use
* #param uniqueName A unique directory name to append to the cache dir
* #return The cache dir
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable for using as a
* disk filename.
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
// http://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* Get the size in bytes of a bitmap in a BitmapDrawable. Note that from Android 4.4 (KitKat)
* onward this returns the allocated memory size of the bitmap which can be larger than the
* actual bitmap data byte count (in the case it was re-used).
*
* #param value
* #return size in bytes
*/
#TargetApi(VERSION_CODES.KITKAT)
public static int getBitmapSize(BitmapDrawable value) {
Bitmap bitmap = value.getBitmap();
// From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be
// larger than bitmap byte count.
if (Utils.hasKitKat()) {
return bitmap.getAllocationByteCount();
}
if (Utils.hasHoneycombMR1()) {
return bitmap.getByteCount();
}
// Pre HC-MR1
return bitmap.getRowBytes() * bitmap.getHeight();
}
/**
* Check if external storage is built-in or removable.
*
* #return True if external storage is removable (like an SD card), false
* otherwise.
*/
#TargetApi(VERSION_CODES.GINGERBREAD)
public static boolean isExternalStorageRemovable() {
if (Utils.hasGingerbread()) {
return Environment.isExternalStorageRemovable();
}
return true;
}
/**
* Get the external app cache directory.
*
* #param context The context to use
* #return The external cache dir
*/
#TargetApi(VERSION_CODES.FROYO)
public static File getExternalCacheDir(Context context) {
if (Utils.hasFroyo()) {
return context.getExternalCacheDir();
}
// Before Froyo we need to construct the external cache dir ourselves
final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
}
/**
* Check how much usable space is available at a given path.
*
* #param path The path to check
* #return The space available in bytes
*/
#TargetApi(VERSION_CODES.GINGERBREAD)
public static long getUsableSpace(File path) {
if (Utils.hasGingerbread()) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
/**
* Locate an existing instance of this Fragment or if not found, create and
* add it using FragmentManager.
*
* #param fm The FragmentManager manager to use.
* #return The existing instance of the Fragment or the new instance if just
* created.
*/
private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
//BEGIN_INCLUDE(find_create_retain_fragment)
// Check to see if we have retained the worker fragment.
RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
// If not retained (or first time running), we need to create and add it.
if (mRetainFragment == null) {
mRetainFragment = new RetainFragment();
fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
}
return mRetainFragment;
//END_INCLUDE(find_create_retain_fragment)
}
/**
* A simple non-UI Fragment that stores a single Object and is retained over configuration
* changes. It will be used to retain the ImageCache object.
*/
public static class RetainFragment extends Fragment {
private Object mObject;
/**
* Empty constructor as per the Fragment documentation
*/
public RetainFragment() {}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make sure this Fragment is retained over a configuration change
setRetainInstance(true);
}
/**
* Store a single object in this Fragment.
*
* #param object The object to store
*/
public void setObject(Object object) {
mObject = object;
}
/**
* Get the stored object.
*
* #return The stored object
*/
public Object getObject() {
return mObject;
}
}
}
In the sample, the cache (LRUCache) is initialized with the following code from a fragment (ImageGridFragment):
ImageCache.ImageCacheParams cacheParams =
new ImageCache.ImageCacheParams(getActivity(), IMAGE_CACHE_DIR);
cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory
// The ImageFetcher takes care of loading images into our ImageView children asynchronously
mImageFetcher = new ImageFetcher(getActivity(), mImageThumbSize);
mImageFetcher.setLoadingImage(R.drawable.empty_photo);
mImageFetcher.addImageCache(getActivity().getSupportFragmentManager(), cacheParams);// <-- here we inititialise cache by method ImageWorker with the fragment context in parameter ?
The methods of ImageWorker that initialize the cache in ImageCache with a fragment context:
/**
* Adds an {#link ImageCache} to this {#link ImageWorker} to handle disk and memory bitmap
* caching.
* #param fragmentManager
* #param cacheParams The cache parameters to use for the image cache.
*/
public void addImageCache(FragmentManager fragmentManager,
ImageCache.ImageCacheParams cacheParams) {
mImageCacheParams = cacheParams;
mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
}
/**
* Adds an {#link ImageCache} to this {#link ImageWorker} to handle disk and memory bitmap
* caching.
* #param activity
* #param diskCacheDirectoryName See
* {#link ImageCache.ImageCacheParams#ImageCacheParams(android.content.Context, String)}.
*/
public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {
mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);
mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
}
How to "instantiate" and initialize the cahe from an activity context with this class?
Thanks
Related
I need to write to a file which is in the assets folder. I must use
context.getResources().getAssets()
but when I use the open(path_to_my_txt) I get an InputStream, and I have found nothing nice to convert it to an OutputStream
How can I write to this file using the getResources method ?
this below class is taken from Unviersal imageloader libraray
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Provides I/O operations
*
* #author Sergey Tarasevich (nostra13[at]gmail[dot]com)
* #since 1.0.0
*/
public final class IoUtils {
/** {#value} */
public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 KB
/** {#value} */
public static final int DEFAULT_IMAGE_TOTAL_SIZE = 500 * 1024; // 500 Kb
/** {#value} */
public static final int CONTINUE_LOADING_PERCENTAGE = 75;
private IoUtils() {
}
/**
* Copies stream, fires progress events by listener, can be interrupted by
* listener. Uses buffer size = {#value #DEFAULT_BUFFER_SIZE} bytes.
*
* #param is
* Input stream
* #param os
* Output stream
* #param listener
* null-ok; Listener of copying progress and controller of
* copying interrupting
* #return <b>true</b> - if stream copied successfully; <b>false</b> - if
* copying was interrupted by listener
* #throws IOException
*/
public static boolean copyStream(InputStream is, OutputStream os,
CopyListener listener) throws IOException {
return copyStream(is, os, listener, DEFAULT_BUFFER_SIZE);
}
/**
* Copies stream, fires progress events by listener, can be interrupted by
* listener.
*
* #param is
* Input stream
* #param os
* Output stream
* #param listener
* null-ok; Listener of copying progress and controller of
* copying interrupting
* #param bufferSize
* Buffer size for copying, also represents a step for firing
* progress listener callback, i.e. progress event will be fired
* after every copied <b>bufferSize</b> bytes
* #return <b>true</b> - if stream copied successfully; <b>false</b> - if
* copying was interrupted by listener
* #throws IOException
*/
public static boolean copyStream(InputStream is, OutputStream os,
CopyListener listener, int bufferSize) throws IOException {
int current = 0;
int total = is.available();
if (total <= 0) {
total = DEFAULT_IMAGE_TOTAL_SIZE;
}
final byte[] bytes = new byte[bufferSize];
int count;
if (shouldStopLoading(listener, current, total))
return false;
while ((count = is.read(bytes, 0, bufferSize)) != -1) {
os.write(bytes, 0, count);
current += count;
if (shouldStopLoading(listener, current, total))
return false;
}
os.flush();
return true;
}
private static boolean shouldStopLoading(CopyListener listener,
int current, int total) {
if (listener != null) {
boolean shouldContinue = listener.onBytesCopied(current, total);
if (!shouldContinue) {
if (100 * current / total < CONTINUE_LOADING_PERCENTAGE) {
return true; // if loaded more than 75% then continue
// loading anyway
}
}
}
return false;
}
/**
* Reads all data from stream and close it silently
*
* #param is
* Input stream
*/
public static void readAndCloseStream(InputStream is) {
final byte[] bytes = new byte[DEFAULT_BUFFER_SIZE];
try {
while (is.read(bytes, 0, DEFAULT_BUFFER_SIZE) != -1)
;
} catch (IOException e) {
// Do nothing
} finally {
closeSilently(is);
}
}
public static void closeSilently(Closeable closeable) {
try {
closeable.close();
} catch (Exception e) {
// Do nothing
}
}
/** Listener and controller for copy process */
public static interface CopyListener {
/**
* #param current
* Loaded bytes
* #param total
* Total bytes for loading
* #return <b>true</b> - if copying should be continued; <b>false</b> -
* if copying should be interrupted
*/
boolean onBytesCopied(int current, int total);
}
}
AssetManager assetManager = getAssets();
InputStream is = null;
try {
is = assetManager.open("filename");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
OutputStream os = null;
IoUtils.copyStream(is, os);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
you can't write to a file in assets fold.But you can write it to file system.
OnClicking an Item in A ListView Fragment, Control passes to Detail Activity, which downloads the data related to this item and sets the appropriate adapters for the viewpagers in this activity.Within this activity, if a user clicks on a image, a FullScreen Activity starts which shows this particular image in fullscreen.
Now My problem is: when user leaves this FullScreen Activity by either tapping Close Icon or Back Button Traversing, How do i reuse the Detail Activity Data(Downloaded earlier for this item) instead of downloading it again.
Solution Should also work for scenario when user clicks a new item on Detail Activity,Data Downloading should begin for this item.
Don't want to use SQLLite, FileSystem for storage. I am ok with downloading data when a item is clicked in a ListView but not when user traverses back from the fullscreen activity
You need to cache the Data so then you don't download the same. There are storage options other then sqlite.
Its upto you to decide who you implement the cache
http://developer.android.com/guide/topics/data/data-storage.html
Here's the implementation
public class ImageCache {
private static final String TAG = "ImageCache";
// Default memory cache size
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 1024 * 5; // 5MB
// Default disk cache size
private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
// Compression settings when writing images to disk cache
private static final CompressFormat DEFAULT_COMPRESS_FORMAT = CompressFormat.JPEG;
private static final int DEFAULT_COMPRESS_QUALITY = 70;
private static final int DISK_CACHE_INDEX = 0;
// Constants to easily toggle various caches
private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
private static final boolean DEFAULT_DISK_CACHE_ENABLED = true;
private static final boolean DEFAULT_CLEAR_DISK_CACHE_ON_START = false;
private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
private DiskLruCache mDiskLruCache;
private LruCache<String, Bitmap> mMemoryCache;
private ImageCacheParams mCacheParams;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
/**
* Creating a new ImageCache object using the specified parameters.
*
* #param cacheParams The cache parameters to use to initialize the cache
*/
public ImageCache(ImageCacheParams cacheParams) {
init(cacheParams);
}
/**
* Creating a new ImageCache object using the default parameters.
*
* #param context The context to use
* #param uniqueName A unique name that will be appended to the cache directory
*/
public ImageCache(Context context, String uniqueName) {
init(new ImageCacheParams(context, uniqueName));
}
/**
* Find and return an existing ImageCache stored in a {#link RetainFragment}, if not found a new
* one is created using the supplied params and saved to a {#link RetainFragment}.
*
* #param fragmentManager The fragment manager to use when dealing with the retained fragment.
* #param cacheParams The cache parameters to use if creating the ImageCache
* #return An existing retained ImageCache object or a new one if one did not exist
*/
public static ImageCache findOrCreateCache(
FragmentManager fragmentManager, ImageCacheParams cacheParams) {
// Search for, or create an instance of the non-UI RetainFragment
final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
// See if we already have an ImageCache stored in RetainFragment
ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
// No existing ImageCache, create one and store it in RetainFragment
if (imageCache == null) {
imageCache = new ImageCache(cacheParams);
mRetainFragment.setObject(imageCache);
}
return imageCache;
}
/**
* Initialize the cache, providing all parameters.
*
* #param cacheParams The cache parameters to initialize the cache
*/
private void init(ImageCacheParams cacheParams) {
mCacheParams = cacheParams;
// Set up memory cache
if (mCacheParams.memoryCacheEnabled) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")");
}
mMemoryCache = new LruCache<String, Bitmap>(mCacheParams.memCacheSize) {
/**
* Measure item size in bytes rather than units which is more practical
* for a bitmap cache
*/
#Override
protected int sizeOf(String key, Bitmap bitmap) {
return getBitmapSize(bitmap);
}
};
}
// By default the disk cache is not initialized here as it should be initialized
// on a separate thread due to disk access.
if (cacheParams.initDiskCacheOnCreate) {
// Set up disk cache
initDiskCache();
}
}
/**
* Initializes the disk cache. Note that this includes disk access so this should not be
* executed on the main/UI thread. By default an ImageCache does not initialize the disk
* cache when it is created, instead you should call initDiskCache() to initialize it on a
* background thread.
*/
public void initDiskCache() {
// Set up disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
File diskCacheDir = mCacheParams.diskCacheDir;
if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {
try {
mDiskLruCache = DiskLruCache.open(
diskCacheDir, 1, 1, mCacheParams.diskCacheSize);
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache initialized");
}
} catch (final IOException e) {
mCacheParams.diskCacheDir = null;
Log.e(TAG, "initDiskCache - " + e);
}
}
}
}
mDiskCacheStarting = false;
mDiskCacheLock.notifyAll();
}
}
/**
* Adds a bitmap to both memory and disk cache.
* #param data Unique identifier for the bitmap to store
* #param bitmap The bitmap to store
*/
public void addBitmapToCache(String data, Bitmap bitmap) {
if (data == null || bitmap == null) {
return;
}
// Add to memory cache
if (mMemoryCache != null && mMemoryCache.get(data) == null) {
mMemoryCache.put(data, bitmap);
}
synchronized (mDiskCacheLock) {
// Add to disk cache
if (mDiskLruCache != null) {
final String key = hashKeyForDisk(data);
OutputStream out = null;
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot == null) {
final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
out = editor.newOutputStream(DISK_CACHE_INDEX);
bitmap.compress(
mCacheParams.compressFormat, mCacheParams.compressQuality, out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (final IOException e) {
Log.e(TAG, "addBitmapToCache - " + e);
} catch (Exception e) {
Log.e(TAG, "addBitmapToCache - " + e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {}
}
}
}
}
/**
* Get from memory cache.
*
* #param data Unique identifier for which item to get
* #return The bitmap if found in cache, null otherwise
*/
public Bitmap getBitmapFromMemCache(String data) {
if (mMemoryCache != null) {
final Bitmap memBitmap = mMemoryCache.get(data);
if (memBitmap != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Memory cache hit");
}
return memBitmap;
}
}
return null;
}
/**
* Get from disk cache.
*
* #param data Unique identifier for which item to get
* #return The bitmap if found in cache, null otherwise
*/
public Bitmap getBitmapFromDiskCache(String data) {
final String key = hashKeyForDisk(data);
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
InputStream inputStream = null;
try {
final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache hit");
}
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (inputStream != null) {
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
}
} catch (final IOException e) {
Log.e(TAG, "getBitmapFromDiskCache - " + e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {}
}
}
return null;
}
}
/**
* Clears both the memory and disk cache associated with this ImageCache object. Note that
* this includes disk access so this should not be executed on the main/UI thread.
*/
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Memory cache cleared");
}
}
synchronized (mDiskCacheLock) {
mDiskCacheStarting = true;
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.delete();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache cleared");
}
} catch (IOException e) {
Log.e(TAG, "clearCache - " + e);
}
mDiskLruCache = null;
initDiskCache();
}
}
}
/**
* Flushes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void flush() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache flushed");
}
} catch (IOException e) {
Log.e(TAG, "flush - " + e);
}
}
}
}
/**
* Closes the disk cache associated with this ImageCache object. Note that this includes
* disk access so this should not be executed on the main/UI thread.
*/
public void close() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
if (!mDiskLruCache.isClosed()) {
mDiskLruCache.close();
mDiskLruCache = null;
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache closed");
}
}
} catch (IOException e) {
Log.e(TAG, "close - " + e);
}
}
}
}
/**
* A holder class that contains cache parameters.
*/
public static class ImageCacheParams {
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
public File diskCacheDir;
public CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;
public int compressQuality = DEFAULT_COMPRESS_QUALITY;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
public boolean clearDiskCacheOnStart = DEFAULT_CLEAR_DISK_CACHE_ON_START;
public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
public ImageCacheParams(Context context, String uniqueName) {
diskCacheDir = getDiskCacheDir(context, uniqueName);
}
public ImageCacheParams(File diskCacheDir) {
this.diskCacheDir = diskCacheDir;
}
/**
* Sets the memory cache size based on a percentage of the device memory class.
* Eg. setting percent to 0.2 would set the memory cache to one fifth of the device memory
* class. Throws {#link IllegalArgumentException} if percent is < 0.05 or > .8.
*
* This value should be chosen carefully based on a number of factors
* Refer to the corresponding Android Training class for more discussion:
* http://developer.android.com/training/displaying-bitmaps/
*
* #param context Context to use to fetch memory class
* #param percent Percent of memory class to use to size memory cache
*/
public void setMemCacheSizePercent(Context context, float percent) {
if (percent < 0.05f || percent > 0.8f) {
throw new IllegalArgumentException("setMemCacheSizePercent - percent must be "
+ "between 0.05 and 0.8 (inclusive)");
}
memCacheSize = Math.round(percent * getMemoryClass(context) * 1024 * 1024);
}
private static int getMemoryClass(Context context) {
return ((ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE)).getMemoryClass();
}
}
/**
* Get a usable cache directory (external if available, internal otherwise).
*
* #param context The context to use
* #param uniqueName A unique directory name to append to the cache dir
* #return The cache dir
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
}
/**
* A hashing method that changes a string (like a URL) into a hash suitable for using as a
* disk filename.
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
// https://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* Get the size in bytes of a bitmap.
* #param bitmap
* #return size in bytes
*/
#TargetApi(12)
public static int getBitmapSize(Bitmap bitmap) {
if (Utils.hasHoneycombMR1()) {
return bitmap.getByteCount();
}
// Pre HC-MR1
return bitmap.getRowBytes() * bitmap.getHeight();
}
/**
* Check if external storage is built-in or removable.
*
* #return True if external storage is removable (like an SD card), false
* otherwise.
*/
#TargetApi(9)
public static boolean isExternalStorageRemovable() {
if (Utils.hasGingerbread()) {
return Environment.isExternalStorageRemovable();
}
return true;
}
/**
* Get the external app cache directory.
*
* #param context The context to use
* #return The external cache dir
*/
#TargetApi(8)
public static File getExternalCacheDir(Context context) {
if (Utils.hasFroyo()) {
return context.getExternalCacheDir();
}
// Before Froyo we need to construct the external cache dir ourselves
final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/";
return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir);
}
/**
* Check how much usable space is available at a given path.
*
* #param path The path to check
* #return The space available in bytes
*/
#TargetApi(9)
public static long getUsableSpace(File path) {
if (Utils.hasGingerbread()) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
/**
* Locate an existing instance of this Fragment or if not found, create and
* add it using FragmentManager.
*
* #param fm The FragmentManager manager to use.
* #return The existing instance of the Fragment or the new instance if just
* created.
*/
public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
// Check to see if we have retained the worker fragment.
RetainFragment mRetainFragment = (RetainFragment) fm.findFragmentByTag(TAG);
// If not retained (or first time running), we need to create and add it.
if (mRetainFragment == null) {
mRetainFragment = new RetainFragment();
fm.beginTransaction().add(mRetainFragment, TAG).commitAllowingStateLoss();
}
return mRetainFragment;
}
/**
* A simple non-UI Fragment that stores a single Object and is retained over configuration
* changes. It will be used to retain the ImageCache object.
*/
public static class RetainFragment extends Fragment {
private Object mObject;
/**
* Empty constructor as per the Fragment documentation
*/
public RetainFragment() {}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make sure this Fragment is retained over a configuration change
setRetainInstance(true);
}
/**
* Store a single object in this Fragment.
*
* #param object The object to store
*/
public void setObject(Object object) {
mObject = object;
}
/**
* Get the stored object.
*
* #return The stored object
*/
public Object getObject() {
return mObject;
}
}
}
There is an example in the docs
http://developer.android.com/training/displaying-bitmaps/index.html
Download the zip file DisplayingBitmaps.zip. The example has a implementation of cache for images.
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
Using Universal Image Loader
Caching images and displaying
Edit:
BitmapFun sample snap shots.
Images are downloaded and cached
Detailed View
Now loading other images when scrolled. Images are not yet cached
With all the three its easy to understand.
In my Photo Collage app for Android I'm using Volley for loading images.
I'm using the DiskBasedCache (included with volley) with 50 mb storage to prevent re-downloading the same images multiple times.
Last time I checked the DiskBasedCache contained about 1000 cache entries.
When my app starts Volley calls mCache.initialize() and it will spend about 10 seconds (!) on my Galaxy S4 to do the following:
List all files in cache folder
Open each and every file and read the header section.
I find that reading 1000+ files at startup is not a very efficient way to load the cache index! :-)
From volley/toolbox/DiskBasedCache.java:
#Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
I'm looking for a fast and scalable solution. Perhaps an alternative DiskBasedCache implementation or suggestions on how to improve the volley library.
Update: (2014-01-06)
Noticing that the Volley cache used a lot of small (1 byte) IO read/writes. I cloned DiskBasedCache.java and encapsulating all FileInputStreams and FileOutputStreams with BufferedInputStream and BufferedOutputStreams. I found that that this optimization gave me a 3-10 times speed up.
This modification has a low risks of bugs compared to writing a new disk cache with a central index file.
Update: (2014-01-10)
Here is new class BufferedDiskBasedCache.java that I'm using now.
package no.ludde.android.ds.android.volley;
/*
* Copyright (C) 2011 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.os.SystemClock;
import com.android.volley.Cache;
import com.android.volley.VolleyLog;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Cache implementation that caches files directly onto the hard disk in the specified
* directory. The default disk usage size is 5MB, but is configurable.
*/
public class BufferedDiskBasedCache implements Cache {
/** Map of the Key, CacheHeader pairs */
private final Map<String, CacheHeader> mEntries =
new LinkedHashMap<String, CacheHeader>(16, .75f, true);
/** Total amount of space currently used by the cache in bytes. */
private long mTotalSize = 0;
/** The root directory to use for the cache. */
private final File mRootDirectory;
/** The maximum size of the cache in bytes. */
private final int mMaxCacheSizeInBytes;
/** Default maximum disk usage in bytes. */
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
/** High water mark percentage for the cache */
private static final float HYSTERESIS_FACTOR = 0.9f;
/** Magic number for current version of cache file format. */
private static final int CACHE_MAGIC = 0x20120504;
/**
* Constructs an instance of the DiskBasedCache at the specified directory.
* #param rootDirectory The root directory of the cache.
* #param maxCacheSizeInBytes The maximum size of the cache in bytes.
*/
public BufferedDiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
/**
* Constructs an instance of the DiskBasedCache at the specified directory using
* the default maximum cache size of 5MB.
* #param rootDirectory The root directory of the cache.
*/
public BufferedDiskBasedCache(File rootDirectory) {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
/**
* Clears the cache. Deletes all cached files from disk.
*/
#Override
public synchronized void clear() {
File[] files = mRootDirectory.listFiles();
if (files != null) {
for (File file : files) {
file.delete();
}
}
mEntries.clear();
mTotalSize = 0;
VolleyLog.d("Cache cleared.");
}
/**
* Returns the cache entry with the specified key if it exists, null otherwise.
*/
#Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
File file = getFileForKey(key);
CountingInputStream cis = null;
try {
cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
CacheHeader.readHeader(cis); // eat header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException ioe) {
return null;
}
}
}
}
/**
* Initializes the DiskBasedCache by scanning for all files currently in the
* specified root directory. Creates the root directory if necessary.
*/
#Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
BufferedInputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(file));
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
/**
* Invalidates an entry in the cache.
* #param key Cache key
* #param fullExpire True to fully expire the entry, false to soft expire
*/
#Override
public synchronized void invalidate(String key, boolean fullExpire) {
Entry entry = get(key);
if (entry != null) {
entry.softTtl = 0;
if (fullExpire) {
entry.ttl = 0;
}
put(key, entry);
}
}
/**
* Puts the entry with the specified key into the cache.
*/
#Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
CacheHeader e = new CacheHeader(key, entry);
e.writeHeader(fos);
fos.write(entry.data);
fos.close();
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
/**
* Removes the specified key from the cache if it exists.
*/
#Override
public synchronized void remove(String key) {
boolean deleted = getFileForKey(key).delete();
removeEntry(key);
if (!deleted) {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
key, getFilenameForKey(key));
}
}
/**
* Creates a pseudo-unique filename for the specified cache key.
* #param key The key to generate a file name for.
* #return A pseudo-unique filename.
*/
private String getFilenameForKey(String key) {
int firstHalfLength = key.length() / 2;
String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
return localFilename;
}
/**
* Returns a file object for the given cache key.
*/
public File getFileForKey(String key) {
return new File(mRootDirectory, getFilenameForKey(key));
}
/**
* Prunes the cache to fit the amount of bytes specified.
* #param neededSpace The amount of bytes we are trying to fit into the cache.
*/
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
if (VolleyLog.DEBUG) {
VolleyLog.v("Pruning old cache entries.");
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
e.key, getFilenameForKey(e.key));
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
if (VolleyLog.DEBUG) {
VolleyLog.v("pruned %d files, %d bytes, %d ms",
prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
}
}
/**
* Puts the entry with the specified key into the cache.
* #param key The key to identify the entry by.
* #param entry The entry to cache.
*/
private void putEntry(String key, CacheHeader entry) {
if (!mEntries.containsKey(key)) {
mTotalSize += entry.size;
} else {
CacheHeader oldEntry = mEntries.get(key);
mTotalSize += (entry.size - oldEntry.size);
}
mEntries.put(key, entry);
}
/**
* Removes the entry identified by 'key' from the cache.
*/
private void removeEntry(String key) {
CacheHeader entry = mEntries.get(key);
if (entry != null) {
mTotalSize -= entry.size;
mEntries.remove(key);
}
}
/**
* Reads the contents of an InputStream into a byte[].
* */
private static byte[] streamToBytes(InputStream in, int length) throws IOException {
byte[] bytes = new byte[length];
int count;
int pos = 0;
while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
pos += count;
}
if (pos != length) {
throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
}
return bytes;
}
/**
* Handles holding onto the cache headers for an entry.
*/
// Visible for testing.
static class CacheHeader {
/** The size of the data identified by this CacheHeader. (This is not
* serialized to disk. */
public long size;
/** The key that identifies the cache entry. */
public String key;
/** ETag for cache coherence. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Headers from the response resulting in this cache entry. */
public Map<String, String> responseHeaders;
private CacheHeader() { }
/**
* Instantiates a new CacheHeader object
* #param key The key that identifies the cache entry
* #param entry The cache entry.
*/
public CacheHeader(String key, Entry entry) {
this.key = key;
this.size = entry.data.length;
this.etag = entry.etag;
this.serverDate = entry.serverDate;
this.ttl = entry.ttl;
this.softTtl = entry.softTtl;
this.responseHeaders = entry.responseHeaders;
}
/**
* Reads the header off of an InputStream and returns a CacheHeader object.
* #param is The InputStream to read from.
* #throws IOException
*/
public static CacheHeader readHeader(InputStream is) throws IOException {
CacheHeader entry = new CacheHeader();
int magic = readInt(is);
if (magic != CACHE_MAGIC) {
// don't bother deleting, it'll get pruned eventually
throw new IOException();
}
entry.key = readString(is);
entry.etag = readString(is);
if (entry.etag.equals("")) {
entry.etag = null;
}
entry.serverDate = readLong(is);
entry.ttl = readLong(is);
entry.softTtl = readLong(is);
entry.responseHeaders = readStringStringMap(is);
return entry;
}
/**
* Creates a cache entry for the specified data.
*/
public Entry toCacheEntry(byte[] data) {
Entry e = new Entry();
e.data = data;
e.etag = etag;
e.serverDate = serverDate;
e.ttl = ttl;
e.softTtl = softTtl;
e.responseHeaders = responseHeaders;
return e;
}
/**
* Writes the contents of this CacheHeader to the specified OutputStream.
*/
public boolean writeHeader(OutputStream os) {
try {
writeInt(os, CACHE_MAGIC);
writeString(os, key);
writeString(os, etag == null ? "" : etag);
writeLong(os, serverDate);
writeLong(os, ttl);
writeLong(os, softTtl);
writeStringStringMap(responseHeaders, os);
os.flush();
return true;
} catch (IOException e) {
VolleyLog.d("%s", e.toString());
return false;
}
}
}
private static class CountingInputStream extends FilterInputStream {
private int bytesRead = 0;
private CountingInputStream(InputStream in) {
super(in);
}
#Override
public int read() throws IOException {
int result = super.read();
if (result != -1) {
bytesRead++;
}
return result;
}
#Override
public int read(byte[] buffer, int offset, int count) throws IOException {
int result = super.read(buffer, offset, count);
if (result != -1) {
bytesRead += result;
}
return result;
}
}
/*
* Homebrewed simple serialization system used for reading and writing cache
* headers on disk. Once upon a time, this used the standard Java
* Object{Input,Output}Stream, but the default implementation relies heavily
* on reflection (even for standard types) and generates a ton of garbage.
*/
/**
* Simple wrapper around {#link InputStream#read()} that throws EOFException
* instead of returning -1.
*/
private static int read(InputStream is) throws IOException {
int b = is.read();
if (b == -1) {
throw new EOFException();
}
return b;
}
static void writeInt(OutputStream os, int n) throws IOException {
os.write((n >> 0) & 0xff);
os.write((n >> 8) & 0xff);
os.write((n >> 16) & 0xff);
os.write((n >> 24) & 0xff);
}
static int readInt(InputStream is) throws IOException {
int n = 0;
n |= (read(is) << 0);
n |= (read(is) << 8);
n |= (read(is) << 16);
n |= (read(is) << 24);
return n;
}
static void writeLong(OutputStream os, long n) throws IOException {
os.write((byte)(n >>> 0));
os.write((byte)(n >>> 8));
os.write((byte)(n >>> 16));
os.write((byte)(n >>> 24));
os.write((byte)(n >>> 32));
os.write((byte)(n >>> 40));
os.write((byte)(n >>> 48));
os.write((byte)(n >>> 56));
}
static long readLong(InputStream is) throws IOException {
long n = 0;
n |= ((read(is) & 0xFFL) << 0);
n |= ((read(is) & 0xFFL) << 8);
n |= ((read(is) & 0xFFL) << 16);
n |= ((read(is) & 0xFFL) << 24);
n |= ((read(is) & 0xFFL) << 32);
n |= ((read(is) & 0xFFL) << 40);
n |= ((read(is) & 0xFFL) << 48);
n |= ((read(is) & 0xFFL) << 56);
return n;
}
static void writeString(OutputStream os, String s) throws IOException {
byte[] b = s.getBytes("UTF-8");
writeLong(os, b.length);
os.write(b, 0, b.length);
}
static String readString(InputStream is) throws IOException {
int n = (int) readLong(is);
byte[] b = streamToBytes(is, n);
return new String(b, "UTF-8");
}
static void writeStringStringMap(Map<String, String> map, OutputStream os) throws IOException {
if (map != null) {
writeInt(os, map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
writeString(os, entry.getKey());
writeString(os, entry.getValue());
}
} else {
writeInt(os, 0);
}
}
static Map<String, String> readStringStringMap(InputStream is) throws IOException {
int size = readInt(is);
Map<String, String> result = (size == 0)
? Collections.<String, String>emptyMap()
: new HashMap<String, String>(size);
for (int i = 0; i < size; i++) {
String key = readString(is).intern();
String value = readString(is).intern();
result.put(key, value);
}
return result;
}
}
Yes, the way DiskBasedCache works it needs to open all the files in initialize(). Which is simply.... not a good idea :-(
You need to make a different implementation that doesent open all the files at startup.
Take a copy of DiskBasedCache and change initialize() to
#Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
}
}
And change get() so it makes an additional check for if the file exists on the file system, like
#Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
File file = getFileForKey(key);
if (entry == null && !file.exists()) { // EXTRA CHECK
// if the entry does not exist, return.
VolleyLog.d("DrVolleyDiskBasedCache miss for " + key);
return null;
}
...
I use this approach in https://play.google.com/store/apps/details?id=dk.dr.radio and it works fine - its robustness have been tested by ~300000 users :-)
You can download a full version of the file from https://code.google.com/p/dr-radio-android/source/browse/trunk/DRRadiov3/src/dk/dr/radio/net/volley/DrDiskBasedCache.java (you'll have to delete some DR Radio specific stuff)
In the streamToBytes(), first it will new bytes by the cache file length, does your cache file was too large than application maximum heap size ?
private static byte[] streamToBytes(InputStream in, int length) throws IOException {
byte[] bytes = new byte[length];
...
}
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
File file = getFileForKey(key);
byte[] data = streamToBytes(..., file.length());
}
If you want to clear the cache, you could keep the DiskBasedCache reference, after clear time's came, use ClearCacheRequest and pass that cache instance in :
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
DiskBasedCache cache = new DiskBasedCache(cacheDir);
RequestQueue queue = new RequestQueue(cache, network);
queue.start();
// clear all volley caches.
queue.add(new ClearCacheRequest(cache, null));
this way will clear all caches, so I suggest you use it carefully. of course, you can doing conditional check, just iterating the cacheDir files, estimate which was too large then remove it.
for (File cacheFile : cacheDir.listFiles()) {
if (cacheFile.isFile() && cacheFile.length() > 10000000) cacheFile.delete();
}
Volley wasn't design as a big data cache solution, it's common request cache, don't storing large data anytime.
------------- Update at 2014-07-17 -------------
In fact, clear all caches is final way, also isn't wise way, we should suppressing these large request use cache when we sure it would be, and if not sure? we still can determine the response data size whether large or not, then call setShouldCache(false) to disable it.
public class TheRequest extends Request {
#Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
// if response data was too large, disable caching is still time.
if (response.data.length > 10000) setShouldCache(false);
...
}
}
My initial thought was to use the DiskLruCache written by Jake Wharton by writing a com.android.volley.Cache wrapper over it.
But I finally implemented a singleton pattern for the Volley, combined with the cache creation in an AsyncTask called from the Application context
public static synchronized VolleyClient getInstance(Context context)
{
if (mInstance == null)
{
mInstance = new VolleyClient(context);
}
return mInstance;
}
private VolleyClient(Context context)
{
this.context = context;
VolleyCacheInitializer volleyCacheInitializer = new VolleyCacheInitializer();
volleyCacheInitializer.execute();
}
private class VolleyCacheInitializer extends AsyncTask<Void, Void, Boolean>
{
#Override
protected Boolean doInBackground(Void... params)
{
// Instantiate the cache with 50MB Cache Size
Cache diskBasedCache = new DiskBasedCache(context.getCacheDir(), 50 * 1024 * 1024);
// Instantiate the RequestQueue with the cache and network.
mRequestQueue = new RequestQueue(diskBasedCache, network);
// Start the queue which calls the DiskBasedCache.initialize()
mRequestQueue.start();
return true;
}
#Override
protected void onPostExecute(Boolean aBoolean)
{
super.onPostExecute(aBoolean);
if(aBoolean)
Log.d(TAG, "Volley request queue initialized");
else
Log.d(TAG, "Volley request queue initialization failed");
}
}
Inside MyApplication class
#Override
public void onCreate()
{
super.onCreate();
// Initialize an application level volley request queue
VolleyClient volleyHttpClient = VolleyClient.getInstance(this);
}
My gif image doesn't run on my tabhost. When I tried to press the lock / power button then go back to the app it runs. I want to fix it but I don't know what the problem in my code is, since it doesn't have any errors. I tried making a new project that doesn't include tabhost and my gif image runs.
Here is my code
AnimatedUtils.java
public class AnimatedUtils extends Activity {
/**
* start/stop animation
*
* #param view
* #param start
*/
static public void startViewAnimation(View view, boolean start) {
if (view != null) {
// background drawable
startDrawableAnimation(view.getBackground(), start);
if (view instanceof ImageView) {
// image drawable
startDrawableAnimation(((ImageView)view).getDrawable(), start);
}
}
}
/**
* start/stop animation
*
* #param d
* #param start
*/
static public void startDrawableAnimation(Drawable d, boolean start) {
if (d instanceof AnimationDrawable) {
if (start) {
((AnimationDrawable)d).start();
} else {
((AnimationDrawable)d).stop();
}
}
}
/**
* load drawable from resource id
*
* #param rsrc
* #param resid
* #return
*/
static public Drawable loadDrawableFromResource(Resources rsrc, int resid) {
// load from resource
Movie movie = Movie.decodeStream(rsrc.openRawResource(resid));
if ((movie != null) && movie.duration() > 0) {
return makeMovieDrawable(rsrc, movie);
} else {
// not animated GIF
return rsrc.getDrawable(resid);
}
}
/**
* load drawable from file path
*
* #param rsrc
* #param path
* #return
*/
static public Drawable loadDrawableFromFile(Resources rsrc, String path) {
// load from file
// Movie movie = Movie.decodeFile(path);
Movie movie = null;
try {
File file = new File(path);
FileInputStream is = new FileInputStream(file);
byte data[] = new byte[(int)file.length()];
is.read(data);
is.close();
movie = Movie.decodeByteArray(data, 0, data.length);
} catch (Exception e) {
}
if ((movie != null) && movie.duration() > 0) {
return makeMovieDrawable(rsrc, movie);
} else {
// not animated GIF
return Drawable.createFromPath(path);
}
}
/**
* make AnimationDrawable from Movie instance
*
* #param rsrc
* #param movie
* #return
*/
static private Drawable makeMovieDrawable(Resources rsrc, Movie movie) {
int duration = movie.duration();
int width = movie.width(), height = movie.height();
AnimationDrawable result = new AnimationDrawable();
result.setOneShot(false); // for loop
Drawable frame = null;
int start = 0;
for (int time = 0; time < duration; time += 10) {
if (movie.setTime(time)) {
if (frame != null) {
// add previous frame
result.addFrame(frame, time - start);
}
// make frame
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.RGB_565); // save heap
//Bitmap.Config.ARGB_8888); // high quality
movie.draw(new Canvas(bitmap), 0, 0);
frame = new BitmapDrawable(rsrc, bitmap);
start = time;
}
}
if (frame != null) {
// add last frame
result.addFrame(frame, duration - start);
}
return result;
}
}
gifimage.java
public class gifimage extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
Drawable d;
d = AnimatedUtils.loadDrawableFromResource(getResources(),
R.drawable.gif1);
((ImageView)findViewById(R.id.imageView1)).setImageDrawable(d);
Drawable e;
e = AnimatedUtils.loadDrawableFromResource(getResources(),
R.drawable.gif2);
((ImageView)findViewById(R.id.imageView2)).setImageDrawable(e);
Drawable f;
f = AnimatedUtils.loadDrawableFromResource(getResources(),
R.drawable.gif3);
((ImageView)findViewById(R.id.imageView3)).setImageDrawable(f);
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// start/stop animation
AnimatedUtils.startViewAnimation(findViewById(R.id.imageView1), hasFocus);
AnimatedUtils.startViewAnimation(findViewById(R.id.imageView2), hasFocus);
AnimatedUtils.startViewAnimation(findViewById(R.id.imageView3), hasFocus);
}
}
Before Writing the code try to see the document so you can save your time, Android is not support .Gif
See List of Supported Formats . you can use frame animation and other way.
I have a ListView with rows which may have, or not, a picture. When there is a picture I start an image loader based on http://developer.android.com/resources/samples/XmlAdapters/src/com/example/android/xmladapters/ImageDownloader.html. One of my modifications is to replace the Color drawable shown while the image is loading by an animated image (a spinning wheel). In order to do that I set a spinner Bitmap in the constructor and I animate it in the download(String url, ImageView imageView) method, if the image to be downloaded is not in the cache. I clear the animation when the image is loaded and set to the ImageView.
The issue is that with the animation code
Animation rotate_picture = AnimationUtils.loadAnimation(context, R.anim.rotate_picture);
imageView.startAnimation(rotate_picture);
in the download method of my ImageDownloader, my adapter goes crazy and sometimes set the animation to downloaded images, or set the default picture to row with no picture.
When I comment these two lines everything is ok (except the spinner is not animated of course).
Anybody can help?
adapter's getView
#Override
public View getView(int position, View convertView, final ViewGroup parent) {
View row = convertView;
ItemHolder holder = null;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new ItemHolder();
holder.title = (TextView)row.findViewById(R.id.title);
holder.date = (TextView)row.findViewById(R.id.date);
holder.desc = (EllipsizingTextView)row.findViewById(R.id.desc);
holder.pic = (ImageView)row.findViewById(R.id.pic);
row.setTag(holder);
}
else
{
holder = (ItemHolder)row.getTag();
}
Item item = data.get(position);
holder.title.setText(item.title);
holder.date.setText(Utils.UnixTime2String(item.published, "/"));
holder.desc.setMaxLines(3);
holder.desc.setText(item.descriptionintro);
if(item.list_images.size()>0){
holder.pic.setVisibility(View.VISIBLE);
imageLoader.download(GlobalData.BASE_URL + item.list_images.get(0), (ImageView) holder.pic);
}
else{
holder.pic.setVisibility(View.GONE);
holder.pic.setImageResource(0);
}
return row;
}
ImageDownloader
public class ImageDownloader {
private static final String LOG_TAG = "ImageDownloader";
private Integer xbound, ybound;
private static Context context;
private static Bitmap defaultPic;
public ImageDownloader(Context context, Bitmap defaultPic, Integer xbound, Integer ybound){
ImageDownloader.context = context;
ImageDownloader.defaultPic = defaultPic;
this.xbound = xbound;
this.ybound = ybound;
}
/**
* Download the specified image from the Internet and binds it to the provided ImageView. The
* binding is immediate if the image is found in the cache and will be done asynchronously
* otherwise. A null bitmap will be associated to the ImageView if an error occurs.
*
* #param url The URL of the image to download.
* #param imageView The ImageView to bind the downloaded image to.
*/
public void download(String url, ImageView imageView) {
resetPurgeTimer();
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
//Animation rotate_picture = AnimationUtils.loadAnimation(context, R.anim.rotate_picture);
//imageView.startAnimation(rotate_picture);
forceDownload(url, imageView);
} else {
cancelPotentialDownload(url, imageView);
imageView.setImageBitmap(bitmap);
}
}
/*
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
private void forceDownload(String url, ImageView view) {
forceDownload(url, view, null);
}
*/
/**
* Same as download but the image is always downloaded and the cache is not used.
* Kept private at the moment as its interest is not clear.
*/
private void forceDownload(String url, ImageView imageView) {
// State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
if (url == null) {
imageView.setImageDrawable(null);
return;
}
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url);
}
}
/**
* Returns true if the current download has been canceled or if there was no download in
* progress on this image view.
* Returns false if the download in progress deals with the same url. The download is not
* stopped in that case.
*/
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
/**
* #param imageView Any imageView
* #return Retrieve the currently active download task (if any) associated with this imageView.
* null if there is no such task.
*/
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
Bitmap downloadBitmap(String url) {
//final int IO_BUFFER_SIZE = 4 * 1024;
final HttpClient client = new DefaultHttpClient();
final HttpGet getRequest = new HttpGet(url);
try {
HttpResponse response = client.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
return null;
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = null;
try {
inputStream = entity.getContent();
return BitmapFactory.decodeStream(new FlushedInputStream(inputStream), null, null);
} finally {
if (inputStream != null) {
inputStream.close();
}
entity.consumeContent();
}
}
} catch (IOException e) {
getRequest.abort();
} catch (IllegalStateException e) {
getRequest.abort();
} catch (Exception e) {
getRequest.abort();
}
return null;
}
/*
* Cancel and remove download task for a given ImageView. Done when going previous/next to avoid "out of memory" when clicking fast
*/
void cancelDownload(ImageView imageView){
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
bitmapDownloaderTask.cancel(true);
removeBitmapDownloaderTask(imageView);
}
}
private static boolean removeBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
downloadedDrawable.clearBitmapDownloaderTask();
return true;
}
}
return false;
}
/*
* An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
*/
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
#Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int b = read();
if (b < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
}
/**
* The actual AsyncTask that will asynchronously download the image.
*/
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
/**
* Actual download method.
*/
#Override
protected Bitmap doInBackground(String... params) {
url = params[0];
return downloadBitmap(url);
}
/**
* Once the image is downloaded, associates it to the imageView
*/
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if(bitmap != null ){
addBitmapToCache(url, bitmap);
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
// Change bitmap only if this process is still associated with it
// Or if we don't use any bitmap to task association (NO_DOWNLOADED_DRAWABLE mode)
if (this == bitmapDownloaderTask) {
if(xbound!=null && ybound!=null){
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int xbounding = dpToPx(xbound);
int ybounding = dpToPx(ybound);
// Determine how much to scale: the dimension requiring less scaling is
// closer to the its side. This way the image always stays inside your
// bounding box AND either x/y axis touches it.
float xScale = ((float) xbounding) / width;
float yScale = ((float) ybounding) / height;
float scale = (xScale <= yScale) ? xScale : yScale;
// Create a matrix for the scaling and add the scaling data
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
// Create a new bitmap and convert it to a format understood by the ImageView
Bitmap scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
width = scaledBitmap.getWidth(); // re-use
height = scaledBitmap.getHeight(); // re-use
BitmapDrawable result = new BitmapDrawable(scaledBitmap);
// Apply the scaled bitmap
imageView.setImageDrawable(result);
// Now change ImageView's dimensions to match the scaled image
//LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) imageView.getLayoutParams();
//params.width = width;
//params.height = height;
//imageView.setLayoutParams(params);
}
else{
imageView.clearAnimation();
imageView.setImageBitmap(bitmap);
}
}
}
}
}
}
private int dpToPx(int dp)
{
float density = context.getResources().getDisplayMetrics().density;
return Math.round((float)dp * density);
}
/**
* A fake Drawable that will be attached to the imageView while the download is in progress.
*
* <p>Contains a reference to the actual download task, so that a download task can be stopped
* if a new binding is required, and makes sure that only the last started download process can
* bind its result, independently of the download finish order.</p>
*/
static class DownloadedDrawable extends BitmapDrawable {
private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
super(context.getResources(), defaultPic);
bitmapDownloaderTaskReference =
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
public void clearBitmapDownloaderTask() {
bitmapDownloaderTaskReference.clear();
}
}
/*
*
* Image loaded listener
*
*/
OnImageDownloaderListener onImageDownloaderListener = null;
public interface OnImageDownloaderListener {
public abstract void onImageLoaded(int tag);
}
public void setOnImageDownloaderListener(OnImageDownloaderListener listener) {
onImageDownloaderListener = listener;
}
private void OnImageLoaded(int tag){
if(onImageDownloaderListener!=null) {
onImageDownloaderListener.onImageLoaded(tag);
}
}
/*
* Cache-related fields and methods.
*
* We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the
* Garbage Collector.
*/
private static final int HARD_CACHE_CAPACITY = 20;
private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds
// Hard cache, with a fixed maximum capacity and a life duration
private final HashMap<String, Bitmap> sHardBitmapCache =
new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
#Override
protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// Entries push-out of hard reference cache are transferred to soft reference cache
sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
return true;
} else
return false;
}
};
// Soft cache for bitmaps kicked out of hard cache
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache =
new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);
private final Handler purgeHandler = new Handler();
private final Runnable purger = new Runnable() {
public void run() {
clearCache();
}
};
/**
* Adds this bitmap to the cache.
* #param bitmap The newly downloaded bitmap.
*/
private void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (sHardBitmapCache) {
sHardBitmapCache.put(url, bitmap);
}
}
}
/**
* #param url The URL of the image that will be retrieved from the cache.
* #return The cached bitmap or null if it was not found.
*/
private Bitmap getBitmapFromCache(String url) {
// First try the hard reference cache
synchronized (sHardBitmapCache) {
final Bitmap bitmap = sHardBitmapCache.get(url);
if (bitmap != null) {
// Bitmap found in hard cache
// Move element to first position, so that it is removed last
sHardBitmapCache.remove(url);
sHardBitmapCache.put(url, bitmap);
return bitmap;
}
}
// Then try the soft reference cache
SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url);
if (bitmapReference != null) {
final Bitmap bitmap = bitmapReference.get();
if (bitmap != null) {
// Bitmap found in soft cache
return bitmap;
} else {
// Soft reference has been Garbage Collected
sSoftBitmapCache.remove(url);
}
}
return null;
}
/**
* Clears the image cache used internally to improve performance. Note that for memory
* efficiency reasons, the cache will automatically be cleared after a certain inactivity delay.
*/
public void clearCache() {
sHardBitmapCache.clear();
sSoftBitmapCache.clear();
}
/**
* Allow a new delay before the automatic cache clear is done.
*/
private void resetPurgeTimer() {
purgeHandler.removeCallbacks(purger);
purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
}
}