在 Picasso 图像加载器中加载位图图像会减慢列表视图中的滚动速度

Posted

技术标签:

【中文标题】在 Picasso 图像加载器中加载位图图像会减慢列表视图中的滚动速度【英文标题】:Loading bitmap images in Picasso Image loader slows down scrolling in a listview 【发布时间】:2015-09-21 23:42:16 【问题描述】:

我正在尝试生成在 sdcard 文件夹中找到的所有视频的缩略图,并使用 Picasso Image loader 在列表视图中填充所有生成的位图。一切正常,但速度非常慢,我无法滚动列表视图,它的滞后非常缓慢。我在该列表视图中有大约 150 个视图。有什么帮助吗?? .谢谢提前。

public class VideoFilesAdapter extends ArrayAdapter<String> 
    private List<String> mpath;
    private Context mContext;
    public static ArrayList<String> mSelectedPaths = null;

    public VideoFilesAdapter(Context context, List<String> path) 
        super(context, R.layout.fileadapter_list, path);
        this.mContext = context;
        this.mpath = path;
    

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) 
        if (convertView == null) 
            LayoutInflater mInflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = mInflater.inflate(R.layout.fileadapter_list, null);
        
        TextView txtTitle = (TextView) convertView.findViewById(R.id.txt);
        ImageView imageView = (ImageView) convertView.findViewById(R.id.img);

        File file = new File(mpath.get(position));

        if (file.exists()) 

            txtTitle.setText(file.getName());

            Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(
                    file.getAbsolutePath(),
                    MediaStore.Video.Thumbnails.MICRO_KIND);

            Picasso.with(mContext).load(getImageUri(mContext, bitmap))
                    .centerInside().resize(100, 100)
                    .placeholder(R.drawable.holder).error(R.drawable.error)
                    .into(imageView);

        

        return convertView;
    

    public Uri getImageUri(Context inContext, Bitmap inImage) 
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 20, bytes);
        String path = Images.Media.insertImage(inContext.getContentResolver(),
                inImage, "Title", null);
        return Uri.parse(path);
    


【问题讨论】:

你的代码的问题是你的 Inflating TextView 和 ImageView 每次单元格视图被回收,基本上它会减慢你的滚动体验。 那么解决办法是什么?? @龙 我现在正在编码,我可能会在明天之前发送给您 :) 我会尽可能解释每个部分。 【参考方案1】:

更新 看起来您并没有以任何方式存储/缓存这些位图,所以它一遍又一遍地做。试试这里找到的解决方案:Video thumbnail arrayadopter is slow on scroll


我不认为毕加索会减慢您ListView 的滚动速度。您的列表项有多复杂?如果它们嵌套了许多视图,可能就是这样,我在 ListView 中有超过 700 个项目,没有性能问题。

【讨论】:

我知道速度变慢是因为ThumbnailUtils.createVideoThumbnail 函数会生成文件夹中所有视频文件的缩略图。有什么办法可以解决吗?【参考方案2】:

当我查看代码时,我看到 TextViewImageView 每次调用 getView 时都会被夸大。通货膨胀是一项非常昂贵的任务,因此我们希望尽可能避免使用它。

在其他任何事情之前,请确保您将在 Gradle 中声明它

compile 'com.android.support:support-v4:21.0.0'

在你的 VideoFilesAdapter.java 中添加这个帮助类

 class Helper 
        public TextView textView;
        public ImageView image;
  

然后我们将使用 LRUcache 来存储图像,这样我们就可以一遍又一遍地使用这个图像而无需做太多的工作。

在你的项目中添加MyLRU.java

import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class MyLRU 

    private LruCache<String, Bitmap> cache = null;
    private static MyLRU myLru = null;

    private MyLRU() 
        int availableMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        cache = new LruCache<String, Bitmap>(availableMemory / 8) 
            @Override
            protected int sizeOf(String key, Bitmap value) 
                // this is the only way to get the Bitmap size below API 12
                return (value.getRowBytes() * value.getHeight()) / 1024;
            
        ;
    

    public static MyLRU getInstance() 
        if (myLru == null) 
            myLru = new MyLRU();
        
        return myLru;
    

    public void addImage(String key, Bitmap image) 
        myLru.addImage(key, image);
    

    public Bitmap getImage(String key) 
        return myLru.getImage(key);
    


在你的VideoFilesAdapter.java中添加一个实用方法

