http://blog.fidroid.com/post/android/ru-he-zheng-que-huo-de-androidnei-wai-sdqia-lu-jing


如何正确获得Android内外SD卡路径

  • 方法一
  • 方法二

外置sd卡路径,也许很多同学在平时的工作中并不会用到,因为现在很多机型都不支持外置sd卡(这也是Google目标),所以并不用考虑外置sd卡的路径问题。除了开发文件管理类的应用之外,其他应用使用 Enviroment 这个类中的一些静态方法就能满足需要。但也有一些特殊需求需要用到外置sd卡路径,那怎么才能准确获得外置sd卡的路径呢?

方法一

//内置sd卡路径String sdcardPath = System.getenv("EXTERNAL_STORAGE"); //内置sd卡路径String sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath();//外置置sd卡路径String extSdcardPath = System.getenv("SECONDARY_STORAGE");

在Enviroment类的源码中获得sd卡路径其实也是通过 System.getnv() 方法来实现的,如隐藏的方法:

/** {@hide} */public static File getLegacyExternalStorageDirectory() {    return new File(System.getenv(ENV_EXTERNAL_STORAGE));} 

注:更详细的内容还是去看Enviroment源码。

另外要注意的是,在API 23版本中 SECONDARY_STORAGE 被移除。

方法二

private static String getStoragePath(Context mContext, boolean is_removale) {        StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);        Class<?> storageVolumeClazz = null;        try {            storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");            Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");            Method getPath = storageVolumeClazz.getMethod("getPath");            Method isRemovable = storageVolumeClazz.getMethod("isRemovable");            Object result = getVolumeList.invoke(mStorageManager);            final int length = Array.getLength(result);            for (int i = 0; i < length; i++) {                Object storageVolumeElement = Array.get(result, i);                String path = (String) getPath.invoke(storageVolumeElement);                boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);                if (is_removale == removable) {                    return path;                }            }        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }        return null;}

通过反射的方式使用在sdk中被 隐藏 的类 StroageVolume 中的方法getVolumeList(),获取所有的存储空间(Stroage Volume),然后通过参数is_removable控制,来获取内部存储和外部存储(内外sd卡)的路径,参数 is_removable为false时得到的是内置sd卡路径,为true则为外置sd卡路径。

在API 23 Enviroment 类中的内部类 UserEnvironment 中有一方法getExternalDirs与此一样,代码如下:

public File[] getExternalDirs() {    final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId,StorageManager.FLAG_FOR_WRITE);    final File[] files = new File[volumes.length];    for (int i = 0; i < volumes.length; i++) {        files[i] = volumes[i].getPathFile();    }    return files;}

再看Enviroment的getExternalStorageDirectory方法实现:

public static File getExternalStorageDirectory() {    throwIfUserRequired();    return sCurrentUser.getExternalDirs()[0];}

可以看出,在API 23时,先是通过getExternalDirs()获取到所有存储空间的File[]数组,这个数组的第一个值:getExternalDirs()[0],即为内置sd卡所在路径。

而在API 23 之前的版本中,并没有类似getExternalDirs()的方法通过StorageVolume直接获得存储空间(Storage Volume),而时通过别的方式来实现的,看关键方法的源码:

public static File getExternalStorageDirectory() {    throwIfUserRequired();    return sCurrentUser.getExternalDirsForApp()[0];}

这里的 getExternalDirsForApp() 和上面的 getExternalDirs() 的作用是一样的,都是得到所有存储空间的File[]数组。

public File[] getExternalDirsForApp() {    return mExternalDirsForApp;}

