您现在的位置是:首页 > 文章详情

Android: 在native中访问assets全解析

日期:2018-12-06点击:415

在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文件夹目录:
iutTBt.jpg

已知完整路径的访问

如果我们已经知道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来访问了。

现在加Android开发群;701740775,可免费领取一份最新Android高级架构技术体系大纲和视频资料,以及五年积累整理的所有面试资源笔记。加群请备注csdn领取xx资料
 

原文链接:https://yq.aliyun.com/articles/687837
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章