生也有涯,知也无涯。这篇文章主要讲述Android: 在native中访问assets全解析相关的知识,希望能为你提供帮助。
本文总结在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‘获得AssetManager
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‘
在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 bufferfree(buffer);
filename = AAssetDir_getNextFileName(assetDir);
}
AAssetDir_close(assetDir);
//remember to close it
使用AAsset_getBuffer读整个文件内容在上面的case中,我们拿到AAsset之后都是malloc内存,然后把文件信息读出来的形式,其实这种方式适合不一次性读取整个文件内容的情况,按照官网的说法就是:
【Android: 在native中访问assets全解析】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:%dmat: %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:%dstart:%dlength:%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 fdmat:%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
推荐阅读
- win10电脑如何看cctv?win10看电视直播的办法
- C# 对WinForm应用程序的App.config的加密
- apply, call , bind
- Java知识弥补-Android开发
- h5打开App的方法。
- 安卓app开发-04- app运行的运行和调试
- 安卓app开发-05-Android xml布局详细介绍
- Android studio使用android:style/Theme.Dialog报错:You need to use a Theme.AppCompat theme (or descendant)
- Android Intent Service