72

I am using the Android NDK to make an application primarily in C for performance reasons, but it appears that file operations such as fopen do not work correctly in Android. Whenever I try to use these functions, the application crashes.

How do I create/write to a file with the Android NDK?

Janusz
  • 187,060
  • 113
  • 301
  • 369
RyanCheu
  • 3,522
  • 5
  • 38
  • 47
  • 1
    Sorry forgot to update this. The root of the problem was not getting permissions to read/write. A similar issue can also be caused by having the sdcard mounted and then trying to open a file in it. – RyanCheu Oct 24 '11 at 19:31

5 Answers5

68

Other answers are correct. You can open a file through the NDK using FILE and fopen, but don't forget to place a permission for it.

In the Android manifest place:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 
JJJ
  • 32,902
  • 20
  • 89
  • 102
Ita
  • 1,282
  • 1
  • 15
  • 21
  • 1
    In manifest http://developer.android.com/guide/topics/manifest/uses-permission-element.html – James Alvarez Feb 20 '15 at 17:57
  • Thanks, it works! And I also found out the updates on the manifest file will only take effect after you re-install the app. Sometimes the eclipse will not re-install the app for you. I delete the app on my phone and re-install it using eclipse. – Iching Chang Jun 16 '15 at 02:17
  • This need to be updated for reading in API31? – Jan Bergström Oct 31 '22 at 01:25
65

File IO works fine on Android using JNI. Perhaps you are trying to open a file with a bad path and not checking the return code? I modified the hello-jni example to demonstrate that it is indeed possible to open file and write to it. I hope this helps.

/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <string.h>
#include <jni.h>
#include <stdio.h>

/* This is a trivial JNI example where we use a native method
 * to return a new VM String. See the corresponding Java source
 * file located at:
 *
 *   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java
 */
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                              jobject thiz )
{
    FILE* file = fopen("/sdcard/hello.txt","w+");

    if (file != NULL)
    {
        fputs("HELLO WORLD!\n", file);
        fflush(file);
        fclose(file);
    }

    return (*env)->NewStringUTF(env, "Hello from JNI (with file io)!");
}

Here is the result after running it on my phone (with an SD card):

$ adb -d shell cat /sdcard/hello.txt
HELLO WORLD!
Tim Kryger
  • 11,166
  • 4
  • 52
  • 41
  • Thanks, that works! I think my problem was that I wasn't using a full path for my fopen command. – RyanCheu Jan 03 '10 at 02:19
  • 2
    Would it be possible to open a file that resides in assets in read only mode? – D R May 31 '10 at 10:35
  • 1
    Opening assets using the NDK is more complicated but I describe the required steps [here](http://stackoverflow.com/questions/2651816/reading-resource-files-from-my-own-apk-in-android-native-environment/2663962#2663962). – Tim Kryger May 31 '10 at 17:23
24

Make sure to use the Java getExternalStorageDirectory() call to get the real path to the sdcard since newer devices don't simply map it to "/sdcard". In that case trying to use a hardcoded location of "/sdcard" will fail.

niko20
  • 241
  • 2
  • 2
  • 3
    This is very important indeed – KaiserJohaan Mar 01 '12 at 19:26
  • 1
    run `adb shell "ls / -l | grep sdcard"` to figure out where your device is mapping the /sdcard directory too (for debugging purpose) – FrickeFresh Feb 03 '19 at 02:36
  • Instead of using `getExternalStorageDirectory` I used getenv("EXTERNAL_STORAGE") but it did not work. Eventhough on adb shell `$EXTERNAL_STORAGE` is `/sdcard/`. I'm using Android 7.1. Should I get path using `getExternalStorageDirectory`? – kanna Apr 18 '19 at 20:25
22

I can also verify that fopen() works correctly, but not if you're trying to access a file in the application's resources or assets folder. I recommend, to avoid having to reinvent the wheel, that you stick any assets you want shipped with your app in the assets folder, where they'll be packaged up for distribution.

In the assets folder case you need to do one of two things, depending on whether the file was compressed by the packager. Both use the AssetManager methods, and you can get the AssetManager from the context/app. File names are always relative to the assets folder, btw: If your have a file "foo.png" directly in the assets folder, you'd open "foo.png," not something like "assets/foo.png".

  1. If the file wasn't compressed (i.e., it's one of the extensions that doesn't get compressed, like .png), you can get a file descriptor from AssetManager.openFd() and pass it to C++. Then you can use fdopen(dup(fd),"r"); to open the file as a FILE*. Note you must fseek() to the offset, and keep track of the length of the file yourself. You're really getting a file handle to the entire assets package, and your file of interest is only a small part.

  2. If your file is compressed, you need to use the Java streaming reader: AssetManager.open() gives you an InputStream you can use the read the file in. This is a PITA because you can't query (AFAIK) the file size; I run a preprocessing step on my assets folder that generates a list of all the files with their respective sizes so I can know, e.g., how big of a buffer to allocate.

If your file is a resource, you may need to go through the Resource class to access it, though it appears resources are also packed into the same assets package. Resource has an openRawResource() call to get the InputStream and an openRawResourceFd() call to get the file descriptor, as above, though.

Good luck.

SomeCallMeTim
  • 4,503
  • 2
  • 28
  • 27
  • 1
    Follow-up note: The "get a file descriptor from AssetManager.openFd()" turns out to be bad form -- it's not part of the blessed SDK. It happens to work on all current phones, but it's not guaranteed to continue working. Getting the path to the .APK file turns out to be not hard, and is left as an exercise to the reader that cares to have completely kosher NDK code. – SomeCallMeTim Feb 16 '11 at 02:38
  • 2
    ...left as an exercise to the reader? How am I supposed to do my job if I can't copy-paste code from stack overflow? – Jon McClung Jul 11 '18 at 19:30
  • ...by copying code from another answer? Or just doing the research yourself. This answer is seven years old. Probably good to build something up from your own new shiny tested code anyway, since I was probably developing for Android 2.x when I wrote this. – SomeCallMeTim Jul 13 '18 at 22:29
  • 1
    For the record, I was mostly trying to make a joke. I just have a distaste for "exercises for the reader" in general from my textbooks in college that always seemed to leave out the answers for those problems I had the most difficulty figuring out. – Jon McClung Jul 14 '18 at 23:32
  • Joke accepted. I will cite Poe's Law as to why I wasn't sure you were joking. ;) https://en.wikipedia.org/wiki/Poe's_law – SomeCallMeTim Jul 16 '18 at 19:15
  • 1
    A useful law to keep in mind. Even in person my dry humor occasionally goes unnoticed, how much more so over the internet. – Jon McClung Jul 16 '18 at 19:19
0

I want to add my two cents to the answers here, on top of setting the correct permissions specified in the answer to this question, make sure to give your app permission to access the storage in the OS. The permissions menu could change from phone to phone, an easy way to get to it is by going to the Settings menu and then searching for "Permissions." This will give you a chance to give your app permission to access the storage (ie sdcard directory) from NDK code.

Rafael Sabino
  • 165
  • 1
  • 8