Android アプリは、自身の apk がインストールされたフォルダのサブフォルダに、自由にファイルを作成できます。 struct android_app* app から、そのパスを取得できますので、 fopen 関数等で、読み書きできます。 しかし、接続したPCなどからは見えないため、非常に不便です。
ここでは、Download のような、PC等からでも見える(パブリックな)フォルダにファイルを作成するための方法を記述しています。 正しいパスを取得するためには、Java を使用する必要があるようで、JNI と呼ばれる手法を使用しています。
ご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
また、本サイトが初めての方は、まずこのページの注意事項をご覧ください。
投稿 December 22, 2017, 最終更新 July 23, 2018(様式変更)
アプリのプライベートな領域に作成したファイルは、USB 接続したPCからも、ES ファイルエクスプローラのようなものでも、見えませんでした。 開発時のログなどは、見えなければ意味がありません。 かといって、どうも logcat は使いにくい。
そこで、ネットからダウンロードしたファイル等が保存される、Download フォルダにファイルを作りたいと思いました。 このパスは、決め打ちでもいいのかも知れませんが・・・やはり正式に取得すべきでしょう。
方法としては、
まず、
JNIEnv* env; JavaVM* vm = app->activity->vm; vm->AttachCurrentThread(&env, NULL);
プログラムの終了時には、デタッチする必要があるようです。
JavaVM* vm = g_app->activity->vm; vm->DetachCurrentThread();
いずれも、メインスレッドから呼び出すことを想定しています。
投稿 December 22, 2017, 最終更新 July 23, 2018(様式変更)
AttachCurrentThread して得られた JNIEnv* env を、JNI で使用します。 最初に1回だけアタッチして、env を保存しておいて再利用、というのは、うまくいかないようです。 何度アタッチしても、アタッチ済みの場合は何もしないようですから、毎回行うべきであるようです。
Java の手順としては、
import android.os.Environment; String path = Environment.getExternalStorageDirectory().getPath();
とすることで、SD カードのパス(Nexus7 のような外部ストレージがない場合は内部ストレージへのパス)を取得できるようです。
Android Developers の Environment(android.os.Environment)を見ると、 getExternalStorageDirectory 関数は static 定義で、 File(java.io.File)を返します。 そして、File の getPath 関数は、 String(java.lang.String)で、 絶対パスを返します。
getExternalStorageDirectory 関数は引数を取らず、外部ストレージへのパスを取得します。 代わりに、Environment の getExternalStoragePublicDirectory( String type ) を呼び出して File を得れば、 パブリックなパスを得られる、ということです。 この関数も static 定義で、File を返します。
C/C++ で Java の Environment クラスを取得するには、次のようにします。
jclass environmentClass = env->FindClass("android/os/Environment");
jclass は、クラスへのポインタ(ハンドル?)になります。 Java のクラスは何でも、jclass として扱うようです。
"android/os/Environment" の部分、大文字・小文字を含め、1文字でも間違えれば NULL が返されるので、注意が必要です。 また、jclass はクラス(の定義)であり、インスタンス(オブジェクト・実体)ではありません。
次に、static File getExternalStoragePublicDirectory(String type) 関数を取得します。
static 定義ですので、Environment にある関数ポインタを取得すれば、直接呼出しができます。
jmethodID getStoragePDirMethod = env->GetStaticMethodID(environmentClass, "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;");
jmethodID は、関数ポインタ(ハンドル?)を保持する型で、 Java の関数は何でも、jmethodID として扱うようです。
GetStaticMethodID 関数は、static の関数を取得するためにのみ、使用可能となっているようです。 第1引数は jclass、第2引数は関数名(1文字でも間違えると NULL が返されます)、第3引数が関数の引数と戻り型です。 表記方法はネットのあちこちで解説されていますが、カッコ内に引数、その右側に戻り型です。 Java クラスのときは "L" で始めて、クラス名をスラッシュ区切りで書いて、";" で終わる、ということです。
GetStaticMethodID 関数の引数に、Environment の static String として定義されている DIRECTORY_DOWNLOADS を指定すれば、 Download へのパスを取得できますが、DIRECTORY_DOWNLOADS がどんな値なのかを取得する必要があります。
投稿 January 11, 2018, 最終更新 July 23, 2018(様式変更)
ここまでで、 getExternalStoragePublicDirectory 関数 の jmethodID を getStoragePDirMethod 変数に取得していますが、引数に Environment.DIRECTORY_DOWNLOADS 等を渡す必要があります。
これらの値は定数で、おそらく調べれば何であるかがわかるとは思いますが、ハードコーディングするより、ちゃんと取得したほうが良いでしょう。 取得方法を、「JNI でスタティックな定数を取得する」に記載しましたので、必要でしたらご参照ください。
jobject DIRECTORY_DOWNLOADS を得られたら、getExternalStoragePublicDirectory 関数に渡して、 File(java.io.File)を得ます。
jobject fileObject = env->CallStaticObjectMethod(environmentClass, getStoragePDirMethod, DIRECTORY_DOWNLOADS);
得られた fileObject には、DIRECTORY_DOWNLOADS へのパスが含まれていますので、 String を返す File.getPath(void) 関数を呼び出して、完了です。
jclass fileClass = env->FindClass("java/io/File"); jmethodID getPathMethod = env->GetMethodID(fileClass, "getPath", "()Ljava/lang/String;"); jobject strPathObject = env->CallObjectMethod(fileObject, getPathMethod);
が、得られた strPathObject は String(java.lang.String)を指す jobject ですので、文字列に変換しないと見えません。
const char* pubDir = env->GetStringUTFChars((jstring)strPathObject, 0); char* dup = strdup(pubDir); env->ReleaseStringUTFChars((jstring)strPathObject, pubDir);
GetStringUTFChars 関数で、const char* に変換して取得しています。 GetStringUTFChars 関数は、
UTF-8 文字の文字列配列を参照するポインタを返します。 この配列は、ReleaseStringUTFChars() によって解放されるまで有効です。
とありますので、strdup 関数でコピーを作成して、解放しています。
DIRECTORY_DOWNLOADS では、
/storage/emulated/0/Download
を取得できました。
表記はこのようですが、ちゃんと内部ストレージの Download フォルダにアクセスできます。
おすすめ記事はありません。