I'm using Grid View in order to show images I have stored, but as I scroll down the images disappear in Image View and don't load again.I also use thread for loading images into Image View.
Here is my code(also these codes are in my grid view adapter class) :
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
Log.e("sara" , "this part takes time");
LayoutInflater inflater = getLayoutInflater();
convertView = getLayoutInflater().inflate(R.layout.gallery_gridsq, parent, false);
iv = (ImageView) convertView.findViewById(R.id.icon);
file = new File(Uri.parse(getItem(position).toString()).getPath());
new myTask(iv, file).execute();
return convertView;
}
private class myTask extends AsyncTask <Void , Void ,Bitmap> {
ImageView iv;
File file;
public myTask(ImageView iv, File file) {
this.iv=iv;
this.file= file;
}
#Override
protected Bitmap doInBackground(Void... params) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inJustDecodeBounds = false;
options.inSampleSize = 8;
try {
bmp = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return bmp;
}
#Override
protected void onPostExecute(Bitmap aVoid) {
iv.setImageBitmap(aVoid);
}
}
I can think of the memory issue that caused me the similar trouble.
Check if your getView is still called when you scroll down with your log: Log.e("sara" , "this part takes time");
If that line is called when you scroll down, check the memory from android monitor provided in the android studio.
click android monitor at the bottom > monitors> and observe if memory reaches the cap depending on the set up (probably 128 or 256MB).
If so you are having issues with memory. The very possible reason is that you are not recycling the list and the memory is stacking. At the peak of memory, the images will not be loaded.
It should be implemented in a way that the images that you already loaded be deleted as you scroll down and load new images.
Also, try using glide for it has a good solution with memory issues and generally better in many fields than bitmapfactory.
Related
I have an android ListView which uses an AsyncTask to download a different image for each ListViewItem. Each image gets added to an ArrayList<Bitmap> the first time it is downloaded and is accessed from the ArrayList<Bitmap> each time after that.
If I slowly scroll through the ListView, each image loads as expected, but if I start scrolling faster, the visible ListViewItems, which have not yet downloaded their images, start to display images from items that have already scrolled off the top of the screen.
Eventually everything syncs up and the images update via the onPostExecution call, but what is going wrong / how can I prevent the images from previous items from appearing in the lower items in the ListView ?
Here's the trimmed down code:
private ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>(500);
#Override
public View getItemView(final int position, View convertView, final ViewGroup parent) {
View itemView;
if (convertView == null) {
itemView = getInflater().inflate(ITEM_LAYOUT, parent, false);
} else {
itemView = convertView;
}
final ImageView mImageView = (ImageView) itemView.findViewById(R.id.app_component_joblistitemview_logo);
if (bitmapArray.size()>position && bitmapArray.get(position)!=null) {
// Item exists at bitmapArray[position], use it
Log.i("JobListView","Setting Existing for Position:"+position);
mImageView.setImageBitmap(bitmapArray.get(position));
} else {
// Have not downloaded this ListView[position] logo yet
bitmapArray.add(position,null); // add temporary null value until the image is downloaded
mImageView.setImageBitmap(null);
new DownloadImageTask().execute(logo.getLogoUUID(), mImageView, position);
}
return itemView;
}
// This AsyncTask downloads an image / logo from a specified URL and then updates the specified ImageView
private class DownloadImageTask extends AsyncTask<Object, Void, Bitmap> {
ImageView imageView;
Integer listViewPosition;
#Override
protected Bitmap doInBackground(Object... params) {
String urlString = "http://www.myimages.com/something/"+params[0].toString();
this.imageView = (ImageView)params[1];
this.listViewPosition = (Integer)params[2];
try {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
Bitmap myBitmap = BitmapFactory.decodeStream(input);
bitmapArray.set(listViewPosition,myBitmap);
return myBitmap;
} catch (Exception e) {
// Log exception
}
}
protected void onPostExecute(Bitmap result) {
imageView.setImageBitmap(result);
}
}
else {
// other methods, then set tag and run task
mImageView.setTag(logo.getLogoUUID());
new DownloadImageTask().execute(logo.getLogoUUID(), mImageView, position);
}
and
int uuid=-1;
protected Bitmap doInBackground(Object... params) {
uuid=params[0];
//other methods
}
protected void onPostExecute(Bitmap result) {
//if tag exists check validity
if(imageView.getTag()==null && imageView.getTag().equals(uuid))
imageView.setImageBitmap(result);
}
lets assume you have 2 pictures - first 10px square, second FHD. when your item needs FHD bitmap Downloader starts downloading, obvius. when you still scrolling and this item is "scrolled out" it apperas (as recycled/convert view) below (or above, depends on scroll direction). then you are setting download 10px bitmap which is ofc much faster. 10 px is set, but previously asynctask downloading fhd picture is still running and still keeps reference to imageView. so when fhd is downloaded it will replace this 10 px. for workaround you might set any identyfying param, your "uuid" is probably perfect for this
how listView and adapter works you may read here and here - check what is convertView in method getView and you might use ViewHolder pattern
PS. I see some null pointers in your code, because of cleaning? example:
View itemView;
final ImageView mImageView = //itemView is just created, findViewById will throw NPE
(ImageView) itemView.findViewById(R.id.app_component_joblistitemview_logo);
Guys my program takes really long to open my gallery activity. I have gone through my code and I think I have found whats making the the activity slow down. I think its the part where the image gets decoded. It slows down when there are many images in my gallery and it looks like it takes time to decode them all.
This is my code
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(_activity);
} else {
imageView = (ImageView) convertView;
}
// THIS BELOW IN SEPARATE THREAD
Bitmap image = decodeFile(_filePaths.get(position), imageWidth, imageWidth);
// THIS ABOVE IN SEPARATE THREAD
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(imageWidth,
imageWidth));
imageView.setImageBitmap(image);
// image view click listener
imageView.setOnClickListener(new OnImageClickListener(position));
return imageView;
}
I think putting the image decoding line in a separate thread might speed up te program. Can anyone tell me how to do it?
You can not use a thread like that. Your whole function will still have to wait for the image to get decoded before returning, because you are returning imageView variable.
First of all, why are you casting convertView to an ImageView, I highly doubt its a good idea to do that. You might have to change your xml and add an imageView, if its missing. Then get a reference to that imageView in this function by using something like
final ImageView listImage = (ImageView) convertView.findViewById(R.id.listImage);
Finally, fire off your thread and set the image into this listImage variable, while returning convertView. Use an ExecuterService.
Here is a simple example.
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final int layoutResourceId; //set this
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
LayoutInflater inflater = (_activity).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId, parent, false);
}
final ImageView listImage = (ImageView) convertView.findViewById(R.id.listImage);
// THIS BELOW IN SEPARATE THREAD
executorService.submit(new Runnable() {
#Override
public void run() {
Bitmap image = decodeFile(_filePaths.get(position), imageWidth, imageWidth);
listImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
listImage.setLayoutParams(new GridView.LayoutParams(imageWidth, imageWidth));
listImage.setImageBitmap(image);
listImage.setOnClickListener(new OnImageClickListener(position));
}
});
return convertView;
}
I hope you got the general idea.
I highly suggest you use Picasso. Google it. You can do all this without having to implement your own threading, get the advantage of caching and do it in far lesser lines of code. You will run out of memory on low end devices because you are not recycling your bitmap.
How do you load images from drawable folder into a ListView in a way this happens fast and does not use large amounts of RAM?
NOTE: this post is deprecated, please use the RecyclerView for creating lists
I've been playing around with loading some (quite large) images stored in the drawable folder into a ListView and in this post I'd like to share the result I came to. Maybe (I hope so) this will save someone plenty of time. I've tested the code I'm posting on several Android 4+ devices and I can say that it runs pretty smoothly and the amount of RAM used stays relatively low. Some explanations go as following:
we are extending the BaseAdapter
images will be loaded in background using an AsyncTask
as common for this kind of adapters, we'll be using an ArrayList<> parametrized with Objects of some custom class. In my app, this class is called Weapon
we will scale the images depending on the screen size
we will apply a font to the TextView in each List Row
Feel free to use this code for any purposes and modify it in any way. The only thing I'm asking for is to test the code properly before claiming that something doesn't work. It works, believe me.
If you have noticed any copy-paste-edit mistakes (since I removed some code that is irrelevant for this little tutorial), your feedback is welcome.
Before I post the code, here's a small state diagram demonstrating the logic of the getView() method:
The code for the Adapter class goes below, I've tried to explain everything you need in comments:
public class WeaponAdapter extends BaseAdapter implements View.OnClickListener {
private ArrayList<Weapon> items;
private LayoutInflater inflater = null;
private WeaponHolder weaponHolder;
private Weapon wp;
private Context c;
private Bitmap bmp;
/*--- a simple View Holder class ---*/
static class WeaponHolder {
public TextView text;
public ImageView image, addFav;
public AsyncImageSetter mImageLoader;
}
/*--- Context and all weapons of specified class are passed here ---*/
public WeaponAdapter(ArrayList<Weapon> items, Context c) {
this.items = (ArrayList<Weapon>) items;
inflater = LayoutInflater.from(c);
this.c = c;
}
#Override
public int getCount() {
return items.size();
}
#Override
public Weapon getItem(int position) {
return items.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
/*--- initialize our Weapon Object ---*/
wp = items.get(position);
if (convertView == null) {
/*--- no View is available. Inflate our list item layout and init the Views we need ---*/
convertView = inflater.inflate(R.layout.category_row, null);
weaponHolder = new WeaponHolder();
weaponHolder.text = (TextView) convertView
.findViewById(R.id.tvCatText);
weaponHolder.image = (ImageView) convertView
.findViewById(R.id.imgCatImage);
weaponHolder.addFav = (ImageView) convertView
.findViewById(R.id.imgAddFav);
convertView.setTag(weaponHolder);
} else {
weaponHolder = (WeaponHolder) convertView.getTag();
/*--- if convertView is not null, cancel the current loading operation to
* improve performance and decrease RAM usage ---*/
weaponHolder.mImageLoader.cancel();
}
/*--- load the image in background ---*/
weaponHolder.mImageLoader = new AsyncImageSetter(c, weaponHolder.image,
wp.getImage(), bmp, weaponHolder.text);
weaponHolder.mImageLoader.execute();
weaponHolder.text.setText(wp.getName());
weaponHolder.addFav.setOnClickListener(this);
return convertView;
}
#Override
public void onClick(View v) {
// do any stuff here
}
}
Here's our AsyncTask that will load and set the images in background.
NOTE: my Weapon class has a getImage() method which returns the resId of the drawable corresponding to a Weapon Object. You can modify this part in a way it works for you.
public class AsyncImageSetter extends AsyncTask<Void, Void, Bitmap> {
private ImageView img;
private int image_resId;
private Bitmap bmp;
private Context c;
private boolean cancel = false;
private int sampleSize;
private TextView txtGunName;
private Typeface font;
public AsyncImageSetter(Context c, ImageView img, int image_ResId,
Bitmap bmp, TextView txtGunName) {
this.img = img;
this.image_resId = image_ResId;
this.bmp = bmp;
this.c = c;
this.txtGunName = txtGunName;
}
public void cancel() {
cancel = true;
}
#Override
protected void onPreExecute() {
/*--- we hide the Views from the user until the content is ready. This will prevent
* the user from seeing an image being "transformed" into the next one (as a result of
* View recycling) on slow devices.
*/
img.setVisibility(View.GONE);
txtGunName.setVisibility(View.GONE);
font = Typeface.createFromAsset(c.getAssets(), "b_reg.otf");
super.onPreExecute();
}
#Override
protected Bitmap doInBackground(Void... params) {
if (!cancel) {
try {
return decodeAndScale(bmp);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onPostExecute(Bitmap result) {
img.setVisibility(View.VISIBLE);
try {
img.setImageBitmap(result);
} catch (Exception e) {
/*--- show an error icon in case something went wrong ---*/
img.setImageResource(R.drawable.ic_warn);
}
txtGunName.setVisibility(View.VISIBLE);
txtGunName.setTypeface(font);
super.onPostExecute(result);
}
private Bitmap decodeAndScale(Bitmap bmp) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = setSampleSize();
return BitmapFactory.decodeResource(c.getResources(), image_resId,
options);
}
private int setSampleSize() {
// TODO add multiple screens check
/*--- modify this method to match your needs ---*/
if (GetSettings.getScreenWidth((Activity) c) >= 320) {
/*--- physical width >= 480px ---*/
sampleSize = 2;
}
return sampleSize;
}}
You may have noticed that I use the getScreenWidth() method from the GetSettings class. Its code is quite simple and returns a dp value representing the device's screen width:
public static int getScreenWidth(Activity a) {
Display display = a.getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float density = a.getResources().getDisplayMetrics().density;
float dpWidth = outMetrics.widthPixels / density;
return (int) dpWidth;
}
Well, that's all and I hope this post did help someone. Cheers.
P.S. if you are definitely sure something doesn't work, most likely it was caused by your internal app structure that is different from the one I use. In this case, I recommend you to do following steps:
Ask a new question so you'll be able to add properly formatted code and LogCat output
Notify me by adding a comment to my post. I will be glad to help you figure out what's wrong
Use the listview scroll state to set the images accordingly.
When listview is flinged, don't set images right at the same time, once the state is idle, use your code to set the images.
In this way it will avoid memory allocation for thos views which are not visible.If you try to set images when list view is scrolled, it can cause out of memory error.
Also, make sure that your drawable is having good size to fit in view. Getting images from drawable have always been fast, main concern here will be of memory usage.
Also create an array of drawables to set as your list item, so while setting the adapter you already have a hand full of drawables to be used for list item and do not populate list view with drawables based on condition
I'm showing list with one ImageView on every row of list.
For that, I download images from net in another AsyncTask using Drawable.createFromStream
And store them as Drawable in ArrayList which I pass to my Adapter class extending BaseAdapter class.
But the images are taken with high-resolution camera, so may be of very large size.
And I'm getting OutOfMemory error.
So my questions :
What is more efficient, storing images as drawable or as bitmap or any other format?
Am I doing right, by storing all images in memory(in array list). i.e. I'm thinking, once I get a image, I will show it on ImageView and will not store in ArrayList.
is there any way, I can compress the images after download, so they will take less space in memory.
My total code is present here
Android documentation provides a very good example showing how to handle bitmaps in your android app. The example uses an on-disk and in-memory cache and loads the images in the background. By doing so, the main UI thread is not slowed down by loading the images.
Loading Bitmaps effectively
In the example the images are loaded from picasa. It's easy, however, to adapt the example, so that pictures stored locally are used. You simply have to write your own ImageLoader extending from the 'ImageResizer':
public class ImageLoader extends ImageResizer {
public ImageLoader(Context context, int imageWidth, int imageHeight) {
super(context, imageWidth, imageHeight);
}
public ImageLoader(Context context, int imageSize) {
super(context, imageSize);
}
#Override
protected Bitmap processBitmap(Object data) {
return decodeSampledBitmapFromFile((String)data, imageWidth, imageHeight);
}
}
But to answer your question directly: it's ok to load images as Bitmaps. But you have to use a cache and weak references, so that the images can be garbage collected in case they are not visible on the screen. Caching them and using a background task for loading allows for a slick UI.
I don't see any efficiency in storing high-density images into memory - it's totally not recommended to store large ammount of images as bitmaps in memory (good for you that you have a good device ;))
See p.1
Try downscaling the images to fit the device's needs - that's not a simple job though. Also, see View.setTag(Object tag)
The adapter
public class MyImageListAdapter extends BaseAdapter implements ImageLoadingNotifier {
private LayoutInflater inflater = null;
public MyImageListAdapter() {
inflater = LayoutInflater)HomeActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return listImageInfo.size();
}
public Object getItem(int position) {
return listImageInfo.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (convertView == null) {
vi = inflater.inflate(R.layout.list_row, null);
}
TextView tvName = (TextView) vi.findViewById(R.id.tv_name);
TextView tvTime = (TextView) vi.findViewById(R.id.tv_time);
ImageView image = (ImageView) vi.findViewById(R.id.iv_image);
final Button btnDelete = (Button) vi.findViewById(R.id.btn_delete);
image.setImageDrawable(R.drawable.default_placeholder);//set default place-holder
new GetDrawableFromUrl(listImageInfo.get(position), vi).execute();
tvName.setText("Name: " + listImageInfo.get(position).getImage_name());
tvTime.setText("Date: " + listImageInfo.get(position).getDate_created());
btnDelete.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
final int position = listView.getPositionForView((View) v.getParent());
positionOgBtnToDelete = position;
Log.v("delete btn clicked", "delete btn no: " + position);
Toast.makeText(HomeActivity.this, "Btn delete position: " + position, Toast.LENGTH_LONG).show();
showAlertToConfirmDelete();
}
});
return vi;
}
}
The AsyncTask GetDrawableFromUrl
public class GetDrawableFromUrl extends AsyncTask<Void, Void, Drawable> {
public ImageInfo imageInfoObj;
private ImageView view;
GetDrawableFromUrl(ImageInfo imageInfo, ImageView view) {
imageInfoObj = imageInfo;
this.view = view;
}
#Override
protected Drawable doInBackground(Void... params) {
try {
return Drawable.createFromStream(((java.io.InputStream) new java.net.URL(imageInfoObj.getImageUrl()).getContent()), "src_name");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Drawable drawable) {
if (drawable != null) {
//imageInfoObj.setImage(drawable);
this.view.setImageDrawable(drawable);
//listImageInfo.add(imageInfoObj); //this one is called when the json is parsed
showImagesInList(); //don't know what it does (??)
}
}
}
The JSON parsing
JSONArray jsonArray = jsonObj.getJSONArray("result");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObjInner = jsonArray.getJSONObject(i);
ImageInfo imageInfo = new ImageInfo();
imageInfo.setImageUrl("http://www.dvimaytech.com/markphoto/" + jsonObjInner.getString("image"));
//new GetDrawableFromUrl(imageInfo).execute(); //don't needed here
imageInfo.setEmail(jsonObjInner.getString("emailid"));
imageInfo.setImage_id(jsonObjInner.getString("image_id"));
imageInfo.setImage_name(jsonObjInner.getString("image_name"));
imageInfo.setAmount(jsonObjInner.getString("amount"));
imageInfo.setImage_description(jsonObjInner.getString("image_description"));
imageInfo.setDate_created(jsonObjInner.getString("date_created"));
listImageInfo.add(imageInfo);
}
And, the use of any kind of List of images becomes unnecesary :)
Instead of starting the async task (GetDrawableFromUrl) when parsing the json objects, you can start the task in getView(...) method. This way you will not be constrained to store the drawables into that ArrayList, since you'll be modifying the ImageView after the image was downloaded. And, by default, you can put a placeholder, until the image is downloaded (or in case there are some network errors).
This way the images will start downloading only when the getView method will be called for that specific item.
The bottom line is that each view from the ListView will keep a reference to it's specific drawable (that was set using vi.setTag(image).
If this helps somehow, you know what to do ;)
There is pretty good library calling AQuery. YOu can use it and simple get all stuff like memory and file caching by writting only 2 line of code. So you even wouldn't need to prepare a drawable, you can call it directly from Adapter.getView() callback.
AQuery aq = new AQuery(rowView);
aq.id(R.id.image).image(url, false, true);
Hope it help you!
From AQuery docs:
Down Sampling (handling huge images)
We are loading a huge image from the network, but we only need the image to be bigger than 200 pixels wide. Passing in the target width of 200 will down sample the image to conserve memory.Aquery will only down sample with power of 2 (2,4,8...) for good image quality and
efficiency.The resulting image width will be between 200 and 399 pixels
String imageUrl = "http://farm6.static.flickr.com/5035/5802797131_a729dac808_b.jpg";
aq.id(R.id.image1).image(imageUrl, true, true, 200, 0);
I am building a book viewer for Android 3.0 Honeycomb. It has been working fine on the Samsung Galaxy Tab but gets a lot of OutOfMemoryErrors on the Motorola Xoom on 3.0.1. Both devices have 48MB VM heap space.
I have 2 activities:
BookActivity - has a SlowGallery which loads 1280x640 images and a small Gallery which loads 160x80 images.
The SlowGallery is a minor override to make it fling one gallery item at a time instead of scrolling quickly.
BitmapActivity - has a single ImageView which loads an 4488x2244 image and a small Gallery which loads 160x80 images. Reducing the size of the image is not an option because the user is intended to enlarge the image to 100%.
I put this in the Bitmap-loading method to reduce the bitmap to 16-bit:
BitmapFactory.Options o2 = new BitmapFactory.Options();
if (compressColor) o2.inPreferredConfig = Bitmap.Config.RGB_565;
o2.inPurgeable = true;
o2.inInputShareable = true;
o2.inSampleSize = (int) scale;
b = BitmapFactory.decodeStream(fis, null, o2);
When I double-tap the BookActivity's Gallery, it calls BitmapActivity, passing the respective image number to be opened. I can do this 1-3 times, entering the BitmapActivity, then clicking Back, before it hits an OutOfMemoryError.
On double-tapping the Gallery it calls PageAdapter.purge(), which appears to visually unload the gallery's images.
This is BookActivity.PageAdapter, which is the adapter for the SlowGallery:
private class PageAdapter extends BaseAdapter {
private final LayoutInflater mInflater;
private Page currentPage;
private boolean purge = false;
private WeakReference<Bitmap> weakReferenceBitmap;
public PageAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
public void unpurge() {
purge = false;
notifyDataSetChanged();
}
public void purge() {
purge = true;
holder = null;
notifyDataSetChanged();
}
#Override
public int getCount() {
if (listPages==null || purge) return 0;
return listPages.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.gallery_pages, null);
holder = new ViewHolderIssues();
holder.image = (ImageView) convertView.findViewById(R.id.ImageViewPage);
convertView.setTag(holder);
} else {
holder = (ViewHolderIssues) convertView.getTag();
}
if (position < listPages.size()) {
currentPage = listPages.get(position);
File f = new File(Engine.PATH + currentPage.getImageMedium());
if (!purge) {
if (!f.exists()) {
holder.image.setImageResource(R.drawable.loading);
Engine.triggerTrickle(currentPage.getImageMedium(), WeightedAsset.IMAGE_MEDIUM, getApplicationContext());
} else {
weakReferenceBitmap = new WeakReference<Bitmap>(Engine.loadImageFromBitmap(currentPage.getImageMedium(), screenWidth, 1, true));
if (weakReferenceBitmap.get()!=null) {
holder.image.setImageBitmap(weakReferenceBitmap.get());
} else {
holder.image.setImageDrawable(null);
}
}
} else {
weakReferenceBitmap.clear();
holder.image.setImageDrawable(null);
holder = null;
System.gc();
}
f = null;
}
return convertView;
}
}
Looking at the HPROF file, under the Dominator Tree, it appears that the BookActivity.SlowGallery still exists even when I am in BitmapActivity. When in BookActivity, SlowGallery takes up 3.2-6.5MB (1.6MB per image) but in BitmapActivity it takes up 1.6MB (SlowGallery.LinearLayout.ImageView.BitmapDrawable = 1.6MB). How do I get rid of SlowGallery?
Ah, kinda complex chunk of code there. The weak references may help, but in general, if using an ImageView in an adapter, before you set the new bitmap you should fetch the old Bitmap and then call Bitmap.recycle(). You should also do that your BitmapActivity when it is destroyed. That should do most of what you need.
Thanks! I've managed to refactor it to recycle the bitmap. However, the gallery was still sticking around because BitmapActivity.onCreate gets called before BookActivity.onDestroy. If I did call the purge() function I would manage to clear most of it except for the clicked/selected view (kept alive by the Gallery double-tap listener!)
I then wiped out the darned gallery and put my own ImageView imageviewCurrentPage (and a ImageView imageviewFlipperPage) and animated them in and out. The imageviewFlipperPage would be destroyed immediately when the animation was done, so I was guaranteed to only have 2 bitmaps loaded at maximum. The Gallery can take up to FIVE Views with the selected, clicked views etc.
I also added this to further isolate memory usage:
<activity android:name=".BitmapActivity" android:process=":BitmapActivity" ...>
However, it came with the complication that static classes are not shared across processes. Hence, if in BookActivity I did Engine.SESSION_ID=5 where SESSION_ID is static, I would not be able to read it in BitmapActivity's Engine.SESSION_ID.