Android – 从计时器线程更新位图

我有一个由ImageView组成的单一Layout的Android项目。

public class MainActivity extends AppCompatActivity { /* original and stretched sized bitmaps */ private Bitmap bitmapOriginal; private Bitmap bitmapStretched; /* the only view */ private ImageView iv; .... } 

这个ImageView由这个可运行的函数更新

  runnable = new Runnable() { @Override public void run() { iv.setImageBitmap(bitmapStretched); } }; 

而runnable是由临时线程运行的临时JNI函数运行的,每秒调用60次。

 public void jniTemporizedCallback(int buf[]) { /* set data to original sized bitmap */ bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight); /* calculate the stretched one */ bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height, false); /* tell the main thread to update the image view */ runOnUiThread(runnable); } 

在绘制了一些框架后,应用程序崩溃,并显示以下消息。

 A/OpenGLRenderer: Task is already in the queue! 

我猜这是因为渲染器没有完成渲染ImageView的前一帧,而生气。

如果我删除runOnUiThread(runnable); 问题消失(显然)

如何避免这一点? 我如何可以将我的应用程序与openGL渲染器同步?

我也尝试扩展ImageView,并将canvas上的位图绘制到onDraw函数中,但是我得到了相同的结果

Solutions Collecting From Web of "Android – 从计时器线程更新位图"

我想你正在尝试创build位图原始ouside线程。 因此,当编译器在60秒后再次尝试调用时,它会得到相同的对象,无法识别任务。 我会build议如下。

  public void jniTemporizedCallback(int buf[]) { // Initialize bitmapOriginal = Bitmap.createBitmap(///) /* set data to original sized bitmap */ bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight); /* calculate the stretched one */ bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height,false); /* tell the main thread to update the image view */ runOnUiThread(runnable); } 

将绘图逻辑与设备帧速率同步的正确方法是使用SurfaceView而不是ImageView。 用您自己的计时器将帧推送到视图,您应该创build一个渲染线程,尽可能快地渲染帧。 当您调用surfaceHolder.lockCanvas() ,Android系统将自动阻塞,直到渲染帧为止。 当您使用unlockCanvasAndPost()解锁canvas时,系统会将缓冲区绘制到屏幕上。

有关更多信息,请参见https://developer.android.com/guide/topics/graphics/2d-graphics.html#on-surfaceview 。 希望这可以帮助!

问题是完全无关的位图本身….

这是与Android RenderThread混淆的实时时钟信号。

此处进一步说明:

Android和JNI实时时钟

在这里提供使用这种渲染方法的目的吗? 你想做什么?在android引擎中有很棒的animationfunction,可能这个任务可以用这个animation来完成。

还有一个,如果你将使用像你这样的代码电池将运行到零非常快因为这将加载CPU / GPU最大。

无论如何 – 尝试从正在运行的任务放置块,在开始时设置bool taskRun = true ,并检查if (!taskRun){ taskRun = true; //here start your task..} if (!taskRun){ taskRun = true; //here start your task..}和ui线程更新ui后,你可以切换到taskRun = false; 使用这个,你可以跳过一些帧,但不应该崩溃。

问题是主线程的Handler保持对Runnable的引用。 当您想要第二次运行Runnable时,旧的Runnable已经在Message Queue ,因此Task is already in the queue消息中。 如果你每次想要像下面的代码那样执行Runnable ,就创build一个Runnable ,我认为这个问题就解决了。

 public void jniTemporizedCallback(int buf[]) { /* set data to original sized bitmap */ bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight); /* calculate the stretched one */ bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height, false); /* tell the main thread to update the image view */ runOnUiThread(new Runnable() { @Override public void run() { iv.setImageBitmap(bitmapStretched); } }); } 

我认为你的理由是正确的,因为你不能确定,Android以60 FPS渲染图像。 是的,我认为你只需要与Android Render同步位图原生callback。 所以,让我们开始。

我更喜欢使用来自并发堆栈Java的Lock。 因为你看,当你locking对象,当你解锁。 如果在Bitmap对象上使用volatile(例如,肯定还有引用限制),则需要在使用Bitmap的地方检查locking此对象。

另外,我认为你应该使用这个例子中的锁(从任何其他线程解锁锁对象)。 所以,这里是例子。 下面的例子将正常工作。 只是不要忘记上下文删除和停止任务:

 public class MainActivity extends AppCompatActivity { /* Initialize lock (avoid lazy init, with your methods) */ private ReentrantLock lock = new ReentrantLock(); ............ private runnableDrawImage = new Runnable() { @Override public void run() { iv.setImageBitmap(bitmapStretched); lock.unlock(); } }; .......... public void jniTemporizedCallback(int buf[]) { /* synchronize by locking state*/ lock.lock(); bitmapOriginal = Bitmap.createBitmap(///) bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight); bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height,false); MainActivity.this.runOnUiThread(runnableDrawImage); } }