Using AsyncTask to load Images in ListView - android

I have one ListView which can hold an image. It depends if image exists or not in SDCARD.
Here my example code:
public class MainActivity extends Activity {
ListView mListView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListView = new ListView(this);
setContentView(mListView);
String[] arr = new String[] {
"/example/images/1.jpg", "/example/images/2.jpg",
"/example/images/3.jpg", "/example/images/4.jpg",
"/example/images/5.jpg", "/example/images/6.jpg",
"/example/images/7.jpg", "/example/images/8.jpg",
"/example/images/9.jpg", "/example/images/1.jpg",
"/example/images/2.jpg", "/example/images/3.jpg",
"/example/images/4.jpg", "/example/images/5.jpg",
"/example/images/6.jpg", "/example/images/7.jpg",
"/example/images/8.jpg", "/example/images/9.jpg",
"/example/images/1.jpg", "/example/images/2.jpg",
"/example/images/3.jpg", "/example/images/4.jpg",
"/example/images/5.jpg", "/example/images/6.jpg",
"/example/images/7.jpg", "/example/images/8.jpg",
"/example/images/9.jpg", "/example/images/1.jpg",
"/example/images/2.jpg", "/example/images/3.jpg",
"/example/images/4.jpg", "/example/images/5.jpg",
"/example/images/6.jpg", "/example/images/7.jpg",
"/example/images/8.jpg", "/example/images/9.jpg"};
List<String> list = Arrays.asList(arr);
MyAdapter adapter = new MyAdapter(this, R.layout.listitem_imv, list);
mListView.setAdapter(adapter);
}
class MyAdapter extends ArrayAdapter<String>{
List<String> mList;
LayoutInflater mInflater;
int mResource;
public MyAdapter(Context context, int resource,
List<String> objects) {
super(context, resource, objects);
mResource = resource;
mInflater = getLayoutInflater();
mList = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if(convertView == null){
view = mInflater.inflate(mResource, null);
}else{
view = convertView;
}
ImageView imageView = (ImageView) view.findViewById(R.id.imv);
TextView textView = (TextView) view.findViewById(R.id.txv);
imageView.setTag(mList.get(position));//tag of imageView == path to image
new LoadImage().execute(imageView);
textView.setText(mList.get(position).toString());
return view;
}
}
class LoadImage extends AsyncTask<Object, Void, Bitmap>{
private ImageView imv;
private String path;
#Override
protected Bitmap doInBackground(Object... params) {
imv = (ImageView) params[0];
path = imv.getTag().toString();
Bitmap bitmap = null;
File file = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() + path);
if(file.exists()){
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
}
return bitmap;
}
#Override
protected void onPostExecute(Bitmap result) {
if(result != null && imv != null){
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
}else{
imv.setVisibility(View.GONE);
}
}
}
}
The 'sdcard/example/images' directory has the images: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 6.jpg, 7.jpg and 9.jpg.
the expected result is:
But, if I scroll the list quickly, some images are inserted in the wrong items.
It happens due to use of convertView in getView() method.
If I use the following code, the code works fine:
//if(convertView == null){
// view = mInflater.inflate(mResource, null);
//}else{
// view = convertView;
//}
view = mInflater.inflate(mResource, null);
When list scrolled quickly, two asyncTasks can reference one same View, due to use of convertView.
How Can I cancel AsyncTask when the View is no longer visible?(and is useb by another item of ListView)
edit
#Override
protected void onPostExecute(Bitmap result) {
if(result != null && imv != null){
if(imv.getTag().equals(path)){
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
}else{
imv.setVisibility(View.GONE);
}
}else{
imv.setVisibility(View.GONE);
}
}

