Reading resource files from native code

14,115 views
Skip to first unread message

rodif

unread,
Feb 1, 2010, 1:13:17 PM2/1/10
to android-ndk
I know this topic has come up many times but I don't think there is a
clear answer. What is the official way to read a resource file from
native code that will be forward compatible?

** Read using .apk:
I've found a way to open up the .apk file and read using the offset/
size of the resource.

1. get application .apk file name (in java)
- context.getPackagemanager().getPackageInfo();

2. get a FileDescriptor object (in java)
- context.getAssets().openFd("someresource.txt")... getLength()/
getOffset();

3. open file (in c/c++)
- fopen(appname); fseek(offset);


Using the above solution seems to be the most common approach.
Although it appears we are steered away from this approach.

Dianne Hackborn: (http://groups.google.com/group/android-ndk/
browse_thread/thread/842ca9d7d82995b0)
Assuming that the start offset and end offset are absolute positions
in your .apk is
not guaranteed to work in the future. (For example, what happens if
one day
we decide that when an app is installed we unpack its .apk into
individual
files?)

** Using FileDescriptors:
Dianne Hackborn:
Get the file descriptor with the Java APIs on Resources of
AssetManager and
hand that to your native code, such as:

-- but there is no clear way to get the FILE handle from a
FileDescriptor.

fadden:
Further, if you look in dalvik/libnativehelper/include/nativehelper/
JNIHelp.h, you can find a helper function called
jniGetFDFromFileDescriptor().

-- fadden found this, but we still 'shouldn't' use this

So is there a way to open a FILE from a FileDescriptor that's
officially supported?

** unzipping the .apk manually
I haven't found too much against this, except for i would rather not
litter my files around.

zet

unread,
Feb 2, 2010, 12:24:17 PM2/2/10
to android-ndk
Just implemented this thing for my game crossplatform game engine.

---------------------------------------
Java side:
RawAssetsDescriptor = mContext.getResources().openRawResourceFd
(R.raw.assets);
if (RawAssetsDescriptor != null)
{
fd = RawAssetsDescriptor.getFileDescriptor();
off = RawAssetsDescriptor.getStartOffset();
len = RawAssetsDescriptor.getLength();
nativeInit(dataDir, fd, off, len);
}
---------------------------------------

---------------------------------------
C side:
...nativeInit(JNIEnv * env, jclass envClass, jobject fd_sys, jlong
off, jlong len)
{
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
if (fdClass != NULL)
{
jclass fdClassRef = (jclass) (*env)->NewGlobalRef(env, fdClass);
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env,
fdClass, "descriptor", "I");

if (fdClassDescriptorFieldID != NULL && fd_sys != NULL)
{
jint fd = (*env)->GetIntField(env, fd_sys,
fdClassDescriptorFieldID);
int myfd = dup(fd);
FILE* myFile = fdopen(myfd, "rb");
if (myFile)
{
fseek(myFile, off, SEEK_SET);
... use your file
}
}
}

}
---------------------------------------

Not sure if it is officially supported code, but originally i found it
somewhere in smth like pinyin keyboard code.

And remember:
1. "assets" file must be uncompressed, overwise openRawResourceFd
returns null, choose .mp3 extention e.g.
2. your have to do it with every file or use your own "virtual"
filesystem.

Also looking forward for any suggestions.

rodif

unread,
Feb 2, 2010, 12:32:34 PM2/2/10
to android-ndk
I like this better than using the .apk file, and I can see this being
forward compatible, but It would still be nice to have an official
stance.

zet

unread,
Feb 2, 2010, 1:33:30 PM2/2/10
to android-ndk
Another approach which i think will be 100% forward compatible (while
JNI works) is to use InputStream and its read methods forwarding byte
[] array to native function. But the disadvantage is double copying.

Dianne Hackborn

unread,
Feb 2, 2010, 3:34:32 PM2/2/10
to andro...@googlegroups.com
openRawResourceFd() is an SDK method that is there for just this use, so yes it will be supported.

