web-dev-qa-db-ja.com

Androidデバイスに接続されているすべてのストレージデバイスのパスのリストを取得します

Androidデバイスに接続されているすべてのストレージデバイスのリストを取得したい。

たとえば、内部ストレージ(ダウンロード、DCIMなどのすべてのフォルダーが存在するストレージ)、SDカードおよびOTGデバイス。

このトピックについて説明しているStackOverflowの投稿がたくさんあることは知っていますが、上記のように私の目的を果たすことはできませんでした。

内部ストレージへのパスを返すEnvironment.getExternalStorageDirectory().getPath()を呼び出すことで、内部ストレージを取得できます。

接続されているすべてのストレージデバイスのリストを取得できる標準のAFAIKがないため、これに関するヘルプは本当にありがたいです。

また、多くのソリューションは、異なるデバイスやAndroidバージョンでは機能しません。

15
Rahulrr2602

1つのクラスを作成できますEnvironmentSDCardCheck

package com.example.storagecheck;

import Android.content.BroadcastReceiver;
import Android.content.Context;
import Android.content.Intent;
import Android.content.IntentFilter;
import Android.os.Build;
import Android.os.Environment;
import Android.os.storage.StorageManager;
import Android.support.v4.content.ContextCompat;
import Android.support.v4.os.EnvironmentCompat;
import Android.util.Log;

import Java.io.File;
import Java.io.IOException;
import Java.lang.reflect.InvocationTargetException;
import Java.lang.reflect.Method;
import Java.util.ArrayList;

public class EnvironmentSDCardCheck {
    private static final String TAG = "EnvironmentSDCardCheck";

    public final static String TYPE_PRIMARY = "primär";
    public final static String TYPE_INTERNAL = "intern";
    public final static String TYPE_SD = "MicroSD";
    public final static String TYPE_USB = "USB";
    public final static String TYPE_UNKNOWN = "unbekannt";

    public final static String WRITE_NONE = "none";
    public final static String WRITE_READONLY = "readonly";
    public final static String WRITE_APPONLY = "apponly";
    public final static String WRITE_FULL = "readwrite";

    private static Device[] devices, externalstorage, storage;
    private static BroadcastReceiver receiver;
    private static boolean useReceiver = true;
    private static String userDir;

    public static Device[] getDevices(Context context) {
        if (devices == null) initDevices(context);
        return devices;
    }

    public static Device[] getExternalStorage(Context context) {
        if (devices == null) initDevices(context);
        return externalstorage;
    }

    public static Device[] getStorage(Context context) {
        if (devices == null) initDevices(context);
        return storage;
    }

