Как известно, в популярной нынче мобильной ОС 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()
после загрузки изображения в текстуру, дабы освободить нативную память.