Основы Android NDK: доступ к ресурсам директории assets из C++

В прошлых статьях показал, как вызвать 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);
}

В логе увидите что-то такое:

Android NDK log

Теперь вы можете работать с ресурсами напрямую из C++ кода. Можете скачать исходники AndroidNDK3.rar.

  Категории: Android, C/C++, java, Коддинг
  • http://ledy-feya.ru/ Helga

    Пусть с печеными блинами
    К вам достаток в дом войдет,
    Чтоб всего добились сами,
    Пусть по жизни вам везет.

    А еще , хлопот не знайте,
    Улыбайтесь, всем назло,
    Масленицу так встречайте,
    Чтобы в жизни повезло
    С МАСЛЕНИЦЕЙ!

    • http://suvitruf.ru Suvitruf

      Внезапно о.О

      И вас с праздником.

  • http://winners-games.com/ Vitalik

    Язык С++, для меня это пока сущий лес. Планирую в скором времени заняться обучением.

  • https://bitbucket.org/pascualle/tengine Papa Pascualle

    Помнится, как долго я пытался побороть гарбич коллектор, при вызове из С аctivity.getResources().getAssets() через jni. Секунд 5 приложение работало, а потом все ресурсы удалялись и все крешилось. Потом обошел это, напрямую вытаскивая из apk ресурсы с помощью zip либы. А потом решил перейти на минималку api9 и по людски работать с AAssetManager.

    Есть у меня к вам вопрос. На хабре посмотрел ваш урок4, нашел там файлы openAL для андроида. Могли бы вы поделиться источником, откуда вы файлы взяли? На всех платформах я использую openAL. Сейчас портов этой либы хватает, но хочется выбрать что-то легкое, так как у меня все 2d. Буквально сегодня хотел садится вечером и начинать реализовывать openSL для андроид-ветки, да вот наткнулся на ваш пост и еще раз задумался…

    • http://suvitruf.ru Suvitruf

      Вроде отсюда качал последнюю версию.

  • Nail

    Отличная статья, но у меня вот вопрос немного не по теме: Получаю вашим методом картинку, вывожу в лог её 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);

    • http://suvitruf.ru Suvitruf

      Так мой метод файл целиком считывает. Там помимо бит с данными ещё биты хэдера. Чтобы работать в C++ с .png, необходимо использовать библиотеку libpng.

  • Алексей

    подскажите, плз, можно ли работать с assets из ndk в android-7 ?
    хидеры для AAssetManager присутствует только в android-14

    • http://suvitruf.ru Suvitruf

      В android-9 тоже есть.