ページ

2013/10/05

[Android]外部SDカードのパスを取得する

Android の機種依存ってそんなに遭遇したことないなーとかつぶやいたら
外部SDカードのパスの取得があるじゃんという意見があり、
それって機種依存なのかーと思った@wada811です。
Environment#getExternalStorageDirectory() | Android Developersを使えば
内部SDカードのパスの場合もあるけど(というか最近の機種はほとんど内部SDだけど)
SDカードのパスを取得することができるので機種依存扱いするのもなんだかなー。

開発者はなぜ外部SDのパスを取得したがるのだろうか。
ユーザーが内部SDではなく外部SDにデータを保存したいと言っているのを見たことがないけど
あえて外部SDのパスの取得に突っ込んで機種依存対応コードで対応しきれなくて苦しむなんてマゾなの?
Environment#getExternalStorageDirectory() | Android Developersでいいじゃんと思うね。
Android で対症療法的なコードを書いたらずっと機種依存対応しなくちゃいけないからジリ貧だと思うなぁ。


ということで根本的なところから解決できるっぽいコードを見つけたのでそれを使うことにしました。
/**
 * 外部SDカードのパスを取得する
 * 
 * @return external SD card path, or null
 */
public static String getExternalSdCardPath(){
    HashSet<string> paths = new HashSet<string>();
    Scanner scanner = null;
    try{
        // システム設定ファイルを読み込み
        File vold_fstab = new File("/system/etc/vold.fstab");
        scanner = new Scanner(new FileInputStream(vold_fstab));
        while(scanner.hasNextLine()){
            String line = scanner.nextLine();
            // dev_mountまたはfuse_mountで始まる行
            if(line.startsWith("dev_mount") || line.startsWith("fuse_mount")){
                // 半角スペースではなくタブで区切られている機種もあるらしい
                // 半角スペース区切り3つめ(path)を取得
                String path = line.replaceAll("\t", " ").split(" ")[2];
                paths.add(path);
            }
        }
    }catch(FileNotFoundException e){
        e.printStackTrace();
        return null;
    }finally{
        if(scanner != null){
            scanner.close();
        }
    }
    // Environment.getExternalStorageDirectory() が内部SDを返す場合は除外
    if(!Environment.isExternalStorageRemovable()){
        paths.remove(Environment.getExternalStorageDirectory().getPath());
    }
    // マウントされているSDカードのパスを追加 
    List<string> mountSdCardPaths = new ArrayList<string>();
    for(String path : paths){
        if(isMounted(path)){
            mountSdCardPaths.add(path);
        }
    }
    // マウントされているSDカードのパス
    String mountSdCardPath = null;
    if(mountSdCardPaths.size() > 0){
        mountSdCardPath = mountSdCardPaths.get(0);
    }
    return mountSdCardPath;

}

/**
 * パスがマウントされているSDカードのパスかチェックする
 * 
 * @param path SD card path
 * @return true if path is the mounted SD card's path, otherwise false
 */
public static boolean isMounted(String path){
    boolean isMounted = false;
    Scanner scanner = null;
    try{
        // マウントポイントを取得する
        File mounts = new File("/proc/mounts");
        scanner = new Scanner(new FileInputStream(mounts));
        while(scanner.hasNextLine()){
            if(scanner.nextLine().contains(path)){
                isMounted = true;
                break;
            }
        }
    }catch(FileNotFoundException e){
        e.printStackTrace();
        return false;
    }finally{
        if(scanner != null){
            scanner.close();
        }
    }
    return isMounted;
}
参考
Android開発日誌:androidとjava関連 AndroidでSDカードのpathを取得する
【android】 SDカードのpathを取得する方法 | 一番かんたんなJava入門

参考元とはちょっと変えています。例外を投げている部分を null で返したり false 返したり。
例外投げておいてキャッチしないなら止まられても困るし取りあえず動くように変更しました。
あと、パスの除外の処理が for 文の中で remove してて怖かったので
有効なものを別のリストに入れるようにしました。ループの中でコレクションの破壊的操作は怖いです。

それにしてもシステム設定ファイルとかマウントポイントのパスなんてよく分かるなぁ。すごい。
ただし、このシステム設定ファイルなんですが、GalaxyNexusや一部のカスタムROMには存在しないそうです。
Nexus7(2013)もありませんでした。コレも機種依存かと思うと微妙ですね。

これで外部SDのパスが取得できるようになるけど
外部SDはマルチユーザ対応が難しいということを忘れてないけないね。
マルチユーザ対応 Android 4.2以降の内部ストレージと外部ストレージ | Tech Booster
Environment#getExternalStorageDirectory() | Android Developersならいい感じにやってくれるから
あえて外部SDへの保存をするべきなのか考えてないとダメだね。