【问题标题】:ListView recycles Bitmap without noticing in lrucacheListView 在 lrucache 中不注意回收 Bitmap
【发布时间】:2014-12-30 23:09:37
【问题描述】:

我正在编写一个画廊应用程序。 它适用于列表片段的 androidstudio 模板,带有 AbsList。

我重写了 getView 以使用一个任务和一个 lrucache 来缓存一些位图。

列表视图中的每个视图都是一个相对布局,其 ImageView 位于 TextView 之上。 如果缓存中没有 Bitmap,则 AsyncTask 将其加载并放入缓存中,getView 在 ImageView 上绘制资源。 加载后,onPostExecute 将位图放入 ImageView 中。

如果缓存上有对应的Bitmap,则设置到ImageView中

我在 getView 的 convertView 参数标记中设置了一个包含 TextView 和 ImageView 以及 id 的对象,以跟踪要绘制的正确位图。

不过,我有这两个问题:

  1. 当我第一次向下滚动时,在任务完成设置正确的位图之前,新的图像视图会与前一个位图一起出现(即使我在适配器的 getView 上绘制了资源位图)我不'不明白为什么。

  2. 当我向后滚动时,大多数情况下应用程序崩溃是因为缓存上的位图被回收了,尽管我不知道是谁回收了它。

谁能帮我理解这里发生了什么?

public View getView(int position, View convertView, ViewGroup parent) {
            Log.i(TAG, "getView: Asking for view " + position);
            GalleryItemViewHolder lViewHolder;
            if (convertView == null) {
                convertView = getActivity().getLayoutInflater().inflate(R.layout
                        .gallery_item, null);
                lViewHolder = new GalleryItemViewHolder();
                convertView.setTag(lViewHolder);

            } else {
                lViewHolder = (GalleryItemViewHolder) convertView.getTag();
            }
            lViewHolder.setId(position);
            lViewHolder.setTextView((TextView) convertView.findViewById(R.id.gallery_infoTextView));
            lViewHolder.setImageView((ImageView) convertView.findViewById(R.id.gallery_imageView));

            lViewHolder.getTextView().setText(getItem(position).getName() + ": (" + getItem
                    (position).getCount() + ")");
            if (!getItem(position).paintCover(lViewHolder.getImageView())) {
                Log.i(TAG,"getView: task");
                new GalleryItemTask(position, lViewHolder)
                        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
            }
            Log.i(TAG,"getView: return");
            return convertView;
        }

Cover 类有这个paintCover 方法,其中mId 是图像的uri/stream

public boolean paintCover(ImageView imageView) {
    Bitmap lBitmap;
    if (mId == null || (lBitmap = BitmapCacheManager.getInstance().get(mId)) == null) {
        i(TAG, "paintCover: Sin Cache ");
        imageView.getHeight();
        imageView.getWidth();
        imageView.setImageResource(android.R.drawable.alert_dark_frame);
        return false;

    } else

    {
        i(TAG, "paintCover: En Cache "+lBitmap.isRecycled());
        imageView.setImageBitmap(lBitmap);
        return true;
    }

}

更多细节。 在 Fragment 的 onCreate 处,我运行这个方法:

private void prepareGalleryLoaders() {
    LoaderManager lm = getLoaderManager();
    Log.i(TAG, "prepareGalleryLoaders: Iniciando loader");
    lm.initLoader(IdConstants.GALLERY_LOADER, null, new GalleryLoaderCallbacks());
}



/**
 * Callbacks para cargar los datos de las galerías
 * Al terminar de cargarlas, se crea el nuevo arreglo
 */
