2010年9月9日木曜日

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

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

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

FileManager.java

package test.filetest;
public class FileManager {
public native int write(byte[] data);
static { System.loadLibrary("filetest"); }
}


test_filetest_FileManager.h

#include <jni.h>

#ifndef _Included_test_filetest_FileManager
#define _Included_test_filetest_FileManager
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint JNICALL Java_test_filetest_FileManager_write
(JNIEnv *, jobject, jbyteArray);

#ifdef __cplusplus
}
#endif
#endif


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

test_filetest_FileManager.cpp

#include "test_filetest_FileManager.h"
#include <string.h>
#include <fcntl.h>
#include <android/log.h>

#define _DBGPRINTF(...) __android_log_print(3, "filetest", __VA_ARGS__)

JNIEXPORT jint JNICALL Java_test_filetest_FileManager_write
(JNIEnv* env, jobject caller, jbyteArray jBin)
{
jbyte* bin = NULL;
int binSize;
int err = 0;
int s;

_DBGPRINTF("Java_test_filetest_FileManager_write");

// ファイルを /data/data/{pkgname} 以下のフォルダに読み書きする
// ここでは hello.dat を書き込み用にオープン(O_WRONLY)。無ければ作成(O_CREAT)
// ファイルのmodeを 0666 に設定
int fd = open("/data/data/test.filetest/hello.dat",
O_WRONLY|O_CREAT,
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
_DBGPRINTF("fd=%d\n", fd);
if (fd < 0) {
_DBGPRINTF("Error: open");
err = -1;
goto error_exit0;
}

// 渡されたJava byte配列を C byte 配列に変換
bin = env->GetByteArrayElements(jBin, NULL);
if (bin == NULL) {
_DBGPRINTF("Error: GetByteArrayElements");
err = -1;
goto error_exit1;
}
// 渡されたJava byte配列の長さを取得
binSize = env->GetArrayLength(jBin);
_DBGPRINTF("Write: %d bytes");

// writeで書き込み
s = write(fd, (void*)bin, binSize);
_DBGPRINTF("Write: ret=%d", s);
if (s < 0) {
_DBGPRINTF("Error: write");
err = -1;
}
// C byte配列を使い終わったのでメモリから解放
env->ReleaseByteArrayElements(jBin, bin, 0);

error_exit1:
// ファイルクローズ
close(fd);

error_exit0:
return err;
}


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(ファイルパーミション)指定が必要のため追加