Android获取拓展外置SD卡(可插拔)路径及读写外置SD卡的方法

有需求做一些类似文件管理器的,就会用到获取外置可移动SD卡的路径。一般的通过Environment或者Context获取的都是手机自带的存储卡路径,类似storage/emulated/0/加后缀。由于谷歌之后的意思是像ios一样,不支持外置USB或者外置可移动SD存储。但是国内的厂商一般都支持。先大概分个类,6.0以下的使用方法一,6.0以上的使用方法二。

Android10 三星S10亲测如下方法获取外置SD卡路径有效:

    public String externalSDCardPath() {
        String externalSDCardPath = "";
        try {
            StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
            // 7.0才有的方法
            List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
            Class<?> volumeClass = Class.forName("android.os.storage.StorageVolume");
            Method getPath = volumeClass.getDeclaredMethod("getPath");
            Method isRemovable = volumeClass.getDeclaredMethod("isRemovable");
            getPath.setAccessible(true);
            isRemovable.setAccessible(true);
            for (int i = 0; i < storageVolumes.size(); i++) {
                StorageVolume storageVolume = storageVolumes.get(i);
                String mPath = (String) getPath.invoke(storageVolume);
                Boolean isRemove = (Boolean) isRemovable.invoke(storageVolume);
                if(isRemove){
                    externalSDCardPath = mPath;
                }
                Log.i("tag2", "mPath is === " + mPath + "isRemoveble == " + isRemove);
            }
        }catch (Exception e){
            Log.i("tag2","e == "+e.getMessage());
        }
        return externalSDCardPath;
    }


方法一

1.遍历env的key里面有SECOENDARY_STORAGE这个值,代表是第二储存,即为外置可移动SD卡。EXTERNAL_STORAGE则对应的是手机内部的存储。在华为荣耀6(4.4)上测试的结果是: 外置 storage/sdcard1 , 内置 storage/emulated/0 。自测了一下,String inpath= System.getenv(“EXTERNAL_STORAGE”)获取内置sd卡,和系统的 Environment.getExternalStorageDirectory().getAbsolutePath()获取的结果是一样的

private static void initSDCardPath() {
        try {
            if(TextUtils.isEmpty(exterPath) && TextUtils.isEmpty(innerPath)){
                Map<String, String> map = System.getenv();
                Set<String> set = System.getenv().keySet();
                Iterator<String> keys = set.iterator();
                while (keys.hasNext()) {
                    String key = keys.next();
                    String value = map.get(key);
                    if ("SECONDARY_STORAGE".equals(key)) {
                        if(!TextUtils.isEmpty(value) && value.contains(":")){
                            exterPath = value.split(":")[0];
                        }else{
                            exterPath = value;
                        }
                    }
                    if ("EXTERNAL_STORAGE".equals(key)) {
                        innerPath = value;
                    }
                    if(!TextUtils.isEmpty(exterPath) && !TextUtils.isEmpty(innerPath)){
                        break;
                    }
                }
                LogUtil.i("file browser", "exterPath= "+exterPath+";innerPath="+innerPath);
                resetDownloadPath();
            }
        } catch (Exception e) {
        }
    }



方法二


1.这个适用6.0+的版本,因为谷歌在6.0+移除了secondary_storage这个值,所以需要另辟蹊径。查看Environment的源码则可以找到答案,

 public static File getExternalStorageDirectory() {
        throwIfUserRequired();
        return sCurrentUser.getExternalDirs()[0];
    }
    //
 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;
        }



所有要获取所有的存储路径,就要用到storageManager,这个类可以通过以下代码获取

StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);

然后查看getVolumeList ,getVolumePaths,或者getStorageVolumes(7.0 api)。这几个方法是@hide不对外使用的,所以用反射就可以了。

/**
     * Returns list of all mountable volumes.
     * @hide
     */
    public StorageVolume[] getVolumeList() {
        if (mMountService == null) return new StorageVolume[0];
        try {
            Parcelable[] list = mMountService.getVolumeList();
            if (list == null) return new StorageVolume[0];
            int length = list.length;
            StorageVolume[] result = new StorageVolume[length];
            for (int i = 0; i < length; i++) {
                result[i] = (StorageVolume)list[i];
            }
            return result;
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to get volume list", e);
            return null;
        }
    }

    /**
     * Returns list of paths for all mountable volumes.
     * @hide
     */
    public String[] getVolumePaths() {
        StorageVolume[] volumes = getVolumeList();
        if (volumes == null) return null;
        int count = volumes.length;
        String[] paths = new String[count];
        for (int i = 0; i < count; i++) {
            paths[i] = volumes[i].getPath();
        }
        return paths;
    }
