2010年9月9日木曜日

Android nativeコードでファイル書き込み

AndroidでJNIを使ってnativeコードからファイルを読み書きする方法を調べた。ファイルは /data/data/{pkgname} 以下のフォルダに読み書きすることができるらしい。読み書きはLinuxでのファイルIOと同じ関数open/read/write/closeが使える。

Javaからnativeの関数に byte[]配列を渡してそれをファイルに書き込む関数を作ってみた。以下のJavaソースをjavahで変換して対応するヘッダファイルを作成。

FileManager.java
  1. package test.filetest;  
  2. public class FileManager {  
  3.     public native int write(byte[] data);  
  4.     static { System.loadLibrary("filetest"); }  
  5. }  


test_filetest_FileManager.h
  1. #include <jni.h>  
  2.   
  3. #ifndef _Included_test_filetest_FileManager  
  4. #define _Included_test_filetest_FileManager  
  5. #ifdef __cplusplus  
  6. extern "C" {  
  7. #endif  
  8.   
  9. JNIEXPORT jint JNICALL Java_test_filetest_FileManager_write  
  10.   (JNIEnv *, jobject, jbyteArray);  
  11.   
  12. #ifdef __cplusplus  
  13. }  
  14. #endif  
  15. #endif  


Java_test_filetest_FileManager_write関数の実装は以下のようにしてみた。エラーから抜けるのにgotoを多用しているので人により好き嫌いあるかもしれないが、NDKだとtry-catchが使えないようなのでこの方法が楽といえば楽。

test_filetest_FileManager.cpp
  1. #include "test_filetest_FileManager.h"  
  2. #include <string.h>  
  3. #include <fcntl.h>    
  4. #include <android/log.h>  
  5.   
  6. #define _DBGPRINTF(...) __android_log_print(3, "filetest", __VA_ARGS__)  
  7.   
  8. JNIEXPORT jint JNICALL Java_test_filetest_FileManager_write  
  9.   (JNIEnv* env, jobject caller, jbyteArray jBin)  
  10. {  
  11.  jbyte* bin = NULL;  
  12.  int binSize;  
  13.  int err = 0;  
  14.  int s;  
  15.   
  16.  _DBGPRINTF("Java_test_filetest_FileManager_write");  
  17.    
  18.  // ファイルを /data/data/{pkgname} 以下のフォルダに読み書きする  
  19.  // ここでは hello.dat を書き込み用にオープン(O_WRONLY)。無ければ作成(O_CREAT)  
  20.  // ファイルのmodeを 0666 に設定   
  21.  int fd = open("/data/data/test.filetest/hello.dat",  
  22.         O_WRONLY|O_CREAT,  
  23.         S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);  
  24.  _DBGPRINTF("fd=%d\n", fd);  
  25.  if (fd < 0) {  
  26.   _DBGPRINTF("Error: open");  
  27.   err = -1;  
  28.   goto error_exit0;  
  29.  }  
  30.   
  31.  // 渡されたJava byte配列を C byte 配列に変換  
  32.  bin = env->GetByteArrayElements(jBin, NULL);  
  33.  if (bin == NULL) {  
  34.   _DBGPRINTF("Error: GetByteArrayElements");  
  35.   err = -1;  
  36.   goto error_exit1;  
  37.  }  
  38.  // 渡されたJava byte配列の長さを取得  
  39.  binSize = env->GetArrayLength(jBin);  
  40.  _DBGPRINTF("Write: %d bytes");  
  41.   
  42.  // writeで書き込み  
  43.  s = write(fd, (void*)bin, binSize);  
  44.  _DBGPRINTF("Write: ret=%d", s);  
  45.  if (s < 0) {  
  46.   _DBGPRINTF("Error: write");  
  47.   err = -1;  
  48.  }  
  49.  // C byte配列を使い終わったのでメモリから解放  
  50.  env->ReleaseByteArrayElements(jBin, bin, 0);  
  51.   
  52. error_exit1:  
  53.  // ファイルクローズ  
  54.  close(fd);  
  55.    
  56. error_exit0:  
  57.  return err;  
  58. }  


Androidのファイルシステムの中身は adb shell コマンドでシェルに入ることで確認できる。プログラムを実行すると、/data/data/test.filetestフォルダには確かにファイルができていた。

# cd /data/data/test.filetest
cd /data/data/test.filetest
# ls -l
ls -l
-rwxr-xrwT app_28 app_28 256 2010-09-09 13:31 hello.dat
drwxr-xr-x system system 2010-09-09 13:23 lib



その上位のフォルダ一覧を見てみると、フォルダ(パッケージ名)ごとに所有者やパーミションがついているのがわかる。アプリごとに所有者が違うということはAndroidはこの仕組みを使ってアプリごとのアクセス管理をしているのだろうか?

# cd /data/data
cd /data/data
# ls -l
ls -l
drwxr-xr-x app_28 app_28 2010-09-09 13:23 test.filetest
drwxr-xr-x app_24 app_24 2010-07-14 12:15 jp.hews.intent
drwxr-xr-x app_21 app_21 2010-01-27 13:32 com.android.mms
drwxr-xr-x app_5 app_5 2010-01-27 13:32 com.android.camera
drwxr-xr-x app_4 app_4 2010-01-27 13:32 com.android.email
...



2010/9/27追記:
openの際に O_CREAT が指定されている場合にはmode(ファイルパーミション)指定が必要のため追加