本帖最后由 wushaominkk 于 2022-3-17 13:51 编辑
Android应用往往会有很多资源文件需要使用,这些资源文件一般会放在assets目录编进apk中,当apk中使用了so库的时候,由于编进apk的资源文件没有固定的目录,因此不能在C层通过设置路径的方式读取;这里提供两种C层读取资源文件的方式,仅供参考。
# 方法一(通过绝对路径访问)
由于在apk的安装过程中assets中的文件并没有从apk包中解压出来,所以在JNI的C++代码中不能按照原始的路径直接进行访问,一种常用的方法为将assets中的文件复制到sdcard的目录下,然后传递绝对路径给JNI中的C++代码中进行访问。
#### 将assets中的文件复制到sdcard的目录下 工具类
[C++] 纯文本查看 复制代码 ```
package com.zili.rtk.activity;
/**
* copy the files and folders of assets to sdCard to ensure that we can read files in JNI part
* @AuThor Qinghao Hu
* @date 2015/9/22
* @version 1.0
* @Email [email]qinghao.hu@nlpr.ia.ac.cn[/email]
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;
public class AssetCopyer {
private static String TAG="AssetCopyer";
/**
* copy all the files and folders to the destination
* @Param context application context
* @param destination the destination path
*/
public static void copyAllAssets(Context context,String destination)
{
copyAssetsToDst(context,"",destination);
}
/**
*
* @param context :application context
* @param srcPath :the path of source file
* @param dstPath :the path of destination
*/
private static void copyAssetsToDst(Context context,String srcPath,String dstPath) {
try {
String fileNames[] =context.getAssets().list(srcPath);
if (fileNames.length > 0)
{
File file = new File(dstPath);
file.mkdirs();
for (String fileName : fileNames)
{
if(srcPath!="")
{
copyAssetsToDst(context,srcPath + "/" + fileName,dstPath+"/"+fileName);
}else{
copyAssetsToDst(context, fileName,dstPath+"/"+fileName);
}
}
}else
{
InputStream is = context.getAssets().open(srcPath);
FileOutputStream fos = new FileOutputStream(new File(dstPath));
byte[] buffer = new byte[1024];
int byteCount=0;
while((byteCount=is.read(buffer))!=-1) {
fos.write(buffer, 0, byteCount);
}
fos.flush();//刷新缓冲区
is.close();
fos.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
```
把assets中的文件中的文件复制到sdcard的目录文件夹下,调用方式为:
```
AssetCopyer.copyAllAssets(this.getApplicationContext(),this.getExternalFilesDir(null).getAbsolutePath());
```
sdcard的目录
```
/storage/emulated/0/Android/data/com.zili.rtk/files
```
目录结构 (assets文件夹在main 下面)
![](/media/202203/2022-03-15_203721_825469.png)
# 方法二 (C层获取AAssetManager指针)
JNI作为C和Java的桥梁可以完成许多工作,因此第一种方法可以通过Java层向C层传一个资源文件类对象,然后C层使用JNI自带的函数来获取,并读取资源内容,如下:
//在assets文件夹下放一个test.txt文件,内容如下:
```
**************************
this is a assets test.
**************************
```
首先,定义native方法:
```
public native int readResourceFile (AssetManager assetManager, String fileName);
```
fileName为放在assets目录下的文件名,assetManager对象可以通过如下代码获得:
```
AssetManager mAssetManager = getAssets();
```
于是Java层可以调用方法
```
result = readResourceFile(mAssetManager, "test.txt");
```
接下来需要看看JNI层代码:
[C++] 纯文本查看 复制代码 ```
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
extern "C"
JNIEXPORT jint JNICALL
Java_com_camera2glview_chauncy_1wang_readassets_MainActivity_readResourceFile(JNIEnv *env, jobject instance, jobject assetManager, jstring fileName_)
{
// TODO
AAssetManager *mAssetManager = AAssetManager_fromJava (env, assetManager);
if (NULL == mAssetManager)
{
LOGCATE("mAssetManager is NULL");
return -1;
}
const char *fileName = env->GetStringUTFChars(fileName_, 0);
if (NULL == fileName){
LOGCATE("fileName is NULL");
return -1;
}
LOGCATD ("fileName is %s", fileName);
AAsset *asset = AAssetManager_open (mAssetManager, fileName, AASSET_MODE_UNKNOWN);
if (NULL == asset){
LOGCATE("asset is NULL");
return -1;
}
off_t bufferSize = AAsset_getLength(asset);
LOGCATV("buffer size is %ld", bufferSize);
char *buffer = (char*)malloc(bufferSize + 1);
if (NULL == buffer){
LOGCATE("buffer memory alloc failed");
return -1;
}
memset(buffer, 0, bufferSize + 1);
int numBytesRead = AAsset_read(asset, buffer, bufferSize);
LOGCATV("numBytesRead: %d, buffer: %s", numBytesRead, buffer);
//readSettingFile(buffer);
if (buffer){
free(buffer);
buffer = NULL;
}
AAsset_close(asset);
env->ReleaseStringUTFChars(fileName_, fileName);
return 0;
}
```
主要的几个函数:
---------------------- AAssetManager_fromJava
---------------------- AAssetManager_open;
---------------------- AAsset_getLength;
---------------------- AAsset_read;
---------------------- AAsset_close;
Log显示如下:
**************************
this is a assets test.
**************************
注:这种方式需要用到android库,在CMakeList.txt中需要添加依赖库!!
```
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
```
# 方法三 (Java层读数据传到C层)
第二种方式为在Java层读取文件,然后将这个buffer传给C层,这种方式其实就是在C层获取Java层读取资源的buffer块,然后存在C层分配的一个内存块中;提供一段参考代码:
java层类定义:
[JavaFX] 纯文本查看 复制代码 ```
public static class ResourceData {
public byte[] test= null;
int test_bytes = 0;
}
```
JNI层代码
[C++] 纯文本查看 复制代码 ```
jclass resource_data_cls = env->GetObjectClass(resourceData);
jfieldID fid_test = env->GetFieldID(resource_data_cls, "test", "[B");
jfieldID fid_test_bytes = env ->GetFieldID(resource_data_cls, "test_bytes ", "I");
jbyteArray jtestArr = (jbyteArray)env->GetObjectField(resourceData, fid_test );
if (NULL == jtestArr) {
LOGCATE ("nativeInit, jbyteArray is NULL");
return -1;
}
// get byte from java
jbyte *byteTest = NULL;
byteTest = env->GetByteArrayElements(jtestArr , NULL);
// get length
int test_bytes = env->GetArrayLength(jtestArr );
// free memory
if (jtestArr ){
env->ReleaseByteArrayElements(jtestArr , byteTest , JNI_ABORT);
byteTest = NULL;
jtestArr = NULL;
}
``` |