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

时间:2016-08-22 11:30:11

标签: android multithreading bitmap opengl-es imageview

我有一个Android项目,由一个带有ImageView的布局组成。

public class MainActivity extends AppCompatActivity {

    /* original and stretched sized bitmaps */
    private Bitmap bitmapOriginal;
    private Bitmap bitmapStretched;

    /* the only view */
    private ImageView iv;

    ....
}

此ImageView由此runnable函数

更新
    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并在画布上绘制位图到onDraw函数中,但我得到了相同的结果

6 个答案:

答案 0 :(得分:6)

我猜你正在尝试创建bitmapOriginal ouside线程。因此,当编译器在60秒后尝试再次调用时,它会获得相同的对象并且无法识别任务。我建议如下更好。

#!/bin/bash
IsSSL=$1
if [ $IsSSL = "Yes" ] || [ $IsSSL = "No" ];
then 
        echo "correct input"
else
        echo "incorrect input"
fi

答案 1 :(得分:5)

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

有关详细信息,请参阅https://developer.android.com/guide/topics/graphics/2d-graphics.html#on-surfaceview。希望这有帮助!

答案 2 :(得分:4)

问题与Bitmap本身完全无关....

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

此处有进一步说明:

Android and JNI real time clock

答案 3 :(得分:4)

在此提供使用此类渲染方法的目的?你想做什么?,android引擎中有很棒的动画功能,可能这个任务可以用这个动画完成。

如果您将使用像您这样的手机电池将会非常快地运行零,这将加载cpu / gpu到最大值。

无论如何

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

答案 4 :(得分:3)

问题是主线程的Handler保留了对Runnable的引用。如果您想第二次运行Runnable,旧的Runnable已经在Message Queue,因此Task is already in the queue消息。如果您每次想要在下面的代码中执行Runnable时创建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);
        }
    });    
}

答案 5 :(得分:1)

我认为你是正确的,因为你无法确定,Android会以60 FPS渲染图像。是的,我认为你只需要将Bitmap Native Callback与Android Render同步。所以,让我们开始吧。

我更喜欢使用来自并发堆栈Java的Lock。因为您看到,当您锁定对象时,以及解锁时。如果在Bitmap对象上使用volatile(例如,确保还有参考限制),则需要检查在使用Bitmap的地方锁定此对象。

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

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

}