BitmapFactory.Options.inBitmap导致经常切换ImageView的位图时撕

2019-08-17 15:50发布

我遇到一个情况我有切换图像非常快的幻灯片显示图像。 图像的数量之多,让我想了JPEG数据存储在存储器中,当我想显示他们他们进行解码。 为了缓解垃圾收集器,我使用BitmapFactory.Options.inBitmap重用位图。

不幸的是,这会导致相当严峻的流泪,我已经尝试了不同的解决方案,如同步,信号,位图的交替之间2-3,然而,没有人可以解决这个问题。

我已经设置了这说明这个问题在GitHub上的示例项目; https://github.com/Berglund/android-tearing-example

我有,其解码位图,将其设置在UI线程上一个线程,休眠5毫秒:

Runnable runnable = new Runnable() {
@Override
public void run() {
        while(true) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 1;

            if(bitmap != null) {
                options.inBitmap = bitmap;
            }

            bitmap = BitmapFactory.decodeResource(getResources(), images.get(position), options);

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });

            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {}

            position++;
            if(position >= images.size())
                position = 0;
        }
    }
};
Thread t = new Thread(runnable);
t.start();

我的想法是,ImageView.setImageBitmap(位图)绘制位图上的下一个垂直同步,但是,我们很可能已经发生这种情况时解码的下一个位图,正因为如此,我们已经开始修改位图像素。 我在正确的方向在想什么?

有没有人有在哪里何去何从任何提示?

Answer 1:

作为替代你目前的做法,你可能会考虑保持JPEG数据,你在做什么,而且还创建一个单独的位图的每个图像,并使用inPurgeableinInputShareable标志。 这些标志分配后盾内存在一个单独的堆您的位图不直接由Java垃圾收集管理,并允许Android的自身丢弃的位图数据时,它已经没有空间,并在需要时对需求的重解码您的JPEG图片。 Android有这一切的专用代码来管理的位图数据,那么为什么不使用它呢?



Answer 2:

您应该使用的ImageView的的onDraw()方法,因为当视图需要借鉴其在屏幕上的内容,方法被调用。

我创建了一个名为MyImageView新的类,它扩展了ImageView的,并覆盖将触发回调,让听众知道,这一观点已经完成了绘图的onDraw()方法

public class MyImageView extends ImageView {

    private OnDrawFinishedListener mDrawFinishedListener;

    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mDrawFinishedListener != null) {
            mDrawFinishedListener.onOnDrawFinish();
        }
    }

    public void setOnDrawFinishedListener(OnDrawFinishedListener listener) {
        mDrawFinishedListener = listener;
    }

    public interface OnDrawFinishedListener {
        public void onOnDrawFinish();
    }

}

在MainActivity,定义3个位图:一个参考正被使用的ImageView的绘制,一个用于解码和一个参考到被再循环用于下一个解码位图中的位图。 我重用vminorov的回答synchronized块,而是把在不同的地方有解释的代码注释

public class MainActivity extends Activity {

    private Bitmap mDecodingBitmap;
    private Bitmap mShowingBitmap;
    private Bitmap mRecycledBitmap;

    private final Object lock = new Object();

    private volatile boolean ready = true;

    ArrayList<Integer> images = new ArrayList<Integer>();
    int position = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        images.add(R.drawable.black);
        images.add(R.drawable.blue);
        images.add(R.drawable.green);
        images.add(R.drawable.grey);
        images.add(R.drawable.orange);
        images.add(R.drawable.pink);
        images.add(R.drawable.red);
        images.add(R.drawable.white);
        images.add(R.drawable.yellow);

        final MyImageView imageView = (MyImageView) findViewById(R.id.image);
        imageView.setOnDrawFinishedListener(new OnDrawFinishedListener() {

            @Override
            public void onOnDrawFinish() {
                /*
                 * The ImageView has finished its drawing, now we can recycle
                 * the bitmap and use the new one for the next drawing
                 */
                mRecycledBitmap = mShowingBitmap;
                mShowingBitmap = null;
                synchronized (lock) {
                    ready = true;
                    lock.notifyAll();
                }
            }
        });

        final Button goButton = (Button) findViewById(R.id.button);

        goButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        while (true) {
                            BitmapFactory.Options options = new BitmapFactory.Options();
                            options.inSampleSize = 1;

                            if (mDecodingBitmap != null) {
                                options.inBitmap = mDecodingBitmap;
                            }

                            mDecodingBitmap = BitmapFactory.decodeResource(
                                    getResources(), images.get(position),
                                    options);

                            /*
                             * If you want the images display in order and none
                             * of them is bypassed then you should stay here and
                             * wait until the ImageView finishes displaying the
                             * last bitmap, if not, remove synchronized block.
                             * 
                             * It's better if we put the lock here (after the
                             * decoding is done) so that the image is ready to
                             * pass to the ImageView when this thread resume.
                             */
                            synchronized (lock) {
                                while (!ready) {
                                    try {
                                        lock.wait();
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                }
                                ready = false;
                            }

                            if (mShowingBitmap == null) {
                                mShowingBitmap = mDecodingBitmap;
                                mDecodingBitmap = mRecycledBitmap;
                            }

                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    if (mShowingBitmap != null) {
                                        imageView
                                                .setImageBitmap(mShowingBitmap);
                                        /*
                                         * At this point, nothing has been drawn
                                         * yet, only passing the data to the
                                         * ImageView and trigger the view to
                                         * invalidate
                                         */
                                    }
                                }
                            });

                            try {
                                Thread.sleep(5);
                            } catch (InterruptedException e) {
                            }

                            position++;
                            if (position >= images.size())
                                position = 0;
                        }
                    }
                };
                Thread t = new Thread(runnable);
                t.start();
            }
        });

    }
}


Answer 3:

你需要做以下的事情,为了摆脱这个问题的。

  1. 添加一个额外的位图,以防止情况下,当UI线程绘制位图,而另一个线程正在修改它。
  2. 实现线程同步,以防止情况下,当后台线程试图解码新位图,但前一个不是由UI线程所示。

我已经修改了你的代码了一下,现在对我来说工作正常。

package com.example.TearingExample;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.util.ArrayList;

public class MainActivity extends Activity {
    ArrayList<Integer> images = new ArrayList<Integer>();

    private Bitmap[] buffers = new Bitmap[2];
    private volatile Bitmap current;

    private final Object lock = new Object();
    private volatile boolean ready = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        images.add(R.drawable.black);
        images.add(R.drawable.blue);
        images.add(R.drawable.green);
        images.add(R.drawable.grey);
        images.add(R.drawable.orange);
        images.add(R.drawable.pink);
        images.add(R.drawable.red);
        images.add(R.drawable.white);
        images.add(R.drawable.yellow);

        final ImageView imageView = (ImageView) findViewById(R.id.image);
        final Button goButton = (Button) findViewById(R.id.button);

        goButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        int position = 0;
                        int index = 0;

                        while (true) {
                            try {
                                synchronized (lock) {
                                    while (!ready) {
                                        lock.wait();
                                    }
                                    ready = false;
                                }

                                BitmapFactory.Options options = new BitmapFactory.Options();

                                options.inSampleSize = 1;
                                options.inBitmap = buffers[index];

                                buffers[index] = BitmapFactory.decodeResource(getResources(), images.get(position), options);
                                current = buffers[index];

                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        imageView.setImageBitmap(current);
                                        synchronized (lock) {
                                            ready = true;
                                            lock.notifyAll();
                                        }
                                    }
                                });

                                position = (position + 1) % images.size();
                                index = (index + 1) % buffers.length;

                                Thread.sleep(5);
                            } catch (InterruptedException ignore) {
                            }
                        }
                    }
                };
                Thread t = new Thread(runnable);
                t.start();
            }
        });
    }
}


Answer 4:

在BM.decode(资源......在网络参与?

如果是,那么u需要优化前瞻连接和数据传输的跨网连接,以及您的工作优化位图和memory.That可能意味着成为善于低延迟或异步传输使用的连接协议(HTTP我猜)。 请确保您不运输比你需要更多的数据? 位图译码经常可以丢弃的像素的80%在创建优化对象填充本地视图。

如果打算为位图数据已经是当地的,有没有关于网络延迟的担忧,然后只专注于保留一个集合类型DStructure(listArray)持有该UI将交换页面转发,页面回事件的片段。

如果您的JPEG文件(PNG格式是无损的位图OPS IMO)分别100k左右,你可以只使用一个std适配器将其加载到片段。 如果他们是很多大,那么你就必须弄清楚位图“压缩”选项与解码使用,以免浪费了很多的记忆在你的片段的数据结构。

如果你需要以优化位图建立一个theadpool,然后做删除涉及在该步骤的任何延迟。

林不知道,它的作品,但如果你想获得更多的复杂的,你可以看看把,与适配器合作的listArray下一个循环缓冲器什么?

IMO - 一旦你的结构,交易片段之间切换,你网页应该是非常快的。 我有记忆各约6图片大小为200K左右及其在页面FWD,页背快直接经验。

我用这个程序作为一个框架,着眼于“页面查看器”的例子。



Answer 5:

它涉及到图像缓存,asycTask处理,从网络等后台下载请阅读此页: http://developer.android.com/training/displaying-bitmaps/index.html

如果您下载并观看网页上的示例项目bitmapfun,我相信它会解决所有的问题。 这是一个完美的样本。



文章来源: BitmapFactory.Options.inBitmap causes tearing when switching ImageView bitmap often