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から取得しました

テキストが正常に表示されていることに言及する必要があります...画像のみが間違っており、最初の要素のみです(写真が利用できない場合)。

誰かが私を正しい方向に向けるか、私が間違っていることを教えていただければ幸いです。

編集: WeakHashMap をもう使用せず、各 ImageView の hashCode を Map に保存することで問題を解決したと思います。これは私が変更したものです:

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 に答える