Основы 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.