gpt4 book ai didi

android - BitmapFactory.Options.inBitmap 经常切换ImageView位图导致撕裂

转载 作者:塔克拉玛干 更新时间:2023-11-01 21:23:54 25 4
gpt4 key购买 nike

我遇到过这样一种情况,我必须在切换图像非常快的幻灯片中显示图像。图像的绝对数量使我想将 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(Bitmap) 在下一个 vsync 上绘制位图,但是,当发生这种情况时,我们可能已经在解码下一个位图,因此,我们已经开始修改位图像素。我的思考方向是否正确?

有没有人知道如何从这里开始?

最佳答案

您应该使用 ImageView 的 onDraw() 方法,因为当 View 需要在屏幕上绘制其内容时会调用该方法。

我创建了一个名为 MyImageView 的新类,它扩展了 ImageView 并覆盖了 onDraw() 方法,该方法将触发回调以让监听器知道此 View 已完成绘制

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 block),但放在不同的地方并在代码注释中进行了解释

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

}
}

关于android - BitmapFactory.Options.inBitmap 经常切换ImageView位图导致撕裂,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15433276/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com