/**
     * Return the list of shared/external storage volumes available to the
     * current user. This includes both the primary shared storage device and
     * any attached external volumes including SD cards and USB drives.
     *
     * @see Environment#getExternalStorageDirectory()
     * @see StorageVolume#createAccessIntent(String)
     */
    public @NonNull List<StorageVolume> getStorageVolumes() {
        final ArrayList<StorageVolume> res = new ArrayList<>();
        Collections.addAll(res,
                getVolumeList(UserHandle.myUserId(), FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE));
        return res;
    }



以下是具体用反射获取外置可移动SD卡路径的方法,在8.0华为Nova2s测试的结果是:外置/storage/0403-0201 内置/storage/emulated/0 . 注意/storage/0403-0201可能跟机型有关,具体的看实际Log. removable为true则意味着这个路径是外置可移动的,即为外置SD卡路径


public String externalSDCardPath() {
        try {
            StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
            // 7.0才有的方法
            List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
            Class<?> volumeClass = Class.forName("android.os.storage.StorageVolume");
            Method getPath = volumeClass.getDeclaredMethod("getPath");
            Method isRemovable = volumeClass.getDeclaredMethod("isRemovable");
            getPath.setAccessible(true);
            isRemovable.setAccessible(true);
            for (int i = 0; i < storageVolumes.size(); i++) {
                StorageVolume storageVolume = storageVolumes.get(i);
                String mPath = (String) getPath.invoke(storageVolume);
                Boolean isRemove = (Boolean) isRemovable.invoke(storageVolume);
                Log.d("tag2", "mPath is === " + mPath + "isRemoveble == " + isRemove);
            }
        }catch (Exception e){
            Log.d("tag2","e == "+e.getMessage());
        }
        return "";
    }


**

补充一点,反射StorageManager获取外置路径使用到了StorageVolume, 但此类在7.0才放开使用,所以编译版本低于24的需要下面的方法。

 /**
     * 6.0使用此方法获取外置SD卡路径,尝试过反射{@link StorageManager#getVolumeList}
     * 但StorageVolume非Public API 编译不通过(7.0改为公开API),故使用UserEnvironment
     * 的内部方法getExternalDirs获取所有的路径,通过{@link Environment#isExternalStorageRemovable(File)}
     * 判断若removable则为外部存储
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private static String getPhysicalExternalFilePathAboveM(){
        try {
            //===========================获取UserEnvironment================
            Class<?> userEnvironment = Class.forName("android.os.Environment$UserEnvironment");
            Method getExternalDirs =userEnvironment.getDeclaredMethod("getExternalDirs");
            getExternalDirs.setAccessible(true);
            //========获取构造UserEnvironment的必要参数UserId================
            Class<?> userHandle = Class.forName("android.os.UserHandle");
            Method myUserId = userHandle.getDeclaredMethod("myUserId");
            myUserId.setAccessible(true);
            int mUserId = (int) myUserId.invoke(UserHandle.class);
            Constructor<?> declaredConstructor = userEnvironment.getDeclaredConstructor(Integer.TYPE);
            // 得到UserEnvironment instance
            Object instance = declaredConstructor.newInstance(mUserId);
            File[] files = (File[]) getExternalDirs.invoke(instance);
            for (int i = 0; i < files.length; i++) {
                if (Environment.isExternalStorageRemovable(files[i])){
                    return files[i].getPath();
                }
            }
        } catch (Exception e) {
            CrashHandler.getInstance().saveExceptionAsCrash(e);
        }
        return "";
    }




华为Nova2S的外置SD卡的路径还有:/storage/sdcard1 , mnt/ext_sdcard
**

怎样操作外置SD卡文件
简单的FileBrowser类似ES文件管理器
具体介绍使用方法的文章

在android 4.4以上系统对于外置sd卡的写操作限制在app的包名目录下
//让系统在sd卡上建立我们的包名目录
context.getExternalFilesDir(null);
————————————————
版权声明:本文为CSDN博主「xingnan4414」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xingnan4414/article/details/79388972

©️2020 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值