欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android: 在native中访问assets全解析

程序员文章站 2022-03-31 13:46:04
本文总结在Android Native C++开发中访问APK中的 assets 资源的方法 在CMake中添加相关NDK LIB的 依赖 因为我们接下来用到的一些函数实现在NDK库libandroid.so中,因此我们直接在 CMakeList.txt 中添加对其依赖即可: 如果没有添加此依赖,显 ......

本文总结在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文件夹目录:
Android: 在native中访问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