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