はじめに
AndroidのSDカードのパスは、機種ごとに異なります。
Android 5.0以上ではそのパスを取得できるAPIがありますが、それより前のOSではそういったAPIは用意されていません。
以前は Environment.getExternalStorageDirectory を使う方法がありましたが、
実際返却されるものは、SDカードのパスとは限らず、機種によっては内部ストレージのパスの可能性もあります。
vold.fstab の中身を見てSDカードのパスを取得する方法もありますが、
私自身試したところ vold.fstab で取得できない機種もありました。
このようにSDカードのパスの取得は、機種依存しやすい所ではありますが、
この記事では機種依存しないようにSDカードのパスを取得する方法を記載しようと思います。
※ ただし、OSごとには取得方法は異なります。
以下は、今回取得するパスの情報になります。
項目 | 内容 |
---|---|
対象OS | Android4.2以上 |
取得するパス | /<SDカードのベースパス>/Android/data/<パッケージ名>/files |
Android4.2以上としているのは、それ未満のOSで動作を確認していないためです。
Android 5.0 以上
Android 5.0 以上には便利なAPIが用意されているので、それを使います。
/**
* SDカードのfilesディレクトリパスのリストを取得する。
* Android5.0以上対応。
*
* @param context
* @return SDカードのfilesディレクトリパスのリスト
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static List<String> getSdCardFilesDirPathListForLollipop(Context context) {
List<String> sdCardFilesDirPathList = new ArrayList<>();
// getExternalFilesDirsはAndroid4.4から利用できるAPI。
// filesディレクトリのリストを取得できる。
File[] dirArr = context.getExternalFilesDirs(null);
for (File dir : dirArr) {
if (dir != null) {
String path = dir.getAbsolutePath();
// isExternalStorageRemovableはAndroid5.0から利用できるAPI。
// 取り外し可能かどうか(SDカードかどうか)を判定している。
if (Environment.isExternalStorageRemovable(dir)) {
// 取り外し可能であればSDカード。
if (!sdCardFilesDirPathList.contains(path)) {
sdCardFilesDirPathList.add(path);
}
} else {
// 取り外し不可能であれば内部ストレージ。
}
}
}
return sdCardFilesDirPathList;
}
Android 4.2〜4.4
Android 4.2〜4.4にはAPIに良い感じのものがないので、リフレクションを使って取得しています。
4.4系には、Context.getExternalFilesDirs は使えますが、Environment.isExternalStorageRemovable が利用できません。
そのため4.4系も、リフレクションを使ったこちらのほうからSDカードのパスを取得するようにしています。
/**
* SDカードのfilesディレクトリパスのリストを取得する。
*
* @param context
* @return SDカードのfilesディレクトリパスのリスト
*/
public static List<String> getSdCardFilesDirPathList(Context context) {
List<String> sdCardFilesDirPathList = new ArrayList<>();
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
try {
// StorageVolumeの一覧を取得する。非公開メソッドなので、リフレクションを使用。
// Environment.getExternalStorageDirectory を追うと、
// StorageManagerを使ってStorageVolumeを取得しているのがわかる。
Method getVolumeListMethod = sm.getClass().getDeclaredMethod("getVolumeList");
Object[] volumeList = (Object[]) getVolumeListMethod.invoke(sm);
for (Object volume : volumeList) {
// getPathFileメソッドは、StorageVolumeのFileオブジェクトを取得するメソッド。
Method getPathFileMethod = volume.getClass().getDeclaredMethod("getPathFile");
File file = (File) getPathFileMethod.invoke(volume);
String storageBasePath = file.getAbsolutePath();
// isRemovableメソッドは、StorageVolumeが取り外し可能かどうかを判定するメソッド。
Method isRemovableMethod = volume.getClass().getDeclaredMethod("isRemovable");
boolean isRemovable = (boolean) isRemovableMethod.invoke(volume);
// ストレージが取り外し可能かどうか(SDカードかどうか)を判定。
if (isRemovable) {
// ベースパスがマウントされているかどうかを判定。
if (isMountedBasePath(storageBasePath)) {
// StorageVolumeの中で、取り外し可能でかつマウント済みのパスは、SDカード。
// マウント済みかどうかを確認しないと、機種によっては /mnt/Private などのパスも含まれてしまうことがある。
if (!sdCardFilesDirPathList.contains(storageBasePath)) {
String sdCardFilesDirPath = storageBasePath + "/Android/data/com.test.h_yama37/files";
sdCardFilesDirPathList.add(sdCardFilesDirPath);
}
}
} else {
// StorageVolumeの中で、取り外し不可能なパスは、内部ストレージ。
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Android4.4系のみ、getExternalFilesDirs で一度filesディレクトリを生成する必要がある。
// Android4.4系は、File.mkdirsなどでfilesディレクトリを生成できないため。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
context.getExternalFilesDirs(null);
}
return sdCardFilesDirPathList;
}
/**
* 指定したベースパスが、マウントされていれば、trueを返す。
*
* @param basePath
* @return ベースパスがマウントされていればtrue。マウントされていなければfalse。
*/
private static boolean isMountedBasePath(String basePath) {
boolean isMounted = false;
BufferedReader br = null;
File mounts = new File("/proc/mounts");
// /proc/mountsが存在しなければ処理を終了する
if (!mounts.exists()) {
return isMounted;
}
try {
// マウントポイントを取得する
br = new BufferedReader(new FileReader(mounts));
String line;
// マウントポイントに該当するパスがあるかチェックする
while ((line = br.readLine()) != null) {
if (line.contains(basePath)) {
// 該当するパスがあればマウントされているため、処理を終える
isMounted = true;
break;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return isMounted;
}