【问题标题】:Android: LruCache issue with BitmapAndroid:位图的 LruCache 问题
【发布时间】:2014-12-31 05:16:09
【问题描述】:

我有一个使用 LruCache 类的全局位图缓存。加载列表视图的缩略图时,首先使用缓存。它工作正常。

但一个问题是:有时缓存中的 Bitmap 实例无法显示在列表视图上。似乎缓存中的此类位图不再有效。我已经检查了缓存中的位图是否不为空且未回收,但似乎仍然无法显示此类位图(即使它不为空且未回收)。

缓存类:

public class ImageCache {

    private LruCache<String, Bitmap> mMemoryCache;

    private static ImageCache instance;

    public static ImageCache getInstance() {
        if(instance != null) {
            return instance;
        }

        instance = new ImageCache();
        instance.initializeCache();

        return instance;
   }

   protected void initializeCache() {

        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 Bitmap getImage(String url) {
        return this.mMemoryCache.get(url);
    }


    public void cacheImage(String url, Bitmap image) {
        this.mMemoryCache.put(url, image);
    }
}

使用缓存的代码在CursorAdapter的子类Adapter类中:

        final ImageCache cache = ImageCache.getInstance();

        // First get from memory cache
        final Bitmap bitmap = cache.getImage(thumbnailUrl);
        if (bitmap != null && !bitmap.isRecycled()) {
            Log.d(TAG, "The bitmap is valid");
            viewHolder.imageView.setImageBitmap(bitmap);
        } 
        else {
            Log.d(TAG, "The bitmap is invalid, reload it.");
            viewHolder.imageView.setImageResource(R.drawable.thumbnail_small);

            // use the AsyncTask to download the image and set in cache
            new DownloadImageTask(context, viewHolder.imageView, thumbnailUrl, dir, filepath).execute();
        }   

DownloadImageTask的代码:

public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {

    private ImageView mImageView;
    private String url;
    private String dir;
    private String filename;
    private Context context;

    public DownloadImageTask(Context context, ImageView imageView, String url, String dir, String filename) {
        this.mImageView = imageView;
        this.url = url;
        this.filename = filename;
        this.dir = dir;
        this.context = context;
        this.cache = cache;
    }

    protected Bitmap doInBackground(String... urls) {
        // String urldisplay = urls[0];
        final Bitmap bitmap = FileUtils.readImage(context, dir, filename, url);

        return bitmap;
    }

    protected void onPostExecute(Bitmap result) {
        final ImageCache cache = ImageCache.getInstance();
        if(result != null) {
            cache.put(url, result);
            mImageView.setImageBitmap(result);
        }

    }
}

任何帮助将不胜感激。谢谢!

更新:我遵循了greywolf82 建议的link:“处理配置更改”部分。我将以下属性放在我的活动类和两个片段类中:

公共 LruCache mMemoryCache;

在activity类中,我尝试在调用fragment时初始化缓存:

        // Get the cache
        mMemoryCache = mIndexFragment.mRetainedCache;
        if (mMemoryCache == null) {
            final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

            // Use 1/8th of the available memory for this memory cache.
            final int cacheSize = maxMemory / 8;

            // Initialize the cache
            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;
                }
            };

            Log.d(TAG, "Initialized the memory cache");
            mIndexFragment.mRetainedCache = mMemoryCache;
        }

在片段类中: setRetainInstance(true);

我将缓存实例传递给适配器构造函数,以便适配器可以使用缓存。

但我仍然遇到同样的问题。

更新 2:

两个接受 LruCache 实例的适配器类:

NewsCursorAdapter:

public class NewsCursorAdapter extends CursorAdapter {

    private static final String TAG = "NewsCursorAdapter";

    private LruCache<String, Bitmap> cache;

    private Context mContext;

