Мне нужно отображать изображения с разными кадрами в секунду, максимальная FPS составляет 30. Изображения взяты из SDcard и все имеют одинаковый размер: 480 x 640. Я создал 3 возможных решения, но каждый из них имеет проблемы:Самый быстрый способ загрузки/отображения растрового изображения; лучший способ повторного использования Bitmap
Ниже представлены результаты при 30 FPS.
I. Не повторное использование растрового изображения
- много GC звонков: приблиз. 30 ГХ в секунду
загрузки процессора: достигать до 92%
private Bitmap bitmap; private void startAnimation1() { TimerTask updateImage = new UpdateImage1(); timer.scheduleAtFixedRate(updateImage, 0, 1000/FPS); } class UpdateImage1 extends TimerTask { @Override public void run() { try { if (i == IMAGES_NR) { i = 0; } bitmap = BitmapFactory.decodeStream(new FileInputStream(framesFiles[i]), null, null); i++; } catch (FileNotFoundException e) { System.out.println("Exception 1: " + e.getMessage()); } runOnUiThread(new Runnable() { @Override public void run() { imgView.setImageBitmap(bitmap); } }); } }
II. повторное использование растровых изображений с помощью BitmapFactory.Options.inBitmap
- GC вызовов ниже - 1 или 2 на второй нагрузке
- CPU: достигать до 84%
После WILE бега анимация, с которой работает приложение:
06-20 15:08:58.158: WARN/System.err(7880): java.lang.ArrayIndexOutOfBoundsException: length=-5131855; regionStart=0; regionLength=1024
06-20 15:08:58.158: WARN/System.err(7880): at java.util.Arrays.checkOffsetAndCount(Arrays.java:1731)
06-20 15:08:58.158: WARN/System.err(7880): at java.io.BufferedInputStream.read(BufferedInputStream.java:273)
06-20 15:08:58.158: WARN/System.err(7880): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
06-20 15:08:58.158: WARN/System.err(7880): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:587)
06-20 15:08:58.158: WARN/System.err(7880): at com.example.SendPreviewOptimization.MyActivity$UpdateImage2.run(MyActivity.java:148)
06-20 15:08:58.158: WARN/System.err(7880): at java.util.Timer$TimerImpl.run(Timer.java:284)
06-20 15:08:58.168: DEBUG/skia(7880): ---- read threw an exception
06-20 15:08:58.168: DEBUG/skia(7880): --- decoder->decode returned false
06-20 15:08:58.168: WARN/System.err(7880): java.lang.IllegalArgumentException: Problem decoding into existing bitmap
06-20 15:08:58.168: WARN/System.err(7880): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:590)
06-20 15:08:58.168: WARN/System.err(7880): at com.example.SendPreviewOptimization.MyActivity$UpdateImage2.run(MyActivity.java:148)
06-20 15:08:58.168: WARN/System.err(7880): at java.util.Timer$TimerImpl.run(Timer.java:284)
06-20 15:08:58.178: ERROR/msm8960.hwcomposer(330): prepareBypass: Unable to setup bypass due to non-pmem memory
06-20 15:08:58.198: ASSERT/libc(7880): Fatal signal 11 (SIGSEGV) at 0xffd1d447 (code=1)
06-20 15:08:58.238: ERROR/msm8960.hwcomposer(330): prepareBypass: Unable to setup bypass due to non-pmem memory
06-20 15:08:58.498: ERROR/MP-Decision(1448): DOWN Ld:25 Ns:1.100000 Ts:190 rq:0.000000 seq:194.000000
06-20 15:08:58.708: INFO/DEBUG(27660): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
private static BitmapFactory.Options bitmapOptions;
private FileInputStream in;
private void startAnimation2() {
bitmapOptions = new BitmapFactory.Options();
// setup bitmap reuse options:
bitmapOptions.inPurgeable = true;
bitmapOptions.inInputShareable = true;
bitmapOptions.inBitmap = reusableBitmap;
bitmapOptions.inMutable = true;
bitmapOptions.inSampleSize = 1;
TimerTask updateImage = new UpdateImage2();
timer.scheduleAtFixedRate(updateImage, 0, 1000/FPS);
}
class UpdateImage2 extends TimerTask {
@Override
public void run() {
try {
if (i == IMAGES_NR) {
i = 0;
}
//** version 1:
in = new FileInputStream(framesFiles[i]);
//decode into existing bitmap
BitmapFactory.decodeStream(in, null, bitmapOptions);
in.close();
//** version 2:
//BitmapFactory.decodeFile(framesFiles[i].getAbsolutePath(), bitmapOptions);
i++;
} catch (Exception e) {
System.out.println("Exception 2: " + e.getMessage());
}
runOnUiThread(new Runnable() {
@Override
public void run() {
imgView.setImageBitmap(reusableBitmap);
}
});
}
}
III. Вариант III: (. Одна вещь, которая делает Bytebuffer
более эффективным является использование прямой памяти) с использованием Буферы
этот вариант я не мог заставить его работать :(
private ByteBuffer buffer; private byte[] b; private IntBuffer mPixels; private void startAnimation3() { buffer = ByteBuffer.allocate(480 * 640 * 6); b = new byte[480 * 640 * 6]; TimerTask updateImage = new UpdateImage3(); timer.scheduleAtFixedRate(updateImage, 0, 1000/FPS); } class UpdateImage3 extends TimerTask { public void run() { try { if (i == IMAGES_NR) { i = 0; } FileInputStream frameInputStream = new FileInputStream(framesFiles[i]); frameInputStream.read(b); buffer.wrap(b); buffer.position(0); reusableBitmap.copyPixelsFromBuffer(buffer); frameInputStream.close(); i++; } catch (Exception e) { System.out.println("Exception 3: " + e.getMessage()); } runOnUiThread(new Runnable() { @Override public void run() { imgView.setImageBitmap(reusableBitmap); } }); } } private ByteBuffer copyToBuffer(Bitmap bitmap) { int size = bitmap.getHeight() * bitmap.getRowBytes(); ByteBuffer buffer = ByteBuffer.allocateDirect(size); bitmap.copyPixelsToBuffer(buffer); return buffer; }
В каждом из вышеуказанных решений я принимаю в логарифмических лотах
ERROR/msm8960.hwcomposer(330): prepareBypass: Unable to setup bypass due to non-pmem memory
, который я не знаю, что именно означает.
Я не работал с повторным использованием Bitmap и не знаю, какое из них является лучшим решением.
Я добавил проект, который я здесь создал: https://www.dropbox.com/sh/3xov369u1bmjpd1/qBQax4t48D, а также 2 кадра/изображения.
Ответ на Нерон T
Я пробовал эту библиотеку:
//Option IV:
private AQuery aquery;
private void startAnimation4() {
aquery = new AQuery(this);
aquery.id(R.id.imgView);
TimerTask updateImage = new UpdateImage4();
timer.scheduleAtFixedRate(updateImage, 0, 1000/FPS);
}
class UpdateImage4 extends TimerTask {
public void run() {
try {
if (i == 29) {
i = 0;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
//load image from file, down sample to target width of 300 pixels
aquery.image(framesFiles[i],300);
}
});
i++;
} catch (Exception e) {
System.out.println("Exception 4: " + e.getMessage());
}
}
}
Это не работает, как я ожидал, - у меня есть мерцающий эфект перед каждой картиной.Я думаю, что сначала он очищает фотографии и после этого добавляет новый :(
спасибо first.I'm используя 'метод runOnUiThread'. Контекст в моем приложении более сложный, чем тот из приведенного выше примера. Я попробую файл jar файла ImageLoading из предоставленной вами ссылки (точнее, вариант загрузки изображения из файла асинхронно), но ответ на версии, которые я раскрыл в моем вопросе, более чем приветствуется. – Paul
Простите, не заметили, что вы запустили его onUIThread, и я спрашивал себя, почему он не дал такую ошибку. Надеюсь, библиотека работает для вас. –
Я обновил ответ с результатами использования этой библиотеки. – Paul