ListView перезаписывает Bitmap, не замечая в lrucache
Я пишу приложение для галереи. Это работает из шаблона androidstudio для фрагмента списка, с AbsList.
Я переопределяю getView, чтобы использовать задачу и lrucache для кэширования некоторых растровых изображений.
Каждое представление из списка - это RelativeLayout с ImageView над TextView. Если в кеше нет растрового изображения, AsyncTask загружает его и помещает в кеш, а getView рисует ресурс в ImageView. После загрузки onPostExecute помещает растровое изображение в ImageView.
Если в кеше имеется соответствующее растровое изображение, оно устанавливается в ImageView
Я установил объект, содержащий TextView и ImageView вместе с идентификатором в теге параметра convertView getView, чтобы отслеживать правильное растровое изображение для рисования.
У меня есть две проблемы:
Когда я прокручиваю вниз в первый раз, новые представления Изображения появляются с предыдущим растровым изображением на мгновение, прежде чем задача завершит настройку правильного растрового изображения (даже если я рисую растровое растровое изображение в getView адаптера), я не понимаю, почему.
Когда я выполняю прокрутку назад, в большинстве случаев происходит сбой приложения, поскольку растровое изображение в кеше оказывается переработанным, хотя я понятия не имею, кто его переработал.
Может кто-нибудь помочь мне понять, что здесь происходит?
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 / поток изображения
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;
}
}
Более детально. В 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();
}
На данный момент обложки не определены, список галерей просто загружается с заголовками, общим содержанием и данными идентификаторов. Изображения (обложки) загружаются в 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;
}
}
1 ответ
Когда я прокручиваю вниз в первый раз, новые представления Изображения появляются с предыдущим растровым изображением на мгновение, прежде чем задача завершит настройку правильного растрового изображения (даже если я рисую растровое растровое изображение в getView адаптера), я не понимаю, почему.
Это должно быть потому, что вы положили notifyDataSetChanged()
в неправильном месте. Пожалуйста, отправьте код, где вы положили его.
Когда я выполняю прокрутку назад, в большинстве случаев происходит сбой приложения, поскольку растровое изображение в кеше оказывается переработанным, хотя я понятия не имею, кто его переработал.
Я думаю, потому что вы не указываете, что делать, если paintCover
является true
:
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
код.