java.lang.OutOfMemoryError on storing images in sqlite db - java

I want to store images in my database. Also I want to check that if the image and title is already in the database. If so, it will not add them to the database. This is my class.
Attractions
public class Attractions extends ListActivity {
DataBaseHandler db = new DataBaseHandler(this);
ArrayList<Contact> imageArry = new ArrayList<Contact>();
List<Contact> contacts;
ContactImageAdapter adapter;
int ctr, loaded;
int [] landmarkImages={R.drawable.oblation,R.drawable.eastwood,R.drawable.ecopark,R.drawable.circle};
String []landmarkDetails = { "Oblation", "Eastwood", "Ecopark", "QC Circle"};
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_attractions);
ctr = db.checkContact(landmarkDetails[loaded]);
// get image from drawable
/**
* CRUD Operations
* */
// Inserting Contacts
Log.d("Insert: ", "Inserting ..");
for(loaded=0; loaded <landmarkDetails.length;loaded++){
Bitmap image = BitmapFactory.decodeResource(getResources(),
landmarkImages[loaded]);
// convert bitmap to byte
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte imageInByte[] = stream.toByteArray();
Log.d("Going to load images", "Image "+ loaded);
Log.d("Goind to load objects", "loading");
if(ctr == 0){
Log.d("Nothing Loaded", "Loading Now");
db.addContact(new Contact(landmarkDetails[loaded], imageInByte));}
Log.d(landmarkDetails[loaded], "Loaded!");
image.recycle();
}
loadFromDb();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.attractions, menu);
return true;
}
public void loadFromDb(){
// Reading all contacts from database
contacts = db.getAllContacts();
for (Contact cn : contacts) {
String log = "ID:" + cn.getID() + " Name: " + cn.getName()
+ " ,Image: " + cn.getImage();
// Writing Contacts to log
Log.d("Result: ", log);
//add contacts data in arrayList
imageArry.add(cn);
}
adapter = new ContactImageAdapter(this, R.layout.screen_list,
imageArry);
ListView dataList = (ListView) findViewById(android.R.id.list);
dataList.setAdapter(adapter);
}
public void onPause(){
super.onPause();
}
public void onResume(){
super.onResume();
}
}
It works fine on the emulator, but I tried testing on my S4 and then after 3 tries of going to this class, it forced stop. I tried it with usb debugging and the logcat showed java.lang.outofmemoryerror . The logcat pointed the error in my contactimageadapter.
ContactImageAdapter
public class ContactImageAdapter extends ArrayAdapter<Contact>{
Context context;
int layoutResourceId;
// BcardImage data[] = null;
ArrayList<Contact> data=new ArrayList<Contact>();
public ContactImageAdapter(Context context, int layoutResourceId, ArrayList<Contact> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ImageHolder holder = null;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new ImageHolder();
holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);
holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon);
row.setTag(holder);
}
else
{
holder = (ImageHolder)row.getTag();
}
Contact picture = data.get(position);
holder.txtTitle.setText(picture._name);
//convert byte to bitmap take from contact class
byte[] outImage=picture._image;
ByteArrayInputStream imageStream = new ByteArrayInputStream(outImage);
Bitmap theImage = BitmapFactory.decodeStream(imageStream);
holder.imgIcon.setImageBitmap(theImage);
return row;
}
static class ImageHolder
{
ImageView imgIcon;
TextView txtTitle;
}
}
And pointed to this line Bitmap theImage = BitmapFactory.decodeStream(imageStream);
I have little (almost none) knowledge on managing images and storing them. I also enable android:largeHeap but still force closes on multiple tries. I hope someone can help me solving this issue, or at least show me a different way of storing text and images to sqlite db. Many thanks!

