Как известно, в популярной нынче мобильной ОС Android есть фатальный недостаток — это язык Java. Более того, каждому приложению выделяется кусок памяти размером от 32-х мегабайт до бесконечности в зависимости от желания производителя мобильного телефона.
Если вы делаете большое приложение с использованием OpenGLES и пачкой больших текстур размером 2048×2048 (а это 16 мегабайт для формата RGBA), то памяти для загрузки у вас не будет. После загрузки второй-третьей, пятой текстуры с помощью BitmapFactory
приложение упадет с OutOfMemoryError
, что есть очень плохо.
Хочу поделиться решением этой проблемы. Она состоит из двух пунктов: загружать изображения из PNG с помощью libpng и исключить работу с большими блоками данных из Java части.
Решение первой части и исходники для прямой загрузки изображения из PNG файла можно найти на stackoverflow.com.
Для работы с памятью создадим вот такой Java класс:
package com.example.memory;
import java.nio.IntBuffer;
public class MemoryManager {
public static native IntBuffer create(int sizeInBytes);
public static native void release(IntBuffer buffer);
}
И нативную часть (язык cpp):
#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jobject JNICALL Java_com_example_memory_MemoryManager_create(
JNIEnv *env,
jclass cls,
jint sizeInBytes)
{
// Выделяем блок памяти
void *buf = malloc(sizeInBytes);
// Создаем буфер
return env->NewDirectByteBuffer(buf, 0);
}
JNIEXPORT void JNICALL Java_com_example_memory_MemoryManager_release(
JNIEnv *env,
jclass cls,
jobject buffer)
{
// Освобождаем память
free(env->GetDirectBufferAddress(buffer));
}
#ifdef __cplusplus
}
#endif
Далее алгоритм работы такой:
- Читаем высоту и ширины изображения из первых 24 байт PNG (подробности в статье на хабре: Получаем тип и размеры изображения без скачивания его целиком, используя Python)
- Выделяем
IntBuffer
с помощьюMemoryManager
размером W*H*4 (32bit RGBA, 4 байта на пиксель) - Считываем в него PNG файл
- Загружаем эти данные в текстуру с помощью
glTexImage2d
- Освобождаем
IntBuffer
вMemoryManager
Честно говоря, я слабо понимаю — почему в нативе вызывается функция NewDirectByteBuffer
, а в Java приходит IntBuffer
, а не ByteBuffer
. Загадка какая-то.
UPD: Если вы решили пойти стандартным путем с загрузкой Bitmap
и GLUtil.texImage2D
, то не забывайте вызывать bitmap.recycle()
после загрузки изображения в текстуру, дабы освободить нативную память.