    public static IntentFilter getRescanIntentFilter() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); 
        filter.addAction(Intent.ACTION_MEDIA_MOUNTED); 
        filter.addAction(Intent.ACTION_MEDIA_REMOVED); 
        filter.addAction(Intent.ACTION_MEDIA_SHARED); 
        filter.addDataScheme("file");
        return filter;
    }

    public static void setUseReceiver(Context context, boolean use) {
        if (use && receiver == null) {
            receiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    Log.i(TAG, "Storage " + intent.getAction() + "-" + intent.getData());
                    initDevices(context);
                }
            };
            context.registerReceiver(receiver, getRescanIntentFilter());
        } else if (!use && receiver != null) {
            context.unregisterReceiver(receiver);
            receiver = null;
        }
        useReceiver = use;
    }

    public static void initDevices(Context context) {
        if (userDir == null) userDir = "/Android/data/" + context.getPackageName();
        setUseReceiver(context, useReceiver);
        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        Class c = sm.getClass();
        Object[] vols;
        try {
            Method m = c.getMethod("getVolumeList", null);
            vols = (Object[]) m.invoke(sm, null); // Android.os.Storage.StorageVolume
            Device[] temp = new Device[vols.length];
            for (int i = 0; i < vols.length; i++) temp[i] = new Device(vols[i]);
            Device primary = null;
            for (Device d : temp) if (d.mPrimary) primary = d;
            if (primary == null) for (Device d : temp)
                if (!d.mRemovable) {
                    d.mPrimary = true;
                    primary = d;
                    break;
                }
            if (primary == null) {
                primary = temp[0];
                primary.mPrimary = true;
            }

            File[] files = ContextCompat.getExternalFilesDirs(context, null);
            File[] caches = ContextCompat.getExternalCacheDirs(context);
            for (Device d : temp) {
                if (files != null) for (File f : files)
                    if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
                        d.mFiles = f;
                if (caches != null) for (File f : caches)
                    if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
                        d.mCache = f;
            }

            ArrayList<Device> tempDev = new ArrayList<Device>(10);
            ArrayList<Device> tempStor = new ArrayList<Device>(10);
            ArrayList<Device> tempExt = new ArrayList<Device>(10);
            for (Device d : temp) {
                tempDev.add(d);
                if (d.isAvailable()) {
                    tempExt.add(d);
                    tempStor.add(d);
                }
            }

            Device internal = new Device(context);
            tempStor.add(0, internal); // bei Storage-Alternativen immer
            if (!primary.mEmulated) tempDev.add(0, internal); // bei Devices nur wenn zusätzlich

            devices = tempDev.toArray(new Device[tempDev.size()]);
            storage = tempStor.toArray(new Device[tempStor.size()]);
            externalstorage = tempExt.toArray(new Device[tempExt.size()]);
        } catch (Exception e) {
            // Fallback auf normale Android-Funktionen
        }

    }

    public static class Device extends File {
        String mUserLabel, mUuid, mState, mWriteState, mType;
        boolean mPrimary, mRemovable, mEmulated, mAllowMassStorage;
        long mMaxFileSize;
        File mFiles, mCache;

        Device(Context context) {
            super(Environment.getDataDirectory().getAbsolutePath());
            mState = Environment.MEDIA_MOUNTED;
            mFiles = context.getFilesDir();
            mCache = context.getCacheDir();
            mType = TYPE_INTERNAL;
            mWriteState = WRITE_APPONLY;
        }

        @SuppressWarnings("NullArgumentToVariableArgMethod")
        Device(Object storage) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            super((String) storage.getClass().getMethod("getPath", null).invoke(storage, null));
            for (Method m : storage.getClass().getMethods()) {
                if (m.getName().equals("getUserLabel") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
                    mUserLabel = (String) m.invoke(storage, null); // ab Android 4.4
                if (m.getName().equals("getUuid") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
                    mUuid = (String) m.invoke(storage, null); // ab Android 4.4
                if (m.getName().equals("getState") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
                    mState = (String) m.invoke(storage, null); // ab Android 4.4
                if (m.getName().equals("isRemovable") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mRemovable = (Boolean) m.invoke(storage, null); // ab Android 4.0
                if (m.getName().equals("isPrimary") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mPrimary = (Boolean) m.invoke(storage, null); // ab Android 4.2
                if (m.getName().equals("isEmulated") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mEmulated = (Boolean) m.invoke(storage, null); // ab Android 4.0
                if (m.getName().equals("allowMassStorage") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mAllowMassStorage = (Boolean) m.invoke(storage, null); // ab Android 4.0
                if (m.getName().equals("getMaxFileSize") && m.getParameterTypes().length == 0 && m.getReturnType() == long.class)
                    mMaxFileSize = (Long) m.invoke(storage, null); // ab Android 4.0
                // getDescription (ab 4.1 mit context) liefert keine sinnvollen Werte
                // getPathFile (ab 4.2) liefert keine sinnvollen Werte
                // getMtpReserveSpace (ab 4.0) für diese Zwecke unwichtig
                // getStorageId (ab 4.0) für diese Zwecke unwichtig
            }
            if (mState == null) mState = getState();

            if (mPrimary)
                mType = TYPE_PRIMARY;
            else {
                String n = getAbsolutePath().toLowerCase();
                if (n.indexOf("sd") > 0)
                    mType = TYPE_SD;
                else if (n.indexOf("usb") > 0)
                    mType = TYPE_USB;
                else
                    mType = TYPE_UNKNOWN + " " + getAbsolutePath();
            }
        }

        public String getType() {
            return mType;
        }

        public String getAccess() {
            if (mWriteState == null) {
                try {
                    mWriteState = WRITE_NONE;
                    File[] root = listFiles();
                    if (root == null || root.length == 0)
                        throw new IOException("root empty/unreadable");
                    mWriteState = WRITE_READONLY;
                    File t = File.createTempFile("jow", null, getFilesDir());
                    //noinspection ResultOfMethodCallIgnored
                    t.delete();
                    mWriteState = WRITE_APPONLY;
                    t = File.createTempFile("jow", null, this);
                    //noinspection ResultOfMethodCallIgnored
                    t.delete();
                    mWriteState = WRITE_FULL;
                } catch (IOException ignore) {
                    Log.v(TAG, "test " + getAbsolutePath() + " ->" + mWriteState + "<- " + ignore.getMessage());
                }
            }
            return mWriteState;
        }

        public boolean isAvailable() {
            String s = getState();
            return (
                    Environment.MEDIA_MOUNTED.equals(s) ||
                            Environment.MEDIA_MOUNTED_READ_ONLY.equals(s)
            );
            // MEDIA_SHARED: als USB freigegeben; bitte Handy auf MTP umstellen
        }

        public String getState() {
            if (mRemovable || mState == null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop)
                    // Android 5.0? Da gibts was neues
                    mState = Environment.getExternalStorageState(this);
                else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat)
                    // Android 4.4? Dann dort nachfragen
                    mState = Environment.getStorageState(this);
                else if (canRead() && getTotalSpace() > 0)
                    // lesbar und Größe vorhanden => gibt es
                    mState = Environment.MEDIA_MOUNTED;
                else if (mState == null || Environment.MEDIA_MOUNTED.equals(mState))
                    // nicht lesbar, keine Größe aber noch MOUNTED || oder ungesetzt => UNKNOWN
                    mState = EnvironmentCompat.MEDIA_UNKNOWN;
            }
            return mState;
        }

        public File getFilesDir() {
            if (mFiles == null) {
                mFiles = new File(this, userDir + "/files");
                if (!mFiles.isDirectory())
                    //noinspection ResultOfMethodCallIgnored
                    mFiles.mkdirs();
            }
            return mFiles;
        }

        public File getCacheDir() {
            if (mCache == null) {
                mCache = new File(this, userDir + "/cache");
                if (!mCache.isDirectory())
                    //noinspection ResultOfMethodCallIgnored
                    mCache.mkdirs();
            }
            return mCache;
        }

        public boolean isPrimary() {
            return mPrimary;
        }

        public boolean isRemovable() {
            return mRemovable;
        }
        public boolean isEmulated() {
            return mEmulated;
        }

        public boolean isAllowMassStorage() {
            return mAllowMassStorage;
        }

        public long getMaxFileSize() {
            return mMaxFileSize;
        }

        public String getUserLabel() {
            return mUserLabel;
        }

        public String getUuid() {
            return mUuid;
        }
    }
}

次に、それを使用して、SDカードまたはUSBまたは不明が現在デバイスに接続されているかどうかを確認できます

このようにして、接続されたsdカード、usbなどを取得できます。

private boolean checkSdCardPermission() {
    boolean flag = false;
    try {
        EnvironmentSDCard.Device[] devices = EnvironmentSDCard.getExternalStorage(MainActivity.this);
        for (EnvironmentSDCard.Device d : devices) {
            if (d.getType().equals(EnvironmentSDCard.TYPE_SD) || d.getType().contains(EnvironmentSDCard.TYPE_UNKNOWN) || d.getType().contains(EnvironmentSDCard.TYPE_USB)) {
                flag = d.isAvailable();
                if (flag)
                    break;
            }
        }
    } catch (Exception e) {
    }
    return flag;
}
0
Amjad Khan

運が良かった

ContextCompat.getExternalFilesDirs

これにより、外付けドライブ上のアプリケーションフォルダを見つけることができます。私はまだこれよりもうまく機能する解決策を見つけていません。

私のユースケースではEnvironment.DIRECTORY_MOVIESを使用していますが、必要に応じて、一般的なDIRECTORY_DOCUMENTSを含む他の定義があります。

2
mksteve

これは、/procからマウントを取得することに関する@Sagarの回答への追加です。/proc/mountinfoまたは/ proc/mountsの代わりに/ proc/self/mountinfoを使用していることに注意してください。 /proc/self/mountinfoの形式について詳しくは、man 5 procfsをご覧ください。次のコードは技術的にファイルを解析しますが、メインスレッドで実行しても安全です(/procはメモリ内ファイルシステムであるため)。

private static final int SANE_SIZE_LIMIT = 200 * 1024 * 1024;

// some hashmap for mapping long values to objects
// personally I am using HPPC maps, the HashMap class is for simplicity
public final HashMap<String> mountMap = new HashMap<>();

public void parse() {
    mountMap.clear();

    CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();

    parseMounts(decoder, true);
}

private int measure(FileChannel fc) throws IOException {
    final ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);

    int totalRead = 0, lastRead;

    do {
        buffer.clear();

        lastRead = fc.read(buffer);

        totalRead += lastRead;

        if (totalRead > SANE_SIZE_LIMIT) {
            throw new IOException("/proc/ file appears to be too big!!");
        }
    } while (lastRead != -1);

    fc.position(0);

    return totalRead;
}

private void parseMounts(CharsetDecoder d, boolean force) {
  File file = new File("/proc/self/mountinfo");

  int mode = ParcelFileDescriptor.MODE_READ_ONLY;

  try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, mode));
    FileChannel fc = new FileInputStream(pfd.getFileDescriptor()).getChannel()) {

    // Measure size of file before reading from it.
    // Virtual files in /proc/ appear to be zero-sized (because
    // their contents are dynamic), but we want to attempt
    // reading it in single read() call to avoid inconsistencies
    final int totalRead = measure(fc);

    try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
         Reader r = Channels.newReader(fis.getChannel(), d, totalRead);
         Scanner scanner = new Scanner(r)) {
      while (scanner.hasNextLine()) {
        scanner.nextInt();
        scanner.nextInt();

        final String[] mm = scanner.next().split(":");

        final int major = Integer.parseInt(mm[0]);
        final int minor = Integer.parseInt(mm[1]);

        final long dev_t = makedev(major, minor);

        final String source = scanner.next();

        // ignore bind-mounts for now
        if ("/".equals(source)) {
          final String location = scanner.next();

          // skip optional parts
          scanner.skip("(.+ -)");

          // type of file system (such as ext4)
          // most useful filesystems can be distinguished by type
          // but "Fuse" is problematic (because most Android
          // distributions implement dynamic permissions on
          // external SD card via custom Fuse filesystem).
          // To make matters worse, that SD card Fuse filesystem is
          // often mounted in several places at once. The code below
          // will throw away duplicate mounts by placing them in
          // HashMap, keyed by uniqie filesystem type ID,
          // but you can do it more cleverly be checking whether
          // a mountpoint directory is accessible (via File#list).
          // You can throw away rest of useless filesystems (such as
          // /mnt/secure/asec) by permission checks and blacklisting
          // well-known paths.
          final String fsType = scanner.next().intern();

          final String subject = scanner.next().intern();

          String created = location + subject + fsType;

          String prev = mountMap.put(dev_t, created);

          if (prev != null) {
            created.next = prev;
          }
        }

        scanner.nextLine();
      }

      return;
    } catch (NumberFormatException | NoSuchElementException nse) {
      // oops.. either a new row type was introduced (not a big deal)
      // or the file changed below our feet (because someone has mounted
      // something). Let's retry once more
      parseMounts(d, false);
    } catch (IOException e) {
        throw new WrappedIOException(e);
    }
}

