2013年1月10日木曜日

外部ストレージパスの取得方法について

自作アプリにバックアップ、復元機能を追加しようと考えて、
外部ストレージ(物理SDカード)の絶対パスを取得するAPIを
探してみた所、必ず取得出来るAPIは存在しない事が判明した。

ではどのようにして取得すればいいのか?と悩みつつ検索した所、
「戌印-INUJIRUSHI」さんの下記サイトに答えがあった。

■ 外部ストレージのパスを取得する(Android 2.2~?)
http://inujirushi123.blog.fc2.com/blog-entry-93.html

APIではなくシステムファイルからマウントポイントおよび
マウント状態を取得するのが肝となっている。
ただし注意事項がいくつかあるので以下に記載しておく。

【注意事項】
(1)"/system/etc/vold.fstab"ファイルが存在しない場合がある。(AOKP等のカスタムROMを導入している場合に該当)
(2)注意事項(1)のファイルが存在しない場合に次点で"/etc/vold.fstab"を参照しに行く事があり、このファイルが存在しない場合もある。
(3)注意事項(1)(2)のファイルが存在しない場合、内部ストレージは必ず存在するものとする。

上記を踏まえて以下にコードを示す。
public class StorageDirectory {

    // vold.fstabファイルのリスト
    private static final String[] FSTAB_FILES = {"/system/etc/vold.fstab", "/etc/vold.fstab"};

    // ストレージパスのリストを取得
    public static List<String> getStorageDirs(){
        List<String> mountList = new ArrayList<String>();
        Scanner scanner = null;

        try{
            File fstabFile = null;

            for(int i = 0; i < FSTAB_FILES.length; i++){
                fstabFile = new File(FSTAB_FILES[i]);

                if(fstabFile.exists()){
                    break;
                }
            }

            scanner = new Scanner(new FileInputStream(fstabFile));

            while(scanner.hasNextLine()){
                String line = scanner.nextLine();

                if(line.startsWith("dev_mount") || line.startsWith("fuse_mount")){
                    String[] args = line.split("[\\s\\t]+");

                    if(args.length > 2){
                        String path = args[2];

                        if(!mountList.contains(path)){
                            mountList.add(path);
                        }
                    }
                }
            }
        } catch(FileNotFoundException e){
            // ファイルが存在しない場合は内部ストレージは必ず存在するものとしてリストに追加
            mountList.add(Environment.getExternalStorageDirectory().getPath());
        } finally{
            if(scanner != null){
                scanner.close();
            }
        }

        for(int i = 0; i < mountList.size(); i++){
            if(!isMounted(mountList.get(i))){
                mountList.remove(i--);
            }
        }

        if(mountList.size() > 1){
            // 外部ストレージが存在する場合は内部ストレージパスを削除
            mountList.remove(Environment.getExternalStorageDirectory().getPath());
        } else if(mountList.isEmpty()){
            // ストレージが存在しない場合は内部ストレージパスを追加(あり得ないはずだが一応)
            mountList.add(Environment.getExternalStorageDirectory().getPath());
        }

        return mountList;
    }

    // 指定パスのマウント状態を取得
    public static boolean isMounted(String path){
        boolean isMounted = false;
        Scanner scanner = null;

        try{
            scanner = new Scanner(new FileInputStream(new File("/proc/mounts")));

            while(scanner.hasNextLine()){
                if(scanner.nextLine().contains(path)){
                    isMounted = true;

                    break;
                }
            }
        } catch (FileNotFoundException e) {
            // 流石に存在しない場合はランタイムエラーで強制終了
            throw new RuntimeException(e);
        } finally {
            if(scanner != null){
                scanner.close();
            }
        }

        return isMounted;
    }

    // 最適と思われるストレージパスを取得
    public static String getPreferredStorageDir(){
        List<String> storageList = getStorageDirs();

        for(int i = 0; i < storageList.size(); i++){
            if(!isMounted(storageList.get(i))){
                storageList.remove(i--);
            }
        }

        if(storageList.size() > 1){
            // 複数のストレージパスが存在する場合は内部ストレージパスを削除
            storageList.remove(Environment.getExternalStorageDirectory().getPath());
        } else if(storageList.isEmpty()){
            // あり得ないが念のため内部ストレージパスを追加
            storageList.add(Environment.getExternalStorageDirectory().getPath());
        }

        return storageList.get(0);
    }
}
プログラム側で決め打ちでストレージパスを選択してもよいが、"vold.fstab"の存在有無、
マウントパスの順序、マウント状態によって毎回変わるため、最終的にはどのストレージを
選択するかの判断をユーザーに委ねる事になってしまう。
多種多様な端末が存在し、かつ確実にストレージパスを取得するAPIが存在しない以上、
このような方法で全てストレージパス取得してユーザーに提示して、選択、操作させるのが
現状の最適解かもしれない。

私の場合は、標準ROMを使用かつ"vold.fstab"を絶対に弄らないユーザーを前提として、
StorageDirectory#getPreferredStorageDirを決め打ちで使用している。
後で「バックアップしたのにファイルが無い!」とか怒られるかも…。

0 件のコメント:

コメントを投稿