23
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

はじめに

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

参考資料

23
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?