この回答 で、より有用な情報(役に立たないファイルシステムへの共通パスなど)を見つけることができます。/proc/mountsと/ proc/mountinfoの形式が異なることに注意してください。下位互換性を損なうことなく形式を改善するために、後で前者の後に導入されました。

上記のコードは特効薬ではありません。個々のファイルシステムについては何も教えてくれません。パスとファイルシステム名だけを教えてくれます。 「vfat」と「ext4」は便利なファイルシステムであり、「procfs」は役に立たないが、「Fuse」のようなものは謎のままであるとあなたはかなり確信することができます。 Android.os.storage.StorageManagerを使用して上記のコードの出力を拡張し、使用可能な場合(マウントパスで一致)に、よりユーザーフレンドリーなファイルシステム名(「SDカード」など)を取得できます。 StatFs を使用して、パーティションの使用可能な空き領域を取得することもできます—役に立たない仮想ファイルシステムは、通常、照会時にゼロの空き領域とゼロの使用可能な領域を返します。最後に、そのような傾向がある場合は、ファイルシステムをユーザーに表示するかどうかを決定するときに、ファイルシステムのマウントオプションを検討できます。例えば。 ro vs rw、—読み取り専用のファイルシステムのマウントは、通常、ユーザーにとってあまり役に立ちません。


