• android: Native 层访问assets目录


    有时候需要把一些文件放置到app里面并且打包出去(比如OpenCV里的模型文件、一些试听的歌曲等),android下面一般都是放在 assets 目录或者是 raw 目录,后者还可以通过R文件访问,前者则不能。从java层访问这两个目录很简单,从native层访问却很少用到,刚好项目中有用到,特记录之:

    一. 在CMake中添加相关的依赖

    这里主要是添加native层用到的一些库函数,包含在libandroid.so中,因此我们直接在 CMakeList.txt 中添加对其依赖即可。

    target_link_libraries( # Specifies the target library.
                           native-lib
                           #lib to link
                           android
                           # other libs
                           )

    二. 获得 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
    }

    在Native层的C++文件中使用  AAssetManager_fromJava 这个函数需要注意,先包含下面两个头文件:

    #include <android/asset_manager.h>
    #include <android/asset_manager_jni.h>

    不然会提示找不到这个函数。

    三. 访问assets下的文件

    我们知道,assets文件夹下面是可以有子文件夹的,因为,下面我以读取图片为例,介绍各种情况的访问方法。
    测试用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);
    
    //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]);
    
    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来访问了。

    参考链接:

    1. Android: 在native中访问assets全解析

  • 相关阅读:
    《软件需求最佳实践》阅读笔记06
    《软件需求最佳实践》阅读笔记05
    《软件需求最佳实践》阅读笔记04
    《软件需求最佳实践》阅读笔记03
    《软件需求最佳实践》阅读笔记02
    项目目标文档
    Eclipse使用maven构建web项目(创建项目)
    eclipse项目上有红叉内部不报错
    ajax文件上传;post方式getparameter获取不到值;getReader取值方法
    去除url中的& = ;并到Map中的方法:
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/14023300.html
Copyright © 2020-2023  润新知