LruCache not working with imageAdapter - android

I'm having 11 images in my resources. I use a GridView to display them.
Because images take a lot of space in my ImageAdapter class I calculate sample size and then decode the resouce as per the tutorial here to efficient load an image.
Before I return the decoded bitmap from decodeSampledBitmapFromResource I'm adding the bitmap to LruCache :
Bitmap b = BitmapFactory.decodeResource(res, resId, options);
String key = Integer.toString(resId);
// Log.i("byte", "b.getByteCount()==" + b.getByteCount());
addBitmapToMemoryCache(key, b); //add to cache
which leads to my getView() to try and get cached bitmap if it's not null - else use the method I mentioned above.
For adding and getting bitmaps from cache I'm using :
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
Log.i("addbitmaptocache", "Add key= " + key);
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
Log.i("getbitmaptocache", "GET KEY= " + key);
return mMemoryCache.get(key);
}
What happens is that if ( cachedBitmap != null ) is never true which makes me believe something is wrong.
full code for the class :
public class ImageAdapter extends BaseAdapter {
private Context mContext;
private int wid;
private static final String AdapterTAG="adapterTAG";
// private ImageView imageView;
private Bitmap mBitmap;
private LruCache<String, Bitmap> mMemoryCache;
public ImageAdapter(Context c, int wid) {
mContext = c;
this.wid = wid;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItem viewHolder;
int new_width = wid/2;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.grid_item, parent, false);
// well set up the ViewHolder
viewHolder = new ViewHolderItem();
viewHolder.textViewItem = (TextView) convertView.findViewById(R.id.textId);
viewHolder.imageViewItem = (ImageView) convertView.findViewById(R.id.imageId);
// store the holder with the view.
convertView.setTag(viewHolder);
} else{
viewHolder = (ViewHolderItem) convertView.getTag();
}
/** ******************** Caching ******************** **/
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Log.i("max", "maxMemory== " + maxMemory );
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 4;
// Log.i("cachesize", "cachesize== " + cacheSize);
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
//Log.i("mMemoryCache", "mMemoryCache= " + mMemoryCache);
viewHolder.textViewItem.setId(position);
viewHolder.imageViewItem.getLayoutParams().width = new_width -5;
viewHolder.imageViewItem.getLayoutParams().height = new_width -5;
viewHolder.imageViewItem.setScaleType(ImageView.ScaleType.CENTER_CROP);
viewHolder.imageViewItem.setPadding(0, 0, 0, 0);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
final String imageKey = String.valueOf(mThumbIds[position]);
final Bitmap cachedBitmap = getBitmapFromMemCache(imageKey); // use cached Bitmap or ... decode
if ( cachedBitmap != null ) {
Log.i("cached", "CACHED BITMAP FOR THE WIN!!!!");
viewHolder.imageViewItem.setImageBitmap(cachedBitmap);
} else {
viewHolder.imageViewItem.setImageBitmap(decodeSampledBitmapFromResource(mContext.getResources(), mThumbIds[position] , new_width, 200));
}
return convertView;
}
static class ViewHolderItem {
TextView textViewItem;
ImageView imageViewItem;
}
// references to our images
private Integer[] mThumbIds = {
R.drawable.wallpaper0, R.drawable.wallpaper1,
R.drawable.wallpaper2, R.drawable.wallpaper3,
R.drawable.wallpaper4, R.drawable.wallpaper5,
R.drawable.wallpaper6, R.drawable.wallpaper7,
R.drawable.wallpaper8, R.drawable.wallpaper9,
R.drawable.wallpaper10
};
public Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// Log.i("req", "reqWidth= " + reqWidth + " reqHeight=" + reqHeight ); // params
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Log.i("options", "Width== " + imageWidth + " Height== " + imageHeight + " Type== " + imageType );
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap b = BitmapFactory.decodeResource(res, resId, options);
String key = Integer.toString(resId);
// Log.i("byte", "b.getByteCount()==" + b.getByteCount());
addBitmapToMemoryCache(key, b); //add to cache
return b;
}
public 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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
Log.i("sample", "size=" +inSampleSize );
return inSampleSize;
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
Log.i("addbitmaptocache", "Add key= " + key);
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
Log.i("getbitmaptocache", "GET KEY= " + key);
return mMemoryCache.get(key);
}
}

You should not be instantiating the mMemoryCache in the getView() method. Place that in the constructor. That's why it can never find the bitmap in cache, because you are constantly destroying and recreating it.

Related

Xamarin Loading Bitmaps Efficiently For Gridview