私がこの方法を人々に説明するとき、彼らはしばしばそれの頑健性に関心を持っています…それはいくつかのランダムなジャンクフォンで機能しますか?今後のOSバージョンでも引き続き利用できますか?これが私の見解です。このメソッドは、多くのリフレクションベースのアドバイスよりも優れています—最悪の場合、/ proc /ファイルから読み取るとIOExceptionが返されます。アプリがクラッシュしたり、リフレクションベースのハッキングのような予期しない動作が発生したりすることはありません。

/procファイルシステムは公式のLinuxAPIであり、Linuxカーネル開発者によって維持されています。異なるカーネルビルドオプションを指定して削除することはできません(たとえば、OSカーネルの必須部分です)。長年利用可能であり、ほとんどのAndroidAPIよりも優れた下位互換性を保持しています。特に/ proc/self/mountinfoは10年以上前に作成され、ほとんどの古いバージョンを除くほとんどの既存のAndroidバージョンで利用できるようになります。

Android開発者は、Linux固有のAPIを公式にサポートしていません。しかし、彼らも彼らを壊すために彼らの邪魔をしません。 Lollipop後のAndroidでの最近のSELinuxの変更の一部では、アプリケーションが他のアプリケーションを密かにスパイできるようになったため、/proc/内の一部のファイルへのアクセスが制限されています。/proc/selfは、アプリケーション自身の情報(アプリケーションで利用可能なファイルシステムに関する情報を含む)のみを公開するように設計されているため、これらの変更により、特に/proc/selfにアクセスできるようになりました。

GoogleがLinuxからFuchensaまたは他の自家製のBSDフォークに移行した場合、/ proc /および他のLinux固有のAPIはおそらく壊れます。だから?あんまり。

1
user1643723

APIレベル9以降、_Android.os.storage.StorageManager_があります。 getStorageVolumes()(APIレベル24以降で使用可能)を呼び出して、ストレージボリュームのリストを取得します。ドキュメントがそれを置くように:

現在のユーザーが使用できる共有/外部ストレージボリュームのリストを返します。これには、プライマリ共有ストレージデバイスと、SDカードやUSBドライブなどの接続された外部ボリュームの両方が含まれます。

結果は_List<StorageVolume>_です。さて、_Android.os.storage.StorageVolume_を見てください:

特定のユーザーの共有/外部ストレージボリュームに関する情報。

たとえば、getDescription()を呼び出すことで、ユーザーに表示されるボリュームの説明を取得できます。 createAccessIntent()アクセス方法を参照してください。

0
Thomas