Android NDK: read file from assets inside of shared library - android

In my app I have to pass a file from assets folder to shared library.
I cannot do it with use of jni right now.
I'm using precompiled shared library in my project, in which I have hardcoded path to my file, but I'm getting error "No such file or directory".
So in my .apk file I have .so file in libs/armeabi-v7a folder and my file in /assets folder.
I have tried to do it like this:
char *cert_file = "/assets/cacert.cert";
av_strdup(cert_file);
And some other paths, but it doesn't work.
Is it possible at all?

You can simply use the AAssetManager class in C++.
Basically you need to:
During the init of you library get a pointer on: AAssetManager* assetManager
Use it to read your file:
// Open your file
AAsset* file = AAssetManager_open(assetManager, filePath, AASSET_MODE_BUFFER);
// Get the file length
size_t fileLength = AAsset_getLength(file);
// Allocate memory to read your file
char* fileContent = new char[fileLength+1];
// Read your file
AAsset_read(file, fileContent, fileLength);
// For safety you can add a 0 terminating character at the end of your file ...
fileContent[fileLength] = '\0';
// Do whatever you want with the content of the file
// Free the memoery you allocated earlier
delete [] fileContent;
You can find the official ndk documentation here.
Edit:
To get the AAssetManager object:
In a native activity, you main function as a paramater android_app* app, you just need to get it here: app->activity->assetManager
If you have a Java activity, you need so send throught JNI an instance of the java object AssetManager and then use the function AAssetManager_fromJava()

Your assets are packaged into your apk, so you can't refer to them directly during runtime like in the sample code you provided. You have 2 options here:
Process the assets as an input stream, using
Context.getAssets().open('cacert.cert')
Copy out your asset to a local file in your files dir, and then reference the filename of the copied file.

Building off of the answer by #Sistr , I used getBuffer along with AASSET_MODE_BUFFER (instead of AAsset_read).
However, I was trying to write the buffer to an ofstream. I got signal crashes using the <<operator:
ofstream myStream(<some file>, ios_base::binary);
auto buffer = AAsset_getBuffer(asset);
myStream << buffer;
I presume this is because the buffer pointer points to the asset without a NULL character at the end (I was reading a text asset; Android source code). Using the write function worked:
ofstream myStream(<some file>, ios_base::binary);
auto buffer = AAsset_getBuffer(asset);
myStream.write((char *)buffer, AAsset_getLength(asset));
This is because the <<operator does not have to call strlen to find out how many characters to write since it is explicitly given by AAsset_getLength.

Related

Open and write files in Android NDK

Why can't I open files in writing mode in C? I surely have all permisions, and the path is correct because it can be open in read mode. For example:
file = fopen(path, "r"); returns a correct file, but
file = fopen(path, "w"); always return null
In the Java part I can write the file, so, the solution I found is write the data in a C array and return it to Java
I see that in other questions or programs people can write in C... is a problem of new Android versions?

File not found: data\image.png (Internal)

