Ответ 1
Проблема полностью не связана с самим растровым изображением....
Это был сигнал синхронизации в реальном времени, запутанный с Android RenderThread.
Дальнейшее объяснение здесь:
У меня есть проект 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, но получил тот же результат
Проблема полностью не связана с самим растровым изображением....
Это был сигнал синхронизации в реальном времени, запутанный с Android RenderThread.
Дальнейшее объяснение здесь:
Я предполагаю, что вы пытаетесь создать 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);
}
Правильный способ синхронизации логики рисования с частотой кадров устройства - использовать SurfaceView вместо ImageView. Вместо того, чтобы нажимать кадры на представление с помощью собственного таймера, вы должны создать рендеринг Thread, который пытается как можно быстрее отображать кадры. Когда вы вызываете surfaceHolder.lockCanvas()
, система Android будет автоматически блокироваться, пока не наступит время для рендеринга фрейма. Когда вы разблокируете холст с помощью unlockCanvasAndPost()
, система выведет буфер на экран.
Подробнее см. https://developer.android.com/guide/topics/graphics/2d-graphics.html#on-surfaceview. Надеюсь, это поможет!
Предусмотреть здесь цели использования такого метода для рендеринга? Что вы хотите сделать?, Есть большие возможности анимации в Android-движке, возможно, эта задача может быть выполнена с помощью этой анимации.
Еще один, если вы будете использовать такие коды, как ваша батарея телефона, будет работать до нуля очень быстро, так как это будет загружать CPU/gpu до максимума.
в любом случае - попробуйте поместить блоки из выполняемой задачи, установите bool taskRun = true
при запуске и проверьте 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
каждый раз, когда хотите выполнить 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. И да, я думаю, вам нужно просто синхронизировать 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);
}
}