はじめに
Android 4.4ではアプリからSDカードに書き込みができなくなる!という記事を読んだのは約1年前だった気がしますが、ようやく国内でも4.4が出荷時インストールされた端末が現れてきました。
確かにアストロファイルマネージャーなどのアプリではファイルを操作することができなくなっていますが。
先日、先輩から「4.4でもSDカードにファイル読み書きできるよ」とのアドバイスをもらい、いろいろ教えてもらったことをまとめてみます。
結論
<SDカードのパス>/Android/data/<アプリのパッケージ名>
以下ではファイルを読み書きできました。
また、SDカードを読み書きする場合も、おなじみのWRITE_EXTERNAL_STORAGE
やREAD_EXTERNAL_STORAGE
といったパーミッションは必要ありません。
他のアプリに影響をあたえるような処理ができなくなっているのは明らかですが、
SDカードを全く使えないという訳では無いようです。
サンプルコード
GitHubでサンプルプロジェクトを公開していますので細かい実装はこちらをご確認ください(開発環境はAndroid Studioを前提としています)
https://github.com/ariarijp/SDCard44Sample
確認環境
TestObjectを使用し、国内でもユーザーが多そうで
(おそらく海外端末ですが)TestObjectにも配備されている「Xperia Z1」と「Galaxy S5」、ガジェオタ的にはぐっとくる「Xperia Z1 Ultra」のAndroid 4.4.2端末で確認しました。
サンプルコードでは「Xperia Z1」または「Xperia Z1 Ultra」のSDカードのパス(/storage/sdcard1)を使用しています。
ほかの端末で検証される場合は、事前にSDカードのパスがどこになっているかを確認し、
コード中のEXTERNAL_STORAGE_PATH
の値を書き換えて使用してください。
説明
サンプルのアプリを実行すると以下のようなログが出力されます。
17:18:08 I MyActivity : getExternalFilesDirを呼び出します
17:18:08 I MyActivity : getExternalFilesDirが返すパス: /storage/emulated/0/Android/data/com.example.sdcard44sample/files/Download
17:18:08 I MyActivity : /storage/sdcard1/Android/data/com.example.sdcard44sample/files/testを作成します
17:18:08 I MyActivity : テキストファイルに書き込んだ文字列: 1404541088409
17:18:08 I MyActivity : /storage/sdcard1/Android/data/com.example.sdcard44sample/files/testが作成されました
17:18:08 I MyActivity : テキストファイルから読み込んだ文字列: 1404541088409
17:18:08 I MyActivity : /storage/sdcard1/Android/data/com.example.sdcard44sample/testを作成します
17:18:08 I MyActivity : テキストファイルに書き込んだ文字列: 1404541088416
17:18:08 I MyActivity : /storage/sdcard1/Android/data/com.example.sdcard44sample/testが作成されました
17:18:08 I MyActivity : テキストファイルから読み込んだ文字列: 1404541088416
17:18:08 I MyActivity : /storage/sdcard1/Android/data/testを作成します
17:18:08 I MyActivity : /storage/sdcard1/Android/data/testの作成に失敗しました
17:18:08 I MyActivity : /storage/sdcard1/Android/testを作成します
17:18:08 I MyActivity : /storage/sdcard1/Android/testの作成に失敗しました
17:18:08 I MyActivity : /storage/sdcard1/testを作成します
17:18:08 I MyActivity : /storage/sdcard1/testの作成に失敗しました
Context
のgetExternalFilesDir
メソッドを呼ぶと、この例では内部ストレージ内の/storage/emulated/0/Android/data/com.example.sdcard44sample/files/Download
にディレクトリーが作成されます。
しかし、同時に外部ストレージの/storage/sdcard1/Android/data/com.example.sdcard44sample/files/Download
にもディレクトリーは作成されており、/storage/sdcard1/Android/data/com.example.sdcard44sample/
以下であれば、ファイルを読み書きできていることがわかります。
裏付け
公式ドキュメントの下記のあたりが関係していそうです。
External Storage Technical Information
Starting in Android 4.4, the owner, group and modes of files on external storage devices are now synthesized based on directory structure. This enables apps to manage their package-specific directories on external storage without requiring they hold the broad WRITE_EXTERNAL_STORAGE permission. For example, the app with package name com.example.foo can now freely access Android/data/com.example.foo/ on external storage devices with no permissions. These synthesized permissions are accomplished by wrapping raw storage devices in a FUSE daemon.
ざっくり意訳すると「Android/data/<パッケージ名>/ならパーミッションを与えなくても自由にアクセスできるよ」といったところでしょうか。間違っていたらごめんなさい。
課題
端末やメーカーごとに外部SDカードのパスが違う問題は4.4以前と同様、依然として解決しないものと思われます。実際、「Galaxy S5」ではSDカードが「/storage/extSdCard」にマウントされていました。
端末のモデルやOSバージョンなどをもとにどのパスを使用するかは今まで通り自前で実装する必要がありそうです。
また、最近の端末は内部ストレージも割と大きいので、今後どうなるかわからないSDカードを使うのはなるべく避けた方がいいような気がします。
そういえば、Xperia Arcなんかはやたらと内部ストレージが小さかったですね。。
追記
APIレベル19からgetExternalFilesDirsが使用できるようになり、内部ストレージ以外に使用できる外部ストレージがあれば、Fileの配列を返してくれます。
外部ストレージがない場合は内部ストレージのパスのみを含んだ1要素の配列が返ってきますので、
以下のようなコードでSDカード上の書き込み可能なパスを取得できるかと思います。
if (Build.VERSION.SDK_INT >= 19) {
File[] extDirs = getExternalFilesDirs(Environment.DIRECTORY_DOWNLOADS);
Log.d(TAG, extDirs[extDirs.length-1].toString());
String extSdDirPath = extSdDir.getAbsolutePath();
}
Xperia Z1やGalaxy S5では配列の最後の要素にSDカードのパスが含まれていますが、そうでない端末があるかもしれませんのでご了承ください。