В прошлых статьях показал, как вызвать C++ методы из Java и вызвать Java методы из C++. В данной статье покажу, как обратиться к ресурсам приложения из C++, используя AAssetManager
из ndk.
Введение
Если ещё не читал статьи о том, как работать с C++ и как вызвать Java методы из C++, советую прочитать.
В этой статье поговорим о том, как обратиться к ресурсам приложения из нативного кода. Вообще методов то несколько для доступа к ресурсам. Назову парочку:
Первый понятен, я думаю. Судя по мануалам и форумам, большинство людей работают с ресурсами, используя фришную библиотеку libzip. Кто не в курсе, apk файл приложения представляет собой архив, поэтом с ним можно работать, используя эту либу. Мне этот вариант не особо понравился. По-мимо того, что не особо уверен в его быстродействии по сравнению с методами из ndk, к тому же либа ещё и место занимать будет лишнее. Так что, расскажу о самом удобном способе, как по мне.
AAssetManager
Методы в принципе похожи на методы по работе с файлами из stdio.h. AAssetManager_open
вместо fopen
, AAsset_read
вместо fread
, AAsset_close
вместо fclose
.
Определимся с задачей. Предположим, вы создаёте игру. Естественно, движок игры у нас на C++. При клике на пункте меню (вьюха в Java), должна произойти загрузка уровня. Логично выделить загрузку текстур (или других ресурсов) в отдельный поток. Так что, при нажатии на пункт меню, создаётся поток, в котором вызывается C++ метод по загрузке ресурсов. Пока для простоты реализуем чтение текстового файла для наглядности.
Код в целом похож на код из прошлой статьи. В Java лишь немного поменяем метод по инициализации и отображении результатов чтения.
//загрузка ресурсов native public void readResources(NativeCalls nativeCallListener, AssetManager mng);
В классе нашего потока MyRunnable
поменяем метод по инициализации, вернее количество параметров. Нам теперь помимо ссылки на объект нужна ещё ссылка на менеджер ресурсов.
public void run() { activity.readResources(activity, activity.getResources().getAssets()); }
На стороне Java всё. Теперь надо поработать на стороне C++. Слегка поменять NativeCallListener
. Исходники можете глянуть в архиве целиком потом, тут рассмотрю ключевые моменты. Думаю, наименование метода в конструкторе сами сможете поменять)
//метод по инициализации JNIEXPORT void JNICALL Java_ru_suvitruf_androidndk_MainActivity_readResources(JNIEnv *pEnv, jobject pThis, jobject pNativeCallListener, jobject assetManager) { listener = NativeCallListener(pEnv, pNativeCallListener); mgr = AAssetManager_fromJava(pEnv, assetManager); listener.readResources(); }
Всё так же создаём экземпляр NativeCallListener
. Получаем менеджер ресурсов и начинаем загрузку ресурсов. Для примера лишь прочитаем один файл, который создадим предварительно в папке assets приложения. Само чтение:
void NativeCallListener::readResources(){ char * name = new char[64]; strcpy(name, "111.txt"); AAssetFile f = AAssetFile(mgr, name); char buf[f.size()+1]; int count = f.read(&buf,f.size(),1); buf[f.size()] = '\0'; LOGI("file read : %s ",buf); LOGI("read count = %i",count); LOGI("file size = %i ",f.size()); LOGI("size of buf= %i ",sizeof(buf)); //sendResult(count); f.close(); }
Строчкой buf[f.size()] = '\0';
мы указываем конец буфера, чтобы при выводе всякого мусора не было.
Обёртка для работы с файлами
Я для таких вещей всегда обёртки делаю. В принципе можете прям тут вызывать AAssetManager_open
, а потом с помощью AAsset_read
читать. Но тогда на той же винде не будет это работать, так как под WIN надо работать с помощью Win API и FILE. Поэтому я создал свой класс c нужным директивами препроцессора.
class AAssetFile{ private: char * fileName; #ifdef __ANDROID__ AAsset* file; AAssetManager *mgr; #else FILE * file; #endif AAssetFile(){} public: #ifdef __ANDROID__ AAsset * getFile(); AAssetFile(AAssetManager *mgr, char * fileName); int read(void* buf, size_t size, size_t count); void close(){AAsset_close(file);LOGI("file %s closed", fileName);} ~AAssetFile(){ } off_t size() { return AAsset_getLength (file); } #else AAssetFile(char * fileName); size_t fread(void *buff, size_t size, size_t count); FILE * getFile(); void close(){fclose(file);} ~AAssetFile(){} #endif };
В зависимости от того, под какую платформу билдите, те методы и будут включены в сборку. Если под Андройд, то будет работать с AAsset, если под Win, то с FILE. Ну и реализация методов именно под Андройд:
AAsset * AAssetFile:: getFile(){ return file; } AAssetFile::AAssetFile(AAssetManager *mgr, char * fileName){ this->mgr = mgr; this->fileName = fileName; file = AAssetManager_open(mgr, fileName, AASSET_MODE_UNKNOWN); if(mgr == NULL) LOGE("MyNative", "AAssetManager NULL"); if (file == NULL) LOGE("_ASSET_NOT_FOUND_ %s",fileName); } int AAssetFile::read(void* buf, size_t size, size_t count){ return AAsset_read (file,buf,size*count); }
В логе увидите что-то такое:

Теперь вы можете работать с ресурсами напрямую из C++ кода. Можете скачать исходники AndroidNDK3.rar.
Пусть с печеными блинами
К вам достаток в дом войдет,
Чтоб всего добились сами,
Пусть по жизни вам везет.
А еще , хлопот не знайте,
Улыбайтесь, всем назло,
Масленицу так встречайте,
Чтобы в жизни повезло
С МАСЛЕНИЦЕЙ!
Внезапно о.О
И вас с праздником.
Язык С++, для меня это пока сущий лес. Планирую в скором времени заняться обучением.
Помнится, как долго я пытался побороть гарбич коллектор, при вызове из С аctivity.getResources().getAssets() через jni. Секунд 5 приложение работало, а потом все ресурсы удалялись и все крешилось. Потом обошел это, напрямую вытаскивая из apk ресурсы с помощью zip либы. А потом решил перейти на минималку api9 и по людски работать с AAssetManager.
Есть у меня к вам вопрос. На хабре посмотрел ваш урок4, нашел там файлы openAL для андроида. Могли бы вы поделиться источником, откуда вы файлы взяли? На всех платформах я использую openAL. Сейчас портов этой либы хватает, но хочется выбрать что-то легкое, так как у меня все 2d. Буквально сегодня хотел садится вечером и начинать реализовывать openSL для андроид-ветки, да вот наткнулся на ваш пост и еще раз задумался…
Вроде отсюда качал последнюю версию.
Отличная статья, но у меня вот вопрос немного не по теме: Получаю вашим методом картинку, вывожу в лог её buffer, выдаёт «?.png». Картинку загружал, чтобы использовать потом как текстуру (openGL ES). Передаю buffer в качестве последнего параметра процедуры glTexImage2D. В результате текстуры не накладывается, вместо этого обычно просто беспорядочные пиксели разных цветов на объекте. Подозреваю, что не правильно передавать buffer. Может его нужно преобразовать как-то перед этим, чтобы получить данные картинки. В чём может быть проблема? Вот так накладываю текстуру:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1,&textureID);
glBindTexture(GL_TEXTURE_2D,textureID);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,64,64,0,GL_RGB,GL_UNSIGNED_BYTE,buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glLoadIdentity();
glTranslatef(dx,dy,0);
glRotatef(angle,0,0,1);
GLfloat tex[] = {1,0, 0,0, 1,1, 0,1};
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glVertexPointer(2,GL_FLOAT,0,vertices);
glTexCoordPointer(2,GL_FLOAT,0,tex);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
Так мой метод файл целиком считывает. Там помимо бит с данными ещё биты хэдера. Чтобы работать в C++ с .png, необходимо использовать библиотеку libpng.
подскажите, плз, можно ли работать с assets из ndk в android-7 ?
хидеры для AAssetManager присутствует только в android-14
В android-9 тоже есть.