Multiple Bitmap update on same android SurfaceView - android

Recently I tried to write an app which captures frame buffer from say USB camera (that need to be displayed as preview), and image processed output that need to be overlapped on the preview. Can anybody give me some pointers how to start? Moreover I need to draw some rectangle on the preview.
I was trying to use multiple SurfaceView to draw but not succeeded. Can anybody help me out?

What you need it the bitmap and image view reference counting. Android keeps image data in native array, which is not recycling automatically when the vm GC running. Vm part is garbage collected one way and the native part is another way and much later. You app may run out of memory pretty quickly.
Here is set of classes that can help. I think I've got them from android image tutorial and modified a bit for my own convenience.
package com.example.android.streaming.ui.cache;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* Sub-class of ImageView which automatically notifies the drawable when it is
* being displayed.
*/
public class RecyclingImageView extends ImageView {
public RecyclingImageView(Context context) {
super(context);
}
public RecyclingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* #see android.widget.ImageView#onDetachedFromWindow()
*/
#Override
protected void onDetachedFromWindow() {
// This has been detached from Window, so clear the drawable
setImageDrawable(null);
super.onDetachedFromWindow();
}
/**
* #see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable)
*/
#Override
public void setImageDrawable(Drawable drawable) {
// Keep hold of previous Drawable
final Drawable previousDrawable = getDrawable();
// Call super to set new Drawable
super.setImageDrawable(drawable);
// Notify new Drawable that it is being displayed
notifyDrawable(drawable, true);
// Notify old Drawable so it is no longer being displayed
notifyDrawable(previousDrawable, false);
}
#Override
public void setImageResource(int resId) {
// Keep hold of previous Drawable
final Drawable previousDrawable = getDrawable();
super.setImageResource(resId);
// Notify new Drawable that it is being displayed
final Drawable newDrawable = getDrawable();
notifyDrawable(newDrawable, true);
// Notify old Drawable so it is no longer being displayed
notifyDrawable(previousDrawable, false);
}
/**
* Notifies the drawable that it's displayed state has changed.
*
* #param drawable
* #param isDisplayed
*/
private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) {
if (drawable != null) {
if (drawable instanceof RecyclingBitmapDrawable) {
// The drawable is a CountingBitmapDrawable, so notify it
((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed);
} else if (drawable instanceof LayerDrawable) {
// The drawable is a LayerDrawable, so recurse on each layer
LayerDrawable layerDrawable = (LayerDrawable) drawable;
for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) {
notifyDrawable(layerDrawable.getDrawable(i), isDisplayed);
}
}
}
}
}
And here is another one, a bitmap itself.
package com.example.android.streaming.ui.cache;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import com.example.android.streaming.StreamingApp;
import com.vg.hangwith.BuildConfig;
/**
* A BitmapDrawable that keeps track of whether it is being displayed or cached.
* When the drawable is no longer being displayed or cached,
* {#link Bitmap#recycle() recycle()} will be called on this drawable's bitmap.
*/
public class RecyclingBitmapDrawable extends BitmapDrawable {
private int cacheRefCount = 0;
private int displayRefCount = 0;
private boolean hasBeenDisplayed;
public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) {
super(res, bitmap);
}
/**
* Notify the drawable that the displayed state has changed. Internally a
* count is kept so that the drawable knows when it is no longer being
* displayed.
*
* #param isDisplayed
* - Whether the drawable is being displayed or not
*/
public void setIsDisplayed(boolean isDisplayed) {
synchronized (this) {
if (isDisplayed) {
displayRefCount++;
hasBeenDisplayed = true;
} else {
displayRefCount--;
}
}
// Check to see if recycle() can be called
checkState();
}
/**
* Notify the drawable that the cache state has changed. Internally a count
* is kept so that the drawable knows when it is no longer being cached.
*
* #param isCached
* - Whether the drawable is being cached or not
*/
public void setIsCached(boolean isCached) {
synchronized (this) {
if (isCached) {
cacheRefCount++;
} else {
cacheRefCount--;
}
}
// Check to see if recycle() can be called
checkState();
}
private synchronized void checkState() {
// If the drawable cache and display ref counts = 0, and this drawable
// has been displayed, then recycle
if (cacheRefCount <= 0 && displayRefCount <= 0 && hasBeenDisplayed && hasValidBitmap()) {
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "No longer being used or cached so recycling. " + toString());
getBitmap().recycle();
}
}
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
}
Now, iun your activity, whatever it does, if it needs to present recyclable image, you add this in xml res:
<com.example.android.streaming.ui.cache.RecyclingImageView
android:id="#+id/ad_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#drawable/bkgd_whitegradient"
android:contentDescription="#string/dummy_desc"
android:padding="20dip"/>
This is just an example, id, background, can be whatever you need.
final RecyclingImageView adImage = (RecyclingImageView) findViewById(R.id.ad_image);
adImage.setImageDrawable(new RecyclingBitmapDrawable(getResources(), getBitmap(this)));
adImage.setVisibility(View.VISIBLE);
Note the getBitmap(), this is an example. It is you who should implement it in a way you need. It returns Bitmap instance. In your case, this Bitmap will be created out of array of bytes you've received from your camera. Let's try to do it here too.
Next, I have a class for managing avatars in my app (long list of users is a good example). It has number of useful static methods, so you may not need to create it.
package com.example.android.streaming.ui.cache;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.json.JSONObject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.util.LruCache;
import com.example.android.streaming.StreamingApp;
import com.example.android.streaming.datamodel.Broadcast;
import com.example.android.streaming.datamodel.Channel;
import com.facebook.model.GraphUser;
import com.parse.ParseFile;
import com.parse.ParseUser;
import com.vg.hangwith.BuildConfig;
import com.vg.hangwith.R;
public class AvatarCache {
private Map<String, LoadImageTask> tasks = new HashMap<String, AvatarCache.LoadImageTask>();
private LruCache<String, RecyclingBitmapDrawable> memoryCache;
public final static int AVATAR_BOUNDS = 100;
private String cacheDir;
private Context context;
public synchronized void addTask(String tag, LoadImageTask task) {
tasks.put(tag, task);
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Added avatar load task for tag " + tag);
}
public synchronized void removeTask(String tag) {
tasks.remove(tag);
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Removed avatar load task for tag " + tag);
}
public synchronized void cancelTasks(int keepLastItems) {
int count = 0;
Iterator<Map.Entry<String, LoadImageTask>> iter = tasks.entrySet().iterator();
while (iter.hasNext() && tasks.size() > keepLastItems) {
Map.Entry<String, LoadImageTask> entry = iter.next();
entry.getValue().cancel(true);
iter.remove();
count++;
}
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Canceled " + count + " avatar load tasks");
}
public void cancelTasks() {
cancelTasks(0);
}
public final static Bitmap downscaleAvatar(Bitmap bitmap) {
if (bitmap.getWidth() > AVATAR_BOUNDS && bitmap.getHeight() > AVATAR_BOUNDS) {
int height = (int) Math.floor(bitmap.getHeight() / ((1.0f * bitmap.getWidth()) / AVATAR_BOUNDS));
Bitmap scaled = Bitmap.createScaledBitmap(bitmap, AVATAR_BOUNDS, height, false);
bitmap.recycle();
bitmap = null;
return scaled;
} else {
return bitmap;
}
}
public final static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public class LoadImageTask extends AsyncTask<Void, Void, RecyclingBitmapDrawable> {
protected RecyclingImageView image;
protected String url, tag;
protected boolean avatar;
public LoadImageTask(String url, String tag, boolean avatar, RecyclingImageView image) {
super();
this.url = url;
this.tag = tag;
this.image = image;
this.avatar = avatar;
image.setTag(R.string.tag_key, tag);
addTask(tag, this);
}
#Override
protected RecyclingBitmapDrawable doInBackground(Void... dummy) {
if (isCancelled() || !isSameImage())
return null;
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(tag);
if (drawable == null) {
drawable = getAvatarFromDiskCache(tag);
if (drawable == null) {
try {
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Loading avatar " + url);
/* First decode bounds to check the image size. */
BitmapFactory.Options options = new BitmapFactory.Options();
/* Calculate if the avatar should be down scaled. */
if (avatar) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream(), null, options);
options.inSampleSize = calculateInSampleSize(options, AVATAR_BOUNDS, AVATAR_BOUNDS);
}
options.inJustDecodeBounds = false;
/* Download down scaled avatar. */
Bitmap bitmap = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream(), null, options);
if (bitmap != null) {
drawable = new RecyclingBitmapDrawable(context.getResources(), bitmap);
if (drawable != null) {
addAvatarToDiskCache(tag, url, drawable);
addAvatarToMemoryCache(tag, drawable);
}
}
} catch (Exception e) {
Log.w(StreamingApp.TAG, "Failed to load and save avatar image. " + e.getMessage());
}
} else {
addAvatarToMemoryCache(tag, drawable);
}
}
return drawable;
}
private synchronized boolean isSameImage() {
// In case that the same image is reused for different avatar (during scroll), this
// function will return false.
Object imageTag = image.getTag(R.string.tag_key);
return imageTag != null && imageTag.equals(tag);
}
private void finishedWithResult(RecyclingBitmapDrawable result) {
if (result != null && isSameImage())
image.setImageDrawable(result);
removeTask(tag);
}
#Override
protected void onPostExecute(RecyclingBitmapDrawable result) {
finishedWithResult(result);
super.onPostExecute(result);
}
#Override
protected void onCancelled(RecyclingBitmapDrawable result) {
finishedWithResult(result);
super.onCancelled();
}
#Override
protected void onCancelled() {
finishedWithResult(null);
super.onCancelled();
}
}
public AvatarCache(Context context) {
super();
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/10th of the available memory for this memory cache. With small avatars like
// we have this is enough to keep ~100 avatars in cache.
final int cacheSize = maxMemory / 10;
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Init avatar cache, size: " + cacheSize + ", max mem size: " + maxMemory);
memoryCache = new LruCache<String, RecyclingBitmapDrawable>(cacheSize) {
#Override
protected int sizeOf(String key, RecyclingBitmapDrawable drawable) {
// The cache size will be measured in kilobytes rather than
// number of items.
Bitmap bitmap = drawable.getBitmap();
int bitmapSize = bitmap != null ? bitmap.getByteCount() / 1024 : 0;
return bitmapSize == 0 ? 1 : bitmapSize;
}
#Override
protected void entryRemoved(boolean evicted, String key, RecyclingBitmapDrawable oldValue,
RecyclingBitmapDrawable newValue) {
// The removed entry is a recycling drawable, so notify it.
// that it has been removed from the memory cache
oldValue.setIsCached(false);
}
};
this.cacheDir = context.getCacheDir().getAbsolutePath();
this.context = context;
}
public void flush() {
int oldSize = memoryCache.size();
memoryCache.evictAll();
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Flush avatar cache, flushed " + (oldSize - memoryCache.size()) + " new size "
+ memoryCache.size());
cancelTasks();
}
public void addAvatarToMemoryCache(String key, RecyclingBitmapDrawable drawable) {
if (getAvatarFromMemCache(key) == null) {
drawable.setIsCached(true);
memoryCache.put(key, drawable);
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Add to avatar cache, size: " + memoryCache.size());
}
}
public RecyclingBitmapDrawable getAvatarFromMemCache(String key) {
return memoryCache.get(key);
}
public void addAvatarToDiskCache(String name, String url, RecyclingBitmapDrawable drawable) throws IOException {
if (drawable == null)
return;
File dir = new File(cacheDir);
if (!dir.exists())
dir.mkdirs();
File file = new File(dir, name);
Bitmap bitmap = drawable.getBitmap();
if (!file.exists() && bitmap != null) {
OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
drawable.getBitmap().compress(Bitmap.CompressFormat.PNG, 85, out);
out.flush();
out.close();
}
}
/*
* Update avatar from the network if older than this.
*/
public static final int AVATAR_MAX_AGE_DAYS = 7;
public RecyclingBitmapDrawable getAvatarFromDiskCache(String name) {
File file = new File(cacheDir, name);
/* Check if cached bitmap is old. */
if ((System.currentTimeMillis() - file.lastModified()) > AVATAR_MAX_AGE_DAYS * 24 * 60 * 60 * 1000)
return null;
try {
Bitmap bitmap = BitmapFactory.decodeFile(file.getCanonicalPath());
if (bitmap != null) {
// Log.w(App.TAG, "Loaded " + (bitmap.getByteCount() / 1024.0f) + "K bitmap " + name + " w: "
// + bitmap.getWidth() + " h: " + bitmap.getHeight());
return new RecyclingBitmapDrawable(context.getResources(), bitmap);
}
} catch (Exception e) {
Log.w(StreamingApp.TAG, "Failed to decode avatar image " + name + ". " + e.getMessage());
}
return null;
}
public static boolean isValidURL(String url) {
try {
new URL(url);
return true;
} catch (Exception e) {
}
return false;
}
public void loadUrlAvatar(String url, String name, RecyclingImageView image, int placeholder, boolean checkDiskCache) {
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(name);
if (drawable == null && checkDiskCache) {
drawable = getAvatarFromDiskCache(name);
if (drawable != null)
addAvatarToMemoryCache(name, drawable);
}
if (drawable == null) {
image.setImageResource(placeholder);
if (url != null && isValidURL(url))
new LoadImageTask(url, name, true, image).execute();
} else {
image.setImageDrawable(drawable);
}
}
public static String getUserAvatarURL(ParseUser user) {
if (user == null)
return null;
if (user.get("avatar") == null || user.get("avatar") == JSONObject.NULL)
return user.getString("avatar_url");
if (user.get("avatar") instanceof JSONObject)
Log.w(StreamingApp.TAG, "JSONObject found instead of ParseFile: " + ((JSONObject) user.get("avatar")).toString());
return ((ParseFile) user.get("avatar")).getUrl();
}
public static String getUserAvatarURL(GraphUser user) {
return "http://graph.facebook.com/" + user.getId() + "/picture";
}
public static String getBroadcastAvatarURL(Broadcast broadcast) {
if (broadcast.getThumbnail() == null)
return null;
return broadcast.getThumbnail().getUrl();
}
public void loadUserAvatar(ParseUser user, RecyclingImageView image, int placeholder, boolean checkDiskCache) {
if (user != null)
loadUrlAvatar(getUserAvatarURL(user), user.getUsername(), image, placeholder, checkDiskCache);
}
public void loadUserAvatar(GraphUser user, RecyclingImageView image, int placeholder, boolean checkDiskCache) {
if (user != null)
loadUrlAvatar(getUserAvatarURL(user), user.getId(), image, placeholder, checkDiskCache);
}
public void loadBroadcastAvatar(Broadcast broadcast, RecyclingImageView image, int placeholder,
boolean checkDiskCache) {
if (broadcast != null)
loadUrlAvatar(getBroadcastAvatarURL(broadcast), broadcast.getObjectId(), image, placeholder, checkDiskCache);
}
public void clearUserAvatar(ParseUser user) {
File file = new File(cacheDir, user.getUsername());
if (file.exists())
file.delete();
memoryCache.remove(user.getUsername());
if (BuildConfig.DEBUG)
Log.d(StreamingApp.TAG, "Remove avatar from cache, size: " + memoryCache.size());
}
public static String getChannelImageURL(Channel channel, boolean small, boolean ageRestricted) {
if (ageRestricted) {
if (small && channel.getSmallRestrictedState() != null)
return channel.getSmallRestrictedState().getUrl();
else if (!small && channel.getLargeRestrictedState() != null)
return channel.getLargeRestrictedState().getUrl();
} else {
if (small && channel.getSmallEmptyState() != null)
return channel.getSmallEmptyState().getUrl();
else if (!small && channel.getLargeEmptyState() != null)
return channel.getLargeEmptyState().getUrl();
}
return null;
}
public static final String channelImageCacheName(Channel channel, boolean small, boolean ageRestricted) {
return channel.getObjectId() + "-" + (ageRestricted ? "age" : "empty") + "-" + (small ? "small" : "large");
}
public boolean loadChannelImage(Channel channel, RecyclingImageView image, boolean checkDiskCache, boolean small,
boolean ageRestricted) {
boolean result = false;
if (channel == null)
return false;
String name = channelImageCacheName(channel, small, ageRestricted);
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(name);
if (drawable == null && checkDiskCache) {
drawable = getAvatarFromDiskCache(name);
if (drawable != null)
addAvatarToMemoryCache(name, drawable);
}
if (drawable == null) {
String url = getChannelImageURL(channel, small, ageRestricted);
result = url != null && isValidURL(url);
if (result)
new LoadImageTask(url, name, false, image).execute();
} else {
image.setImageDrawable(drawable);
result = true;
}
return result;
}
public void loadUrlImage(String url, RecyclingImageView image, String name, boolean checkDiskCache) {
RecyclingBitmapDrawable drawable = getAvatarFromMemCache(name);
if (drawable == null && checkDiskCache) {
drawable = getAvatarFromDiskCache(name);
if (drawable != null)
addAvatarToMemoryCache(name, drawable);
}
if (drawable == null) {
if (url != null && isValidURL(url))
new LoadImageTask(url, name, false, image).execute();
} else {
image.setImageDrawable(drawable);
}
}
}
Note, it uses Parse framework at some places. Just ignore it.
In this example, AvatarCache is loading image by url in doInBackground() function. As you can see it gets an input stream of out url. You can modify it to feed it some different input stream that you use for loading your image. Then you also need to modify loadUrlImage(). In other words, just remove the url thing.
And this is how you can use it with Uri. Modify it for using input stream or array of bytes. Just use appropriate BitmapFactory.decodeSomething() method.
public Bitmap getBitmap(Uri uri) {
BitmapFactory.Options options = new BitmapFactory.Options();
AssetFileDescriptor fd = null;
Bitmap b = null;
try {
fd = getContentResolver().openAssetFileDescriptor(uri, "r");
if (fd != null) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, options);
options.inSampleSize = AvatarCache.calculateInSampleSize(options, AvatarCache.AVATAR_BOUNDS, AvatarCache.AVATAR_BOUNDS);
options.inJustDecodeBounds = false;
b = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, options);
try {
fd.close();
} catch (IOException e) {
}
}
} catch (Exception e) {
e.printStackTrace();
}
return b;
}