You have multiple places where whole image (assuming it is big) keeps in memory:
Contact object has it. All loaded images are in imageArry which is instance level variable.
public class Attractions extends ListActivity {
DataBaseHandler db = new DataBaseHandler(this);
ArrayList<Contact> imageArry = new ArrayList<Contact>();
in ContactImageAdapter.getView method you create another copy of image as BMP in holder object and pass it out of method.
So, at some point you do not have enough memory to keep all of them. Also I sure that decodeStream needs some more memory to perform.
After all it is not predictable when each new holder created in getView will be cleaned by GC.
Usually for such situation when object created as new in some method, then passed back to the calling method, that object will be collected only by Full GC.
So, as "Software Sainath" said, do not store images in database…
and do not keep them in memory either.
P.S. Then provide to the view a link to the external image file. That also will save time to load a view. Image will be in cache and if user at least once got it, it will not pass through the network again.
I guess images there are not frequently change them self. another image of Contact will be another file…

I wrote an answer to the somewhat similar problem some while ago, here is the link that you can check. The problem is in the approach of saving the images into the database, you should not be doing this. Instead, write the images as files on the phone memory and use it further.

Don't store Image to Sqlite Database eventually, you will ran into out of memory error after three or five image saved to database. It's not the best practice, maximum memory allocated for field in a row in sqlite is less than 3mb, be aware of this.
Instead of saving Images to database, Keep the images inside your app folder, save the path to the Database.
Your are loading your image as it is to your Image adapter. Let's say your image is 1280x720 resolution and 2mb in size, it will take the same space in your memory Heap.
You can either scaledown your image and load it as bitmap to your Adapter ImageView like this.
Before loading your image as Bitmap get it height and width.
//Code read the image and give you image height and width.it won't load your bitmap.
BitmapFactory.Options option = new BitmapFactory.Options();
option.inJustDecodeBounds = true;
BitmapFactory.decodeFile(your_image_url,option);
int image_original_width = option.outHeight;
int image_original_height = option.outWidth;
Now to scale down your Image you have to know the ImageView width and height. This is because we are going to scale down the image matching the imageview with pixel perfection.
int image_view_width = image_view.getWidht();
int image_view_height = image_view.getHeight();
int new_width;
int new_height;
float scaled_width;
if(image_original_width>image_view_width)
{ //if the image_view width is lesser than original_image_width ,you have to scaled down the image.
scale_value =(float)image_original_width/(float)image_view_width;
new_width = image_original_width/scaled_value;
new_height = image_orignal_height/scale_value
}
else
{
// use the image_view width and height as sacling value widht and height;
new_width = image_view_width;
new_height = image_view_height;
}
Now Scale Down your bitmap and load it like this.
// this will load a bitmap with 1/4 the size of the original one.
// this to lower your bitmap memory occupation in heap.
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = 4;
Bitmap current_bitmap = BitmapFactory.decodeFile(image_url,option);
Bitmap scaled_bitmap = Bitmap.createScaledBitmap(current_bitmap,new_width,new_height,true);
holder.imgIcon.setImageBitmap(scaled_bitmap);
//release memory occupied by current_bitmap in heap, as we are no longer using it.
current_bitmap.recycle();
If you want to understand a little more about Bitmap and memory view this link.
If you don't want to handle rescaling bitmap by yourself. you can use Glide or Picasso library which does the same.
I have written an article about using Picasso to load image in listview, which will help you to start, if you are looking to use picasso.
http://codex2android.blogspot.in/2015/11/picasso-android-example.html

Please make sure to use the quick garbage collection eligible reference type while loading the images from the network
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import android.graphics.Bitmap;
public class MemoryCache {
private Map<String, SoftReference<Bitmap>> cache=Collections.synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
public Bitmap get(String id){
if(!cache.containsKey(id))
return null;
SoftReference<Bitmap> ref=cache.get(id);
return ref.get();
}
public void put(String id, Bitmap bitmap){
cache.put(id, new SoftReference<Bitmap>(bitmap));
}
public void clear() {
cache.clear();
}
}

Don't store Image to Sqlite Database. It's not the best practice.
Instead of saving Images to database, Keep the images in a storage, but if you want to keep them private then keep them inside your app folder and save the path to the Database.
Use one of the well known libraries like http://square.github.io/picasso/ or https://github.com/bumptech/glide, they offer great help with memory issues and also some cool transition effects.
I recommend using Glide because it works very well on device with low memory restrictions

Related

Glide loads images from firebase painfully slow using URLs

I'm trying to create a RecyclerView that is populated by ImageViews in each cell and each image corresponds to an image in Firebase Storage.
I have a list of Strings that is passed into my RecyclerView adapter and each one represents a URL to an image in Firebase Storage. I load each image inside the onBindViewHolder().
What i get in return is a very VERY slow loading of a few images (around 5-see picture) and then it takes around 4 minutes to load another 5 and it never seems to load any other images after these.
I've read multiple posts on StackOverflow but most of them just tell you to use fitCenter() or centerCrop() but that doesn't change anything in my case. I also read in Glide's documentation that Glide will automatically downsample your images so i shouldn't need to do it manually, right? Any ideas what i could be doing wrong here? The Url Strings are successfully retrieved from Firebase and the queries are resolved almost instantly so i don't think there is any issue there.
UPDATE:
I've made some modifications in the onBindViewHolder() method in order to explicitly request caching of the images from Glide and i also used the thumbnail API to download lower resolutions of the images. Now more images are loading but each one still takes around 7 seconds to load which obviously is too long. If you have any suggestions let me know please.
Here's how the RecyclerView is set up in my main activity:
iconsRCV = findViewById(R.id.cardIconsRCV)
iconsRCV.layoutManager = GridLayoutManager(this,5) // set the layout manager for the rcv
val iconUrls : ArrayList<String> = ArrayList() // initialize the data with an empty array list
val adapter = CardIconAdapter(this,iconUrls) // initialize the adapter for the recyclerview
iconsRCV.adapter = adapter // set the adapter
Note that i get new data when certain queries are done and then i call adapter.notifyDataSetChanged() to pass new data to the RecyclerView.
CardIconAdapter.java:
public class CardIconAdapter extends RecyclerView.Adapter<CardIconAdapter.ViewHolder> {
private RequestOptions requestOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).centerCrop().error(R.drawable.applogotmp);
private List<String> urlsList;
private Context context;
class ViewHolder extends RecyclerView.ViewHolder {
ImageView iconImg;
ViewHolder(#NonNull View view) {
super(view);
iconImg = view.findViewById(R.id.cardIcon);
}
}
public CardIconAdapter(Context cntxt, List<String> data) {
context = cntxt;
urlsList = data;
}
#NonNull
#Override
public CardIconAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_icons_rcv_item,parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CardIconAdapter.ViewHolder holder, int position) {
GlideApp.with(context).load(urlsList.get(position)).apply(requestOptions).into(holder.iconImg);
}
#Override
public int getItemCount() {
return urlsList.size();
}
}
P.S. The image sizes in Firebase are mostly udner 200KB but with a small few reaching 4MB. Also, the ImageView in the R.layout.card_icons_rcv_item layout is 75x75 in size.
Hope you have used latest version of glide.
There are few ways for better image loading and caching,
credit goes to this nice article .
1. Enable Disk Cache
val requestOptions = RequestOptions().diskCacheStrategy(DiskCacheStrategy.ALL)
Glide.with(context).load(url).apply(requestOptions).into(imageView)
2. List item
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.signature(ObjectKey(signature))
Glide.with(context).load(url).apply(requestOptions).into(imageView)
3. Override Image Size (Optional)
val requestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.signature(ObjectKey(signature))
.override(100, 100) // resize does not respect aspect ratio
Glide.with(context).load(url).apply(requestOptions).into(imageView)
4. Add Thumbnail Url
// With thumbnail url
Glide.with(context).load(url)
.thumbnail(Glide.with(context).load(thumbUrl))
.apply(requestOptions).into(imageView)
// Without thumbnail url
// If you know thumbnail size
Glide.with(context).load(url)
.thumbnail(Glide.with(context).load(url).apply(RequestOptions().override(thumbSize)))
.apply(requestOptions).into(imageView)
// With size multiplier
Glide.with(context).load(url)
.thumbnail(0.25f)
.apply(requestOptions).into(imageView)
5. Setup Monthly Schedule for Cleaning
// This method must be called on the main thread.
Glide.get(context).clearMemory()
Thread(Runnable {
// This method must be called on a background thread.
Glide.get(context).clearDiskCache()
}).start()
6. To Transform bitmap
// TODO remove after transformation is done
.diskCacheStrategy(SOURCE) // override default RESULT cache and apply transform always
.skipMemoryCache(true) // do not reuse the transformed result while running
.diskCacheStrategy(DiskCacheStrategy.ALL) // It will cache your image after loaded for first time
.format(DecodeFormat.PREFER_ARGB_8888) //for better image quality
.dontTransform() // to load image faster just skip transform
.placeholder(R.drawable.placeholder) // use place holder while image is being load

