经常切换ImageView位图时,BitmapFactory.Options.inBitmap会导致撕裂

Joa*_*und 13 android bitmap tearing bitmapfactory

我遇到过一种情况,我必须以幻灯片形式显示图像,以便快速切换图像.大量的图像使我想要将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();
Run Code Online (Sandbox Code Playgroud)

我的想法是ImageView.setImageBitmap(Bitmap)在下一个vsync上绘制位图,但是,当发生这种情况时,我们可能已经解码了下一个位图,因此,我们已经开始修改位图像素.我在想正确的方向吗?

有谁有任何关于从哪里去的提示?

Bin*_*ran 7

您应该使用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();
    }

}
Run Code Online (Sandbox Code Playgroud)

在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();
            }
        });

    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在这种情况下,j__m值得赏金.我刚刚获得了200点声望的赏金,并将在24小时后分配给j__m. (2认同)

j__*_*__m 4

作为当前方法的替代方法,您可以考虑保留 JPEG 数据,同时为每个图像创建一个单独的位图,并使用inPurgeableinInputShareable标志。这些标志在不由 Java 垃圾收集器直接管理的单独堆上为位图分配后备内存,并允许 Android 本身在没有空间时丢弃位图数据,并在需要时根据需要重新解码 JPEG 。Android 拥有所有这些专用代码来管理位图数据,那么为什么不使用它呢?