I'm working with lazy loading a Gallery
of images (one class for the Gallery
and Adapter
, and another for the lazy loading part). The second class uses runOnUiThread
to update the first class using its context, but it seems that it's calling the getView()
method of the first class' adapter again. This means that getView()
is being called twice for every image in the Gallery. Check out the code below.
The weird thing is that second getView()
call is only being called on the selected image in the Gallery
widget. If four images are shown at the same time, getView()
will be called on those four images once, and three additional times on the selected image.
Any ideas why this is happening?
Here's the adapter
public class ImageAdapter extends BaseAdapter {
public HorizontalImageLoader horImageLoader;
public ImageAdapter() {
horImageLoader = new HorizontalImageLoader(Main.this);
}
public int getCount() {
return coverFileNames.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(Main.this);
} else {
imageView = (ImageView) convertView;
}
// If I just use this, getView() is only called once per image (the correct way):
// imageView.setImageResource(R.drawable.noposterxl);
// If I just use this, getView() is only called once per
// image, and additional times on the selected image (not correct):
horImageLoader.DisplayImage(coverFileNames.get(position), imageView, position);
return imageView;
}
}
Here's the HorizontalImageLoader class (based on this example)
public class HorizontalImageLoader {
private Activity activity;
private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
public HorizontalImageLoader(Activity activity) {
this.activity = activity;
photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
}
public void DisplayImage(String fileUrl, ImageView imageView, final int pos) {
imageViews.put(imageView, fileUrl);
queuePhoto(fileUrl, activity, imageView, pos);
imageView.setImageResource(R.drawable.noposterxl);
}
private void queuePhoto(String url, Activity activity, ImageView imageView, int position) {
//This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them.
photosQueue.Clean(imageView);
PhotoToLoad p=new PhotoToLoad(url, imageView, position);
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
}
//start thread if it's not started yet
if(photoLoaderThread.getState()==Thread.State.NEW)
photoLoaderThread.start();
}
private Bitmap getBitmap(String fileUrl, int position) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bm = BitmapFactory.decodeFile(fileUrl, options);
return bm;
}
//Task for the queue
private class PhotoToLoad
{
public String url;
public ImageView imageView;
public int pos;
public PhotoToLoad(String u, ImageView i, int p){
url=u;
imageView=i;
pos = p;
}
}
PhotosQueue photosQueue=new PhotosQueue();
public void stopThread()
{
photoLoaderThread.interrupt();
}
//stores list of photos to download
class PhotosQueue
{
private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>();
//removes all instances of this ImageView
public void Clean(ImageView image)
{
try {
for(int j=0 ;j<photosToLoad.size();){
if(photosToLoad.get(j).imageView==image)
photosToLoad.remove(j);
else
++j;
}
} catch (Exception e) {
// Do nothing
}
}
}
class PhotosLoader extends Thread {
public void run() {
try {
while(true)
{
//thread waits until there are any images to load in the queue
if(photosQueue.photosToLoad.size()==0)
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.wait();
}
if(photosQueue.photosToLoad.size()!=0)
{
PhotoToLoad photoToLoad;
synchronized(photosQueue.photosToLoad){
photoToLoad=photosQueue.photosToLoad.pop();
}
Bitmap bmp = getBitmap(photoToLoad.url, photoToLoad.pos);
String tag=imageViews.get(photoToLoad.imageView);
if(tag!=null && tag.equals(photoToLoad.url)){
BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
Activity a=(Activity)photoToLoad.imageView.getContext();
a.runOnUiThread(bd); // This seems to be causing the additional calls to getView()
}
}
if(Thread.interrupted())
break;
}
} catch (InterruptedException e) {
//allow thread to exit
}
}
}
PhotosLoader photoLoaderThread=new PhotosLoader();
//Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable
{
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i){
bitmap=b;
imageView=i;
}
public void run()
{
if(bitmap!=null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(R.drawable.noposterxl);
}
}
}
UPDATED AGAIN
If I do the following in my getView()
method, this is what LogCat says:
Code:
Log.d("POSITION", "Current position: " + position);
horImageLoader.DisplayImage(coverFileNames.get(position), imageView, position);
LogCat:
09-03 13:59:11.920: DEBUG/POSITION(15278): Current position: 1
09-03 13:59:11.960: DEBUG/POSITION(15278): Current position: 2
09-03 13:59:11.960: DEBUG/POSITION(15278): Current position: 3
09-03 13:59:11.960: DEBUG/POSITION(15278): Current position: 0
09-03 13:59:12.110: DEBUG/POSITION(15278): Current position: 1
09-03 13:59:12.240: DEBUG/POSITION(15278): Current position: 1
09-03 13:59:12.300: DEBUG/POSITION(15278): Current position: 1
(note the additional three log entries with "Current position: 1" at the bottom)
If I do this, then here's what LogCat says:
Code:
Log.d("POSITION", "Current position: " + position);
imageView.setImageResource(R.drawable.noposterxl);
LogCat:
09-03 14:02:47.340: DEBUG/POSITION(15412): Current position: 1
09-03 14:02:47.360: DEBUG/POSITION(15412): Current position: 2
09-03 14:02:47.370: DEBUG/POSITION(15412): Current position: 3
09-03 14:02:47.370: DEBUG/POSITION(15412): Current position: 0
(note that this is returning the correct result - only one call per image)
PS. My Gallery
is set to select index 1 first, that's why position 1 is called first.