private class GalleryLoaderCallbacks implements LoaderManager.LoaderCallbacks<List<Gallery>> {
    @Override
    public Loader<List<Gallery>> onCreateLoader(int id, Bundle args) {

        return new GalleriesLoader(getActivity());
    }private class GalleryItemTask extends AsyncTask<Void, Void, Gallery> {
    private static final String TAG = "GalleryItemTask";
    private int mId;
    private String mCoverId;
    private GalleryItemViewHolder mViewHolder;
    private Bitmap mBitmap;

    GalleryItemTask(int id, GalleryItemViewHolder galleryItemViewHolder) {
        mViewHolder = galleryItemViewHolder;
        mId = id;
    }



    @Override
    protected void onPostExecute(Gallery galleries) {

        if (mId != mViewHolder.getId()) {
            Log.i(TAG, "onPostExecute: IDs difieren!!! "+mId+" - "+mViewHolder.getId());
            mBitmap.recycle();
            mBitmap=null;
            return;
        }
        // Validar y actualizar bitmap


        mViewHolder.getImageView().setImageBitmap(mBitmap);
        //mGalleries.get(mId).setBitmap(mBitmap);

        super.onPostExecute(galleries);
    }

    @Override
    protected Gallery doInBackground(Void... params) {
        // generar bitmap (y posiblemente agregarlo a algún cache)


        String[] queryProjection = {
                MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.TITLE};
        String[] selectionArgs = new String[]{String.valueOf(mGalleries.get(mId).getId())};
        Cursor lCursor = getView().getContext().getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                queryProjection, MediaStore.Images.ImageColumns.BUCKET_ID + "= ?",
                selectionArgs, MediaStore.Images.ImageColumns.TITLE);
        lCursor.moveToFirst();
        while (!lCursor.isAfterLast()) {
            //Log.i(TAG,"doInBackground: "+mGalleries.get(mId).getName()+" - "+lCursor.getString
            //        (1)+" - "+ lCursor.getString(0));
            lCursor.moveToNext();
        }
        lCursor.moveToFirst();
        Log.i(TAG, "doInBackground: " + mId + " - " + mViewHolder.getId());

        BitmapFactory.Options lOptions = new BitmapFactory.Options();
        lOptions.inJustDecodeBounds = true;
        mBitmap = BitmapFactory.decodeFile(lCursor.getString(0), lOptions);
        lOptions.inSampleSize = ImageUtils.calculateInSampleSize(lOptions, 256, 256);
        lOptions.inJustDecodeBounds = false;
        mBitmap = BitmapFactory.decodeFile(lCursor.getString(0), lOptions);

        BitmapCacheManager.getInstance().put(lCursor.getString(0), mBitmap);


        //if(mGalleries.get(mId).getBitmap()!=null)
        //    mGalleries.get(mId).getBitmap().recycle();
        //mGalleries.get(mId).setBitmap(mBitmap);



        if(!mGalleries.get(mId).hasCover()) {
            SimpleCover lSimpleCover=new SimpleCover(getActivity(),lCursor.getString(0));
            mGalleries.get(mId).setCover(lSimpleCover);
        }
        lCursor.close();
        return null;
    }
}


    @Override
    public void onLoadFinished(Loader<List<Gallery>> loader, List<Gallery> data) {
        if (mGalleries != null) {
            mGalleries.clear();

        } else
            mGalleries = new ArrayList<Gallery>();
        mGalleries.addAll(data);
        for (Gallery lGallery : data) {
            Log.i(TAG, "onLoadFinished: " + lGallery.getName());
        }

        mAdapter.notifyDataSetChanged();
    }

此时,还没有定义封面,画廊列表只是加载了标题、总内容和 id 数据。图像(封面)从列表适配器的 getView 加载。