public static Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) 
        int sourceWidth = source.getWidth();
        int sourceHeight = source.getHeight();

        // Compute the scaling factors to fit the new height and width, respectively.
        // To cover the final image, the final scaling will be the bigger
        // of these two.
        float xScale = (float) newWidth / sourceWidth;
        float yScale = (float) newHeight / sourceHeight;
        float scale = Math.max(xScale, yScale);

        // Now get the size of the source bitmap when scaled
        float scaledWidth = scale * sourceWidth;
        float scaledHeight = scale * sourceHeight;

        // Let's find out the upper left coordinates if the scaled bitmap
        // should be centered in the new size give by the parameters
        float left = (newWidth - scaledWidth) / 2;
        float top = (newHeight - scaledHeight) / 2;

        // The target rectangle for the new, scaled version of the source bitmap will now
        // be
        RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);

        // Finally, we create a new bitmap of the specified size and draw our new,
        // scaled bitmap onto it.
        Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
        Canvas canvas = new Canvas(dest);
        canvas.drawBitmap(source, null, targetRect, null);

        return dest;
    

这是您的VideoFilesAdapter.java的完整修改

public class VideoFilesAdapter extends ArrayAdapter<String> 
    private List<String> mpath;
    private Context mContext;
    public static ArrayList<String> mSelectedPaths = null;
    private MyLRU lruCache;

    public VideoFilesAdapter(Context context, List<String> path) 
        super(context, R.layout.fileadapter_list, path);
        this.mContext = context;
        this.mpath = path;
        // My LRU
        lruCache = MyLRU.getInstance();
    

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) 
        View v;
        if (convertView == null) 
            LayoutInflater mInflater = (LayoutInflater) mContext
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = mInflater.inflate(R.layout.fileadapter_list, null);
            Helper h = new Helper();
            h.textView = (TextView) convertView.findViewById(R.id.txt);
            h.textView = (ImageView) convertView.findViewById(R.id.img);
            v.setTag(h);
         else 
            v = convertView;
        

        Helper myHelper = (Helper) v.getTag();

        File file = new File(mpath.get(position));
        String fullPath = file.getAbsolutePath();

        if (file.exists()) 
            myHelper.textView.setText(file.getName());

            Bitmap cacheImage = lruCache.getImage(fullPath);
            if (cacheImage == null) 
                Bitmap bm = scaleCenterCrop(ThumbnailUtils.createVideoThumbnail(fullPath, MediaStore.Video.Thumbnails.MICRO_KIND),100,100);
                lruCache.addImage(fullPath, bm);
                cacheImage = bm;
            
            myHelper.image.setImageBitmap(cacheImage);

        

        return v;
    

    class Helper 
        public TextView textView;
        public ImageView image;
    

    public static Bitmap scaleCenterCrop(Bitmap source, int newHeight, int newWidth) 
        int sourceWidth = source.getWidth();
        int sourceHeight = source.getHeight();

        // Compute the scaling factors to fit the new height and width, respectively.
        // To cover the final image, the final scaling will be the bigger
        // of these two.
        float xScale = (float) newWidth / sourceWidth;
        float yScale = (float) newHeight / sourceHeight;
        float scale = Math.max(xScale, yScale);

        // Now get the size of the source bitmap when scaled
        float scaledWidth = scale * sourceWidth;
        float scaledHeight = scale * sourceHeight;

        // Let's find out the upper left coordinates if the scaled bitmap
        // should be centered in the new size give by the parameters
        float left = (newWidth - scaledWidth) / 2;
        float top = (newHeight - scaledHeight) / 2;

        // The target rectangle for the new, scaled version of the source bitmap will now
        // be
        RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);

        // Finally, we create a new bitmap of the specified size and draw our new,
        // scaled bitmap onto it.
        Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
        Canvas canvas = new Canvas(dest);
        canvas.drawBitmap(source, null, targetRect, null);

        return dest;
    


如您所见,我暂时删除了 getImageUri 方法和 Picasso,因为它已经没有意义了,因为我们已经有一个缓存,即 LRU-cache。

我希望这会提高您的应用性能:)

【讨论】:

07-05 23:14:17.282: E/AndroidRuntime(4681): java.lang.***Error: 堆栈大小 8MB 07-05 23:14:17.282: E/AndroidRuntime(4681): 在com.example.myapp.helper.MyLRU.getImage(MyLRU.java:34) 我认为问题是您的设备内存不足,请尝试将 availableMemory / 8 更改为 availableMemory / 4 并告诉我会发生什么发生。

以上是关于在 Picasso 图像加载器中加载位图图像会减慢列表视图中的滚动速度的主要内容,如果未能解决你的问题,请参考以下文章

Picasso 图片在 android 中加载缓慢,为啥?

从相机/图库加载图像位图时会旋转 [Android 9]

如何在MFC中加载真彩色的图像资源啊

当我在我的 android 应用程序中从图库中加载图像时,为啥位图返回较小的图像?

如何从资源位图文件中加载图像数据以进行 directshow 过滤器?

当应用程序未运行时,无法在 intentservice 上使用 Picasso 目标获取图像位图