    public NewsCursorAdapter(Context context, LruCache<String, Bitmap> cache) {
        super(context, null, false);
        this.mContext = context;
        this.cache = cache;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {

        final Setting setting = ApplicationContext.getSetting();
        // Get the view holder
        ViewHolder viewHolder = (ViewHolder) view.getTag();

        final String thumbnail = cursor.getString(NewsContract.Entry.THUMBNAIL_CURSOR_INDEX);
        if(thumbnail != null) {
            String pictureDate = cursor.getString(NewsContract.Entry.PIC_DATE_CURSOR_INDEX);
            final String dir = "thumbnails/" + pictureDate + "/";
            final String filepath = thumbnail + "-small.jpg";
            final String thumbnailUrl = setting.getCdnUrl() + dir + filepath;

            //final ImageCache cache = ImageCache.getInstance();

            // First get from memory cache
            final Bitmap bitmap = cache.get(thumbnailUrl);
            if (bitmap != null && !bitmap.isRecycled()) {
                Log.d(TAG, "The bitmap is valid: " + bitmap.getWidth());
                viewHolder.imageView.setImageBitmap(bitmap);
            } 
            else {
                Log.d(TAG, "The bitmap is invalid, reload it.");
                viewHolder.imageView.setImageResource(R.drawable.thumbnail_small);

                new DownloadImageTask(viewHolder.imageView, thumbnailUrl, dir, filepath).execute();
            }   
        }
        else {
            viewHolder.imageView.setVisibility(View.GONE);
        }
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {

        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        View view = inflater.inflate(R.layout.listview_item_row, parent,
                false);
        // Initialize the view holder
        ViewHolder viewHolder = new ViewHolder();

        viewHolder.titleView = (TextView) view.findViewById(R.id.title);
        viewHolder.timeView = (TextView) view.findViewById(R.id.news_time);
        viewHolder.propsView = (TextView) view.findViewById(R.id.properties);
        viewHolder.imageView = (ImageView) view.findViewById(R.id.icon);
        view.setTag(viewHolder);

        return view;
    }

    static class ViewHolder {
          TextView titleView;
          TextView timeView;
          TextView propsView;
          ImageView imageView;

    }

    private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
        private ImageView mImageView;
        private String url;
        private String dir;
        private String filename;

        public DownloadImageTask(ImageView imageView, String url, String dir, String filename) {
            this.mImageView = imageView;
            this.url = url;
            this.filename = filename;
            this.dir = dir;
        }

        protected Bitmap doInBackground(String... urls) {

            final Bitmap bitmap = FileUtils.readImage(mContext, dir, filename, url);
            return bitmap;
        }

        protected void onPostExecute(Bitmap result) {
            //final ImageCache cache = ImageCache.getInstance();
            if(result != null) {
                cache.put(url, result);
                mImageView.setImageBitmap(result);
            }

        }
    }
}

列表适配器,NewsTopicItemAdapter:

public class NewsTopicItemAdapter extends ArrayAdapter<NewsTopicItem> {

    private Context context = null;

    private EntryViewHolder viewHolder;

    private HeaderViewHolder headerViewHolder;

    private LruCache<String, Bitmap> mCache;

    public NewsTopicItemAdapter(Context context, List<NewsTopicItem> arrayList, LruCache<String, Bitmap> cache) {
        super(context, 0, arrayList);
        this.context = context;
        this.mCache = cache;
    }

    public void setItems(List<NewsTopicItem> items) {
        this.addAll(items);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        final NewsTopicItem item = getItem(position);
        View view;
        if(!item.isHeader()) {
            view = this.getEntryView((NewsTopicEntry)item, convertView, parent);
        }
        else {
            view = this.getHeaderView((NewsTopicHeader)item, convertView, parent);
        }

        return view;
    }

    protected View getEntryView(NewsTopicEntry newsItem, View convertView, ViewGroup parent) {

        View view;

            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            viewHolder = new EntryViewHolder();

            view = inflater.inflate(R.layout.listview_item_row, parent,
                        false);
            // Initialize the view holder
            viewHolder.titleView = (TextView) view.findViewById(R.id.title);
            viewHolder.timeView = (TextView) view.findViewById(R.id.news_time);
            viewHolder.propsView = (TextView) view.findViewById(R.id.properties);
            viewHolder.imageView = (ImageView) view.findViewById(R.id.icon);
            view.setTag(viewHolder);

        viewHolder.propsView.setText(newsItem.getSource());

        if (newsItem.getThumbnail() != null) {

            final String dir = "thumbnails/" + newsItem.getPictureDate() + "/";
            final String filepath = newsItem.getThumbnail() + "-small.jpg";
            final String thumbnailUrl = "http://www.oneplusnews.com/static/" + dir + filepath;

            //final ImageCache cache = ImageCache.getInstance();

            // First get from memory cache
            final Bitmap bitmap = mCache.get(thumbnailUrl);
            if (bitmap != null && !bitmap.isRecycled()) {
                viewHolder.imageView.setImageBitmap(bitmap);
            } else {
                viewHolder.imageView.setImageResource(R.drawable.thumbnail_small);

                new DownloadImageTask(viewHolder.imageView, thumbnailUrl, dir, filepath).execute();
            }           
        }
        else {
            viewHolder.imageView.setVisibility(View.GONE);
        }

        viewHolder.titleView.setText(newsItem.getTitle());
        viewHolder.timeView.setText(DateUtils.getDisplayDate(newsItem.getCreated()));

        return view;

    }

