写点什么

Android C++ 系列:访问 Assets 文件夹

作者:轻口味
  • 2021 年 11 月 16 日
  • 本文字数:2255 字

    阅读完需:约 7 分钟

Android C++系列:访问Assets 文件夹

Java 层 Assets

assets 目录是 Android 的一种特殊目录,用于放置 APP 所需的固定文件,且该文件被打包到 APK 中时,不会被编码到二进制文件。Android 还存在一种放置在 res 下的 raw 目录,该目录与 assets 目录不同。区别点:


  1. assets 目录不会被映射到 R 中,因此,资源无法通过 R.id 方式获取,必须要通过 AssetManager 进行操作与获取;res/raw 目录下的资源会被映射到 R 中,可以通过 getResource()方法获取资源。

  2. 多级目录:assets 下可以有多级目录,res/raw 下不可以有多级目录。

  3. 编码(都不会被编码):assets 目录下资源不会被二进制编码;res/raw 应该也不会被编码。


Java 层我们通过 Context 获取 AssetsManager,然后调用 AssetsManager 的 open 方法获取 assets 下文件的输入流:


AssetManager am = getAssets();  InputStream is = am.open("filename");  
复制代码

JNI 层 Assets

很多时候我们需要在 JNI 中直接操作 Assets 下的文件,比如我们要使用 Assets 下的图片资源作为 Opengl 贴图,或者使用 Assets 下的 tflite 模型文件加载模型。

加载纹理图片

在 OpenGL 开发中,我们要渲染一张图片,通常先是得到一张图片对应的 Bitmap ,然后将该 Bitmap 作为纹理上传到 OpenGL 中。在 Android 中有封装好的 GLUtils 类的 texImage2D 方法供我们调用。


public static void texImage2D(int target, int level, int internalformat,             Bitmap bitmap, int type, int border)
复制代码


该方法的底层原理实际上也是解析了该 Bitmap ,得到了 Bitmap 所有的像素数据,类似于 Android NDK 关于 Bitmap 操作的 AndroidBitmap_lockPixels 方法,如果你不太了解该方法,可以参考《Android C++系列:JNI 操作 Bitmap》。


得到了所有像素数据之后,实际最终还是调用了 OpenGL 的 glTexImage2D 来实现纹理上传。当然,如果可以直接得到所有数据,也不需要走解析 Bitmap 这一步了,这种场景最常见的就是把相机作为输入了。


接下来我们会通过 Android NDK 开发中去渲染一张图片,步骤还是如上,从图像解析到纹理上传,不同的是我们将会解析 Assets 文件夹中的图片,而不是一张已经保存在手机 SDCard 上的图片。


相比于前者,SDCard 上的图片已经有了绝对地址了,直接把地址传到 stb_image 库就可以完成解析了(参考之前的文章 ),而 Assets 文件夹的内容在手机上可没有绝对地址。


一开始陷入了误区,想着怎么去获得文件的绝对地址,看到了 AssetManager 的 AAsset_openFileDescriptor 方法以为拿到文件描述符就万事大吉了,结果打印的地址是这样的 /proc/9941/fd/79 ,这基本的不可用了。


换个思路,在 Java 中去加载 Assets 目录下的图片:


 InputStream is = getAssets().open(fileName); 
复制代码


通过 AssertManager 的 open 方法直接拿到文件的输入流了。


而在 NDK 开发中同样的方式是行不通的,这里要采用另外一种方式,但其实意思都差不多的:


 // NDK 中是 AssetManager     AAssetManager *mgr = AAssetManager_fromJava(env, assetManager);     // 打开 Asset 文件夹下的文件     AAsset *pathAsset = AAssetManager_open(mgr, assetPath, AASSET_MODE_UNKNOWN);     // 得到文件的长度     off_t assetLength = AAsset_getLength(pathAsset);     // 得到文件对应的 Buffer     unsigned char *fileData = (unsigned char *) AAsset_getBuffer(pathAsset);     // stb_image 的方法,从内存中加载图片     unsigned char *contnet = stbi_load_from_memory(fileData, assetLength, &w, &h, &n, 0);
复制代码


NDK 中可拿不到像 Java 那样的输入流,但是可以通过 AssetManager 的 AAsset_getBuffer 或者是 AAsset_read 方法去获取文件内容。


看到上面那两个 API 基本就稳了,再配合 stb_image 介绍过的方法,stbi_load_from_memory 从内存中加载图片的像素数据,最后就是 glTexImage2D 方法实现纹理上传。

Assets 方法类封装

#include <string>#include <vector>#include <android/asset_manager.h>#include "util_asset.h"
//#define STB_IMAGE_IMPLEMENTATION#include <stb/stb_image.h>


boolasset_read_file (AAssetManager *assetMgr, char *fname, std::vector<uint8_t>&buf) { AAsset* assetDescriptor = AAssetManager_open(assetMgr, fname, AASSET_MODE_BUFFER); if (assetDescriptor == NULL) { return false; }
size_t fileLength = AAsset_getLength(assetDescriptor);
buf.resize(fileLength); int64_t readSize = AAsset_read(assetDescriptor, buf.data(), buf.size());
AAsset_close(assetDescriptor);
return (readSize == buf.size());}
uint8_t *asset_read_image (AAssetManager *assetMgr, char *fname, int32_t *img_w, int32_t *img_h){ int32_t width, height, channel_count; uint8_t* img_buf; bool ret;
/* read asset file */ std::vector<uint8_t> read_buf; ret = asset_read_file (assetMgr, fname, read_buf); if (ret != true) return nullptr;
/* decode image data to RGBA8888 */ img_buf = stbi_load_from_memory (read_buf.data(), read_buf.size(), &width, &height, &channel_count, 4);
*img_w = width; *img_h = height;
return img_buf;}
voidasset_free_image (uint8_t *image_buf){ stbi_image_free (image_buf);}
复制代码

总结

今天我们介绍了 Android Assets 文件夹使用,包括 Java 层和 JNI 层,并详细介绍了 JNI 层 AAssetManager 接口的使用。

发布于: 4 小时前阅读数: 4
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android、音视频、AI相关领域从业者。 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
Android C++系列:访问Assets 文件夹