列表中的第一项错误图像

时间:2013-05-31 07:24:24

标签: java android android-listview

过去几天我一直在努力解决这个问题。我已尝试堆栈溢出时可用的所有内容但我无法解决此问题。

仅当我的第一个项目没有有效网址并且仅针对第一个元素时才会出现此错误。 向下滚动后,加载了正确的图像。

基本思想是我放置了一个临时图像,该图像将替换为从JSON加载的图像。如果没有url(或无效),则应显示特定的默认值,具体取决于我正在显示的动物的类型(如果它是狗将显示狗图像,如果它是猫将显示猫图像等等)。

以下是我使用的ImageLoader类中的代码:

    package ro.nextlogic.petsplus.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import ro.nextlogic.petsplus.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.widget.ImageView;

public class ImageLoader {

    static MemoryCache memoryCache=new MemoryCache();
    static FileCache fileCache;
    private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
    ExecutorService executorService;    
    /**
     * The maximum number of threads used when loading images. 
     */
    private static final int MAX_THREADS = 5;
    private Context context;

    private volatile static ImageLoader instance;

    /** Returns singleton class instance */
    public static ImageLoader getInstance(Context context) {
        if (instance == null) {
            synchronized (ImageLoader.class) {
                if (instance == null) {
                    instance = new ImageLoader(context);
                }
            }
        }
        return instance;
    }

    private ImageLoader(Context context) {
        this.context = context;
        fileCache=new FileCache(context);

        executorService = Executors.newFixedThreadPool(MAX_THREADS);
    }

    final int stub_id = R.drawable.default_other;
    public void displayImage(String url, ImageView imageView, final int REQUIRED_SIZE) {
        if (url == null) {
            return;
        }
        Log.i("BITMAP", "imageView = " + imageView + "\nurl = " + url);
        imageViews.put(imageView, url);
        Bitmap bitmap=memoryCache.get(url);
        if (bitmap!=null && !bitmap.isRecycled() ) {
            imageView.setImageBitmap(bitmap);
        } else {
            queuePhoto(url, imageView, REQUIRED_SIZE);
            imageView.setImageResource(stub_id);
        }
    }

    private void queuePhoto(String url, ImageView imageView, final int REQUIRED_SIZE) {
        PhotoToLoad p=new PhotoToLoad(url, imageView, REQUIRED_SIZE);
        executorService.submit(new PhotosLoader(p));
    }

    public static Bitmap getBitmap(final String url, final int REQUIRED_SIZE) {
        File f = fileCache.getFile(url);

        //from SD cache
        Bitmap b = decodeFile(f, REQUIRED_SIZE);
        if(b != null)
            return b;

        //from web
        try {
            Bitmap bitmap=null;
            URL imageUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            conn.setInstanceFollowRedirects(true);
            InputStream is=conn.getInputStream();
            OutputStream os = new FileOutputStream(f);
            Utils.CopyStream(is, os);
            os.close();
            bitmap = decodeFile(f, REQUIRED_SIZE);
            return bitmap;
        } catch (Throwable ex){
           ex.printStackTrace();
           if(ex instanceof OutOfMemoryError)
               memoryCache.clear();
           return null;
        }
    }

    //decodes image and scales it to reduce memory consumption
    private static Bitmap decodeFile(final File f, final int REQUIRED_SIZE) {
        try {
            //  Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            FileInputStream stream1=new FileInputStream(f);
            BitmapFactory.decodeStream(stream1,null,o);
            stream1.close();

            //  The new size we want to scale to
//            final int REQUIRED_SIZE=70;   // 70 is best for Thumbnail
            //  Get the width and height of the image
            int width_tmp=o.outWidth, height_tmp=o.outHeight;
            //  Find the correct scale value. It should be the power of 2.
            int scale=1;
            while(true){
                if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                    break;
                width_tmp/=2;
                height_tmp/=2;
                scale*=2;
            }
            //  Decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize=scale;
            FileInputStream stream2=new FileInputStream(f);
            Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2);
            stream2.close();
            return bitmap;
        } catch (FileNotFoundException e1) {
//          Log.e("IMAGELOADER", "FileNotFoundException: ", e1);
        } catch (IOException e2) {
            Log.e("IMAGELOADER", "IOException: ", e2);
        }
        return null;
    }

    //Task for the queue
    private class PhotoToLoad {
        public final String url;
        public final ImageView imageView;
        public final int REQUIRED_SIZE;
        public PhotoToLoad(final String u, final ImageView i, final int rq){
            url=u;
            imageView=i;
            REQUIRED_SIZE = rq;
        }
    }

    class PhotosLoader implements Runnable {
        PhotoToLoad photoToLoad;
        PhotosLoader(PhotoToLoad photoToLoad) {
            this.photoToLoad=photoToLoad;
        }

        @Override
        public void run() {
            try{
                if(imageViewReused(photoToLoad))
                    return;
                Bitmap bmp = getBitmap(photoToLoad.url, photoToLoad.REQUIRED_SIZE);
                memoryCache.put(photoToLoad.url, bmp);
                if(imageViewReused(photoToLoad))
                    return;
                BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad);
                Activity a=(Activity)photoToLoad.imageView.getContext();
                a.runOnUiThread(bd);
            }catch(Throwable th){
                th.printStackTrace();
            }
        }
    }

    boolean imageViewReused(PhotoToLoad photoToLoad) {
        String tag=imageViews.get(photoToLoad.imageView);
        if(tag==null || !tag.equals(photoToLoad.url))
            return true;
        return false;
    }

    //Used to display bitmap in the UI thread
    class BitmapDisplayer implements Runnable {
        Bitmap bitmap;
        PhotoToLoad photoToLoad;
        public BitmapDisplayer(Bitmap b, PhotoToLoad p) {
            bitmap = b;
            photoToLoad = p;
        }
        public void run() {
            if(imageViewReused(photoToLoad))
                return;
            if(bitmap != null)
                Utils.imageViewAnimatedChange(context, photoToLoad.imageView, bitmap);
//                photoToLoad.imageView.setImageBitmap(bitmap);
        }
    }

    public void clearCache() {
        memoryCache.clear();
        fileCache.clear();
    }
}