So i saw this xamarin document about loading large bitmaps efficiently. Yet im struggling to implement it for the gridview.
https://developer.xamarin.com/recipes/android/resources/general/load_large_bitmaps_efficiently/
So how can we implement it for a gridview's adapter?
Thank you in the advance.
So how can we implement it for a gridview's adapter?
You can create a class(ThumbImageFactory below) to wrap all the functions mentioned in the document:
public class ThumbImageFactory
{
public readonly Context context;
public ThumbImageFactory(Context c)
{
context = c;
}
public static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
float height = options.OutHeight;
float width = options.OutWidth;
double inSampleSize = 1D;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = (int)(height / 2);
int halfWidth = (int)(width / 2);
// Calculate a inSampleSize that is a power of 2 - the decoder will use a value that is a power of two anyway.
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth)
{
inSampleSize *= 2;
}
}
return (int)inSampleSize;
}
public Bitmap LoadScaledDownBitmapForDisplay(Resources res, BitmapFactory.Options options, int reqWidth, int reqHeight,int resourceId)
{
// Calculate inSampleSize
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeResource(res, resourceId, options);
}
public BitmapFactory.Options GetBitmapOptionsOfImage(int resourceId)
{
BitmapFactory.Options options = new BitmapFactory.Options
{
InJustDecodeBounds = true
};
// The result will be null because InJustDecodeBounds == true.
Bitmap result = BitmapFactory.DecodeResource(context.Resources, resourceId, options);
int imageHeight = options.OutHeight;
int imageWidth = options.OutWidth;
return options;
}
}
Create an ThumbImageFactory instance in your Adapter and call the functions in GetView:
public class ImageAdapter : BaseAdapter
{
private readonly Context context;
private ThumbImageFactory thumbFactory;
public ImageAdapter(Context c)
{
context = c;
thumbFactory = new ThumbImageFactory(c);
}
...
public override View GetView(int position, View convertView, ViewGroup parent)
{
ImageView imageView;
if (convertView == null)
{
imageView = new ImageView(this.context);
imageView.LayoutParameters = new AbsListView.LayoutParams(150, 150);
imageView.SetScaleType(ImageView.ScaleType.CenterCrop);
imageView.SetPadding(0, 0, 0, 0);
}
else
{
imageView = (ImageView)convertView;
}
Bitmap bitmap = GetThumbImage(thumbIds[position]);
imageView.SetImageBitmap(bitmap);
return imageView;
}
public Bitmap GetThumbImage(int resourceId)
{
BitmapFactory.Options options = thumbFactory.GetBitmapOptionsOfImage(resourceId);
Bitmap bitmap=thumbFactory.LoadScaledDownBitmapForDisplay(context.Resources, options, 150, 150, resourceId);
return bitmap;
}
}
Notes: we can't modify the GetView to async, so I changed all the functions in document to sync functions. Here is is complete Demo.

Gridview images mixed up on scroll