mExternalDirsForApp 是在 Enviroment 类中的内部类 UserEnvironment 的构造方法中初始化的,Enviroment#UserEnvironment构造函数源码如下:

        public UserEnvironment(int userId) {            // See storage config details at http://source.android.com/tech/storage/            String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);            String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);            String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);            String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);            if (TextUtils.isEmpty(rawMediaStorage)) {                rawMediaStorage = "/data/media";            }            ArrayList<File> externalForVold = Lists.newArrayList();            ArrayList<File> externalForApp = Lists.newArrayList();            if (!TextUtils.isEmpty(rawEmulatedTarget)) {                // Device has emulated storage; external storage paths should have                // userId burned into them.                final String rawUserId = Integer.toString(userId);                final File emulatedSourceBase = new File(rawEmulatedSource);                final File emulatedTargetBase = new File(rawEmulatedTarget);                final File mediaBase = new File(rawMediaStorage);                // /storage/emulated/0                externalForVold.add(buildPath(emulatedSourceBase, rawUserId));                externalForApp.add(buildPath(emulatedTargetBase, rawUserId));                // /data/media/0                mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);            } else {                // Device has physical external storage; use plain paths.                if (TextUtils.isEmpty(rawExternalStorage)) {                    Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default");                    rawExternalStorage = "/storage/sdcard0";                }                // /storage/sdcard0                externalForVold.add(new File(rawExternalStorage));                externalForApp.add(new File(rawExternalStorage));                // /data/media                mEmulatedDirForDirect = new File(rawMediaStorage);            }            // Splice in any secondary storage paths, but only for owner            final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE);            if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) {                for (String secondaryPath : rawSecondaryStorage.split(":")) {                    externalForVold.add(new File(secondaryPath));                    externalForApp.add(new File(secondaryPath));                }            }            mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]);            mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]);        }

也可以根据这个方法得到一个获取所有存储空间的路径的方法getStorageDirectories()

/**     * Returns all available SD-Cards in the system (include emulated)     * 

* Warning: Hack! Based on Android source code of version 4.3 (API 18) * Because there is no standard way to get it. * TODO: Test on future Android versions 4.4+ * * @return paths to all available SD-Cards in the system (include emulated) */ private static final Pattern DIR_SEPARATOR = Pattern.compile("/"); public List<String> getStorageDirectories() { // Final set of paths final ArrayList<String> rv = new ArrayList<String>(); // Primary physical SD-CARD (not emulated) final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE"); // All Secondary SD-CARDs (all exclude primary) separated by ":" final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE"); // Primary emulated SD-CARD final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET"); if (TextUtils.isEmpty(rawEmulatedStorageTarget)) { // Device has physical external storage; use plain paths. if (TextUtils.isEmpty(rawExternalStorage)) { // EXTERNAL_STORAGE undefined; falling back to default. rv.add("/storage/sdcard0"); } else { rv.add(rawExternalStorage); } } else { // Device has emulated storage; external storage paths should have // userId burned into them. final String rawUserId; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { rawUserId = ""; } else { final String path = Environment.getExternalStorageDirectory().getAbsolutePath(); final String[] folders = DIR_SEPARATOR.split(path); final String lastFolder = folders[folders.length - 1]; boolean isDigit = false; try { Integer.valueOf(lastFolder); isDigit = true; } catch (NumberFormatException ignored) { } rawUserId = isDigit ? lastFolder : ""; } // /storage/emulated/0[1,2,...] if (TextUtils.isEmpty(rawUserId)) { rv.add(rawEmulatedStorageTarget); } else { rv.add(rawEmulatedStorageTarget + File.separator + rawUserId); } } // Add all secondary storages if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) { // All Secondary SD-CARDs splited into array final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator); Collections.addAll(rv, rawSecondaryStorages); } rootmode = Sp.getBoolean("rootmode", false); if (rootmode) rv.add("/"); File usb = getUsbDrive(); if (usb != null && !rv.contains(usb.getPath())) rv.add(usb.getPath()); return rv; }

public File getUsbDrive() {        File parent;        parent = new File("/storage");        try {            for (File f : parent.listFiles()) {                if (f.exists() && f.getName().toLowerCase().contains("usb") && f.canExecute()) {                    return f;                }            }        } catch (Exception e) {        }        parent = new File("/mnt/sdcard/usbStorage");        if (parent.exists() && parent.canExecute())            return (parent);        parent = new File("/mnt/sdcard/usb_storage");        if (parent.exists() && parent.canExecute())            return parent;        return null;    }

综上分析,通过方法一和方法二都可以正确的获取内外sd卡路径,但方法一会存在以下问题:

1、API>=23 时方法一无效(暂未测试)

2、有些厂商的Rom改动太多,对相关原生API的支持存在问题,这时方法一可能会存在问题。

3、其他一些情况造成的原因(基本与2差不多,是ROM等因素造成的)

所以,在使用时建议使用方法二来获取内外置sd卡路径,在API 23(Android 6.0)之前使用getStorageDirectories() 应该也是OK的。


更多相关文章

  1. Android 更新UI的两种方法——handler和runOnUiThread(
  2. android在一个app程序中,打开另一个app的方法
  3. 小米5手机Android运行程序闪退出错解决方法
  4. Android 查看SHA1值的方法
  5. 如何正确实现Android启动屏画面的方法(避免白屏)
  6. Android中Failed to 。。。。。。timeout错误的解决方法

随机推荐

  1. 玩玩Android
  2. Android中贪吃蛇游戏的学习(一)
  3. 深入理解Android消息处理系统——Looper
  4. Android智能指针 (sp & wp)
  5. java代码调试,Android代码调试。
  6. [导入]“开放”Android难敌Apple iOS?
  7. Android:最全面的 Webview 详解
  8. 推送 从入门到放弃
  9. Android音乐播放器【安卓进化二十】
  10. Android(安卓)OOM原因总结