Android:SDカード(外部ストレージ)のパスを取得する方法(機種依存対策)

More than 1 year has passed since last update.


はじめに

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;
}


参考資料