Android: Get integer value of images in SD storage?

I'm looking to add images from my SD card storage into an Interger array list.
At the moment I can display images from my drawable folder because they are (somehow) in int format, for example: Log.d("MyTag", R.drawable.ic_launcher_background)); currently returns me 2131230825.
I can access my SD card images in a way to return me: /storage/emulated/0/Android/data/com.hangr.hangr/files/Pictures/Hangr_20181119__153130.jpg (String format). And I can also make bitmaps out of them with "Bitmap myBitmap = BitmapFactory.decodeFile(f.get(i));" (Bitmap format).
Any idea on how I can pass these string/bitmap forms into an int object?
package com.myapp.myapp;
imports ...
public class test extends Activity {
public ArrayList<Integer> mThumbIds = new ArrayList<>();
File[] listFile;
ArrayList<String> f = new ArrayList<String>();// list of file paths
ArrayList<Bitmap> myBitmapArrayList = new ArrayList<Bitmap>();// list of bitmaps
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
getFromSdcard();
GridView gridview = (GridView) findViewById(R.id.gridview);
gridview.setAdapter(new ImageAdapter(this));
// Test: adding drawable items to my interger list
mThumbIds.add(R.drawable.logo);
}
//************** important function that calls from my SD card
public void getFromSdcard(){
File file = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (file.isDirectory()) {
listFile = file.listFiles();
for (int i = 0; i < listFile.length; i++)
{
f.add(listFile[i].getAbsolutePath());
Bitmap myBitmap = BitmapFactory.decodeFile(f.get(i));
fml.add(myBitmap);
}
}
//************** TESTING THE VALUE OF MY VARIABLES
Log.d("MyTag", "File file " + file); ///storage/emulated/0/Android/data/com.myapp.myapp/files/Pictures
Log.d("MyTag", "File length " + file.isDirectory()); //true
Log.d("MyTag", "First element in f: " + f.get(0)); ///storage/emulated/0/Android/data/com.myapp.myapp/files/Pictures/20181119__153130.jpg
Log.d("MyTag", "All the Bitmaps? " + myBitmapArrayList); //returns huge list of bitmaps, i.e. android.graphics.Bitmap#39bd71a
Log.d("MyTag", "Drawables? " + (R.drawable.ic_launcher_background)); //returns me 2131230825
}
public class ImageAdapter extends BaseAdapter {
//************** code to zoom in on images, from https://www.androidbegin.com/tutorial/android-gridview-zoom-images-animation-tutorial/
public View getView(final int position, View convertView, ViewGroup parent) {
final ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mThumbIds.get(position));
// imageView.setImageListener(bottoms_Listener);
imageView.setTag(mThumbIds.get(position));
imageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
int id = (Integer) arg0.getTag();
zoomImageFromThumb(arg0, id);
}
});
return imageView;
}
private void zoomImageFromThumb...
}
Before you get mad at me for not "trying", I have. The hardest part was pulling from the device storage and accessing the photos in there. I just dont get why drawable images are ints and why there's no seemingly straightforward way I can turn my images into ints for similar use.
You can't get their integer references, because they don't have any.
R.whatever (R.drawable, R.string, R.xml, etc) are classes generated by Android Studio that hold integer fields whose names correspond to your resources. When you use something like getDrawable(), Android uses the integer you passed to find the corresponding resource and load it as an image. This is all done because the resources are compiled and stored in the APK itself, and aren't accessible with paths.
However, images on internal storage or your SD card aren't resources. They have directly accessible paths, and aren't inside any APKs. Android doesn't give them integers IDs because they don't need them, and it's just not how it works.
To get images from storage, you need to use paths.

