【问题标题】:Optimizing Bitmap creation/sizing in Android在 Android 中优化位图创建/大小
【发布时间】:2013-01-07 17:48:16
【问题描述】:

我正在寻找有关如何优化以下代码的想法,该代码是 for 循环的主体:

final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
options.inJustDecodeBounds = false;
ImageButton view = new ImageButton(this);
view.setBackgroundColor(0);
view.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
     finalThis.onBorderClicked(v);
}
});
view.setTag(i); //i is the loop counter

Bitmap big = BitmapFactory.decodeResource(getResources(), borders[i], options);
Bitmap smaller = Bitmap.createScaledBitmap(big, borderDimension, borderDimension, false);

view.setImageBitmap(smaller);

view.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT));
((LinearLayout.LayoutParams)view.getLayoutParams()).setMargins(0, 0, -5, 0);
group.addView(view);

big.recycle();

基本上我有 600x600 的图像,我想让它们的大小约为 50dp。所以很自然,我先将样本大小设置为4(600 / 4 = 150),这样我就不会占用不必要的内存,然后进一步将图像缩小到指定大小。然后我用它把它加载到ImageButton上,我还回收了更大的,现在没用的Bitmap。

这是一个动态 UI 创建代码,ImageButtons 被添加到 HorizontalScrollView。问题是,在一种情况下,我必须在 UI 中添加一百多个 ImageButtons,而这段代码非常慢。

问题是:我怎样才能让它工作得更快?它目前在MainActivity 上执行,多线程(可能是AsyncTask)在这种情况下有帮助吗?还有其他方法可以更快地将位图设置为所需的大小吗?如果没有办法显着加快这个过程,那么有没有办法逐个(但以相同的顺序)逐步添加 ImageButton,以便在所有这些发生时 UI 不会冻结?

【问题讨论】:

    标签: android optimization bitmap


    【解决方案1】:

    我在http://developer.android.com/training/displaying-bitmaps/index.html找到了一个很好的解决方案

    它有以下代码,将Bitmap动态设置为ImageView。

    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        private final WeakReference<ImageView> imageViewReference;
        private int data = 0;
    
    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }
    
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }
    
    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
    }
    

    你可以通过使用来调用它,

    public void loadBitmap(int resId, ImageView imageView) {
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        task.execute(resId);
    }
    

    在这里,只需将ImageView 更改为ImageButton。

    编辑:

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {
    
        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);
    
        // Calculate inSampleSize
        options.inSampleSize = 4;
    
        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }
    

    你可以找到更多关于处理Bitmaps Efficientlyhere

    【讨论】:

    • 这适用于一个 ImageView,但 OP 正在创建数百个。
    • 您可以为您的ImageButton 和resources 创建一个HashMap 或Array。循环并会工作。
    • @ChintanRathod decodeSampledBitmapFromResource的声明和实现在哪里?这与我所做的基本相同,只是在 AsyncTask 中。
    • 你在question中已经写好的代码就是方法。请参阅我的更新答案。
    【解决方案2】:

    您不需要自己缩放图像。您可以通过要求 ImageButton 为您扩展来做到这一点,这要快得多。

    ImageButton view = new ImageButton(this);
    view.setBackgroundColor(0);
    view.setOnClickListener(new OnClickListener(){
    @Override
    public void onClick(View v) {
         finalThis.onBorderClicked(v);
    }
    });
    view.setTag(i); //i is the loop counter
    
    view.setAdjustViewBounds(true);
    view.setMaxHeight(borderDimension);
    view.setMaxWidth(borderDimension);
    view.setImageResource(border[i]);
    
    view.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    ((LinearLayout.LayoutParams)view.getLayoutParams()).setMargins(0, 0, -5, 0);
    group.addView(view);
    

    我已更改布局参数以使用 WRAP_CONTENT 作为高度,因为我不知道您的图像看起来如何。您可以稍微调整一下布局,但这通常应该可以解决问题。

    【讨论】:

    • 考虑到我放置的元素数量,这段代码很快就会生成OutOfMemoryException。
    【解决方案3】:

    将您的代码从主线程中取出不会使应用程序处理图像的速度更快,但它会在该过程发生时保持 UI 对用户的响应,并且当您拥有这么多数据时绝对是必要的步骤一次加载。但是,请记住,您不能从主线程以外的任何线程操作视图层次结构,因此您不能将整个操作放在AsyncTask.doInBackground() 中。您将不得不拆分花费最多时间的行(即您的decodeResource() 和createScaledBitmap() 调用),但实例化和向父级添加视图的代码需要在onPostExecute() 或publishProgress() 中发生(取决于你如何实现任务逻辑)。

    问题是,在一种情况下,我必须向 UI 添加一百多个 ImageButton,而这段代码非常慢。

    要记住的另一件重要事情是,任何时候在您的层次结构中拥有超过 100 个基于图像的视图仍然是一大块浪费的内存。加快这个过程的最好方法不是一次添加所有视图,而是只在需要时添加它们(即当用户滚动时),然后删除/回收不再出现在屏幕上的视图。这就是 AdapterView 实现的工作方式,正因如此,只使用您需要的内存,您的应用程序将更快更高效。

    我知道框架中没有 HorizontalListView,但是有 3rd 方实现可以为您提供此功能,或者您可以查看其中一个框架类的源代码以构建您自己的。

    【讨论】:

    • 谢谢。我目前不介意图像占用的内存,总体思路很有效。解决方案是将AsyncTask 子类化,将循环移动到doInBackground() 并为每个要添加的元素调用publishProgress()。这不会像我想要的那样冻结 UI 并一一添加元素。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-11
    相关资源
    最近更新 更多