I'm trying to solve this issue about mixed up images in gridview when scrolling. I already saw similar posts here about this issue but unfortunately I didn't solve it yet.
I use asynctask to load the images in the gridView, and I have only imageView in the grid.
Thanks!
Here is my code:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
ImageView img;
if (convertView == null) {
img = new ImageView(GalleryActivity.this);
WindowManager wm = (WindowManager) GalleryActivity.this.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
width = width / 3 - 4;
img.setLayoutParams(new AbsListView.LayoutParams(width, width));
img.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
img = (ImageView) convertView;
}
File dics = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
File dir = new File(dics, "yyy");
if (!dir.exists() && !dir.mkdirs()) {
img.setImageResource(holder(position + 1));
} else {
String file_name = dir.getAbsolutePath() + "/" + (position + 1) + ".jpg";
if (new File(file_name).exists()) {
BitmapWorkerTask task = new BitmapWorkerTask(img);
task.execute(file_name);
items[position].setStatus(1);
} else {
img.setImageResource(holder(position + 1));
}
}
return img;
}
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private String data;
public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(String... params) {
data = params[0];
return decodeSampledBitmapFromResource(data,100,100);
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
public Bitmap decodeSampledBitmapFromResource(String resId,
int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId,options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(resId);
}
public 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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
The reason behind your problem is that the BitmapWorkerTask is not finished by starting order so it could override the newest result by older one.
To avoid this you have to keep one BitmapWorkerTask for each view and cancel the old one if it's still working so it doesn't override the result of the new BitmapWorkerTask
You can use setTag and getTag methods to achive this purpose and keep a reference of the latest BitmapWorkerTask working on each view.
You are not setting Tag to ImageView while starting image loading thread that why is set image to current one show at screen when thread completes it job .
you should pass some id as a parameter in BitmapWorkerTask class .

Android Gridview Recycling Images Even With ViewHolder/AsyncTask Best Practices

the gridview in the below code still does not hold images in memory and load them using the LRUCache once the user scrolls. The view still gets recycled.
I am following best practices like using AsyncTask and ViewHolder from the Google Docs. Also, loading about 400 high quality pictures is still taking about 10 seconds before the first image is shown. What can I do to fix the recycling issue and speed the loading of the high-res images up?
public class CustomGridOfFilesEXIFDataAdapter extends ArrayAdapter<EXIFDataMarkerHolder> {
private LayoutInflater layoutInflater;
public int position;
private LruCache<String, Bitmap> mMemoryCache;
ArrayList<Uri> imageList;
//http://www.coderzheaven.com/2013/09/01/faster-loading-images-gridviews-listviews-android-menory-caching-complete-implemenation-sample-code/
public CustomGridOfFilesEXIFDataAdapter(Context context, ArrayList<EXIFDataMarkerHolder> listOfFiles) {
super(context, 0, listOfFiles);
layoutInflater = LayoutInflater.from(context);
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number
// of items.
return bitmap.getByteCount();
}
};
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolderItems viewHolder;
EXIFDataMarkerHolder fileExifData = getItem(position);
if(convertView==null){
convertView = layoutInflater.inflate(R.layout.maps_image_grid_single, null);
viewHolder = new ViewHolderItems();
viewHolder.vhImage = (ImageView) convertView.findViewById(R.id.img);
viewHolder.vhImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
viewHolder.position = position;
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolderItems) convertView.getTag();
}
if (viewHolder.vhImage != null) {
final String imageKey = fileExifData.filepath;
final Bitmap bm = getBitmapFromMemCache(imageKey);
if (bm == null) {
if (cancelPotentialDownload(fileExifData.filepath, viewHolder.vhImage)) {
ImageConfiguratorAsyncTask task = new ImageConfiguratorAsyncTask(viewHolder.vhImage);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
viewHolder.vhImage.setImageDrawable(downloadedDrawable);
task.execute(fileExifData.filepath);
}
}
}
return convertView;
}
static class ViewHolderItems {
ImageView vhImage;
int position;
}
//region "Async File Retrieval"
class ImageConfiguratorAsyncTask extends AsyncTask<String, ViewHolderItems, Bitmap> {
private String filepath;
private final WeakReference<ImageView> imageViewReference;
public ImageConfiguratorAsyncTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground(String... params) {
Bitmap b = downloadBitmap(params[0]);
addBitmapToMemoryCache(String.valueOf(params[0]), b);
return b;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
ImageConfiguratorAsyncTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
}
}
}
private Bitmap downloadBitmap(String filepath) {
try {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filepath, options);
options.inSampleSize = ImageManipulation.calculateInSampleSize(options, 125, 125);
options.inJustDecodeBounds = false;
Bitmap scaledDownFile = BitmapFactory.decodeFile(filepath, options);
scaledDownFile = Bitmap.createScaledBitmap(scaledDownFile, 125, 125, true);
//scaledDownFile = ImageManipulation.decodeBitmapFromFile(filepath, 50, 50);
return scaledDownFile;
} catch (Exception e) {
} finally {
}
return null;
}
}
static class DownloadedDrawable extends ColorDrawable {
private final WeakReference<ImageConfiguratorAsyncTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(ImageConfiguratorAsyncTask bitmapDownloaderTask) {
super(Color.WHITE);
bitmapDownloaderTaskReference =
new WeakReference<ImageConfiguratorAsyncTask>(bitmapDownloaderTask);
}
public ImageConfiguratorAsyncTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
ImageConfiguratorAsyncTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.filepath;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
// The same URL is already being downloaded.
return false;
}
}
return true;
}
private static ImageConfiguratorAsyncTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return (Bitmap) mMemoryCache.get(key);
}
//endregion
}
public class ImageManipulation {
public static Bitmap decodeBitmapFromFile(String filepath, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filepath, options);
// Calculate inSampleSize
options.inSampleSize = ImageManipulation.calculateInSampleSize(options, 20, 20);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap scaledDownFile = BitmapFactory.decodeFile(filepath, options);
scaledDownFile = ImageManipulation.getResizedBitmap(scaledDownFile, 100);
return scaledDownFile;
}
public static Bitmap getResizedBitmap(Bitmap image, int maxSize) {
int width = image.getWidth();
int height = image.getHeight();
float bitmapRatio = (float)width / (float) height;
if (bitmapRatio > 0) {
width = maxSize;
height = (int) (width / bitmapRatio);
} else {
height = maxSize;
width = (int) (height * bitmapRatio);
}
return Bitmap.createScaledBitmap(image, width, height, true);
}
public 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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}