Android slideshow leads to Memory leaks

I want to do an image slideshow on Android with only full sceen Images. No thumbnail (like in gallery), no zoom, no pinch. Only left/right slide to move from one picture to the other.
Pcitures are saved on the data folder and there are ~2000 pictures.
I use a full screen ViewPager for displaying the picures. Of course I can't load all the 2000 at once so I only load the needed picture when the user swipes to the corresponding view.
Each time the user swipes to a new viewPager position I call this code
public void onNewViewToDisplay(Integer newPosition, Integer previousPosition) {
File file = functionToGetTheCorrespondingFile(newPosition);
Bitmap bp;
bp = BitmapFactory.decodeFile(file.getPath());
((ImageView)imageAdapter.views.get(newPosition)).setImageBitmap(bp);
if (previousPosition != null && previousPosition != newPosition) {
BitmapDrawable bitmapDrawable =
((BitmapDrawable)((ImageView)imageAdapter.views.get(previousPosition)).getDrawable());
// Set the previous imageView to empty view
((ImageView) imageAdapter.views.get(previousPosition)).setImageResource(0);
// Then free previous bitmap memory
if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
bitmapDrawable.getBitmap().recycle();
}
}
}
So I expect my code to always free the memory used by the previous displayed imageview.But when I look at my memory occupation
Leading to a memory leakage. What am I doing wrong?
Your approach is not efficient, because you create each bitmap every time the user swipes.
You should use LruCache to store the bitmaps so you don't have to re-create one that was already decoded.
OK I found the reason why I add these memory leaks. This was because I had a reference to the views in my PagerAdapter.
You need to have no reference to the views and implement the destroyItem like this
public void destroyItem (ViewGroup container, int position, Object object)
{
((ImageView)object).setImageResource(0);
BitmapDrawable bmd = (BitmapDrawable) ((ImageView) object).getDrawable();
if (bmd != null) bmd.getBitmap().recycle();
View view = (View)object;
((ViewPager) container).removeView(view);
view = null;
}