As you can see, AvatarCache is only used statically in this example. In case you need to manage a lot of images, like a photo lib preview.create AvatarCache in your app instance. Also add memory mgnt methods.
#Override
public void onCreate() {
super.onCreate();
avatarCache = new AvatarCache(this);
}
public void onTrimMemory(int level) {
if (level == TRIM_MEMORY_COMPLETE || level == TRIM_MEMORY_RUNNING_CRITICAL || level == TRIM_MEMORY_RUNNING_LOW) {
if (avatarCache != null)
avatarCache.flush();
}
super.onTrimMemory(level);
}
#Override
public void onLowMemory() {
Log.w(StreamingApp.TAG, "Low memory event received. Clear avatars cache.");
if (avatarCache != null)
avatarCache.flush();
super.onLowMemory();
}
And then you can use it a way like this:
avatarCache.loadUserAvatar(...);
It will automatically load the image and place it to the cache. When the app is short of memory, cache will be flushed.
Hope this helps. It is quite a lot of stuff here but when you go through this once, you will never have issues with images in your android app. Happy coding!
PS. Ask questions if you need. Just be specific with what you need asking and give also some context of your particular use case.

Related

Load large Bitmaps

I have two following methods those load my Bitmaps. And when I've tried to load huge image 2000px x 6000px I've got UIfreeze for about 3 seconds. Can anubody help me to improve loading speed of huge images? I's important for me on screen orientation change, because these methods always have been called by that lib and my UI always freezes for about 3 second before rotate...
/**
* Async task used to get image details
*/
private static class TilesInitTask implements Runnable {
private final WeakReference<ScaleImageView> viewRef;
private final WeakReference<Context> contextRef;
private final WeakReference<DecoderFactory<? extends ImageRegionDecoder>> decoderFactoryRef;
private final Uri source;
private ImageRegionDecoder decoder;
private Exception exception;
public TilesInitTask(ScaleImageView view, Context context, DecoderFactory<? extends ImageRegionDecoder> decoderFactory, Uri source) {
this.viewRef = new WeakReference<>(view);
this.contextRef = new WeakReference<>(context);
this.decoderFactoryRef = new WeakReference<DecoderFactory<? extends ImageRegionDecoder>>(decoderFactory);
this.source = source;
}
public void run() {
ScaleImageView view = null;
try {
String sourceUri = source.toString();
Context context = contextRef.get();
DecoderFactory<? extends ImageRegionDecoder> decoderFactory = decoderFactoryRef.get();
view = viewRef.get();
if (context != null && decoderFactory != null && view != null) {
decoder = decoderFactory.make();
Point dimensions = decoder.init(context, source);
int sWidth = dimensions.x;
int sHeight = dimensions.y;
int exifOrientation = view.getExifOrientation(sourceUri);
if (view.sRegion != null) {
sWidth = view.sRegion.width();
sHeight = view.sRegion.height();
}
view.onTilesInited(decoder, sWidth, sHeight, exifOrientation);
}
} catch (Exception e) {
Log.e(TAG, "Failed to initialise bitmap decoder", e);
this.exception = e;
if (view != null && view.onImageEventListener != null) {
view.onImageEventListener.onImageLoadError(exception);
}
}
}
}
/**
* Called by worker task when decoder is ready and image size and EXIF orientation is known.
*/
private synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) {
// If actual dimensions don't match the declared size, reset everything.
if (this.sWidth > 0 && this.sHeight > 0 && (this.sWidth != sWidth || this.sHeight != sHeight)) {
reset(false);
if (bitmap != null) {
if (!bitmapIsCached) {
bitmap.recycle();
}
bitmap = null;
bitmapIsPreview = false;
bitmapIsCached = false;
}
}
this.decoder = decoder;
this.sWidth = sWidth;
this.sHeight = sHeight;
this.sOrientation = sOrientation;
checkReady();
checkImageLoaded();
invalidate();
requestLayout();
}
/**
* Async task used to load images
*/
private static class TileLoadTask implements Runnable {
private final WeakReference<ScaleImageView> viewRef;
private final WeakReference<ImageRegionDecoder> decoderRef;
private final WeakReference<Tile> tileRef;
private Exception exception;
public TileLoadTask(ScaleImageView view, ImageRegionDecoder decoder, Tile tile) {
this.viewRef = new WeakReference<>(view);
this.decoderRef = new WeakReference<>(decoder);
this.tileRef = new WeakReference<>(tile);
tile.loading = true;
}
public void run() {
ScaleImageView view = null;
try {
view = viewRef.get();
ImageRegionDecoder decoder = decoderRef.get();
Tile tile = tileRef.get();
if (decoder != null && tile != null && view != null && decoder.isReady()) {
synchronized (view.decoderLock) {
// Update tile's file sRect according to rotation
view.fileSRect(tile.sRect, tile.fileSRect);
if (view.sRegion != null) {
tile.fileSRect.offset(view.sRegion.left, view.sRegion.top);
}
tile.bitmap = decoder.decodeRegion(tile.fileSRect, tile.sampleSize);
tile.loading = false;
view.onTileLoaded();
}
} else if (tile != null) {
tile.loading = false;
}
} catch (Exception e) {
Log.e(TAG, "Failed to decode tile", e);
this.exception = e;
if (view != null && view.onImageEventListener != null) {
view.onImageEventListener.onTileLoadError(exception);
}
}
}
}
/**
* Called by worker task when a tile has loaded. Redraws the view.
*/
private synchronized void onTileLoaded() {
checkReady();
checkImageLoaded();
if (isBaseLayerReady() && bitmap != null) {
if (!bitmapIsCached) {
bitmap.recycle();
}
bitmap = null;
bitmapIsPreview = false;
bitmapIsCached = false;
}
invalidate();
}
Working with bitmaps and pictures is a sensitive operation. I suggest you to use a library for these kind of operations suces as
https://github.com/nostra13/Android-Universal-Image-Loader
http://square.github.io/picasso/
Edit:
Here you can how to add and init library.
Correct usage of Universal Image Loader
After you init with best configuration you can use file paths below which is suitable for your operation:
"http://example.com/image.png" // from Web
"file:///mnt/sdcard/image.png" // from SD card
"file:///mnt/sdcard/video.mp4" // from SD card (video thumbnail)
"content://media/external/images/media/13" // from content provider
"content://media/external/video/media/13" // from content provider (video thumbnail)
"assets://image.png" // from assets
"drawable://" + R.drawable.img // from drawables (non-9patch images)
And finally you can load it with:
ImageLoader imageLoader = ImageLoader.getInstance(); // Get singleton instance
imageLoader.displayImage(imageUri, imageView);
Good luck.