java.lang.OutOfMemoryError while display the images in gallery

When i was loading images form sd card i got the exception
java.lang.OutOfMemoryError at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
Here is my code:
public class Images extends Activity implements OnItemLongClickListener {
private Uri[] mUrls;
String[] mFiles = null;
ImageView selectImage;
Gallery g;
static final String MEDIA_PATH = new String("/mnt/sdcard/DCIM/Camera/");
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selectImage = (ImageView) findViewById(R.id.image);
g = (Gallery) findViewById(R.id.gallery);
File images = new File(MEDIA_PATH);
Log.i("files", images.getAbsolutePath());
File[] imagelist = images.listFiles(new FilenameFilter() {
#Override
public boolean accept(File dir, String name) {
return ((name.endsWith(".jpg")) || (name.endsWith(".png")));
}
});
Log.i("files", imagelist.toString());
String[] mFiles = null;
mFiles = new String[imagelist.length];
for (int i = 0; i < imagelist.length; i++) {
mFiles[i] = imagelist[i].getAbsolutePath();
}
System.out.println(mFiles.length);
mUrls = new Uri[mFiles.length];
System.out.println(mUrls.length);
for (int i = 0; i < mFiles.length; i++) {
mUrls[i] = Uri.parse(mFiles[i]);
}
g.setAdapter(new ImageAdapter(this));
// g.setOnItemSelectedListener(this);
g.setOnItemLongClickListener(this);
}
public class ImageAdapter extends BaseAdapter {
// int mGalleryItemBackground;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return mUrls.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
Log.i("ok5", "ok");
ImageView i = new ImageView(mContext);
i.setImageURI(mUrls[position]);
Log.i("ok", "ok");
i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
i.setLayoutParams(new Gallery.LayoutParams(100, 100));
return i;
}
private Context mContext;
}
#Override
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
selectImage.setImageURI(mUrls[arg2]);
System.out.println("path: "+mUrls[arg2]);
Uri f = mUrls[arg2];
File f1 = new File(f.toString());
System.out.println("f1: "+f1);
return false;
}
While you load large bitmap files, BitmapFactory class provides several decoding methods (decodeByteArray(), decodeFile(), decodeResource(), etc.).
STEP 1
Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it.
STEP 2
To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize to true in your BitmapFactory.Options object.
For example, an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image.
Here’s a method to calculate a sample size value that is a power of two based on a target width and height:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public 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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Please read this link for details. http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

Only the original Thread error