Picasso image downloading & caching behavior

So I recently wanted to try out the caching feature of the Picasso library, & I got into this confusing situation:
I retrieve the images' file names & paths from my web server (using Retrofit2), & I store them into ImageComponent objects (model):
public class ImageComponent {
private int id; // 'id' in database
private String filename; // image name
private String path; // image path in server storage
private Bitmap bitmap;
// Overloaded constructor
// Getters & setters
}
So now that the loading is successful, I populate a RecyclerView with these images using Picasso. The loading and inflation process is successful, but it gets a little tricky when caching the images.
Case1: using android.util.LruCache
(For convenience, I will post the entire code of the Recyclerview's adapter. I will try to be concise)
// imports
import android.util.LruCache;
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> {
private Context mContext; // Activity's context
private List<ImageComponent> mImages; // The imageComponents to display
// The contreversial, infamous cache
private LruCache<Integer, Bitmap> mImageCache;
public ImageAdapter(Context context, List<ImageComponent> images) {
mContext = context;
mImages = images;
// Provide 1/8 of available memory to the cache
final int maxMemory = (int)(Runtime.getRuntime().maxMemory() /1024);
final int cacheSize = maxMemory / 8;
mImageCache = new LruCache<>(cacheSize);
}
#Override
public ImageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Nothing special
}
#Override
public void onBindViewHolder(final ImageAdapter.ViewHolder holder, final int position) {
// Current ImageComponent
ImageComponent imageComponent = mImages.get(position);
// Full image path in server storage
String imagePath = Constants.SERVER_IP_ADDRESS + Constants.UPLOADS_DIRECTORY
+ imageComponent.getPath();
// Display the file's name
holder.text.setText(imageComponent.getFilename());
final ImageView imageView = holder.image;
// Get bitmap from cache, check if it exists or not
Bitmap bitmap = mImageCache.get(imageComponent.getId());
if (bitmap != null) {
Log.i("ADAPTER", "BITMAP IS NOT NULL - ID = " + imageComponent.getId());
// Image does exist in cache
holder.image.setImageBitmap(imageComponent.getBitmap());
}
else {
Log.i("ADAPTER", "BITMAP IS NULL");
// Callback to retrieve image, cache it & display it
final Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
ImageComponent img = mImages.get(position);
// Display image
holder.image.setImageBitmap(bitmap);
// Cache the image
img.setBitmap(bitmap);
mImages.set(position, img);
mImageCache.put(img.getId(), bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
// Tag the target to the view, to keep a strong reference to it
imageView.setTag(target);
// Magic
Picasso.with(mContext)
.load(imagePath)
.into(target);
}
}
#Override
public int getItemCount() {
return mImages.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView text;
// Constructor & view binding, not that special
}
}
RESULT1
(Notice those 2 last images, & how they show other previous images before displaying the correct one)
A few notes:
I ran across a problem, where the images weren't displayed at all. After some research, I found this answer which suggested binding the target to the ImageView. (worked)
I didn't quite understand how Picasso caches the images. Is it an automatic or manual process ? This answer states that Picasso handles this task for you. But when I actually tried it out (without the android Lrucache), no caching seemed to be done : The images were getting reloaded every time I scroll back & forth.
Actually I was going to post a second use case where things went even more wrong, using the Picasso's Lrucache (images were being shown randomly , & change with every scroll), but I think this post is already long enough.
My questions are:
Why do I get that weird behavior ? (as shown in the attached GIF)
How does this whole caching process work ? Should I (or could I) use a Lrucache when making use of Picasso ?
What's the difference between the Lrucache that comes with the SDK & Picasso's ? (Performance, best use case scenarios, etc...)
I think using both LRU cache and Picasso is causing the weird behaviour. I have used Picasso to cache Image to an Adapter, which works completely fine. you can check in here
Picasso cache Image automatically when used with adapter, it will cache like this, if the child item of list/Recycler view is not visible it will stop caching the image for the respective child.So it's better to use Picasso alone with Adapter.
The main usage of Picasso over LRU cache is that, Picasso is easy to use.
ex : specifying Memory cache Size in Picasso.
Picasso picasso = new Picasso.Builder(context)
.memoryCache(new LruCache(250))
.build();
Picasso also allow you to notify user with an Image when there is an error in downloading, a default holder for Imageview before loading the complete image.
Hope it helps.

Why doesn't android ImageAdapter load images lazily?

I have this ImageAdapter for android's listView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
RelativeLayout relativeLayout = null;
Offer currentOffer = mOffersList.get(position);
if (convertView == null) { // create a new view if no recycling
// available
// Make up a new view
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.offer_list_item, null);
relativeLayout = (RelativeLayout) view
.findViewById(R.id.offerImage);
} else {
view = (View) convertView;
relativeLayout = (RelativeLayout) view
.findViewById(R.id.offerImage);
setBackgroundDrawable(relativeLayout, null);
}
String imageUrl = "";
imageUrl = currentOffer.getImageUrl().toString();
Bitmap bitmap = imageCache.get(imageUrl);
if (bitmap != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bitmap);
setBackgroundDrawable(relativeLayout, dr);
} else {
if (!downloadingImageUrls.contains(imageUrl)) {
downloadingImageUrls.add(imageUrl);
new DownloadImageAsyncTask().execute(imageUrl);
}
}
return view;
}
and this:
class DownloadImageAsyncTask extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... params) {
String imageUrl = params[0];
try {
Bitmap bitmap = BitmapFactory
.decodeStream((InputStream) new URL(imageUrl)
.getContent());
imageCache.put(imageUrl, bitmap);
} catch (IOException e) {
Log.e("DownloadImageAsyncTask", "Error reading bitmap" + e);
}
downloadingImageUrls.remove(imageUrl);
return null;
}
#Override
protected void onPostExecute(Void result) {
notifyDataSetChanged();
}
}
why do all of the list items loaded together? it's don' asynchronously but yet not one by one. All together.
how can i load it lazily?
and why is this code more efficient?
// better
public class DownloadImageAsyncTask2 extends
AsyncTask<String, Void, Bitmap> {
private final ImageView imageView;
public DownloadImageAsyncTask2(ImageView imageView) {
this.imageView = imageView;
}
#Override
protected void onPreExecute() {
Log.i("DownloadImageAsyncTask", "Starting image download task...");
}
#Override
protected Bitmap doInBackground(String... params) {
try {
return BitmapFactory.decodeStream((InputStream) new URL(
params[0]).getContent());
} catch (IOException e) {
Log.e("DownloadImageAsyncTask", "Error reading bitmap" + e);
}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
To answer your initial question, any adapter you write is exactly as it's called; it's an adapter. It lets you translate your data into Views displayed in an AdapterView. It would not make sense to force all views to be "loaded lazily" and, quite frankly, should not do that for a few reasons.
Maybe you have prefetched all your images and don't need lazy loading. Maybe they even just come from resources and don't need to be handled in a special way at all.
How would it know from where to lazily load your images (database, memory map, file, network resource, other android service, etc.)? That's your job when writing the adapter.
You might argue it would be nice for Adapter subclasses to have a loadLazily(Uri image, ImageView view) function. If so why not subclass AbsAdapter yourself and add it or submit a patch to the AOSP. I, however, doubt you will find agreement that even a function like that should be included as part of an Adapter. It's really your job to manage your data in an efficient manner, despite how normal it is to lean on the system for data management in Android.
To answer your other questions about the two methods of lazy loading you proposed, the difference is that the first method causes the AdapterView to reload all of its Views whereas in the second method you are simply invalidating the view into which you are loading the image.
I don't actually agree the second method is "better" on a whole because every time your configuration is changed or process dies and Views need to be reloaded you need to make new calls over the network or to the file system to load your images. If you cache them as you do in the first method then you can at least avoid needing to reload all the images through configuration changes. If you wrote a separate image loading service that ran in its own process (not something I'm recommending) you could also avoid the second case (of your :default process getting killed).
Point is, you're responsible for loading your images, not Android. If you want it done for you take a look at these great image loading or otherwise general resource acquisition libraries for android: Local image caching solution for Android: Square Picasso vs Universal Image Loader . I've personally used Universal-Image-Loader before and admit it works as advertised. However, it caches all your images on external storage which might not be an option for you.
The listview can call getView for all items in its onMeasure (cf. measureHeightOfChildren in listView.java).
So, with this getView, it loads all images.
To load lazily, you need to load images elsewhere: let the views ask the image when needed (onDraw) to a manager (which will load them asynchronously).
notifyDataSetChanged() with update all the listview (meaning recycling views, calling getView ...), imageView.setImageBitmap will update only the ImageView so it's a better idea.
use this imageloader it works IMAGELOADER
You can use this library https://github.com/thest1/LazyList. It's very easy to use.

Categories

Resources