本文总结在 Android Native C++ 开发中访问 APK 中的 assets 资源的方法
在 CMake 中添加相关 NDK LIB 的 依赖
因为我们接下来用到的一些函数实现在 NDK 库 libandroid.so 中, 因此我们直接在 CMakeList.txt 中添加对其依赖即可:
- target_link_libraries( # Specifies the target library.
- native-lib
- #lib to link
- Android
- # other libs
- )
如果没有添加此依赖, 显然会提示 undefined reference 错误, 比如:
- error: undefined reference to 'AAssetManager_fromJava'
- error: undefined reference to 'AAssetManager_open'
- error: undefined reference to 'AAsset_getLength'
- error: undefined reference to 'AAsset_getBuffer'
- error: undefined reference to 'AAsset_close'
- error: undefined reference to 'AAssetManager_open'
- error: undefined reference to 'AAsset_getLength'
- error: undefined reference to 'AAsset_openFileDescriptor'
- error: undefined reference to 'AAsset_close'
- error: undefined reference to 'AAssetManager_openDir'
- error: undefined reference to 'AAssetDir_getNextFileName'
- error: undefined reference to 'AAssetManager_open'
获得 AssetManager
在 Java 中, 我们可以通过 Context.getAssets() 轻松获得 AssetManager. 在 NDK 中, 提供了 AAssetManager_fromJava 来获得 Native 中对应的 AAssetManager. 顾名思义, fromJava, 肯定是要从 Java 层获取了, 也即意味着要通过 JNI 来获得. 代码如下:
- /***code in Java, such as MainActivity.java***/
- //decale the jni func
- public native void setNativeAssetManager(AssetManager assetManager);
- //call it, such as during Activity.onCreate()
- setNativeAssetManager(getAssets());
- /***end of java***/
- /***code in native c++***/
- extern "C"
- JNIEXPORT void JNICALL
- Java_willhua_androidstudioopencl_MainActivity_setNativeAssetManager(
- JNIEnv *env,
- jobject instance,
- jobject assetManager) {
- AAssetManager *nativeasset = AAssetManager_fromJava(env, assetManager);
- //the use of nativeasset
- }
下面所有的代码都是在 Java_willhua_androidstudioopencl_MainActivity_setNativeAssetManager 内实现.
访问 assets 下的文件
我们知道, assets 文件夹下面是可以有子文件夹的, 因为, 下面我以读取图片为例, 介绍各种情况的访问方法. 例子中用到 OpenCV 的相关方法, 在此不介绍, 自行了解.
测试用 assets 文件夹目录:
已知完整路径的访问
如果我们已经知道 assets 下某个文件的完整路径, 比如 "sz.jpg","dir/cs.jpg", 那么我们可以直接把这个路径传给 AAssetManager_open 来获得 AAsset.
- //open file
- AAsset *assetFile = AAssetManager_open(nativeasset, "sz.jpg", AASSET_MODE_BUFFER);
- //this will also be ok
- //AAsset *assetFile = AAssetManager_open(nativeasset, "dir/cs.jpg", AASSET_MODE_BUFFER);
- //get file length
- size_t fileLength = AAsset_getLength(assetFile);
- char *dataBuffer2 = (char *) malloc(fileLength);
- //read file data
- AAsset_read(assetFile, dataBuffer2, fileLength);
- //the data has been copied to dataBuffer2, so , close it
- AAsset_close(assetFile);
- //decode the file data to cv::Mat
- std::vector<char> vec2(dataBuffer2, dataBuffer2 + fileLength);
- cv::Mat mat2 = cv::imdecode(vec2, CV_LOAD_IMAGE_COLOR);
- LOGD("asset file %d x %d %d", mat2.cols, mat2.rows, mat2.channels());
- //free malloc
- free(dataBuffer2);
获取文件下的名字并访问之
如果我们只知道文件夹的名字, 但并不知道文件夹下面有哪些具体文件, 比如我们只知道有个 dir 文件夹, 但不知道下面的具体情况. 那么我们可以使用 AAssetDir_getNextFileName 来获取文件夹的文件名. 但是有个问题, 这个方法只能获得文件夹下的文件名, 而无法获得子文件夹, 有哪位知道的请告知.
注意: AAssetDir_getNextFileName 只返回文件名, 而不是该文件的完整路径, 比如只会返回 cs.jpg, 而不是 dir/cs.jpg, 所以如果直接把 AAssetDir_getNextFileName 的返回结果传给 AAssetManager_open 会读取不到正确的文件, 返回 NULL.
- AAssetDir *assetDir = AAssetManager_openDir(nativeasset, "dir");
- const char *filename = AAssetDir_getNextFileName(assetDir);
- while (filename != NULL){
- char fullname[1024];
- sprintf(fullname, "dir/%s", filename); //get the full path
- AAsset *file = AAssetManager_open(nativeasset, fullname, AASSET_MODE_BUFFER);
- if(file == NULL){
- LOGD("FILE NULL %s", filename);
- break;
- }
- size_t fileLength = AAsset_getLength(file);
- LOGD("filename next:%s, size:%d", filename, fileLength);
- char *buffer = (char*)malloc(fileLength);
- AAsset_read(file, buffer, fileLength);
- AAsset_close(file);
- //do something with the buffer
- free(buffer);
- filename = AAssetDir_getNextFileName(assetDir);
- }
- AAssetDir_close(assetDir); //remember to close it
使用 AAsset_getBuffer 读整个文件内容
在上面的 case 中, 我们拿到 AAsset 之后都是 malloc 内存, 然后把文件信息读出来的形式, 其实这种方式适合不一次性读取整个文件内容的情况, 按照官网的说法就是:
Attempt to read 'count' bytes of data from the current offset.
也就是 AAsset_read 应该配合 AAsset_seek 使用更美味.
对于一次性读取整个文件的内容, 更好的方式是使用 AAsset_getBuffer
- AAsset *assetFile = AAssetManager_open(nativeasset, "sz.jpg", AASSET_MODE_BUFFER);
- size_t fileLength = AAsset_getLength(assetFile);
- const char *dataBuffer2 =(const char *) AAsset_getBuffer(assetFile);
- std::vector<char> vec2(dataBuffer2, dataBuffer2 + fileLength);
- cv::Mat mat2 = cv::imdecode(vec2, CV_LOAD_IMAGE_COLOR);
- LOGD("asset file lenght:%d mat: %d x %d %d", fileLength, mat2.cols, mat2.rows, mat2.channels());
- AAsset_close(assetFile);
以 FileDescriptor 的方式来读取
我们可以使用 AAsset_openFileDescriptor 来获取 FileDescriptor, 然后再进行其他操作. 需要注意的是, AAsset_openFileDescriptor 返回当前 fd 的起始 seek 位置 start 以及文件长度 length. 在读取内容之前记得要先 seek 到 start, 否则会发现文件内容不对. 见代码中的 lseek.
- AAsset *assetFile = AAssetManager_open(nativeasset, "sz.jpg", AASSET_MODE_BUFFER);
- size_t fileLength = AAsset_getLength(assetFile);
- LOGD("before fd fileLength:%d",fileLength);
- off_t start = 0, length = 0;
- int fd = AAsset_openFileDescriptor(assetFile, &start, &length);
- LOGD("fd:%d start:%d length:%d", fd, start, length);
- lseek(fd, start, SEEK_CUR); //NOTICE
- char *dataBuffer = (char*)malloc(fileLength);
- memset(dataBuffer, 0, fileLength);
- read(fd, dataBuffer, fileLength);
- close(fd); //close fd
- LOGD("read_ %d %d %d", dataBuffer[0], dataBuffer[1], dataBuffer[2]);
- std::vector<char> vec2(dataBuffer, dataBuffer + fileLength);
- cv::Mat mat2 = cv::imdecode(vec2, CV_LOAD_IMAGE_COLOR);
- LOGD("use fd mat:%d x %d %d", mat2.cols, mat2.rows, mat2.channels());
- AAsset_close(assetFile);
获得 fd 之后, 也可以通过他来获得一个 FILE:FILE * file = fdopen(fd, "rb"); 但是一定要记得 fclose(file). 总的来说不如 read 方便.
open mode
AAssetManager_open 需要传入一个 mode 参数, 各参数的含义如下, 按需使用.
- AASSET_MODE_UNKNOWN: Not known how the data is to be accessed
- AASSET_MODE_RANDOM: Read chunks, and seek forward and backward
- AASSET_MODE_STREAMING: Read sequentially, with an occasional
- forward seek
- AASSET_MODE_BUFFER: Attempt to load contents into memory, for fast
- small reads
细节提示
AAsset 是只读的, 比如上面获得 FILE 之后, 不能用来写.
AAsset provides access to a read-only asset.
记得 AAsset_close
记得 AAssetDir_close
关于压缩文件
Android APK 中有些文件是会进行压缩的, 而有些文件则因为本身就是已经压缩过的, 不再进行压缩, 具体有:
- /* these formats are already compressed, or don't compress well */
- static const char* kNoCompressExt[] = {
- ".jpg", ".jpeg", ".PNG", ".gif",
- ".wav", ".mp2", ".mp3", ".ogg", ".aac",
- ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
- ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
- ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv"
- };
那么对于在 APK 中会被压缩的文件, 比如 txt 文件, 就不能使用 AAsset_openFileDescriptor 来读了, 否则, 会返回 - 1 这样的无效 fd. 对于会被压缩的文件, 那么就只能使用 AAsset_read 或者 AAsset_getBuffer 来访问了.
参考
Developers NDK
不压缩文件后缀
SOF
来源: https://www.cnblogs.com/willhua/p/9692529.html