Androidアプリ開発の仕事で、アプリからSDカードヘデータを出力するという要件があったんですが、調査をした感じだと、最近の端末ではSDカードヘ直接保存するのが難しい場合があるようです。
AndroidアプリでSDカードにデータを保存する処理
まず、AndroidアプリでSDカードにデータを保存する方法は以下のような感じです。
public boolean saveFile(Context context,String file,String data){
String output = data;
FileOutputStream outputStream;
//SDカードの状態を取得
String sdCardState = Environment.getExternalStorageState();
String sdPath = null;
sdPath = Environment.getExternalStorageDirectory() + "/" + file;
if (sdCardState.equals(Environment.MEDIA_MOUNTED)) {
//SDカードがマウントされている状態
new Util().Log("Environment.MEDIA_MOUNTED");
try {
outputStream = new FileOutputStream(sdPath);
outputStream.write(output.getBytes());
outputStream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} else if (sdCardState.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) { //SDカードが読取専用の場合
new Util().Log("Environment.MEDIA_MOUNTED_READ_ONLY");
return false;
} else if (sdCardState.equals(Environment.MEDIA_REMOVED)) {//SDカードが挿入されていない場合
new Util().Log("Environment.MEDIA_REMOVED");
return false;
} else if (sdCardState.equals(Environment.MEDIA_UNMOUNTED)) {//SDカードがマウントされていない場合
new Util().Log("Environment.MEDIA_UNMOUNTED");
return false;
} else {//その他の場合
new Util().Log("Environment.OTHER");
return false;
}
}
実機で確認してみると、不思議な現象が発生する。。
上記のコードで問題ないと思っていたんですが、実機でアプリからSDカードに保存するはずが、内部ストレージに保存されてしまう現象・SDカードを挿していないのに保存できてしまう現象(内部ストレージに保存される)が発生しました。
しかもSDカードを挿していない状態で、「Environment.getExternalStorageState()」で外部ストレージの状態を取得して見ると、「Environment.MEDIA_MOUNTED」が返されるので、SDカードがマウントされている状態という謎の現象。。。
AndroidにとってSDカードは外部ストレージの選択肢の一つ
AndroidとSDカードの関係性がいまいち理解できていなかったので、この機会に調査してみることにしました。
調べた限りだと、Android側からみると、SDカードは外部ストレージの中の一つであって、アプリから外部ストレージに保存しようとしたとしても、SDカードに保存される保証はないようです。
以下の記事がわかりやすかったです。
Androidの機種によって内部ストレージに保存されてしまう場合があるらしい。。
色々調査した結果、以下の記事を発見しました。
Nexus7などの内蔵ストレージが大きい端末では、外部ストレージに内蔵ストレージも割り当てられているものがあり(ほとんどがそうなのかな?)、さらにSDカードスロットがあるものもあり、SDカードが外部ストレージの一部に割り当てられたりする。で、厄介なのが、外部ストレージがSDカードかそうでないのか、SDカードのマウント先を知るAPIがなかったりする・・・(/system/etc/vold.fstabを開いてごにょごにょするとわかるらしいが)
つまり、最近の端末では外部ストレージに内部ストレージを割り当てているもの(エミュレートしているもの)がほとんどらしく、アプリからSDカード(外部ストレージ)に保存しようとしても、Android側でエミュレートした内部ストレージに保存されてしまうということなんだと思います。
エミュレート状態を解除できる方法が見つからない。。
SDカードに保存されない原因が、「内部ストレージを外部ストレージにエミュレートしていること」っていうのがわかって、エミュレートを解除できれば、本来の外部ストレージの状態になるから、アプリからSDカードに直接保存できるんじゃないか?
と考えたんですが、僕の使っている端末(ZenPhone 4)ではエミュレートを解除する方法、データの保存先を切り替える方法が見つかりませんでした。
もし知っている方がいたら教えてください。。。
現状の最善策
要件としては、アプリからSDカードに保存したいということでしたが、上記にあげたような問題があるので、現状考えられる最善策は以下かなーと思います。
パターン1.SDカードが挿入されていない場合
内部ストレージに保存する or SDカードが挿入されていないの表示
パターン2.SDカードが挿入されている場合(エミュレートされていない場合)
外部ストレージに保存する(多分SDカードに保存されるはず?)
パターン3.SDカードが挿入されている場合(エミュレートされている場合)
内部ストレージに保存する or SDカードが挿入されていないの表示
Androidアプリから端末から取得できる情報としては、
外部ストレージが抜き差し可能なのものなのか?は「Environment.isExternalStorageRemovable()」で取得でき、
エミュレートされているものなのか?は
「Environment.isExternalStorageEmulated()」で取得できます。
なので、エミュレートされていない・かつ抜き差し可能な外部ストレージだったら、おそらくSDカードだから保存できる、
エミュレートされている・かつ抜き差しできない外部ストレージだったら多分内部ストレージなので、そのまま内部ストレージに保存するか、SDカードに保存できないエラーメッセージを出すって感じかな。
とりあえず、エミュレートしている端末でもSDカードに保存できる方法がないか調査したいと思います。