fopen/fread APK Assets from NativeActivity on Android
Asked Answered
E

2

7

I have only been able to find solutions dated 2010 and earlier. So I wanted to see if there was a more up-to-date stance on this.

I'd like to avoid using Java and purely use C++, to access files (some less-or-more than 1MB) stored away in the APK. Using AssetManager means I can't access files like every other file on every other operating system (including iOS).

If not, is there a method in C++ where I could somehow map fopen/fread to the AssetManager APIs?

Eleaseeleatic answered 6/8, 2013 at 21:6 Comment(2)
Since assets aren't files, the answer is likely "no". Assets are entries in the ZIP archive that is the APK.Brevity
Technically, since an APK is nothing more than a fancy zip file it is perfectly possible to access any file anywhere in the APK as if it were a zip file in c++. It does however require some java, but only to get your apk install location. I know for a fact reeading is not an issue but I never tried writing to an APK.Cydnus
G
12

I actually found pretty elegant answer to the problem and blogged about it here.

The summary is:

  • The AAssetManager API has NDK bindings. This lets you load assets from the APK.
  • It is possible to combine a set of functions that know how to read/write/seek against anything and disguise them as a file pointer (FILE*).
  • If we create a function that takes an asset name, uses AssetManager to open it, and then disguises the result as a FILE* then we have something that's very similar to fopen.
  • If we define a macro named fopen we can replace all uses of that function with ours instead.

My blog has a full write up and all the code you need to implement in pure C. I use this to build lua and libogg for Android.

Gilgamesh answered 14/2, 2015 at 18:46 Comment(1)
for file size: size_t file_size (FILE *fp) { android_seek(fp, 0L, SEEK_END); size_t sz = ftell(fp); android_seek(fp, 0L, SEEK_SET); return sz; }Hype
C
7

Short answer

No. AFAIK mapping fread/fopen in C++ to AAssetManager is not possible. And if were it would probably limit you to files in the assets folder. There is however a workaround, but it's not straightforward.

Long Answer

It IS possible to access any file anywhere in the APK using zlib and libzip in C++. Requirements : some java, zlib and/or libzip (for ease of use, so that's what I settled for). You can get libzip here: http://www.nih.at/libzip/

libzip may need some tinkering to get it to work on android, but nothing serious.

Step 1 : retrieve APK location in Java and pass to JNI/C++

String PathToAPK;
ApplicationInfo appInfo = null;
PackageManager packMgmr = parent.getPackageManager();
try {
    appInfo = packMgmr.getApplicationInfo("com.your.application", 0);
} catch (NameNotFoundException e) {
    e.printStackTrace();
    throw new RuntimeException("Unable to locate APK...");
}

PathToAPK = appInfo.sourceDir;

Passing PathToAPK to C++/JNI

JNIEXPORT jlong JNICALL Java_com_your_app(JNIEnv *env, jobject obj, jstring PathToAPK)
{
    // convert strings
    const char *apk_location = env->GetStringUTFChars(PathToAPK, 0);

    // Do some assigning, data init, whatever...
    // insert code here

    //release strings
    env->ReleaseStringUTFChars(PathToAPK, apk_location);

    return 0;
}

Assuming that you now have a std::string with your APK location and you have zlib on libzip working you can do something like this:

if(apk_open == false)
{
    apk_file = zip_open(apk_location.c_str(), 0, NULL);

    if(apk_file == NULL)
    {
        LOGE("Error opening APK!");
        result = ASSET_APK_NOT_FOUND_ERROR;
    }else
    {
        apk_open = true;
        result = ASSET_NO_ERROR;
    }
}

And to read a file from the APK:

if(apk_file != NULL){
    // file you wish to read; **any** file from the APK, you're not limited to regular assets
    const char *file_name = "path/to/file.png";

    int file_index;
    zip_file *file;
    struct zip_stat file_stat;

    file_index = zip_name_locate(apk_file, file_name, 0);

    if(file_index == -1)
    {
        zip_close(apk_file);
        apk_open = false;

        return;
    }

    file = zip_fopen_index(apk_file, file_index, 0);
    if(file == NULL)
    {
        zip_close(apk_file);
        apk_open = false;

        return;
    }

    // get the file stats
    zip_stat_init(&file_stat);
    zip_stat(apk_file, file_name, 0, &file_stat);
    char *buffer = new char[file_stat.size];

    // read the file
    int result = zip_fread(file, buffer, file_stat.size);
    if(result == -1)
    {
        delete[] buffer;
        zip_fclose(file);

        zip_close(apk_file);
        apk_open = false;

        return;
    }

    // do something with the file
    // code goes here

    // delete the buffer, close the file and apk
    delete[] buffer;
    zip_fclose(file);

    zip_close(apk_file);
    apk_open = false;

Not exactly fopen/fread but it gets the job done. It should be pretty easy to wrap this to your own file reading function to abstract the zip layer.

Cydnus answered 7/8, 2013 at 15:56 Comment(3)
libz is provided by Android toolchain these days - would there be any dangers in using it over libzip? Are they even different/same?Eleaseeleatic
None at all. I use the android provided libz as well; libzip is just a library that sits on top libz to make reading/writing a little easier.Cydnus
One quick fix you need delete[] not delete on the buffer.Subarid

© 2022 - 2024 — McMap. All rights reserved.