    protected View getHeaderView(NewsTopicHeader header, View convertView, ViewGroup parent) {

        View view;


            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            headerViewHolder = new HeaderViewHolder();

            view = inflater.inflate(R.layout.news_list_header, parent,
                        false);
            // Initialize the view holder
            headerViewHolder.topicView = (TextView) view.findViewById(R.id.topic);

            view.setTag(headerViewHolder);
            final View imageView = view.findViewById(R.id.more_icon);
            imageView.setOnClickListener(new OnClickListener() {
                public void onClick(View v) {
                    // Start the Fragement
                }
            });

        Topic topic = header.getTopic();
        if(topic.isKeyword()) {
            headerViewHolder.topicView.setText(topic.getName());
        }
        else {
            // This is a hack to avoid error with - in android
            headerViewHolder.topicView.setText(ResourceUtils.getStringByName(context, topic.getName()));
        }

        return view;

    }


    private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
        private ImageView mImageView;
        private String url;
        private String dir;
        private String filename;

        public DownloadImageTask(ImageView imageView, String url, String dir, String filename) {
            this.mImageView = imageView;
            this.url = url;
            this.filename = filename;
            this.dir = dir;
        }

        protected Bitmap doInBackground(String... urls) {

            final Bitmap mIcon11 = FileUtils.readImage(context, dir, filename, url);
            return mIcon11;
        }

        protected void onPostExecute(Bitmap result) {
            //final ImageCache cache = ImageCache.getInstance();
            if(result != null) {
                mCache.put(url, result);
                mImageView.setImageBitmap(result);
            }

        }
    }



    static class EntryViewHolder {
          TextView titleView;
          TextView timeView;
          TextView propsView;
          ImageView imageView;
          TextView topicView;
    }

    static class HeaderViewHolder {
          TextView topicView;
    }
}

更新3:我附上了eclipse的调试信息:第一张图片是工作位图,第二张是缓存中的非工作位图。我没有发现任何可疑之处。

缓存中工作位图的调试信息:

缓存中非工作位图的调试信息:

【问题讨论】:

    标签: android android-bitmap android-lru-cache


    【解决方案1】:

    我终于找到了问题所在。这是因为适配器。在适配器中,如果不需要缩略图,我将一些 ImageView 设置为不可见。当用户滚动列表视图时,此类 ImageView 实例将被重用,但可见性不会更新。

    所以缓存本身现在可以了。解决方案是检查 ImageView 的可见性并在需要时对其进行更新。

    非常感谢 graywolf82 抽出宝贵时间和关于单例模式的提示。

    【讨论】:

      【解决方案2】:

      单例模式是邪恶的 :) 请完全避免它并使用带有setReteainInstance(true) 的片段,如here 解释的那样

      【讨论】:

      • 非常感谢灰狼!我会试试这个,但不知何故,有没有办法拥有一个全局缓存?如果我将片段与缓存一起使用,那么将需要 2 个缓存(因为我有 2 个片段)并且许多位图将被缓存两次。这就是我想要避免的。
      • 我将单例更改为普通类,并将其实例传递给片段。仍然有同样的问题。
      • 您可以扩展 Application 类并将您的单例放在那里作为替代方案。你能发布你调用cacheImage的代码部分吗?
      • 您好,greywolf82,感谢您的回复。正如上面更新中提到的,现在我尝试在我的两个片段和主要活动中声明缓存实例(LruCache),然后将缓存实例传递给适配器对象,但仍然是同样的问题。我将尝试为每个片段设置缓存,看看这是否可行。
      • CursorAdapter 似乎有些问题。对于这两个片段,我有 2 个不同的适配器:ArrayAdapter 和 CursorAdapter。 ArrayAdapter 中的缓存没有问题。我正在检查确切的问题是什么。但是你能告诉为什么单例在这种情况下不起作用,谢谢!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-14
      • 1970-01-01
      • 2014-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多