Android - обновить растровое изображение по таймеру

У меня есть проект 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 = 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, но получил тот же результат

Ответы

Ответ 1

Проблема полностью не связана с самим растровым изображением....

Это был сигнал синхронизации в реальном времени, запутанный с Android RenderThread.

Дальнейшее объяснение здесь:

Часы реального времени Android и JNI

Ответ 2

Я предполагаю, что вы пытаетесь создать bitmapOriginal. Поэтому, когда компилятор пытается снова позвонить через 60 секунд, он получает одинаковые объекты и не может идентифицировать задачу. Я бы предложил лучше, как показано ниже.

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

Ответ 3

Правильный способ синхронизации логики рисования с частотой кадров устройства - использовать SurfaceView вместо ImageView. Вместо того, чтобы нажимать кадры на представление с помощью собственного таймера, вы должны создать рендеринг Thread, который пытается как можно быстрее отображать кадры. Когда вы вызываете surfaceHolder.lockCanvas(), система Android будет автоматически блокироваться, пока не наступит время для рендеринга фрейма. Когда вы разблокируете холст с помощью unlockCanvasAndPost(), система выведет буфер на экран.

Подробнее см. https://developer.android.com/guide/topics/graphics/2d-graphics.html#on-surfaceview. Надеюсь, это поможет!

Ответ 4

Предусмотреть здесь цели использования такого метода для рендеринга? Что вы хотите сделать?, Есть большие возможности анимации в Android-движке, возможно, эта задача может быть выполнена с помощью этой анимации.

Еще один, если вы будете использовать такие коды, как ваша батарея телефона, будет работать до нуля очень быстро, так как это будет загружать CPU/gpu до максимума.

в любом случае - попробуйте поместить блоки из выполняемой задачи, установите bool taskRun = true при запуске и проверьте if (!taskRun){ taskRun = true; //here start your task..}, а на ui поток после обновления ui вы можете переключиться на taskRun = false;. Используя это, вы можете пропустить некоторые фреймы, но должны не сбой.

Ответ 5

Проблема в том, что 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);
        }
    });    
}

Ответ 6

Я думаю, что вы правы с разумом, потому что вы не можете быть уверены, что Android рендерит изображения в 60 FPS. И да, я думаю, вам нужно просто синхронизировать Bitmap Native Callback с Android Render. Итак, начнем.

Я предпочитаю использовать Lock из concurrency стека Java. Потому что вы видите, когда вы блокируете объект, и когда вы разблокируете. В случае использования волатильности (например, обязательно там также ссылочных ограничений) в объекте Bitmap, вам необходимо проверить блокировку этого объекта в самых разных местах, где вы используете Bitmap.

Также я думаю, что вы должны использовать Lock из ЭТО ПРИМЕР (чтобы разблокировать объект 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);
   }

}