放假几天 发表于 2022-3-16 11:38

android JNI开发C++代码对assets文件的访问

本帖最后由 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的目录下 工具类
```
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 qinghao.hu@nlpr.ia.ac.cn
*/

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 contextapplication context
   * @param destination the destination path
   */
    publicstatic 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
   */
    privatestatic 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;
                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层代码:
```

#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层类定义:
```
public static class ResourceData {
      public byte[] test= null;
      int test_bytes = 0;
    }
```
JNI层代码
```
      
      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;
    }
```
页: [1]
查看完整版本: android JNI开发C++代码对assets文件的访问