You can send in the ImageView to the task constructor and keep a reference to the image path there. Now at onPostExecute, check if the current tag of the ImageView is the same as the one that you started with. If yes, then set the image. If no, don't do anything.
However, this means that the image will be downloaded in any case. You'll just not set the wrong image on the view.
EDIT:
First pass the ImageView to the task constructor:
new LoadImage(imageView).execute()
Then save a reference to the ImageView and image path in LoadImage constructor. It is important to save the path in the constructor and not in doInBackground to ensure that we don't run into multi threading problems. Then at onPostExecute we check the current path.
class LoadImage extends AsyncTask<Object, Void, Bitmap>{
private ImageView imv;
private String path;
public LoadImage(ImageView imv) {
this.imv = imv;
this.path = imv.getTag().toString();
}
#Override
protected Bitmap doInBackground(Object... params) {
Bitmap bitmap = null;
File file = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() + path);
if(file.exists()){
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
}
return bitmap;
}
#Override
protected void onPostExecute(Bitmap result) {
if (!imv.getTag().toString().equals(path)) {
/* The path is not same. This means that this
image view is handled by some other async task.
We don't do anything and return. */
return;
}
if(result != null && imv != null){
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
}else{
imv.setVisibility(View.GONE);
}
}
}

This Android Developers Blog post will give you a complete reference project for this complete with caching. Just replace the Http access code with SD card file reads.

I hope this helps.
After lot of search I have this working solution.
public class CustomAdapter extends ArrayAdapter<String>{
/*
public CustomAdapter(Context context , String[] video) {
super(context,R.layout.custom_row, video);
}
*/
private final Activity context;
private final String[] video;
static class ViewHolder {
public TextView videoTitle;
public ImageView videoThumbnail;
public int position;
public String path;
}
public CustomAdapter(Activity context, String[] video) {
super(context, R.layout.custom_row, video);
this.context = context;
this.video = video;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater videoInflator = LayoutInflater.from(getContext());
View customView = videoInflator.inflate(R.layout.custom_row, parent, false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.position = position;
viewHolder.path = video[position];
viewHolder.videoTitle = (TextView) customView.findViewById(R.id.videoTitle);
viewHolder.videoThumbnail = (ImageView) customView.findViewById(R.id.videoThumbnail);
//rowView.setTag(viewHolder);
//}
customView.setTag(viewHolder);
final String videoItem = video[position];
int index=videoItem.lastIndexOf('/');
String lastString=(videoItem.substring(index +1));
index = lastString.indexOf(".mp4");
lastString=(lastString.substring(0,index));
viewHolder.videoTitle.setText(lastString);
new AsyncTask<ViewHolder, Void, Bitmap>() {
private ViewHolder v;
#Override
protected Bitmap doInBackground(ViewHolder... params) {
v = params[0];
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoItem, MediaStore.Images.Thumbnails.MINI_KIND);
return thumb;
}
#Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
if (v.position == position) {
// If this item hasn't been recycled already, hide the
// progress and set and show the image
v.videoThumbnail.setImageBitmap(result);
}
}
}.execute(viewHolder);
return customView;
}
}

Maybe you should try:
view = mInflater.inflate(mResource,parent,null);
Check this blog it explains the similar issue:
http://www.doubleencore.com/2013/05/layout-inflation-as-intended/

What I would do (unless you have thousands of images):
1. create a data structure - a simple class holding a String name to be displayed and a bitmap
2. create an adapter for it
3. in the getView method assign the correct bitmap to the correct ImageView.
In your case though you can create a similar data structure but holding not a bitmap but an AsyncTask. Anyway you need to bind the asynctask to the string into one item. An array (or arraylist) of such items will be fed to your adapter. Displayed will be an imageview and a textview.
AsyncTask can be cancelled with cancel().

Hey I found the solution to this problem just use following function instead of your function
#Override
protected void onPostExecute(Bitmap result) {
if (!imv.getTag().toString().equals(rec_id)) {
return;
}
if(result != null && imv != null){
int index = id.indexOf(imv.getTag().toString());
if(list.getFirstVisiblePosition()<=index && index<=list.getLastVisiblePosition())
{
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
}
}else{
imv.setImageBitmap(icon);
imv.setVisibility(View.GONE);
}
}
Here list is the object of listview. Just pass your list view object to your adapter and paste this function instead of your onPostExecute function.

Related

Android custom CursorAdapter with AsyncTask