这就是我所说的:

    @Override
public View getView(int position, View convertView, ViewGroup parent) {
    View rowView = convertView;
    final ViewHolder holder;

    if (convertView == null) {
        rowView = inflator.inflate(R.layout.shelter_animal_rowlayout, parent, false);
        holder = new ViewHolder();
        holder.animalImg = (ImageView) rowView.findViewById(R.id.shelter_animal_image);
        holder.animalName = (TextView) rowView.findViewById(R.id.shelter_animal_name);
        holder.animalDescription = (TextView) rowView.findViewById(R.id.shelter_animal_description);
        rowView.setTag(holder);            
    } else {
        holder = ((ViewHolder) rowView.getTag());
    }

    AnimalItem animalItem = filteredModelItemsArray.get(position);
    if (animalItem != null) {
        // Display the animal name, set "Unknown" if not available
        if (!TextUtils.isEmpty(animalItem.name) &&                  // Not empty
                !animalItem.name.contains("Unknown")) {     // Not Unknown
            holder.animalName.setText(animalItem.name);
        } else {
            holder.animalName.setText(R.string.shelter_animal_name);
        }

        // Display the animal description, set "Unknown" if not available
        if (!TextUtils.isEmpty(animalItem.description) &&                   // Not empty
                !animalItem.description.contains("Unknown")) {  // Not Unknown
            holder.animalDescription.setText(Html.fromHtml(animalItem.description));    
        } else {
            holder.animalDescription.setText(R.string.shelter_animal_description);
        }

        // Display the animal image
        if (animalItem.photo != null) {
            imageLoader.displayImage(animalItem.photo, holder.animalImg, 70);
        } else if (animalItem.animal.contains("Dog")) {
            holder.animalImg.setImageResource(R.drawable.default_dog);
        } else if (animalItem.animal.contains("Cat")) {
            holder.animalImg.setImageResource(R.drawable.default_cat);
        } else {
            holder.animalImg.setImageResource(android.R.drawable.ic_menu_help);
        }
    } else {
        Toast.makeText(context, "NO animals retrieved from server!", Toast.LENGTH_LONG).show();
    }

return rowView;

}

animalItem.photo是JSON的网址 animalItem.animal是从JSON获得的动物的类型

我应该提到文本显示正常...只有图像错误且仅适用于第一个元素(当照片不可用时)。

如果有人能指出我正确的方向或告诉我做错了什么,我将不胜感激。

修改 我想通过不再使用WeakHashMap并在Map中保存每个ImageView的hashCode来解决这个问题。所以我改变了这一点:

private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());

private Map<Integer, String> imageViews=Collections.synchronizedMap(new HashMap<Integer, String>());

并保存并获取值:

imageViews.put(imageView, url);

imageViews.put(imageView.hashCode(), url);

imageViews.get(photoToLoad.imageView);

imageViews.get(photoToLoad.imageView.hashCode());

解: 我已经通过添加取消加载图像的方法来解决问题而不影响性能:

这是方法(在ImageLoader中声明):

public void cancelDisplayTaskFor(ImageView imageView) {
    imageViews.remove(imageView);
}

并将在我设置图像的自定义ArrayAdapter中调用此方法:

            // Display the animal image
        if (animalItem.photo != null) {
            imageLoader.displayImage(animalItem.photo, holder.animalImg, 70);
        } else if (animalItem.animal.contains("Dog")) {
            imageLoader.cancelDisplayTaskFor(holder.animalImg);
            holder.animalImg.setImageResource(R.drawable.default_dog);
        } else if (animalItem.animal.contains("Cat")) {
            imageLoader.cancelDisplayTaskFor(holder.animalImg);
            holder.animalImg.setImageResource(R.drawable.default_cat);
        } else {
            imageLoader.cancelDisplayTaskFor(holder.animalImg);
            holder.animalImg.setImageResource(android.R.drawable.ic_menu_help);
        }

希望这会帮助别人:)

2 个答案:

答案 0 :(得分:1)

   // change this code inside your imageloader


      public void run() {
        if(imageViewReused(photoToLoad))
            return;
        if(bitmap != null)
         {
              Utils.imageViewAnimatedChange(context, photoToLoad.imageView, bitmap);

         }
        else
         {
              imageView.setImageResource(stub_id);// so if bitmap is null it will set  this default image
         }
    }

答案 1 :(得分:0)

但是你必须在通知之前替换适配器列表或数组(忘记提及),然后通知notifyDataChanged()