--
You received this message because you are subscribed to the Google Groups "android-ndk" group.
To post to this group, send email to andro...@googlegroups.com.
To unsubscribe from this group, send email to android-ndk...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/android-ndk?hl=en.




--
Dianne Hackborn
Android framework engineer
hac...@android.com

Note: please don't send private questions to me, as I don't have time to provide private support, and so won't reply to such e-mails.  All such questions should be posted on public forums, where I and others can see and answer them.

fadden

unread,
Feb 2, 2010, 4:02:40 PM2/2/10
to android-ndk

Well, that and the fact that InputStream is just generally terrible.

"descriptor" is a package-private field inside FileDescriptor, and as
such isn't part of the API, is subject to change, etc. On the other
hand, FileDescriptor objects are much more useful if you can pry the
fd out of them.

One code note... this:

jclass fdClassRef = (jclass) (*env)->NewGlobalRef(env, fdClass);

looks like it's going to leak a global reference every time you call
nativeInit, which it looks like you do every time you open an asset
file. You only need to convert fdClass to a global reference if
you're planning to keep the class object reference around after
nativeInit returns (e.g. in a global). Once you've got the integer fd
you probably don't need to use the class ref again.

rodif

unread,
Feb 2, 2010, 10:42:19 PM2/2/10
to android-ndk
I don't think the question is if openRawResourceFd() is going to be
forward compatible. But is this code going to be forward compatible.

jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass,
"descriptor", "I");

As fadden mentions:

"descriptor" is a package-private field inside FileDescriptor, and as
such isn't part of the API, is subject to change, etc. On the other
hand, FileDescriptor objects are much more useful if you can pry the
fd out of them.

> > android-ndk...@googlegroups.com<android-ndk%2Bunsu...@googlegroups.com>


> > .
> > For more options, visit this group at
> >http://groups.google.com/group/android-ndk?hl=en.
>
> --
> Dianne Hackborn
> Android framework engineer

> hack...@android.com

zet

unread,
Feb 3, 2010, 1:59:25 AM2/3/10
to android-ndk
On Feb 2, 11:02 pm, fadden <fad...@android.com> wrote:
> One code note... this:
>
>   jclass fdClassRef = (jclass) (*env)->NewGlobalRef(env, fdClass);
>
> looks like it's going to leak a global reference every time you call
> nativeInit, which it looks like you do every time you open an asset
> file.  You only need to convert fdClass to a global reference if
> you're planning to keep the class object reference around after
> nativeInit returns (e.g. in a global).  Once you've got the integer fd
> you probably don't need to use the class ref again.

Yes i know, it is just for fast code sample, if init is used more than
once than programmer should care about it :)

dev.dri

unread,
Mar 11, 2010, 9:08:03 AM3/11/10
to android-ndk
I think I found a solution involving zero copy (on our side, asset
manager may make a copy, but that's inevitable anyway). Need to test
it though.
My idea is to do this on Java side:
AssetFileDescriptor afd = AssetManager.openFd("some_asset.dat");
FileInputStream fis = afd.createInputStream();
FileChannel channel = fis.getChannel();

MappedByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, 0,
channel.size());

MappedByteBuffer is a direct buffer, so it can be accessed by native
code directly, without copying. I believe this technique will be
future-proof, because it uses only documented API, no dependence on
assets not being compressed etc.
Is this real? Or will Android just be copying the data during each of
my steps, resulting in even worse preformance?

Regards,
dev.dri

dev.dri

unread,
Mar 11, 2010, 12:54:34 PM3/11/10
to android-ndk
Okay, I was too quick with this solution. FileChannel is possible only
for uncompressed resources. Also, I didn't manage to read the mapped
buffer correctly.
Still, copying assets in compressed form to the c code is much better
than transfering uncompressed bitmaps or processing them in Java.

captainpikachu

unread,
Mar 31, 2010, 5:23:39 PM3/31/10
to android-ndk
I'm a little late to the discussion, but consider this method for
loading an uncompressed resource file into native memory:

- get a request to load an uncompressed resource file from a native
function
- call back to Java to get file size from the resource's file
descriptor
- malloc required memory in native code
- create a direct byte buffer with NewDirectByteBuffer in native code
- pass this buffer back to Java to use for reading the resource file
- Java code then uses java.nio.channels.FileChannel to load the file:
AssetFileDescriptor.createInputStream().getChannel().read( passed
in ByteBuffer jobject passed in );


Three questions:

- Is this method efficient since the nio channel can just load
directly into the native buffer without doing any conversion or double
copying?

- Is the jobject returned from NewDirectByteBuffer a
java.nio.ByteBuffer that can be passed directly to the Java code or am
I missing a step?

- Is this method considered a safer, more future-supported method than
the fdopen & fseek method mentioned earlier?

Clapfoot

unread,
Apr 2, 2010, 11:07:52 PM4/2/10
to android-ndk
It would be nice if we can get a final answer on what the definitive
method for reading files from native is.

So far it seems like the best method is using the following to get the
native FD and then passing that into fopen:

jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass,
"descriptor", "I");

However, we know that we can't rely on the field named "descriptor"
being there in the future since its a private field. That said, is
there a more definitive solution or is this currently the best way?

Thanks

On Mar 31, 5:23 pm, captainpikachu <natebamber...@gmail.com> wrote:
> I'm a little late to the discussion, but consider this method for

> loading an uncompressed resourcefileinto native memory:
>
> - get a request to load an uncompressed resourcefilefrom a native

Kakyo

unread,
Dec 18, 2012, 5:08:42 PM12/18/12
to andro...@googlegroups.com
I just wanna say thanks to this thread.
It solved my problem.

bdk

unread,
Dec 19, 2012, 7:46:25 AM12/19/12
to andro...@googlegroups.com
I'd also like to say thanks for this thread / questions / answers.

Another really good resource:

  http://www.google.com/events/io/2011/sessions/bringing-c-and-c-games-to-android.html

be sure to check the session presentation and notes

best

limelect

unread,
Dec 20, 2012, 4:15:52 AM12/20/12
to andro...@googlegroups.com
Message has been deleted
Message has been deleted

NoAngel

unread,
Dec 9, 2013, 10:32:47 PM12/9/13
to andro...@googlegroups.com
I got same question and solved it by myself.
On Java side:
int GetAssetOffset(String filename) {
        int filesize=0;
        try {
            android.content.res.AssetFileDescriptor fd=this.getAssets().openFd(filename);
            filesize=(int)fd.getStartOffset();
        } catch(java.io.IOException e) {}
        return filesize;
}
String GetAPKPath() {
        return this.getPackageResourcePath();
}

and C++ side calls these then:

if(assetoffset>0)
{
            f=fopen(apkpath,"rb");
            fileoffset+=assetoffset;
}
if(f==NULL)
{
            printf("file open failed %s\n",path.c_str());
            return false;
}

uint32_t fileoffset_aligned=fileoffset&(~(VM_PAGE_SIZE-1));
mmapptr=mmap(NULL,mmapsize,PROT_READ,MAP_SHARED,fileno(f),fileoffset_aligned);
fclose(f);

Igor Zinken

unread,
Dec 8, 2016, 5:56:06 PM12/8/16
to android-ndk
Hi, I'm curious how you defined/retrieved the value for VM_PAGE_SIZE ?

Glenn Kasten

unread,
Dec 9, 2016, 10:05:32 AM12/9/16
to android-ndk
You could use getpagesize() or sysconf(_SC_PAGESIZE)
If these are not in the NDK .h and .so, you should be able
to workaround by using syscall().

Igor Zinken

unread,
Dec 12, 2016, 3:30:55 PM12/12/16
to android-ndk
Thaks for that! I can confirm "NoAngel" solution works wonderfully.
Reply all
Reply to author
Forward
0 new messages