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‘

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

原文地址:https://www.cnblogs.com/willhua/p/9692529.html

时间: 2024-10-22 08:21:35

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

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

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53939176 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每天都有文章更新. 在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),Glide的基本用法 . 在多数情况下,我们想要在界面上加载并展示一

android中的动画全解析

Android为我们提供了2中动画 一: Tween Animation 渐变动画 通过对特定对象做图像的变换,例如: 平移, 缩放,旋转, 淡入淡出 等. 二: Frame Animation 帧动画 创建一个淡入阿瓦不了可以按照指定的时间间隔一个一个显示.顺序播放 下面我们就详细介绍一下这两中动画: 首先我们介绍 渐变动画: 1. Tween Animation 1. 渐变动画有四种样式: alpha: 渐变透明度 scale: 缩放尺寸伸缩动画 translate: 移动动画效果 rota

Android架构(一)MVP全解析

前言 关于架构的文章,博主很早就想写了,虽说最近比较流行MVVM,但是MVP以及MVC也没有过时之说,最主要还是要根据业务来选择合适的架构.当然现在写MVP的文章很多,也有很多好的文章,但是大多数看完后还是一头雾水,用最少的文字表述清楚是我一贯的风格(这里小小的装逼一下),所以还是自己总结比较靠谱. 1.回顾MVC 讲到MVP前我们有必要回顾下MVC,MVC(Model-View-Controller,模型-视图-控制器)模式是80年代Smalltalk-80出现的一种软件设计模式,后来得到了广

Android 异步加载神器Loader全解析

在之前呢,我们经常会有这种需求,比如在某个activity,或者某个fragment里面,我们需要查找某个数据源,并且显示出来,当数据源自己更新的时候,界面也要及时响应. 当然咯,查找数据这个过程可能很短,但是也可能很漫长,为了避免anr,我们都是开启一个子线程去查找,然后通过handler来更新我们的ui界面.但是,考虑到activity和 fragment 复杂的生命周期,上述的方法 使用起来会很不方便,毕竟你要考虑到保存现场 还原现场 等等复杂的工作来保证你的app无懈可击.所以后来呢谷歌

react native中Unable to load script from assets &#39;index.android.bundle&#39;解决方案

刚刚朋友问我,说是创建好一个项目,运行后报错:Unable to load script from assets 'index.android.bundle',以前好好的没出现这种现象,于是我找到一个解决方案,分享一下. 解决这个问题的方案是: 进入你该项目的根目录下的 android目录下的app目录下的src文件下的mian文件,(可能说的有点绕),在main件夹下,创建一个assets文件,这个文件是rn的资源文件夹! 之后用dos进入你的项目根目录,执行一下命令: react-nativ

Android异步加载全解析之Bitmap

Android异步加载全解析之Bitmap 在这篇文章中,我们分析了Android在对大图处理时的一些策略--Android异步加载全解析之大图处理  戳我戳我 那么在这篇中,我们来对图像--Bitmap进行一个更加细致的分析,掌握Bitmap的点点滴滴. 引入 Bitmap这玩意儿号称Android App头号杀手,特别是3.0之前的版本,简直就是皇帝般的存在,碰不得.摔不得.虽然后面的版本Android对Bitmap的管理也进行了一系列的优化,但是它依然是非常难处理的一个东西.在Androi

Android系统启动过程全解析

Android系统是一款基于Linux的移动操作系统,那么Android是如何启动起来的呢?本文就详细阐述Android系统的启动过程. 从内核之上,我们首先应该从文件系统的init开始,因为 init 是内核进入文件系统后第一个运行的程序,通常我们可以在linux的命令行中指定内核第一个调用谁,如果没指定那么内核将会到/sbin/./bin/ 等目录下查找默认的init,如果没有找到那么就报告出错. init.c位置:system/core/init/init.c. 在init.c的main函

Android 之 IPC 进程通信全解析

Android 之 IPC 进程通信全解析 本篇博客的框架 什么是IPC IPC(Inter-Process Communication) 进程间通信,是指两个不同进程之间数据交换的过程. 在明确其之前,需要先搞懂几个概念: 线程:CPU可调度的最小单位,是程序执行流的最小单元:线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源. 进程: 一个执行单元,在PC 和移动设备上一

Android异步加载全解析之引入一级缓存

Android异步加载全解析之引入缓存 为啥要缓存 通过对图像的缩放,我们做到了对大图的异步加载优化,但是现在的App不仅是高清大图,更是高清多图,动不动就是图文混排,以图代文,如果这些图片都加载到内存中,必定会OOM.因此,在用户浏览完图像后,应当立即将这些废弃的图像回收,但是,这又带来了另一个问题,也就是当用户在浏览完一次图片后,如果还要返回去再进行重新浏览,那么这些回收掉的图像又要重新进行加载,保不准就要那些无聊到蛋疼的人在那一边看你回收GC,一边看你重新加载.这两件事情,肯定是互相矛盾的