I'm trying to build a list with an image that is taken from the device and a text. It turns out that taking images from the phone that was from the phone's camera is a task that takes a while so I'm trying to make it as fast as possible so the user experience won't get slower. All I got from this is that it looks like all the images are loaded in one ImageView and than the images spread to all the other ImageViews (I'm not completely sure that my implementation of the ViewHolder technique and Custom CursorAdapter is correct).
public class MyCustomCurserAdapter extends CursorAdapter {
static class ViewHolder {
public TextView nameText;
public ImageView imageThumbnail;
}
Cursor cursor;
public MyCustomCurserAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
// TODO Auto-generated constructor stub
}
#Override
public void bindView(View view, Context arg1, Cursor cursor) {
ViewHolder holder = (ViewHolder)view.getTag();
int pathCol = cursor.getColumnIndex(NewPicSQLiteHelper.COLUMN_PATH);
String imageInSD = cursor.getString(pathCol);
File imgFile = new File(imageInSD);
if(imgFile.exists()){
int nameCol = cursor.getColumnIndex(NewPicSQLiteHelper.COLUMN_PIC_NAME);
String name = cursor.getString(nameCol);
if (name != null)
holder.nameText.setText(name);
ImageTask task = new ImageTask(holder.imageThumbnail);
task.execute(imgFile);
}
}
#Override
public View newView(Context arg0, Cursor cur, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.new_pic_item, parent, false);
ViewHolder holder = new ViewHolder();
holder = new ViewHolder();
holder.nameText = (TextView) view.findViewById(R.id.pic_name_entry);
holder.imageThumbnail = (ImageView) view.findViewById(R.id.pic_thumbnail);
// The tag can be any Object, this just happens to be the ViewHolder
view.setTag(holder);
return view;
}
private class ImageTask extends AsyncTask<File, Void, Bitmap>{
private final WeakReference <ImageView> imageViewReference;
public ImageTask(ImageView imageView) {
imageViewReference = new WeakReference <ImageView> (imageView);
}
#Override
protected Bitmap doInBackground(File... params) {
String path = params[0].getAbsolutePath();
return decodeSampledBitmapFromResource(path,75,75);
}
#Override
protected void onPostExecute(Bitmap result) {
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
if (imageView != null) {
if (result != null) {
imageView.setImageBitmap(result);
imageView.setVisibility(ImageView.VISIBLE);
} else {
imageView.setVisibility(ImageView.INVISIBLE);
}
}
}
}
private Bitmap decodeSampledBitmapFromResource(String orgImagePath, int reqWidth, int reqHeight) {
}
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
}
}
I think probable reason thats its taking time is because the images would be of at least 1 mb size further you can change to thumbnail and retrieve it and also if still taking time you could put lazy downloading which is done when we take image from the server(Basically what it does is it loads the text and shows image when we get the image)
Remove ImageTask AsyncTask ..
Use a Libraries like Glide or Picasso . Very effective for almost any case where you need to fetch, resize, and display a remote image.
I used a glide for loading images from phone storage uri
Use any of the above and see the difference

How to improve ListView's loading time faster?

