読者です 読者をやめる 読者になる 読者になる

Android 4.4におけるSDカード書き込みを試してみる

Android 4.4 (Kit Kat) ではアプリからSDカードへの書き込みが制限されたという話を聞きますが、このたびmicroSDカードスロットを持つAndroid 4.4機: HTC J butterfly (HTL23) を入手したので、少し試してみました。

Android 4.4における外部ストレージ関連の仕様

Android公式ドキュメントの Android 4.4 APIs によると、外部ストレージへのアクセスに関して以下の仕様が記述されています。

  • 新設されたgetExternalFilesDirs() により、複数の外部ストレージ (典型的にはエミュレートされたストレージとSDカード) 上のアプリ固有のディレクトリが、Fileオブジェクトの配列として取得できる。
  • getExternalFilesDirs() で得られるFile配列の最初の要素は、デバイスのプライマリ外部ストレージ (primary external storage) とみなされる。これは、従来から用意されているgetExternalFilesDir() の戻り値と同一である。
  • アプリ固有のストレージ領域 (getExternalFilesDirs() で得られるディレクトリ配下) への書き込みには、WRITE_EXTERNAL_STORAGE権限は不要。
  • アプリ間で共有されるストレージ領域 (getExternalStoragePublicDirectory() で得られるディレクトリ配下) への書き込みには、WRITE_EXTERNAL_STORAGE権限が必要。

上記のドキュメントでは明言されていないようですが、HTL23を含む多くのAndroid 4.4機種では、アプリからのSDカードへの書き込みは、アプリ固有ディレクトリ配下に限定されるようです。SDカードのディレクトリ名を取得する標準的な手段が提供されたのは改善点で、SDカードへの書き込みが制限されたのはセキュリティ向上であるにせよ面倒には違いないというところでしょうか。

実験

HTL23は内蔵ストレージ+SDカードという構成で、それぞれ以下のディレクトリが割り当てられています。

  • 内蔵ストレージ: /storage/emulated/0/
  • SDカード: /storage/ext_sd/

Google Playから取得可能なファイルマネージャ系アプリからのファイル作成を試してみると、内蔵ストレージ上には新規ファイルが作れるものの、SDカードには作れないことが分かります。

Android 4.4 API仕様との対応でこの挙動を確認するために、簡単なサンプルアプリを作ってみました。コード全体はGitHubに上げています。
https://github.com/m-kawato/KitKatStorageTest

f:id:m-kawato:20140907231129p:plain

共有ストレージ領域の取得 (Environment.getExternalStorageDirectory())

getExternalStorageDirectory() は、初期のAndroidから用意されているメソッドで、"primary external storage directory" を返すと定義されています。Android APIリファレンスによると、実際のSDカードとは限らず、デバイス内蔵ストレージのディレクトリが返される実装もある旨が記載されています。

Environment | Android Developers

StorageTest.java

String externalDir = Environment.getExternalStorageDirectory().getPath();
// => "/storage/emulated/0"

アプリ固有ディレクトリのリスト取得 (Context#getExternalFilesDirs())

Context#getExternalFilesDirs は、Android 4.4で追加されたメソッドで、内蔵ストレージ・SDカード含めた、アプリ専用に割り当てられたディレクトリのリスト (Fileオブジェクトの配列) を取得します。

StorageTest.java

File[] dirs = getExternalFilesDirs(null);
// =>
// dirs[0].path() == /storage/emulated/0/Android/data/net.m_kawato.storagetest/files
// dirs[1].path() == /storage/ext_sd/Android/data/net.m_kawato.storagetest/files

ファイル書き込みとパーミッション

getExternalFilesDirs() で取得されるディレクトリと、その上位の内部ストレージ/SDカードのトップディレクトリへのファイル書き込みを試行しました。さらに、WRITE_EXTERNAL_STORAGE パーミッションを指定した場合と指定しない場合の違いについても試してみました。

  • (A) 内蔵ストレージ内のアプリ固有領域 /storage/emulated/0/Android/data/net.m_kawato.storagetest/files
  • (B) 内蔵ストレージのトップディレクトリ /storage/emulated/0
  • (C) SDカード内のアプリ固有領域 /storage/ext_sd/Android/data/net.m_kawato.storagetest/files
  • (D) SDカードのトップディレクトリ /storage/ext_sd

AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

結果は以下の通りです。

  • (A)(C)は WRITE_EXTERNAL_STORAGE 有無に関わらず成功
  • (B)は WRITE_EXTERNAL_STORAGE を指定した場合に限り成功
  • (D)は WRITE_EXTERNAL_STORAGE 有無に関わらず失敗

(A)(C)については、getExternalFilesDirs() で取得されるディレクトリはパーミッション不要で読み書き可能というAPI仕様に一致します。(B)については、getExternalStorageDirectory() で取得されるストレージ=primary external storageはWRITE_EXTERNAL_STORAGE権限で書き込み可能というAPI仕様に一致します。(D)については明確な記述を見つけられませんでしたが、アプリ固有ディレクトリでもprimary external storageでもないディレクトリは、パーミッション指定に関わらず書き込み禁止と解釈しました。
(primary external storageがSDカードであるようなAndroid 4.4機種が存在するかどうかにも興味がありますが)

f:id:m-kawato:20140908001629p:plain