Scala で Android アプリ開発(NDK 編)
下準備
始めに /path/to/hello-world/project/build.scala を次のように修正する。
// ..snip.. object AndroidBuild extends Build { lazy val main = Project ( "Hello World", file("."), settings = General.fullAndroidSettings ++ AndroidNdk.settings // これを追加 ) // ..snip.. }
次に /path/to/hello-world/src/main/jni/ を作成する。
$ make -p /path/to/hello-world/src/main/jni/
ラッパークラスを作る
ものは試しにファイルの情報を取得するラッパ /path/to/hello-world/src/main/scala/FileStat.scala を作成する。
package com.github.cooldaemon.HelloWorld import _root_.java.io.File import _root_.java.util.{Map => JMap} import _root_.scala.collection.JavaConversions._ object extendFileStat { implicit def fileToFileStat(file: File): FileStat = new FileStat(file) } class FileStat(file: File) { System.loadLibrary("file_stat") @native private[this] def getStat(name: String): JMap[String, Long] def stat: Map[String, Long] = mapAsScalaMap(getStat(file.toString)).toMap }
C 関連のファイルを用意する
.class ファイルを作るため一度コンパイルし、その後、javah でラッパクラスから C ヘッダファイルを作成する。
$ cd /path/to/hello-world $ sbt > compile > exit $ javah -o ./src/main/jni/file_stat.h -classpath ./target/scala-2.9.1/classes com.github.cooldaemon.HelloWorld.FileStat
sbt 経由で javah を実行する方法は試していない。
Makefile として /path/to/hello-world/src/main/jni/Android.mk を作成する。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := file_stat LOCAL_SRC_FILES := file_stat.c #LOCAL_C_INCLUDES += $(SBT_MANAGED_JNI_INCLUDE) include $(BUILD_SHARED_LIBRARY)
最後にファイル本体 /path/to/hello-world/src/main/jni/file_stat.c を作成する。
#include <sys/stat.h> #include "file_stat.h" jobject NewLong(JNIEnv* env, jlong value) { jclass longClass = (*env)->FindClass(env, "java/lang/Long"); jmethodID init = (*env)->GetMethodID(env, longClass, "<init>", "(J)V"); jobject longObj = (*env)->NewObject(env, longClass, init, value); (*env)->DeleteLocalRef(env, longClass); return longObj; } JNIEXPORT jobject JNICALL Java_com_github_cooldaemon_HelloWorld_FileStat_getStat (JNIEnv *env, jobject obj, jstring name) { jclass mapClass = (*env)->FindClass(env, "java/util/HashMap"); jmethodID init = (*env)->GetMethodID(env, mapClass, "<init>", "(I)V"); jobject mapObj = (*env)->NewObject(env, mapClass, init, 1); jmethodID put = (*env)->GetMethodID(env, mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); jboolean iscopy; const char *mfile = (*env)->GetStringUTFChars(env, name, &iscopy); struct stat finfo; lstat(mfile, &finfo); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "dev"), NewLong(env, finfo.st_dev) ); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "ino"), NewLong(env, finfo.st_ino) ); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "mode"), NewLong(env, finfo.st_mode) ); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "nlink"), NewLong(env, finfo.st_nlink) ); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "uid"), NewLong(env, finfo.st_uid) ); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "gid"), NewLong(env, finfo.st_gid) ); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "rdev"), NewLong(env, finfo.st_rdev) ); (*env)->CallObjectMethod(env, mapObj, put, (*env)->NewStringUTF(env, "size"), NewLong(env, finfo.st_size) );
コンパイルしてみる。
$ sbt > android:ndk-build
使用する
val f = new File(context.getFilesDir, "foo") import extendFileStat._ f.stat foreach {case (k, v) => Log.d(k + ":" + v.toString)}