I implemented my ListView using custom adapter extended from ArrayAdapter.
My problem is sometimes ListView is loaded slowly. That means a blank activity is loaded first without the ListView, then the ListView comes out. At worst case, I am prompted to "force closed or wait". I like to improve that slow loading as it is annoying to the user.
But sometimes, loading is fast and almost immediate.
But I like to make sure my ListView design is correct and the design does not have any problem with that slow loading. So that this discussion will be useful for other people who are facing the same problem as mine.
My ListView is designed as follow.
Each ListItem has three components, thumbnail image, ID text, and arrow image as shown in the figure attached .
In loading process of the ListView, (1)All ID text are retrieved from the database and populated into a ListArray List<String> listIDs
public class MyListFragment extends ListFragment implements OnItemClickListener {
dbHelper = new TrackerDBAdapter(getActivity());
dbHelpLatLong = new LatLogDBAdapter(getActivity());
dbHelpNotification = new NotificationDatabaseAdapter(getActivity());
dbHelper.open();
Cursor cursor = dbHelper.fetchAllTrackerInTheList();
listIDs = new ArrayList<String>();
activationStatus = new ArrayList<String>();
thisListFragmentContext = getActivity();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
listIDs.add(cursor.getString(1));
}
dbHelper.close();
(2)Then my custom list adapter is called.
adapter = new customList_Adaptor(thisListFragmentContext,
R.layout.list_row, listIDs, this);
}
That is the loading process inside my `ListFragment`.
(3) The following class is my custom ArrayAdapter and I implemented to load thumbnail ImageView using AsyncTask. My query are
(i)I still have retrieving ID text from database, and loading arrow image. Should I put those processes into AsyncTask as well?
(ii)If I need it, should I implement another AsyncTask or use the same AsyncTask used for thumbnail image loading?
(iii)Among these, which aspect of the program design I still can improve that is suspicious to my slow loading?
public class customList_Adaptor extends ArrayAdapter<String>{
protected static final int CAMERA_REQUEST = 0;
private TrackerDBAdapter dbHelper;
private Context context;
private List<String> listIDs = new ArrayList<String>();
private List<String> activationState = new ArrayList<String>();
public MyListFragment mMyListFragment;
public customList_Adaptor(Context context, int textViewResourceId,
List<String> objects, List<String> activationStatus, MyListFragment mMyListFragment) {
super(context, textViewResourceId, objects);
this.setContext(context);
this.listIDs = objects;
this.activationState = activationStatus;
this.mMyListFragment= mMyListFragment;
dbHelper = new TrackerDBAdapter(context);
}
#Override
public int getCount() {
// TODO Auto-generated method stub
if(listIDs != null)
return listIDs.size();
else
return 0;
}
#Override
public String getItem(int arg0) {
// TODO Auto-generated method stub
if(listIDs != null)
return listIDs.get(arg0);
else
return null;
}
#Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
ViewHolder viewHolder=new ViewHolder();
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if(vi==null){
vi = inflater.inflate(R.layout.list_row, parent, false);
viewHolder.id=(TextView)vi.findViewById(R.id.title);
viewHolder.thumbnailImage=(ImageView)vi.findViewById(R.id.list_image);
viewHolder.activationStatus = (TextView)vi.findViewById(R.id.activated);
//lazy load image
BitmapWorkerTask task = new BitmapWorkerTask(viewHolder.thumbnailImage);
task.execute(position);
viewHolder.arrow=(ImageView)vi.findViewById(R.id.list_arrow);
vi.setTag(viewHolder);
}
else
viewHolder=(ViewHolder) vi.getTag();
viewHolder.thumbnailImage.setOnClickListener(new onMyClick(position));
// Setting all values in listview
viewHolder.id.setText(listIDs.get(position));
if(activationState.get(position).equals("Not activated yet")){
viewHolder.activationStatus.setText(activationState.get(position));
viewHolder.activationStatus.setTextColor(android.graphics.Color.GRAY);
}
else if(activationState.get(position).equals("Activated"))
viewHolder.activationStatus.setText("");
return vi;
}
public class onMyClick implements OnClickListener {
private final int pos;
public onMyClick(int pos) {
this.pos = pos;
}
#Override
public void onClick(View v) {
MyListFragment.clickedimageView = (ImageView) v.findViewById(R.id.list_image);
mMyListFragment.imagepos(pos);
}
}
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
//Lazy image update
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
setData(params[0]);
Bitmap bitmap = null;
dbHelper.open();
Cursor mCursor = dbHelper.getImagebyIDnumber(getData());
byte[] img_bytes = mCursor.getBlob(13);
bitmap = BitmapFactory.decodeByteArray(img_bytes, 0, img_bytes.length);
dbHelper.close();
return bitmap;
}
// 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 int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
}
public class ViewHolder {
TextView id;
TextView activationStatus;
ImageView thumbnailImage;
ImageView arrow;
}
I did a few things to make it faster in loading the app.
I am not sure which one is the solution.
(1) I load all data from sql database including text and thumbnail images using AsyncTask.
(2) I change thumbnail image format from png to jpg.
(3) Then I clear the cache manually.
The app looks like faster in loading, but sometimes it is still slow. Most of the times, it is faster than before.
I am still making improvement to my app.
Thanks

Images are repeating in ListView

