本文总结在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

Android: 在native中访问assets全解析的更多相关文章

  1. Android图片加载框架最全解析(八),带你全面了解Glide 4的用法

    本篇将是我们这个Glide系列的最后一篇文章. 其实在写这个系列第一篇文章的时候,Glide就推出4.0.0的RC版了.那个时候因为我一直研究的都是Glide 3.7.0版本,再加上RC版本还不太稳定 ...

  2. Android图片加载框架最全解析(四),玩转Glide的回调与监听

    大家好,今天我们继续学习Glide. 在上一篇文章当中,我带着大家一起深入探究了Glide的缓存机制,我们不光掌握了Glide缓存的使用方法,还通过源码分析对缓存的工作原理进行了了解.虽说上篇文章和本 ...

  3. Android Activity启动流程源码全解析(2)

    接上之前的分析 ++Android Activity启动流程源码全解析(1)++ 1.正在运行的Activity调用startPausingLocked 一个一个分析,先来看看startPausing ...

  4. Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能

    我们的Glide系列文章终于要进入收尾篇了.从我开始写这个系列的第一篇文章时,我就知道这会是一个很长的系列,只是没有想到竟然会写这么久. 在前面的六篇文章中,我们对Glide的方方面面都进行了学习,包 ...

  5. Android图片加载框架最全解析(六),探究Glide的自定义模块功能

    不知不觉中,我们的Glide系列教程已经到了第六篇了,距离第一篇Glide的基本用法发布已经过去了半年的时间.在这半年中,我们通过用法讲解和源码分析配合学习的方式,将Glide的方方面面都研究了个遍, ...

  6. Android图片加载框架最全解析(五),Glide强大的图片变换功能

    大家好,又到了学习Glide的时间了.前段时间由于项目开发紧张,再加上后来又生病了,所以停更了一个月,不过现在终于又可以恢复正常更新了.今天是这个系列的第五篇文章,在前面四篇文章的当中,我们已经学习了 ...

  7. Android图片加载框架最全解析(三),深入探究Glide的缓存机制

    在本系列的上一篇文章中,我带着大家一起阅读了一遍Glide的源码,初步了解了这个强大的图片加载框架的基本执行流程. 不过,上一篇文章只能说是比较粗略地阅读了Glide整个执行流程方面的源码,搞明白了G ...

  8. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),G ...

  9. Android图片加载框架最全解析(一),Glide的基本用法

    现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后来的新兴军Glide和Picasso,当然还有 ...

随机推荐

  1. [Swift]LeetCode164. 最大间距 | Maximum Gap

    Given an unsorted array, find the maximum difference between the successive elements in its sorted f ...

  2. [Swift]LeetCode832. 翻转图像 | Flipping an Image

    Given a binary matrix A, we want to flip the image horizontally, then invert it, and return the resu ...

  3. Underscore.js 源码学习笔记(下)

    上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...

  4. Java集合类常见的问题

    本篇文章将尝试回答一些Java集合类常见的问题,以作知识梳理之用. ArrayList和LinkList以及Vector的区别 就实现方式而言: ArrayList是以数组的方式实现的列表. Link ...

  5. 12.Flask-Restful

    定义Restful的视图 安装:pip install flask-restful 如果使用Flask-restful,那么定义视图函数的时候,就要继承flask_restful.Resourse类, ...

  6. java代码之美(5)---guava之Multiset

    guava之Multiset 一.概述 Guava提供了一个新集合类型Multiset,它可以多次添加相等的元素,且和元素顺序无关.Multiset继承于JDK的Cllection接口,而不是Set接 ...

  7. redis 系列18 事件

    一.概述 Redis服务器是一个事件驱动程序,服务器需要处理两类事件:1文件事件,2时间事件.文件事件是关于客户端与服务器之间的通信操作.时间事件是关于服务器内部的一些定时操作.本篇还是参照" ...

  8. Android应用系列:仿MIUI的Toast动画效果实现(有图有源码)

    前言 相信有些人用过MIUI,会发现小米的Toast跟Android传统的Toast特么是不一样的,他会从底部向上飞入,然后渐变消失.看起来效果是挺不错的,但是对于Android原生Toast是不支持 ...

  9. 探索Windows命令行系列(4):通过命令操作文件和文件夹

    1.文件夹操作 1.1.DIR(directory)命令 1.2.TREE 命令 1.3.CD(change directory)命令 1.4.MD(make directory)命令 1.5.RD( ...

  10. .NET快速信息化系统开发框架 V3.2 -> Web 用户管理模块编辑界面-组织机构选择支持级联选择

    下拉框级联选择功能非常的实用,框架用户管理编辑界面对组织机构的选择在3.2版本中新增了级联选择的支持,让组织机构的选择更加的方便与高效,也不容易出错. 我们框架的组织机构结合实际分成了5种类型,分别为 ...