GalleryItemTask 类:

 private class GalleryItemTask extends AsyncTask<Void, Void, Gallery> {
    private static final String TAG = "GalleryItemTask";
    private int mId;
    private String mCoverId;
    private GalleryItemViewHolder mViewHolder;
    private Bitmap mBitmap;

    GalleryItemTask(int id, GalleryItemViewHolder galleryItemViewHolder) {
        mViewHolder = galleryItemViewHolder;
        mId = id;
    }



    @Override
    protected void onPostExecute(Gallery galleries) {

        if (mId != mViewHolder.getId()) {
            Log.i(TAG, "onPostExecute: IDs difieren!!! "+mId+" - "+mViewHolder.getId());
            mBitmap.recycle();
            mBitmap=null;
            return;
        }
        // Validar y actualizar bitmap


        mViewHolder.getImageView().setImageBitmap(mBitmap);
        //mGalleries.get(mId).setBitmap(mBitmap);

        super.onPostExecute(galleries);
    }

    @Override
    protected Gallery doInBackground(Void... params) {
        // generar bitmap (y posiblemente agregarlo a algún cache)


        String[] queryProjection = {
                MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.TITLE};
        String[] selectionArgs = new String[]{String.valueOf(mGalleries.get(mId).getId())};
        Cursor lCursor = getView().getContext().getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                queryProjection, MediaStore.Images.ImageColumns.BUCKET_ID + "= ?",
                selectionArgs, MediaStore.Images.ImageColumns.TITLE);
        lCursor.moveToFirst();
        while (!lCursor.isAfterLast()) {
            //Log.i(TAG,"doInBackground: "+mGalleries.get(mId).getName()+" - "+lCursor.getString
            //        (1)+" - "+ lCursor.getString(0));
            lCursor.moveToNext();
        }
        lCursor.moveToFirst();
        Log.i(TAG, "doInBackground: " + mId + " - " + mViewHolder.getId());

        BitmapFactory.Options lOptions = new BitmapFactory.Options();
        lOptions.inJustDecodeBounds = true;
        mBitmap = BitmapFactory.decodeFile(lCursor.getString(0), lOptions);
        lOptions.inSampleSize = ImageUtils.calculateInSampleSize(lOptions, 256, 256);
        lOptions.inJustDecodeBounds = false;
        mBitmap = BitmapFactory.decodeFile(lCursor.getString(0), lOptions);

        BitmapCacheManager.getInstance().put(lCursor.getString(0), mBitmap);


        //if(mGalleries.get(mId).getBitmap()!=null)
        //    mGalleries.get(mId).getBitmap().recycle();
        //mGalleries.get(mId).setBitmap(mBitmap);



        if(!mGalleries.get(mId).hasCover()) {
            SimpleCover lSimpleCover=new SimpleCover(getActivity(),lCursor.getString(0));
            mGalleries.get(mId).setCover(lSimpleCover);
        }
        lCursor.close();
        return null;
    }
}

【问题讨论】:

    标签: android listview bitmap recycle


    【解决方案1】:

    当我第一次向下滚动时,新的图像视图会出现一个 在任务完成设置之前的一瞬间之前的位图 正确的位图(即使我在适配器的 getView) 我不明白为什么。

    应该是因为你把notifyDataSetChanged()放错了地方。请把代码贴在你放的地方。

    当我向后滚动时,大多数情况下应用程序崩溃是因为位图打开 缓存结果被回收了,虽然我不知道谁回收了 它。

    我认为这是因为您没有指定如果 paintCovertrue 该怎么做:

    if (!getItem(position).paintCover(lViewHolder.getImageView())) {
           Log.i(TAG,"getView: task");
           new GalleryItemTask(position, lViewHolder)
                   .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
    }
    else
    {
       //what should the adapter do if paintCover is true?
    }
    

    如果错误仍然存​​在,请发布您的GalleryItemTask 代码。

    【讨论】:

    • 如果paintCover返回false,表示它在ImageView上绘制了一个模板,线程运行创建Bitmap并将其放入缓存,稍后在ImageView上绘制。如果它返回true,则表示在缓存中找到了图像,并放入了ImageView,所以什么都不做。然而,在尝试发现后发现缓存上的图像被回收了,但我不知道是谁做的,也不知道是怎么发生的。
    • 如果paintCover为真,你想做什么?
    • 谢谢,我刚刚发现我做错了什么......如果id不同,我错误地回收了位图,因为我已经将它放入缓存中。我的错。但是以某种方式观看/讨论这里的代码让我很清楚。如果paintCover 为真,则表示封面已使用最终的位图绘制(因为它是在缓存中找到的),所以目前什么都没有。
    • @dabicho 我明白了。太好了 :) 只是一个建议,最好的做法是始终在 getView 中指定“if”和“else”以避免一些错误。您可能需要进行一些谷歌搜索才能更好地理解它。
    猜你喜欢
    • 2019-09-05
    • 2011-10-23
    • 1970-01-01
    • 2012-05-31
    • 1970-01-01
    • 2012-10-05
    • 2023-03-19
    • 2016-03-09
    • 2016-05-24
    相关资源
    最近更新 更多