3

In the PC version of this codebase, I simply have the following directory structure:

 - myApp.exe
 - resources
 - - images
 - - - blah0.png
 - - - blah1.png
 - - shaders
 - - - blahA.spv
 - - - blahB.spv

So, from my C++ code, I can simply request fopen("resources/images/blah0.png",...), for example.

With android, it appears to instead be required to

#include <android/native_activity.h>
...
AAsset* file = AAssetManager_open(aassman, "resources/images/blah0.png", AASSET_MODE_STREAMING);
size_t len = AAsset_getRemainingLength64(file);
char *buff = malloc(len);
size_t read = AAsset_read(file, buff, len);
AAsset_close(file);

Unfortunately, file is coming up nullptr, likely because it can't find that resources tree.

My build.gradle script has the following snippet:

  sourceSets {
    main {
      manifest.srcFile 'AndroidManifest.xml'
      assets.srcDirs 'build/cmake/release/arm64-v8a/resources'
    }
  }

^ That points to the same resources directory outlined at the top of this list.


What do I need to do to ensure that the above resources hierarchy gets simply transferred into my .apk such that it might be accessed via the AAssetManager snippet above?


Some additional experimentation:

I altered the assets.srcDirs path to some/random/path/on/my/hd, and then ran

  AAssetDir *rootAssets = AAssetManager_openDir(aassman, "");
  const char *f = AAssetDir_getNextFileName(rootAssets);
  while(f)
  {
    print(f);
    f = AAssetDir_getNextFileName(rootAssets);
  }
  AAssetDir_close(rootAssets);

which simply enumerates the found files in the top directory. I noticed that it enumerated all of the files which were in some/random/path/on/my/hd, but none of the directories. (When I point it to my resources folder, it enumerates nothing). I have no idea if that's because AAssetDir_getNextFileName literally only gets files, or if gradle's srcDirs only copies files, or if the whole AAssetManager ecosystem only works on flat directories, or what.

I'm astounded that there isn't clear documentation showing the use case of "including a resources directory with your NDK app", as that seems like a really normal thing to need to do.


edit: I was asked for clarification re: "how cmake is run". I have as part of my build.gradle file, the following snippet:

  externalNativeBuild {
    cmake {
      version "3.18.1"
      path "../CMakeLists.txt"
      buildStagingDirectory buildDir
    }
  }
Phildo
  • 986
  • 2
  • 20
  • 36

3 Answers3

2

Ok so my problem was three fold:

  1. gradle is in charge of building my cmake project which constructs the resources folder. because there is no explicit dependency given between the cmake process and the packing of assets, it seemed to have been pulling from that folder at an unpredictable time (potentially before its construction is finished). (If someone knows how to specify such a dependency, please let me know!)
  2. when I set the folder to be resources, it doesn't result in root/resources/{images,shaders}; it results in root/{images,shaders}. (If someone knows how to specify to instead do the former, please let me know!)
  3. AAssetDir_getNextFileName does not enumerate folders. (There's nothing I can do about this!)
Phildo
  • 986
  • 2
  • 20
  • 36
  • It's not clear how you run the cmake project that builds the resources folder. – Alex Cohn Aug 23 '20 at 13:49
  • @AlexCohn see edit appending original question. I've currently "solved" my issue by pointing to a pre-built resources dir outside the gradle-directed cmake build, though that requires I manually and consciously make sure that folder is up to date and in place (counter to the whole point of having build systems). so if anyone can let me know how I can reincorporate the cmake into gradle while making sure gradle waits for the cmake build to connect the resources directory, I'd appreciate it! – Phildo Aug 23 '20 at 15:28
  • It would be nice if you could move this non-answer to the question section. – Alex Cohn Aug 24 '20 at 08:33
1
  1. I would suggest to simply use AAssetManager_open(aassman, "/images/blah0.png") instead of fopen("resources/images/blah0.png", "r"), but if it's so important to you, you can set assets.srcDirs 'build/cmake/release/arm64-v8a and exclude everything except "resources".

  2. Here is an outline of a pure-C++ solution. Alternatively, you can also call the Java API (as here) via JNI.

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
0

At present, the entrance to transfer resources can be normalized. At present, I can only think of AAssetManager. So the essence of this question is: if in cmake, copy resources into 'assets.srcDirs'?

But unfortunately, there is no relatively stable output path in the current NDK cmake. So we have no way to directly set a stable path for 'assets.srcDirs'.

But I found 'CMAKE_ANDROID_ASSETS_DIRECTORIES' in cmake. According to the standard, this macro definition will point to 'assets.srcDirs'.

To do this, we only need to write a copy script in cmake (given below), and copy the resources into the 'CMAKE_ANDROID_ASSETS_DIRECTORIES' directory after each compilation.

Also, the execution phase of cmake will be earlier than 'AssetPack' in android studio. Therefore, the resources copied by cmake will eventually enter the android apk correctly.

Of course, the front is an ideal situation. 'CMAKE_ANDROID_ASSETS_DIRECTORIES' is not included in the android studio NDK script standard. To do this, we need to manually add the 'CMAKE_ANDROID_ASSETS_DIRECTORIES' definition to the build.gradle file and cmake compilation parameters. The specific script can add the following definition in the build.gradle file:

android {
     defaultConfig {
         externalNativeBuild {
             cmake {
                 arguments \"-DCMAKE_ANDROID_ASSETS_DIRECTORIES=$\{android.sourceSets.main.assets.srcDirs[0]\}\"
             }
         }
     }
}

As for how cmake copies resources, you can refer to the following:

add_custom_command(
     TARGET ${target}
     POST_BUILD
     COMMAND ${CMAKE_COMMAND} -E copy_if_different
         "${RES_BASE_PATH}/${FNAME}"
         "${CMAKE_ANDROID_ASSETS_DIRECTORIES}/${FNAME}"
     COMMENT "Copy ${FNAME}"
     )

Please change '${target}' to the name that appears in your 'add_target'. '${RES_BASE_PATH}' is the root path of your resource. Please set '${FNAME}' to your relative '${RES_BASE_PATH}' directory path.