I have implemented android app which should download images from server and display them in ListView, but very interesting thing occures while images are downloading
As you can see in video pictures which haven't been downloaded yet are represented by those which have been already downloaded. How that can happen? I've thinking about it almost two days.
http://www.youtube.com/watch?v=lxY-HAuJO0o&feature=youtu.be
here is my code of ListView adapter.
public class MoviesAdapter extends ArrayAdapter<ParkCinema> {
private ArrayList<ParkCinema> movieDataItems;
private Activity context;
public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<ParkCinema> movieDataItems) {
super(context, textViewResourceId, movieDataItems);
this.context = context;
this.movieDataItems = movieDataItems;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.movie_data_row, null);
}
ParkCinema movie = movieDataItems.get(position);
if (movie!=null){
ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
String url = movie.poster();
if (url!=null) {
Bitmap bitmap = fetchBitmapFromCache(url);
if (bitmap==null) {
new BitmapDownloaderTask(imageView).execute(url);
}
else {
imageView.setImageBitmap(bitmap);
}
}
}
return convertView;
}
private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>();
private void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (bitmapCache) {
bitmapCache.put(url, bitmap);
}
}
}
private Bitmap fetchBitmapFromCache(String url) {
synchronized (bitmapCache) {
final Bitmap bitmap = bitmapCache.get(url);
if (bitmap != null) {
return bitmap;
}
}
return null;
}
private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private final WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
#Override
protected Bitmap doInBackground (String... source) {
url = source[0];
Bitmap image;
try{
image = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
return image;
}
catch(Exception e){Log.e("Error", e.getMessage()); e.printStackTrace();}
return null;
}
#Override
protected void onPostExecute(Bitmap bitmap) {
addBitmapToCache(url, bitmap);
imageViewReference.get().setImageBitmap(bitmap);
}
}
}
Edit 3:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.movie_data_row, null);
}
ParkCinema movie = movieDataItems.get(position);
ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
if (movie!=null){
String url = movie.poster();
if (url != null) {
Bitmap bitmap = fetchBitmapFromCache(url);
if (bitmap == null) {
imageView.setImageResource(R.drawable.no_image);
new BitmapDownloaderTask(imageView).execute(url);
}
else {
imageView.setImageBitmap(bitmap);
}
}
else {
imageView.setImageResource(R.drawable.no_image);
}
}
else {
imageView.setImageResource(R.drawable.no_image);
}
return convertView;
}
Aha! I think I may know the issue. Right now, your getView method sets your ImageView like this:
Gets movie object at position
Pulls out the movie's thumbnail url
Using that url, it tries to find the image in the cache
If it finds the image, it sets it
If it can't find the image, it starts an async network request to go get it, and sets it after it gets downloaded.
Your issus arises since ListView reuses its rows' Views. When the first View scrolls off the screen, rather than inflate a new one, ListView passes the now offscreen row's View in as convertView for you to reuse (this is for efficiency).
When your getView gets a convertView that is getting reused, its ImageView has already been set from the row that had it before, so you see the old image from the offscreen row's View. With your current getView process, you check for the new row's image, and it doesn't find it in the cache, it starts a request to download it. While it is downloading, you see the old image until you get the new image.
To fix this, you need to make sure you set every field in the row's View immediately, to make sure you don't have any Views showing stale data. I would suggest you set the ImageView to the default drawable resource (you have set in your R.layout.movie_data_row) while you wait for the network download to get the image.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.movie_data_row, null);
}
ParkCinema movie = movieDataItems.get(position);
ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
if (movie != null) {
String url = movie.poster();
if (url != null) {
Bitmap bitmap = fetchBitmapFromCache(url);
if (bitmap == null) {
// Set the movie thumbnail to the default icon while we load
// the real image
imageView.setImageResource(R.drawable.movie_thumb_icon);
new BitmapDownloaderTask(imageView).execute(url);
}
else {
// Set the image to the bitmap we get from the cache
imageView.setImageBitmap(bitmap);
}
}
else {
// Set the movie thumbnail to the default icon, since it doesn't
// have a thumbnail URL
imageView.setImageResource(R.drawable.movie_thumb_icon);
}
}
else {
// Set the movie thumbnail to the default icon, since there's no
// movie data for this row
imageView.setImageResource(R.drawable.movie_thumb_icon);
}
-Edit-
Updated to be even more robust, using your drawable. You also have an issue with your BitmapDownloaderTask, it does not handle errors/null. Try adding this as well.
#Override
protected void onPostExecute(Bitmap bitmap) {
addBitmapToCache(url, bitmap);
if (bitmap == null) {
// Set the movie thumbnail to the default icon, since an error occurred while downloading
imageViewReference.get().setImageResource(R.drawable.movie_thumb_icon);
}
else {
imageViewReference.get().setImageBitmap(bitmap);
}
}
i had this issue and implemented lruCache ...i believe you need api 12 and above or use the compatiblity v4 library. lurCache is fast memory but it also has a budget, so if your worried about that you can use a diskcache ...its all described here http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
I'll now provide my implementation which is a singleton i call from anywhere like this:
//where first is a string and other is a imageview to load
DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar);
here's the ideal code to cache and then call the above in getView of an adapter when retrieving the web image:
public class DownloadImageTask {
private LruCache<String, Bitmap> mMemoryCache;
/* create a singleton class to call this from multiple classes */
private static DownloadImageTask instance = null;
public static DownloadImageTask getInstance() {
if (instance == null) {
instance = new DownloadImageTask();
}
return instance;
}
//lock the constructor from public instances
private DownloadImageTask() {
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
}
public void loadBitmap(String avatarURL, ImageView imageView) {
final String imageKey = String.valueOf(avatarURL);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_launcher);
new DownloadImageTaskViaWeb(imageView).execute(avatarURL);
}
}
private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
private Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
/* a background process that opens a http stream and decodes a web image. */
class DownloadImageTaskViaWeb extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
public DownloadImageTaskViaWeb(ImageView bmImage) {
this.bmImage = bmImage;
}
protected Bitmap doInBackground(String... urls) {
String urldisplay = urls[0];
Bitmap mIcon = null;
try {
InputStream in = new java.net.URL(urldisplay).openStream();
mIcon = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
addBitmapToMemoryCache(String.valueOf(urldisplay), mIcon);
return mIcon;
}
/* after decoding we update the view on the mainUI */
protected void onPostExecute(Bitmap result) {
bmImage.setImageBitmap(result);
}
}
}
Views are reused for performance with Adapters. You should use another approch .
You have to have a class holder which reuse your views. In your case you class should be something like this:
public class MoviesAdapter extends ArrayAdapter<ParkCinema> {
private ArrayList<ParkCinema> movieDataItems;
private Activity context;
public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<ParkCinema> movieDataItems) {
super(context, textViewResourceId, movieDataItems);
this.context = context;
this.movieDataItems = movieDataItems;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.movie_data_row, null);
holder = new ViewHolder();
holder.imageView = (BarImageView) convertView.findViewById(R.id.movie_thumb_icon);
} else {
holder = (ViewHolder) convertView.getTag();
}
ParkCinema movie = movieDataItems.get(position);
if (movie!=null){
String url = movie.poster();
if (url!=null) {
Bitmap bitmap = fetchBitmapFromCache(url);
if (bitmap==null) {
new BitmapDownloaderTask(imageView).execute(url);
}
else {
imageView.setImageBitmap(bitmap);
}
}
}
return convertView;
}
private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>();
private void addBitmapToCache(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (bitmapCache) {
bitmapCache.put(url, bitmap);
}
}
}
private Bitmap fetchBitmapFromCache(String url) {
synchronized (bitmapCache) {
final Bitmap bitmap = bitmapCache.get(url);
if (bitmap != null) {
return bitmap;
}
}
return null;
public static class ViewHolder {
ImageView imageView;
}
}
I have spent hours trying to figure this one out as well...Thanks to Steven Byle's solution...
Here is my solution to something similar when a user selects an item from a list:
adapter.setSelectedIndex(position);
then in the custom adapter:
public void setSelectedIndex(int ind)
{
selectedIndex = ind;
notifyDataSetChanged();
}
and then finally in the getView method of the adapter:
if(selectedIndex!= -1 && position == selectedIndex)
{
holder.tab.setBackgroundColor(Color.BLACK);
}
else{
holder.tab.setBackgroundColor(Color.DKGRAY);
}
So in conclusion make sure you assign default values
In my case i used Picasso library instead of AsyncTask for downloading image.
enter link description here
Also write if else condition, that is set null to image if url is not available
instead of using the convertview object create a new view each time.
View localView = ((LayoutInflater)parentscreen.getSystemService("layout_inflater")).inflate(R.layout.activity_list_row, null);
By inflating as above.

