【问题标题】:Load media thumbnails efficiently有效加载媒体缩略图
【发布时间】:2016-07-08 21:55:42
【问题描述】:

我花了几天的时间来解决这个问题,但仍然找不到解决方案。我是 Android 新手,所以我的代码可能很乱!

我有一个显示图像和视频缩略图的 RecyclerView(网格布局)。它将媒体文件加载到特定文件夹中。但是当我启动这个活动时,它占用了很多内存!

为了加载缩略图,我创建了两个线程。

线程 1) MediaLoadThread,用于查询 SDCard 中的媒体文件。它循环遍历光标和队列缩略图解码任务到不同的线程。

线程 2) ThumbnailLoaderThread 解码每个单独的缩略图。它接收内容解析器、媒体类型(图像或视频)和媒体 ID。它使用基本的 .getThumbnail() 方法。在完成获取缩略图后,它会触发对其调用者线程(MediaLoadThread)的响应回调。

3) 当 MediaLoadThread(Thread 1) 收到回调时,它会触发另一个回调,让活动更新给定位置的适配器项。适配器更新 UI,最后缩略图 ImageView 从占位符更改为实际缩略图。

:::这是我的代码:::

1) MediaLoadThread.java

@Override
public void run() {
    mMediaDataArr.clear();
    mLoaderThread.start(); // Prepping the thread 2
    mLoaderThread.prepareHandler(); 

      // .... SD Card query stuff .....     

        if (mediaCursor != null && mediaCursor.moveToFirst()) {
            do {
                mMediaDataArr.add(new MediaData(videoCursor.getInt(columnIndexId),
                        mediaCursor.getLong(columnIndexDate), //ID
                        mediaCursor.getInt(columnIndexType), //MEDIA TYPE
                        null); //THUMBNAIL BITMAP (NULL UNTIL THE ACTUAL THUMBNAIL IS DECODED)
            } while (mediaCursor.moveToNext());
            mediaCursor.close();
            mResponseHandler.post(new Runnable() {
                @Override
                public void run() {
                // This callback lets the activity activate the adapter and recyclerview so that the user can interact with recyclerview before the app finishes decoding thumbnails. 
                    mCallback.onVideoLoaded(mMediaDataArr); 
                }
            });

            //Passing tasks to thread 2
            for (int i = 0; i < mMediaDataArr.size(); i++) {
                mLoaderThread.queueTask(
                        mMediaDataArr.get(i).getmMediaType(),
                        i, mMediaDataArr.get(i).getmMediaId());
            }
        }
    }
}

// This is triggered by thread 2 when it finishes decoding 
@Override
public void onThumbnailLoaded(final int position, final Bitmap thumbnail) {
        mResponseHandler.post(new Runnable() {
            @Override
            public void run() {
                mCallback.onThumbnailPrepared(position, thumbnail);
            }
        });
}

2) ThumbnailLoaderThread.java

public void queueTask(int mediaType, int position, int videoId) {
    mWorkerHandler.obtainMessage(mediaType, position, videoId).sendToTarget();
}

public void prepareHandler() {
    mWorkerHandler = new Handler(getLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            int type = msg.what;
            final int position = msg.arg1;
            int videoId = msg.arg2;
            try {
                if (type == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) {
                    Bitmap newThumb = MediaStore.Images.Thumbnails
                            .getThumbnail(mCr, videoId,
                                    MediaStore.Images.Thumbnails.MINI_KIND, null);
                    postResult(position, newThumb);

                } else if (type == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO) {
                    Bitmap newThumb = MediaStore.Video.Thumbnails
                            .getThumbnail(mCr, videoId,
                                    MediaStore.Video.Thumbnails.MINI_KIND, null);
                    postResult(position, newThumb);
                } 
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
    });
}

private void postResult(final int position, final Bitmap newThumb) {
        mResponseHandler.post(new Runnable() {
            @Override
            public void run() {
                mCallback.onThumbnailLoaded(position, newThumb);
            }
        });
}

3) LibraryActivity.java

@Override
public void onThumbnailPrepared(int position, Bitmap thumbnail) {
    if (thumbnail != null && position < mData.size()) {
        MediaData updatedData = mData.get(position);
        updatedData.setmThumbnail(thumbnail);
        mData.set(position, updatedData);
        mVideoAdapter.notifyItemChanged(position);
    }
}

流程是这样的。

1) Activity 启动线程 1。

2) 线程 1 开始查询文件并启动线程 2。它通过光标循环传递媒体 id。

3) 线程 2 解码具有给定媒体 ID 的缩略图。

4) 解码完成后,线程 2 使用结果位图触发对线程 1 的回调。

5) 线程 1 接收到 bitmap 并通过回调将 bitmap 传递给活动。

6) Activity 接收缩略图并使用给定的位图更新 RecyclerView 数据。

它工作正常,但是当系统为此任务分配近 50MB 内存时...考虑到它只加载 100 个缩略图,我认为它相当繁重。

:::我试过的:::

1) 我提取了每个单独缩略图的 URI,并让 recyclerview 适配器在绑定时使用给定的 URI 加载图像。它工作正常并且没有消耗那么多内存,但是因为它在绑定项目时加载图像,所以每当我滚动屏幕时它会重新加载缩略图并有一点延迟。

2) 我让适配器使用直接缩略图路径加载缩略图。但是当用户清理 /.thumbnails 文件夹时,它将不起作用。

3) 当线程解码缩略图时,我将 BitmapFactory.Options 采样大小设置为 4。但是当它仍然很重,有时甚至更慢时......

4) 在 MediaData 对象中,它保存缩略图位图作为成员变量。所以我在适配器将其加载到 ImageView 后立即将其设为空。仍然很重,并且因为我将对象的缩略图设为空,所以当我向后滚动时它只显示占位符。

我真的不知道。任何帮助将不胜感激!

【问题讨论】:

    标签: java android multithreading image


    【解决方案1】:

    您可以使用nostra universal 图像加载器库来加载图像。这个库非常适合图像加载,还有一些其他库,如 Picassoglide 等,您可以使用它们来代替手动编码。

    【讨论】:

    • 我尝试使用毕加索,但它会加载实际图像。不是缩略图。自己加载图片文件是不是太重了? :(
    • 您可以根据您的布局大小调整图像大小 下面是代码 Picasso.with(context) .load(url) .resize(50, 50) .centerCrop() .into(imageView)
    • 谢谢。那我应该在哪里添加那行?如果我将它添加到我的适配器的 bindViewHolder 方法中,它会在我滚动时调整图像大小。
    • 好的,我在 bindViewHolder 方法中添加了这一行。 Picasso.with(context).load(currentData.getUri()).centerCrop().resize(mWidth, mWidth).into(holder.mThumbnailIV);。好一点。但在绑定项目时仍会加载图像。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-15
    • 1970-01-01
    • 1970-01-01
    • 2011-02-23
    • 1970-01-01
    • 2012-06-17
    • 1970-01-01
    相关资源
    最近更新 更多