在没有 OutOfMemoryError 的情况下加载 AlbumArtworks

Loading AlbumArtworks without OutOfMemoryError

我想将专辑插图加载到我的音乐应用程序中并在回收站视图中预览它们。我试过 Googles Developer Guide,但我实际上加载了大约 200 个位图,所以这不起作用!

我不知道该怎么做!

这是我当前的代码:

if (cSong != null && cSong.moveToFirst()) {
    do {
        int iIDCol = cSong.getColumnIndex(MediaStore.Audio.Media._ID);
        int iTitleCol = cSong.getColumnIndex(MediaStore.Audio.Media.TITLE);
        int iArtistCol = cSong.getColumnIndex(MediaStore.Audio.Media.ARTIST);
        int iAlbumCol = cSong.getColumnIndex(MediaStore.Audio.Media.ALBUM);
        int iDurationCol = cSong.getColumnIndex(MediaStore.Audio.Media.DURATION);
        int iAlbumIDCol = cSong.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);

        Bitmap bCover = null;
        BitmapFactory.Options bOptions = new BitmapFactory.Options();
        bOptions.inJustDecodeBounds = true;

        //Throws OutOfMemoryError
        /*try {
            Uri ArtworkUri = Uri.parse("content://media/external/audio/albumart");
            Uri uri = ContentUris.withAppendedId(ArtworkUri, iAlbumIDCol);
            ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(uri, "r");

            if (pfd != null) {
                FileDescriptor fd = pfd.getFileDescriptor();
                BitmapFactory.decodeFileDescriptor(fd, null, bOptions);

                bOptions.inSampleSize = calculateInSampleSize(bOptions, 100, 100);

                bOptions.inJustDecodeBounds = false;

                bCover = BitmapFactory.decodeFileDescriptor(fd, null, bOptions);

                pfd = null;
                fd = null;
            }
        }
        catch (IOException ioe) {
            BitmapFactory.decodeResource(mContext.getResources(), R.drawable.standardartwork, bOptions);

            bOptions.inSampleSize = calculateInSampleSize(bOptions, 100, 100);

            bOptions.inJustDecodeBounds = false;

            bCover = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.standardartwork, bOptions);
        }*/

        SongList.add(new Song(cSong.getLong(iIDCol), cSong.getString(iTitleCol), cSong.getString(iArtistCol), cSong.getString(iAlbumCol), cSong.getInt(iDurationCol), bCover));
    }
    while (cSong.moveToNext());

任何我的 InSampleSize 方法:

private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;

    int size = 1;

    if (height > reqHeight && width > reqWidth) {
        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        while ((halfHeight / size) > reqHeight && (halfWidth / size) > reqWidth) {
            size *= 2;
        }
    }

    return size;
}

谢谢!

在内存中保存 200 张位图并不是一个好主意,即使它们已缩小。最简单的解决方案是将 Uri 保存到封面而不是位图,并仅在需要时解码位图。

所以解析代码看起来像这样:

Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
if (cSong != null && cSong.moveToFirst()) {
do {
    int iIDCol = cSong.getColumnIndex(MediaStore.Audio.Media._ID);
    int iTitleCol = cSong.getColumnIndex(MediaStore.Audio.Media.TITLE);
    int iArtistCol = cSong.getColumnIndex(MediaStore.Audio.Media.ARTIST);
    int iAlbumCol = cSong.getColumnIndex(MediaStore.Audio.Media.ALBUM);
    int iDurationCol = cSong.getColumnIndex(MediaStore.Audio.Media.DURATION);
    int iAlbumIDCol = cSong.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID);
    Uri coverUri = ContentUris.withAppendedId(artworkUri, iAlbumIDCol);

    SongList.add(new Song(cSong.getLong(iIDCol), cSong.getString(iTitleCol), cSong.getString(iArtistCol), cSong.getString(iAlbumCol), cSong.getInt(iDurationCol), coverUri));
}
while (cSong.moveToNext());

然后在您的 RecyclerView.Adapter onBindViewHolder(RecyclerView.ViewHolder holder, int position) 方法中进行解码:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    Bitmap image = decodeBitmap(songsList.get(position).getBitmapUrl());
    if (image != null) {
        holder.imageView.setImageBitmap(image);
    } else {
        holder.imageView.setImageResource(R.drawable.standardartwork);
    }
}

private Bitmap decodeBitmap(Uri uri) {
    ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(uri, "r");
    try {
        if (pfd != null) {
            FileDescriptor fd = pfd.getFileDescriptor();
            BitmapFactory.decodeFileDescriptor(fd, null, bOptions);

            bOptions.inSampleSize = calculateInSampleSize(bOptions, 100, 100);

            bOptions.inJustDecodeBounds = false;

            return BitmapFactory.decodeFileDescriptor(fd, null, bOptions);
        } else {
            return null;
        }
    } catch (IOException ioe) {
        return null;
    }
}

这将允许您只在内存中保留您实际必须在屏幕上显示的位图,其他位图将被回收。

注意:这里的片段只是为了说明如何解决您的问题,但这会降低 RecyclerView 的性能,因为解码是在主线程上完成的。如果你的图像很小,你不会注意到它,但如果它们很大,你就必须异步解码。如果是大图像,请考虑使用 Picasso 或 Glide 等图像处理库。

我使用 Library Picasso 解决了这个问题,它处理图像的加载过程。

Picasso.with(mContext).load(ImageUri).resize(Width, Height).into(ImageView);