Android Bad image quality after scaling bitmap

Android Bad image quality after scaling bitmap;
This is the code I'm using live wallpaper application. There is constant background and how much I have use an image quality, the quality is deteriorating.
To improve the quality of images in the following code where I need to change?
Please could you help?
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.util.SparseArray;
public class BitmapManager {
private static final String TAG = BitmapManager.class.getSimpleName();
private static final boolean DEBUG = true;
private SparseArray<Bitmap> mData = new SparseArray<Bitmap>();
private static BitmapManager mInstance = null;
private static final Object mLock = new Object();
private Context mContext;
private BitmapManager(Context context) {
mContext = context;
}
public static BitmapManager getInstance(Context context) {
if (mInstance == null) {
synchronized (mLock) {
if (mInstance == null) {
mInstance = new BitmapManager(context);
}
}
}
return mInstance;
}
public Bitmap getBitmap(int id) {
return getBitmap(id, -1, -1);
}
/**
* Not thread safe!
*/
public Bitmap getBitmap(int id, int scaleToWidth, int scaleToHeight) {
String name;
if (DEBUG) {
name = mContext.getResources().getResourceName(id);
}
Bitmap b = mData.get(id);
final boolean noScale = scaleToHeight == -1 || scaleToWidth == -1;
if (DEBUG && noScale) {
Log.v(TAG, "Scaling disabled.");
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
boolean bitmap_updated = true;
if (b == null) {
if (DEBUG) {
Log.v(TAG, "Miss! Loading: "+name);
}
b = BitmapFactory.decodeResource(mContext.getResources(), id, options);
} else if ((b.getWidth() > scaleToWidth || b.getHeight() > scaleToHeight) && !noScale) {
b.recycle();
if (DEBUG) {
Log.v(TAG, "Size changed! Reloading: "+name);
}
b = BitmapFactory.decodeResource(mContext.getResources(), id, options);
} else {
if (DEBUG) {
Log.v(TAG, "Skipping, bitmap already loaded");
}
bitmap_updated = false;
}
// Do scaling only if original bitmap is bigger than needed dimensions
if ((b.getWidth() > scaleToWidth || b.getHeight() > scaleToHeight) && !noScale) {
if (DEBUG) {
Log.v(TAG, "Size does not match! Scaling.");
}
Bitmap scaled = Bitmap.createScaledBitmap(b, scaleToWidth, scaleToHeight, false);
b.recycle();
b = scaled;
}
if (bitmap_updated) {
mData.put(id, b);
}
return b;
}
public void clear() {
for(int i = 0; i < mData.size(); i++) {
Bitmap b = mData.valueAt(i);
b.recycle();
}
mData.clear();
}}
try options.inSampleSize = 2; instead of options.inScaled = false;
i have faced same problem which solved with this.

Example using Androids lrucache

I need help understanding androids LruCache. I want to use to load images into my gridview in order make the loading/scrolling better. Can someone post an example code using LruCache please. Thanks in advance.
Below is a class I made for using LruCache, this is based on the presentation Doing More With Less: Being a Good Android Citizen given at Google I/O 2012.
Check out the movie for more information about what I'm doing in the TCImageLoader class:
public class TCImageLoader implements ComponentCallbacks2 {
private TCLruCache cache;
public TCImageLoader(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
int maxKb = am.getMemoryClass() * 1024;
int limitKb = maxKb / 8; // 1/8th of total ram
cache = new TCLruCache(limitKb);
}
public void display(String url, ImageView imageview, int defaultresource) {
imageview.setImageResource(defaultresource);
Bitmap image = cache.get(url);
if (image != null) {
imageview.setImageBitmap(image);
}
else {
new SetImageTask(imageview).execute(url);
}
}
private class TCLruCache extends LruCache<String, Bitmap> {
public TCLruCache(int maxSize) {
super(maxSize);
}
#Override
protected int sizeOf(ImagePoolKey key, Bitmap value) {
int kbOfBitmap = value.getByteCount() / 1024;
return kbOfBitmap;
}
}
private class SetImageTask extends AsyncTask<String, Void, Integer> {
private ImageView imageview;
private Bitmap bmp;
public SetImageTask(ImageView imageview) {
this.imageview = imageview;
}
#Override
protected Integer doInBackground(String... params) {
String url = params[0];
try {
bmp = getBitmapFromURL(url);
if (bmp != null) {
cache.put(url, bmp);
}
else {
return 0;
}
} catch (Exception e) {
e.printStackTrace();
return 0;
}
return 1;
}
#Override
protected void onPostExecute(Integer result) {
if (result == 1) {
imageview.setImageBitmap(bmp);
}
super.onPostExecute(result);
}
private Bitmap getBitmapFromURL(String src) {
try {
URL url = new URL(src);
HttpURLConnection connection
= (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
Bitmap myBitmap = BitmapFactory.decodeStream(input);
return myBitmap;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
}
#Override
public void onLowMemory() {
}
#Override
public void onTrimMemory(int level) {
if (level >= TRIM_MEMORY_MODERATE) {
cache.evictAll();
}
else if (level >= TRIM_MEMORY_BACKGROUND) {
cache.trimToSize(cache.size() / 2);
}
}
}
I've found a really easy way that work perfectly for me...
This is the Cache.java class. In this class, the static getInstance() method enables us to create only one cache instance in the whole application. getLru() method is used to retrieve the cached object, it will be shown later how to use it. This cache is generic, meaning you can save any Object type into it. The cache memory size here is set to 1024. It can be changed if it is too small:
import android.support.v4.util.LruCache;
public class Cache {
private static Cache instance;
private LruCache<Object, Object> lru;
private Cache() {
lru = new LruCache<Object, Object>(1024);
}
public static Cache getInstance() {
if (instance == null) {
instance = new Cache();
}
return instance;
}
public LruCache<Object, Object> getLru() {
return lru;
}
}
This is the code in your activity where you save the bitmap to the cache:
public void saveBitmapToCahche(){
//The imageView that you want to save it's bitmap image resourse
ImageView imageView = (ImageView) findViewById(R.id.imageViewID);
//To get the bitmap from the imageView
Bitmap bitmap = ((BitmapDrawable)imageview.getDrawable()).getBitmap();
//Saving bitmap to cache. it will later be retrieved using the bitmap_image key
Cache.getInstance().getLru().put("bitmap_image", bitmap);
}
This is the code where you retrieve the bitmap from the cache, then set an imageView to this bitmap:
public void retrieveBitmapFromCache(){
//The imageView that you want to set to the retrieved bitmap
ImageView imageView = (ImageView) findViewById(R.id.imageViewID);
//To get bitmap from cache using the key. Must cast retrieved cache Object to Bitmap
Bitmap bitmap = (Bitmap)Cache.getInstance().getLru().get("bitmap_image");
//Setting imageView to retrieved bitmap from cache
imageView.setImageBitmap(bitmap));
}
THAT'S ALL! As you can see this is rather easy and simple.
EXAMPLE:
In my application, All the views are saved in class variables so they can be seen by all the methods in the class. In my first activity, I save the image bitmap to the cache in an onClickButton() method, right before I start a new activity using intent. I also save a string value in my cache:
public void onClickButton(View v){
Bitmap bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
String name = textEdit.getText().toString();
Cache.getInstance().getLru().put("bitmap_image", bitmap);
Cache.getInstance().getLru().put("name", name);
Intent i = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(i);
}
Then I navigate from the second activity to a third activity also using intents. In the last activity I save other objects into my cache, then go back to the first activity using an intent. Once I'm back in the first activity, the onCreate() method will start. In that method, I check if my cache has any bitmap value or any String value separately (based on my application business):
public ImageView imageView;
public EditText editText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
//...Other code...
//The imageView that you want to save it's bitmap image resourse
imageView = (ImageView) findViewById(R.id.imageViewID);
//The editText that I want to save it's text into cache
editText = (EditText)findViewById(R.id.editTextID);
if(Cache.getInstance().getLru().get("name")!=null){
editText.setText(Cache.getInstance().getLru().get("name").toString());
}
if(Cache.getInstance().getLru().get("bitmap_image")!=null){
imageView.setImageBitmap((Bitmap)Cache.getInstance().getLru().get("bitmap_image"));
}
//...Other code...
}
Take a look at Caching Bitmaps where the use of LruCache is demonstrated.
The relevant portion of the code from the page is as follows:-
private LruCache mMemoryCache;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final int memClass = ((ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE)).getMemoryClass();
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 8;
mMemoryCache = new LruCache(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number of items.
return bitmap.getByteCount();
}
};
...
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
https://techienotes.info/2015/08/28/caching-bitmaps-in-android-using-lrucache/
This link has a full project having sample application to load images into Gridview using LruCache.
This class is using LruCache and taken from the code given in the link
public class ImageAdapter extends BaseAdapter{
private String TAG = getClass().getSimpleName();
Context mContext;
ArrayList<Uri> imageList;
private LruCache<String, Bitmap> mLruCache;
public ImageAdapter (Context context){
mContext = context;
//Find out maximum memory available to application
//1024 is used because LruCache constructor takes int in kilobytes
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/4th of the available memory for this memory cache.
final int cacheSize = maxMemory / 4;
Log.d(TAG, "max memory " + maxMemory + " cache size " + cacheSize);
// LruCache takes key-value pair in constructor
// key is the string to refer bitmap
// value is the stored bitmap
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes
return bitmap.getByteCount() / 1024;
}
};
imageList = new ArrayList<Uri>();
//Change this directory to where the images are stored
String imagesFolderPath = Environment.getExternalStorageDirectory().getPath()+"/backups/";
File imageSrcDir = new File (imagesFolderPath);
// if directory not present, build it
if (!imageSrcDir.exists()){
imageSrcDir.mkdirs();
}
ArrayList<File> imagesInDir = getImagesFromDirectory(imageSrcDir);
for (File file: imagesInDir){
// imageList will hold Uri of all images
imageList.add(Uri.fromFile(file));
}
}
#Override
public int getCount() {
return imageList.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
/**
*
* #param position The position of the item within the
* adapter's data set of the item whose view we want.
* #param convertView it is the view to be reused
* #param parent The parent that this view will eventually be attached to
* #return a View corresponding to the data at the specified position.
*/
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
Bitmap thumbnailImage = null;
if (convertView == null){
imageView = new ImageView(mContext);
imageView.setLayoutParams(
//150,150 is size of imageview to display image
new GridView.LayoutParams(150, 150));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
}
else {
imageView = (ImageView)convertView;
}
// Use the path as the key to LruCache
final String imageKey = imageList.get(position).toString();
//thumbnailImage is fetched from LRU cache
thumbnailImage = getBitmapFromMemCache(imageKey);
if (thumbnailImage == null){
// if asked thumbnail is not present it will be put into cache
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(imageKey);
}
imageView.setImageBitmap(thumbnailImage);
return imageView;
}
/**
* This function returns the files from a directory
* #param parentDirPath source directory in which images are located
* #return list of Files
*/
private ArrayList<File> getImagesFromDirectory (File parentDirPath){
ArrayList <File> listOfImages = new ArrayList<File>();
File [] fileArray = null;
if ( parentDirPath.isDirectory() ){//parentDirPath.exists() &&
// &&
// parentDirPath.canRead()){
fileArray = parentDirPath.listFiles();
}
if (fileArray == null){
return listOfImages; // return empty list
}
for (File file: fileArray){
if (file.isDirectory()){
listOfImages.addAll(getImagesFromDirectory(file));
}
else {
// Only JPEG and PNG formats are included
// for sake of simplicity
if (file.getName().endsWith("png") ||
file.getName().endsWith("jpg")){
listOfImages.add(file);
}
}
}
return listOfImages;
}
/**
* This function will return the scaled version of original image.
* Loading original images into thumbnail is wastage of computation
* and hence we will take put scaled version.
*/
private Bitmap getScaledImage (String imagePath){
Bitmap bitmap = null;
Uri imageUri = Uri.parse (imagePath);
try{
BitmapFactory.Options options = new BitmapFactory.Options();
/**
* inSampleSize flag if set to a value > 1,
* requests the decoder to sub-sample the original image,
* returning a smaller image to save memory.
* This is a much faster operation as decoder just reads
* every n-th pixel from given image, and thus
* providing a smaller scaled image.
* 'n' is the value set in inSampleSize
* which would be a power of 2 which is downside
* of this technique.
*/
options.inSampleSize = 4;
options.inScaled = true;
InputStream inputStream = mContext.getContentResolver().openInputStream(imageUri);
bitmap = BitmapFactory.decodeStream(inputStream, null, options);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bitmap;
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mLruCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mLruCache.get(key);
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground(String... params) {
final Bitmap bitmap = getScaledImage(params[0]);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
// onPostExecute() sets the bitmap fetched by doInBackground();
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = (ImageView)imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
}
Utility Class to save and retrieve Bitmap from own Cache.
package com.roomco.android.utils;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
public class MyCache {
private static MyCache instance;
private LruCache<Object, Object> lru;
private MyCache() {
lru = new LruCache<Object, Object>(1024);
}
public static MyCache getInstance() {
if (instance == null) {
instance = new MyCache();
}
return instance;
}
public LruCache<Object, Object> getLru() {
return lru;
}
public void saveBitmapToCahche(String key, Bitmap bitmap){
MyCache.getInstance().getLru().put(key, bitmap);
}
public Bitmap retrieveBitmapFromCache(String key){
Bitmap bitmap = (Bitmap)MyCache.getInstance().getLru().get(key);
return bitmap;
}
}
Usage:
//Save bitmap in cache
MyCache.getInstance().saveBitmapToCahche("your_key",bitmap);
// Get bitmap from cache
MyCache.getInstance().retrieveBitmapFromCache("your_key");

Android List view update

I am struggling to update a list view with data from a database, this works nicely by using a SimpleCursorAdapter. But the image view on the rows is not updated on the activity start, I have to scroll through the list a few times and only then the images are loaded in the image view.
This is the binder i am using for the SimpleCursorAdapter:
private class PromotionViewBinder implements SimpleCursorAdapter.ViewBinder {
private int done;
public boolean setViewValue(View view, Cursor cursor, int index) {
Log.e(""+cursor.getCount(),"");
View tmpview = view;
if (index == cursor.getColumnIndex(PromotionsTable.SEEN_COL)) {
boolean read = cursor.getInt(index) > 0 ? true : false;
TextView title = (TextView) tmpview;
if (!read) {
title.setTypeface(Typeface.DEFAULT_BOLD, 0);
} else {
title.setTypeface(Typeface.DEFAULT);
}
return true;
} else if (tmpview.getId() == R.id.promotions_list_row_image){
String imageURL = cursor.getString(index);
Log.e("",imageURL);
imageRetriever.displayImage(imageURL, (ImageView)tmpview);
return true;
} else {
return false;
}
}
}
The image retriever class is the LazyList example from here. As you will see this is using a runnable to retrieve the images and once the task is done is automatically updating the given imageView...Do you think that the reference to the imageView is lost somewhere on the way?
Thanx in advance,
Nick
package com.tipgain.promotions;
The image retriever class:
/**
* This class is used for retrieving images from a given web link. it uses local
* storage and memory to store the images. Once a image is downloaded
* successfully the UI gets updated automatically.
*
*
*/
public class ImageRetriever {
private final String TAG = ImageRetriever.class.getName();
private MemoryImageCache memoryImgCache = new MemoryImageCache();
private LocalStorageImageCache localFileCache;
private Map<ImageView, String> imageViewHolders = Collections
.synchronizedMap(new WeakHashMap<ImageView, String>());
private ExecutorService execService;
final int defaultImageID = R.drawable.photo_not_available;
public ImageRetriever(Context context) {
localFileCache = new LocalStorageImageCache(context);
execService = Executors.newFixedThreadPool(5);
}
public void displayImage(String url, ImageView imageView) {
imageViewHolders.put(imageView, url);
Bitmap bmp = memoryImgCache.retrieve(url);
if (bmp != null) {
Log.e("case 1", " " + (bmp != null));
imageView.setImageBitmap(bmp);
} else {
Log.e("case 2", " " + (bmp == null));
addImageToQueue(url, imageView);
imageView.setImageResource(defaultImageID);
}
}
private void addImageToQueue(String url, ImageView imageView) {
NextImageToLoad img = new NextImageToLoad(url, imageView);
execService.submit(new ImagesRetriever(img));
}
/**
* This method is used for retrieving the Bitmap Image.
*
* #param url
* String representing the url pointing to the image.
* #return Bitmap representing the image
*/
private Bitmap getBitmap(String url) {
File imageFile = localFileCache.getFile(url);
// trying to get the bitmap from the local storage first
Bitmap bmp = decodeImageFile(imageFile);
if (bmp != null)
return bmp;
// if the file was not found locally we retrieve it from the web
try {
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) imageUrl
.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
InputStream is = conn.getInputStream();
OutputStream os = new FileOutputStream(imageFile);
Utils.CopyStream(is, os);
os.close();
bmp = decodeImageFile(imageFile);
return bmp;
} catch (MalformedURLException e) {
Log.e(TAG, e.getMessage());
} catch (FileNotFoundException e) {
Log.e(TAG, e.getMessage());
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
return null;
}
/**
* This method is used for decoding a given image file. Also, to reduce
* memory, the image is also scaled.
*
* #param imageFile
* #return
*/
private Bitmap decodeImageFile(File imageFile) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(imageFile), null,
options);
// Find the correct scale value. It should be the power of 2.
// Deciding the perfect scaling value. (^2).
final int REQUIRED_SIZE = 100;
int tmpWidth = options.outWidth, tmpHeight = options.outHeight;
int scale = 1;
while (true) {
if (tmpWidth / 2 < REQUIRED_SIZE
|| tmpHeight / 2 < REQUIRED_SIZE)
break;
tmpWidth /= 2;
tmpHeight /= 2;
scale *= 2;
}
// decoding using inSampleSize
BitmapFactory.Options option2 = new BitmapFactory.Options();
option2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(imageFile),
null, option2);
} catch (FileNotFoundException e) {
Log.e(TAG, e.getLocalizedMessage());
}
return null;
}
private boolean reusedImage(NextImageToLoad image) {
Context c = image.imageView.getContext();
c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);
String tag = imageViewHolders.get(image.imageView);
if ((tag == null) || (!tag.equals(image.url)))
return true;
return false;
}
/**
* Clears the Memory and Local cache
*/
public void clearCache() {
memoryImgCache.clear();
localFileCache.clear();
}
/**
* This class implements a runnable that is used for updating the promotions
* images on the UI
*
*
*/
class UIupdater implements Runnable {
Bitmap bmp;
NextImageToLoad image;
public UIupdater(Bitmap bmp, NextImageToLoad image) {
this.bmp = bmp;
this.image = image;
Log.e("", "ui updater");
}
public void run() {
Log.e("ui updater", "ui updater");
if (reusedImage(image))
return;
Log.e("nick", "" + (bmp == null) + " chberugv");
if (bmp != null){
image.imageView.setImageBitmap(bmp);
Context c = image.imageView.getContext();
c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);
}else
image.imageView.setImageResource(defaultImageID);
}
}
private class ImagesRetriever implements Runnable {
NextImageToLoad image;
ImagesRetriever(NextImageToLoad image) {
this.image = image;
}
public void run() {
Log.e("images retirever", " images retriever");
if (reusedImage(image))
return;
Bitmap bmp = getBitmap(image.url);
memoryImgCache.insert(image.url, bmp);
if (reusedImage(image))
return;
UIupdater uiUpdater = new UIupdater(bmp, image);
Activity activity = (Activity) image.imageView.getContext();
activity.runOnUiThread(uiUpdater);
//Context c = image.imageView.getContext();
//c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);
}
}
/**
* This class encapsulates the image being downloaded.
*
* #author Nicolae Anca
*
*/
private class NextImageToLoad {
public String url;
public ImageView imageView;
public NextImageToLoad(String u, ImageView i) {
url = u;
imageView = i;
}
}
}
Modified Runnable:
class UIupdater implements Runnable {
Bitmap bmp;
NextImageToLoad image;
public UIupdater(Bitmap bmp, NextImageToLoad image) {
this.bmp = bmp;
this.image = image;
}
public void run() {
if (reusedImage(image))
return;
if (bmp != null){
image.imageView.setImageBitmap(bmp);
Context c = image.imageView.getContext();
c.getContentResolver().notifyChange(PromotionsProvider.CONTENT_URI, null);
}else
image.imageView.setImageResource(defaultImageID);
}
}
Thats an interesting way to do what you are doing. Have you tried extending the Simple Cursor Adapter?
What you do is implement a ViewHolder and put your imageview in it.
Then in your ImageRetriever, write a Listener which will be called once the image is ready and retrieved.
Implement this listener in the Viewholder.
You create the view in getView() and request for the image in BindView().
Once the image gets loaded, the list will be refreshed automatically.
one way to do it is by calling notifyDataSetChenged on listview, and another was is to have adapter as member variable and when something changes on listview you call a function that assigns new listadapter to member adapter. That way your list will be redraw on change.
I guess, you have to use some handler, calling after image load, which will call notifyDataSetChanged for list adapter

Issue with animation in image loader

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);
}
}

Categories

Resources