scrolling listfragment not fluent with photo

i have a listfragment whith users photo. When i scroll my list it is slow. Then i load my photo async:
public class UserArrayAdapter extends ArrayAdapter<Friend>{
.
.
.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.list_user, parent, false);
TextView textView = (TextView) rowView.findViewById(R.id.label);
textView.setText(friends.get(position).getName());
String s = friends.get(position).getName();
String Contact_Id = friends.get(position).getID();
new BackgroundLoadPhoto(context, Contact_Id, rowView).execute();
return rowView;
}
class BackgroundLoadPhoto extends AsyncTask<Void, Void, Void> {
Context mContext;
String Contact_Id;
View rowView;
Bitmap photo;
ImageView profile;
public BackgroundLoadPhoto(Context mContext,String Contact_Id,View rowView){
this.mContext=mContext;
this.Contact_Id=Contact_Id;
this.rowView=rowView;
profile = (ImageView)rowView.findViewById(R.id.logo);
}
#Override
protected void onPostExecute(Void result) {
//set real user immage
if(photo!=null){
profile.setImageBitmap(photo);
}
}
#Override
protected void onPreExecute() {
//set default photo
profile.setImageResource(R.drawable.search_marker);
}
#Override
protected Void doInBackground(Void... params) {
//get user photo in background
photo=getPhoto(Contact_Id);
return null;
}
private Bitmap getPhoto(String Contact_Id){
Bitmap my_btmp = null;
//only if it is a real user..
if(Contact_Id!="-1"){
Uri my_contact_Uri = Uri.
withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(Contact_Id));
InputStream photo_stream = ContactsContract
.Contacts.openContactPhotoInputStream(mContext.getContentResolver(),my_contact_Uri);
BufferedInputStream buf =new BufferedInputStream(photo_stream);
my_btmp = BitmapFactory.decodeStream(buf);
}
return my_btmp;
}
}
now i can scrolling my list fluent..but i can see photo after a while..i don't like! how i can do? thanks!
you're loading the bitmaps asynchronously and when you scroll they're not ready yet so you see them once they're ready.
you can improve performance by using a memory cache for the bitmaps, such as http://developer.android.com/reference/android/util/LruCache.html
see this - https://developers.google.com/events/io/sessions/gooio2012/103/

