2

在过去的几天里,我一直在为这个问题而苦苦挣扎。我已经尝试了堆栈溢出时可用的所有方法,但我无法解决这个问题。

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

基本思想是我放置一个临时图像,该图像将替换为从 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 的 url animalItem.animal也是从 JSON 获得的动物类型

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

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

编辑: 我想我通过不再使用 Wea​​kHashMap 并在 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);
        }

希望这对其他人有帮助:)

4

2 回答 2

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
         }
    }
于 2013-05-31T08:15:51.283 回答
0

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

于 2013-05-31T07:42:05.930 回答