I am baffled by this error -
Only the original thread that created a view hierarchy can touch its views.
I have a class, which is called within a runnable/thread block in the UI. No attempt - as far as I can see ??? - is made to manipulate the UI within that runnable, or the class it calls, as below.....
public class MonthSort {
Handler handler;
int imageWidth;
List<PhotoData> photoList;
public MonthSort(Handler handler2, int width, List<PhotoData> pList) {
photoList = new ArrayList<PhotoData>();
photoList = pList;
imageWidth = width;
handler = handler2;
}
public void sortFiles()
{
int month, photoCount;
File fileName = new File("");
Message msg = handler.obtainMessage();
for (int i = 0; i < 12; i++) {
month = i + 1;
photoCount = 0;
for (PhotoData pd : photoList) {
if(month == pd.month)
{
if(photoCount == 0)
fileName = pd.fileName;
photoCount++;
}
}
if(photoCount != 0)
{
Bundle bundle = new Bundle();
bundle.putString("filename", fileName.toString());
bundle.putInt("month", month);
bundle.putInt("count", photoCount);
byte[] thumbNail = getThumbnail(fileName, imageWidth);
bundle.putByteArray("thumbnail", thumbNail);
msg.setData(bundle);
handler.dispatchMessage(msg);
}
}
Bundle bundle = new Bundle();
bundle.putBoolean("end", true);
msg.setData(bundle);
handler.dispatchMessage(msg);
}
private byte[] getThumbnail(File file, int size)
{
/** The object of this code is to reduce the bitmap for thumbnail display,
* Not just to reduce dimensions, but to reduce the physical size of the
* bitmap ready, so that several bitmaps can remain in memory without
* an outOfMemoryException error.*/
byte[] thumbnail;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(
file.toString(), options);
options.inSampleSize = calculateInSampleSize(
options, imageWidth, imageWidth);
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(file.toString(),
options);
/*now the size of the Bitmap is manageable, we set about sizing the
* thumbnail correctly, preserving the Aspect Ratio */
final int REQUIRED_SIZE = imageWidth;
int thumbHeight = REQUIRED_SIZE, thumbWidth = REQUIRED_SIZE;
float ratio = (float) bitmap.getWidth() // Work out the aspect ratio.
/ (float) bitmap.getHeight();
if (ratio == 1) {
thumbHeight = REQUIRED_SIZE;
thumbWidth = REQUIRED_SIZE;
} else if (ratio < 1) {
thumbHeight = REQUIRED_SIZE;
thumbWidth = (int) ((float) REQUIRED_SIZE * (float) ratio);
} else {
thumbWidth = REQUIRED_SIZE;
thumbHeight = (int) ((float) REQUIRED_SIZE / (float) ratio);
}
Bitmap bitmap2 = Bitmap.createScaledBitmap(
bitmap, thumbWidth, thumbHeight, false);
ByteArrayOutputStream out;
try {
out = new ByteArrayOutputStream();
bitmap2.compress(CompressFormat.JPEG, 30, out); // Compress the bitmap
thumbnail = out.toByteArray();
out.close(); // close the out stream.
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
thumbnail = new byte[1];
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
thumbnail = new byte[1];
}
return thumbnail;
}
private int calculateInSampleSize(Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if(height > reqHeight || width > reqWidth)
{
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
The main thread has the handler at the top (See code below - Just the handler code for brevity) as normal, and uses the notifyDataSetChanged() method of a custom adapter (code included)...
public class MonthActivity extends Activity {
List<PhotoData> photoList;
static List<MonthData> photos;
int imageWidth;
GridView photoGrid;
static ImageAdapter2 iAdapter2;
static int year;
Thread monthSortThread;
static Handler handler2 = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg)
{
super.handleMessage(msg);
Bundle bundle = msg.getData(); // Get the message sent to the Handler.
boolean ended = bundle.getBoolean("end");
if(ended)
{
iAdapter2.notifyDataSetChanged();
//Toast.makeText(getBaseContext(), "FINISHED !!!", Toast.LENGTH_LONG).show();
} else
{
MonthData md = new MonthData();
md.monthValue = bundle.getInt("month");
md.monthString = getMonthString(md.monthValue);
Log.d("Debug", md.monthString + " " + String.valueOf(year));
md.count = bundle.getInt("count");
byte[] tn = bundle.getByteArray("thumbnail");
md.thumbnail = BitmapFactory.decodeByteArray(tn, 0, tn.length);
photos.add(md);
iAdapter2.notifyDataSetChanged();
}
}
};
(Adapter code)
public class ImageAdapter2 extends BaseAdapter{
List<MonthData> photos;
Context context;
int year, imageWidth;
public ImageAdapter2 (Context ct, List<MonthData> pList, int yr, int i) {
photos = new ArrayList<MonthData>();
photos = pList;
context = ct;
year = yr;
imageWidth = i;
}
#Override
public int getCount() {
return photos.size();
}
#Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public long getItemId(int arg0) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View myView = null;
if(convertView == null)
{
LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
myView = li.inflate(R.layout.grid_cell, null);
} else
{
myView = convertView;
}
TextView tv = (TextView)myView.findViewById(R.id.photoText);
if(year == 0)
{
int count = photos.get(position).count;
tv.setText(String.valueOf(count));
} else
{
int count = photos.get(position).count;
String month = photos.get(position).monthString;
String yearString = String.valueOf(year);
tv.setText(month + " " + yearString + " (" + String.valueOf(count) + ")");
}
ImageView iv = (ImageView)myView.findViewById(R.id.photoViewGridCell);
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
iv.setPadding(0, 0, 0, 0);
iv.setLayoutParams(new LinearLayout.LayoutParams(imageWidth, imageWidth));
iv.setMaxHeight(imageWidth);
iv.setMaxWidth(imageWidth);
iv.setImageBitmap(photos.get(position).thumbnail);
return myView;
}
}
Please note that MonthActivity is called, via an Intent, on selecting a Custom View (specifically a collection of views, in a separate xml layout file) ImageAdapter2 is just a small variation on a similar Adapter used for the "starting" activity, with a slightly different custom view.
Also, ImageAdapter2 is properly "connected" to the Layout required, and initiated in the onCreate() method, it even successfully runs the constructor, but despite having several different breakpoints within the adapter's getView method none of them are ever reached when debugging... very frustrating.. any ideas ?
Put your Runnable in runOnUiThread method when you call it.

Categories

Resources