#Override
public void create() {
batch = new SpriteBatch();
texture = new Texture(Gdx.files.internal("data/image.png"));
sprite = new Sprite(texture);
}
Heres the main code to it. I believe I have linked the Android and the Desktop Assets folder but it still give me the error. I must have done something wrong but I don't know what. The image is located in C:\Users\Dakota Pederson\Desktop\Slingshot Steve\android\assets
Below is the full error:
Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: Couldn't load file: data/image.png
at com.badlogic.gdx.graphics.Pixmap.<init>(Pixmap.java:140)
at com.badlogic.gdx.graphics.glutils.FileTextureData.prepare(FileTextureData.java:64)
at com.badlogic.gdx.graphics.Texture.load(Texture.java:130)
at com.badlogic.gdx.graphics.Texture.<init>(Texture.java:121)
at com.badlogic.gdx.graphics.Texture.<init>(Texture.java:100)
at com.badlogic.gdx.graphics.Texture.<init>(Texture.java:92)
at com.dakotapederson.slingshotsteve.SlingshotSteve.create(SlingshotSteve.java:18)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:136)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)
Caused by: com.badlogic.gdx.utils.GdxRuntimeException: File not found: data\image.png (Internal)
at com.badlogic.gdx.files.FileHandle.read(FileHandle.java:136)
at com.badlogic.gdx.files.FileHandle.readBytes(FileHandle.java:220)
at com.badlogic.gdx.graphics.Pixmap.<init>(Pixmap.java:137)
... 8 more
Try Gdx.files.local() to load your file, instead of Gdx.files.internal(), as the "internal" files are read out of the (compressed) APK and may not be "visible" like a normal file. (This depends a lot on what the code you're passing a pathname to wants to do with it.)
For a libGDX FileHandle file object use file.file() to pass a File handle, instead of file.path() to pass a String that might get misinterpreted.
For a libGDX FileHandle file use file.read() to pass an InputStream directly to the consumer (instead of passing a string or file handle and expecting them to open it).
A libGDX "internal" file maps to an Android AssetManager file, so they can be a bit wonky in some cases. Specifically, they may be compressed and stored within a .jar or .apk and not visible to traditional file reading functions.

cocos2dx Android: .txt map files were not copied into the /data/data/com.cocos2dx.application/ folder

The question is next:
I have some .txt files in my Resources folder in iOs/win32 cocos2dx project(everything works perfect there). I'm trying to port this one to Android and everything goes fine except one thing I can't find my .txt map files when the project is built. ALL pictures in assets folder are shown I use such code for it
std::stringstream ss;
ss << "tutorialMap" << i << ".txt";
std::string fileName = ss.str();
const char *fullPath = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(fileName.c_str());
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
std::stringstream strStream;
strStream<<"/data/data/com.cocos2dx.application/"<<fileName;
std::string fName = strStream.str();
fullPath = fName.c_str();
CCLog("File name is:%s",fullPath);
#endif
std::ifstream t(fullPath);
std::string str((std::istreambuf_iterator<char>(t)),std::istreambuf_iterator<char>());
CCLog("file content: %s",str.c_str());
when iOS/win32 build works last line does shows me files content but in android it is empty. Looks like files are not there.
Please help!!!!
P.S. Also one week ago (when the project wasn't ready) I was trying to do the same and everything was fine. Can you give me maybe some link or something else where I can find what is the reason of it.
Android keeps folder assets ziped in .apk file so you must go there in other way something like:
unsigned char * bufferSize = 0;
unsigned char * charbuffer = CCFileUtils::sharedFileUtils()->getFileData(genrefile, "file", &bufferSize );
assets/ This is empty. You can use it to store raw asset files. Files
that you save here are compiled into an .apk file as-is, and the
original filename is preserved. You can navigate this directory in the
same way as a typical file system using URIs and read files as a
stream of bytes using the the AssetManager. For example, this is a
good location for textures and game data.
Look to documentation
About assets

File acess on Android App when using Android NDK

I have a native shared library compiled using android-ndk
one of the C API functions is:
int load_config(const char *fn, Config *cfg)
I have wrapped the function with JNI and successfuly called it from Java, e.g.
Config cfg = new Config();
my_shared_lib.load_config("test.gcf", cfg);
System.out.println("cfg.rx_id = " + cfg.getRx_id());
This prints the expected value from the Config data structure
But now that I have ported it on to an Android emulator I am confused as how to handle the string representing the filename of the configuration data
I have tried adding test.gcf to the $(PROJECT)/assets dir then:
Config cfg = new Config();
my_shared_lib.load_config("assets/test.gcf", cfg);
outputText.append("cfg.rx_id = " + cfg.getRx_id());
but this doesn't work, i.e. the expected value from the Config data structure isn't ouput (I just get a blank)
In other words, what do you do when your native library function expects a filename as a parameter?
Assets are not stored as files, neither on the device nor on the emulator. They're stored in the APK, which is a ZIP archive; using C/C++ file access functions won't ever work on them. For access to assets from JNI code, use AAssetManager.
Now, the config file probably does not belong to the assets. Assets are by definition read-only; you want your config to be changeable, right? The writeable data folder on Android is retrieved on the Java side via the Context.getDir() function; you derive your filename from that, and pass it to the C side. Something like this:
File Path = Ctxt.getDir("MyData", 0);
File FileName = new File(Path, "test.gcf");
my_shared_lib.load_config(FileName.toString(), cfg);
The said folder would map to the following path in the Android filesystem:
/data/data/com.mypackagename/app_MyData . Different if moving the app to an SD card is enabled.
If you want to ship a default config file in assets but keep it as writeable in the data folder as the app runs, you just have to copy it from assets to the data folder on the first run. Example here.

Access Android APK Asset data directly in c++ without Asset Manager and copying

I'm using pure C++ in my engine to create a game engine in android. There is no single java file. Basically it is a game which should only be stored to external memory. When I move my asset data manually via adb to my external sd card the game works already fine and stable.
adb push ..\..\Bin\Data /sdcard/Android/data/com.fantasyhaze.%SMALL_PACKAGE_NAME%/files/Data/
This is not a good solution because it can not be delivered. Therefore I have my assets-data in Assets folder
which gets moved into the apk file during building process with the following structure:
Assets/Data/MoreFolders/Withsubfolders
Assets/Data/EngineData.zip
Assets/Data/ScriptData.zip
But I don't know where those files are on the file systems to access them in c++ code.
So I tried to get the path to the file directories. And because of a bug in the native activity state
I have to retrieve the information in normal code.
// bug in 2.3 internalDataPath / externalDataPath = null using jni code instead
//FHZ_PRINTF("INTERNAL inter PATH = %s\n", state->activity->internalDataPath);
//FHZ_PRINTF("EXTERNAL inter PATH = %s\n", state->activity->externalDataPath);
c++ code for its equivalent to android.os.Environment.getFilesDir()
and android.os.Environment.getExternalStorageState() ect
// getPath() - java
JNIEnv *jni_env = Core::HAZEOS::GetJNIEnv();
jclass cls_Env = jni_env->FindClass("android/app/NativeActivity");
jmethodID mid_getExtStorage = jni_env->GetMethodID(cls_Env, "getFilesDir","()Ljava/io/File;");
jobject obj_File = jni_env->CallObjectMethod( gstate->activity->clazz, mid_getExtStorage);
jclass cls_File = jni_env->FindClass("java/io/File");
jmethodID mid_getPath = jni_env->GetMethodID(cls_File, "getPath","()Ljava/lang/String;");
jstring obj_Path = (jstring) jni_env->CallObjectMethod(obj_File, mid_getPath);
const char* path = jni_env->GetStringUTFChars(obj_Path, NULL);
FHZ_PRINTF("INTERNAL PATH = %s\n", path);
jni_env->ReleaseStringUTFChars(obj_Path, path);
// getCacheDir() - java
mid_getExtStorage = jni_env->GetMethodID( cls_Env,"getCacheDir", "()Ljava/io/File;");
obj_File = jni_env->CallObjectMethod(gstate->activity->clazz, mid_getExtStorage, NULL);
cls_File = jni_env->FindClass("java/io/File");
mid_getPath = jni_env->GetMethodID(cls_File, "getAbsolutePath", "()Ljava/lang/String;");
obj_Path = (jstring) jni_env->CallObjectMethod(obj_File, mid_getPath);
path = jni_env->GetStringUTFChars(obj_Path, NULL);
FHZ_PRINTF("CACHE DIR = %s\n", path);
jni_env->ReleaseStringUTFChars(obj_Path, path);
// getExternalFilesDir() - java
mid_getExtStorage = jni_env->GetMethodID( cls_Env,"getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
obj_File = jni_env->CallObjectMethod(gstate->activity->clazz, mid_getExtStorage, NULL);
cls_File = jni_env->FindClass("java/io/File");
mid_getPath = jni_env->GetMethodID(cls_File, "getPath", "()Ljava/lang/String;");
obj_Path = (jstring) jni_env->CallObjectMethod(obj_File, mid_getPath);
path = jni_env->GetStringUTFChars(obj_Path, NULL);
FHZ_PRINTF("EXTERNAL PATH = %s\n", path);
jni_env->ReleaseStringUTFChars(obj_Path, path);
//getPackageCodePath() - java
mid_getPath = jni_env->GetMethodID(cls_Env, "getPackageCodePath", "()Ljava/lang/String;");
obj_File = jni_env->CallObjectMethod(gstate->activity->clazz, mid_getPath);
obj_Path = (jstring) jni_env->CallObjectMethod(obj_File, mid_getPath);
path = jni_env->GetStringUTFChars(obj_Path, NULL);
FHZ_PRINTF("Looked up package code path = %s\n", path);
which works quite well and results in
INTERNAL PATH = /data/data/com.fantasyhaze.rememory/files
CACHE DIR = /data/data/com.fantasyhaze.rememory/cache
EXTERNAL PATH = /mnt/sdcard/Android/data/com.fantasyhaze.rememory/files
Looked up package code path = /mnt/asec/com.fantasyhaze.rememory-2/pkg.apk
But there are no files from the assets folders...
and I need to access a folder as normal working directory to read the files. which would be possible in
/mnt/sdcard/Android/data/com.fantasyhaze.rememory/files/Data
But moving all data from the asset folder (wherever it is) via asset manager to this folder, causes doubled consumption of memory.
assets >1GB would mean assets >2GB which doesn't make sense. More than that the assert folder seems not to work recusively and only for small data files
which is not possible when using bigger pak files. Maybe the files can be access directly from the apk when using
the unzip system and then uzip my ressource files, but therefore I have to optain the apk path anyway.
So My questions:
Where is the Assets folder in the apk on the file system?
What would be the code (c++) to retrieve the apk location or the location of the executable
Can I access it directly with a normal file open method or only if I unpack it. If I can use it without unpacking, how?
What would be the code (c++) to retrieve the information if the sd card is mounted?
I hope someone can help me :)
Edit: Added cache directory and the package directory code (and it's output paths) , to provide the source for everyone else who needs it.
I'm posting an answer instead of marking this as a duplicate because technically, you're asking for more details than are provided in the question/answer I'm going to link here. So, to answer the first of your 4 points, see Obtaining the name of an Android APK using C++ and the NativeActivity class.
As for checking to see if the SD card is mounted, that should be rather simply. Simply attempt to open the directory or a file on the SDcard (that you know should be there) and if it fails, you know the SD card is not available. See What's the best way to check if a file exists in C? (cross platform).
You may want to have a look at "Expansion Files" see APK Expansion Files.
As far is i understand this, this gives you a system that automatically puts resource files (your expansion file) on the "shared storage location".
So i think that this would solve your problem of duplicated data.
There is also a short introduction post on the android-developers blog.

Categories

Resources