Load asynchronous images in listView

I want to load images from a server, AFTER loading the data in a list view.
I know that a lot of topics existing for this problem but I haven't found the solution...
So this is my code :
//asyncTackClass for loadingpictures
public class LoadImagesThread extends AsyncTask<Bundle, Void, Bitmap> {
private ImageView view;
private Bitmap bm;
private Context context;
private final WeakReference<ImageView> imageViewReference;
private final String BUNDLE_URL = "url";
private final String BUNDLE_NAME = "name";
private final String BUNDLE_BM = "bm";
public LoadImagesThread(Context context, ImageView view) {
this.context=context;
imageViewReference = new WeakReference<ImageView>(view);
}
#Override
protected Bitmap doInBackground(Bundle... b) {
Bitmap bm =null;
if (StorageHelper.getBitmap(b[0].getString(BUNDLE_NAME)) != null) { // Check the sdcard
bm = StorageHelper.getBitmap(b[0].getString(BUNDLE_NAME));
Log.w("LoadImagesThread", "Get image from sdcard : "+b[0].getString(BUNDLE_NAME));
} else { // Check the server
bm = ServiceHelper.getBitmapFromURL(b[0].getString(BUNDLE_URL));
StorageHelper.saveBitmap(bm, b[0].getString(BUNDLE_NAME)); // Save image on sdcard
Log.w("LoadImagesThread", "Get image from server : "+b[0].getString(BUNDLE_NAME));
}
return bm;
}
#Override
protected void onPostExecute(final Bitmap bm) {
super.onPostExecute(bm);
if (bm != null){ //if bitmap exists...
view = imageViewReference.get();
// Fade out
Animation fadeOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fadeoutimage);
fadeOutAnimation.setAnimationListener(new AnimationListener() {
public void onAnimationStart(Animation animation) {
}
public void onAnimationRepeat(Animation animation) {
}
public void onAnimationEnd(Animation animation) {
// Fade in
view.setImageBitmap(bm);
Animation fadeInAnimation = AnimationUtils.loadAnimation(context, R.anim.fadeinimage);
view.startAnimation(fadeInAnimation);
}
});
// Launch the fadeout
view.startAnimation(fadeOutAnimation);
}else{ //if not picture, display the default ressource
view.setImageResource(R.drawable.productcarre);
}
}
}
The code is used to display a Bitmap in a ImageView
And this is the adapter:
public class ListViewShoplistStoresAdapter extends BaseAdapter {
private ArrayList<Shop> shopList;
private Activity activity;
private HashMap<Integer, ImageView> views;
private final String BUNDLE_URL = "url";
private final String BUNDLE_NAME = "name";
private final String BUNDLE_POS = "pos";
private final String BUNDLE_ID = "id";
public ListViewShoplistStoresAdapter(Activity activity, ArrayList<Shop> shopList) {
super();
this.activity = activity;
this.shopList = shopList;
this.views = new HashMap<Integer, ImageView>();
}
public int getCount() {
return shopList.size();
}
public Object getItem(int position) {
return shopList.get(position);
}
public long getItemId(int position) {
return shopList.get(position).getId();
}
private class ViewHolder {
public TextView store;
public TextView name;
public ImageView ImageStore;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder view;
LayoutInflater inflator = activity.getLayoutInflater();
if(convertView == null) {
view = new ViewHolder();
convertView = inflator.inflate(R.layout.listviewshops, null);
view.store = (TextView) convertView.findViewById(R.id.store);
view.name = (TextView) convertView.findViewById(R.id.name);
view.ImageStore = (ImageView) convertView.findViewById(R.id.imgstore);
convertView.setTag(view);
}else {
view = (ViewHolder) convertView.getTag();
}
Typeface regular=Typeface.createFromAsset(activity.getAssets(), "fonts/RobotoRegular.ttf");
view.store.setTypeface(regular);
Typeface light=Typeface.createFromAsset(activity.getAssets(), "fonts/RobotoLight.ttf");
view.store.setTypeface(light);
Brand brand = StorageHelper.getBrand(activity, shopList.get(position).getBrandId());
if (brand == null) {
Log.e("SetShopInAdapter","Brand null");
Toast.makeText(activity, "Impossible d'afficher la liste de magasins", Toast.LENGTH_LONG).show();
} else {
view.store.setText(brand.getName());
view.name.setText(shopList.get(position).getName());
view.ImageStore.setImageResource(R.drawable.productcarre);
}
Bundle b = new Bundle();
//url of the pict
b.putString(BUNDLE_URL, ServiceHelper.getImageUrl("brand", brand.getName()));
// name of image
b.putString(BUNDLE_NAME, ServiceHelper.getCleanImageName(brand.getName()));
//position in the listView
b.putInt(BUNDLE_POS, position);
//id of the current object
b.putInt(BUNDLE_ID, brand.getId());
//put info in the map in order to display in the onPostExecute
if(views.get(position)==null){
views.put(position, view.ImageStore);
// launch thread
new LoadImagesThread(activity.getApplicationContext(), view.ImageStore).execute(b);
}
return convertView;
}
}
So, when I used a GridView, there were no problems, but when I use a ListView the image is changed only in the first item !
Example:
I want to display product images for "car", "house" and "apple" items.
The code will launch the thread and all images (car then house and finally apple) will be displayed in the first item (the car item)...
And the house and the apple while not have images !!
Do you know what I should do ?
Thanks
There is a lot about this here on SO..
asynchrnous loading like that is called "Lazy Loading"
https://github.com/thest1/LazyList
for full implementation of such a process with list
For loading images in general i recomend this :
https://github.com/nostra13/Android-Universal-Image-Loader
You can use volley to improve in listview performance.
Take a look at this simple example: Android Custom ListView with Image and Text using Volley

Categories

Resources