查看原文
其他

获取手机SD卡路径之爬坑解决方案

2017-03-03 androidstarjack 终端研发部

  android 系统是开源的,于是各种产商各种瞎改android系统 ,导致不同版本的手机的SD卡的路径千奇百怪。三星,HTC...等比较特殊。有时候让我们Android程序员感到很迷茫,不得不怀疑自己的人生。为什么总是坑我们这些Android程序员?抱怨是没有用的,只有不断去才坑,不断的去总结了。在这里记录下我试验的几种方案。

方案一:通过Enviroment类获取存储设备路径

  android的官方文档上说,采用Enviroment.getExternalStorageDirectory()方法可以得到android设备的外置存储(即外插SDCARD),如果android设备有外插SDCARD的话就返回外插SDCARD的根目录路径,如果android设备没有外插SDCARD的话就返回android设备的内置SDCARD的路径。这套方案很快就被否决了,因为Enviroment类的这个方法里面的路径也是写死的,只有原生的android系统才使用这套方案,被更改过的anroid体统很多设备的路径都改了。

方案二:读取system/etc/vold.fstab文件的内容来获取存储设备路径

参考文档:http://blog.csdn.net/bbmiku/article/details/7937745          内置和外置SD卡的信息存在system/etc/vold.fstab 里面,我们可以从这里获得外置SD卡的路径。经本人实验,发现很多疑问。我的机子是三星I9300,我的机子没有外插SDCARD。通过eclipse获取vold.fstab文件,打开来看,有用的内容如下:         dev_mount sdcard /storage/extSdCard auto /devices/platform/s3c-sdhci.2/mmc_host/mmc1/         dev_mount sda /storage/UsbDriveA auto /devices/platform/s5p-ehci         dev_mount sdb /storage/UsbDriveB auto /devices/platform/s5p-ehci         dev_mount sdc /storage/UsbDriveC auto /devices/platform/s5p-ehci         dev_mount sdd /storage/UsbDriveD auto /devices/platform/s5p-ehci         dev_mount sde /storage/UsbDriveE auto /devices/platform/s5p-ehci         dev_mount sdf /storage/UsbDriveF auto /devices/platform/s5p-ehci

  这里可没有我的内置SDCARD的路径啊,不懂。打开手机的文件系统发现我的内置的SDCARD路径是:/storage/emulated/0。于是我到eclipse的DDMS中去看下我的手机文件系统,发现storage路径下的文件结构为:

  从这个文件结构可以看出,真正有内容的应该是emulated/legacy和sdcard0才对,再从后面的连接来看,最后这两个目录都应该是指向/mnt/shell/emulated/0。接着打开/mnt/shell/emulated/0来看看,果然是我的sdcard目录

这让我很疑惑,这样的话,读取vold.fstab文件来获取sdcard目录不就得不到/mnt/shell/emulated/0目录了么。方案二失败。

方案三:

  方案三的原理是linux命令,在命令窗口中输入 mount 或者 cat /proc/mounts 可得到系统挂载的存储。你也可以在DOS窗口中输入 adb shell -> mount ,或者 adb shell -> cat /proc/mounts 来查看( ”->“ 符号只是一个分割符,不要输)。好,我来DOS窗口中输入adb shell -> mount 来看下会得到什么

  我借来的这部手机有外插SDCARD。可以看到最后两条应该是挂载SDCARD信息了。不过它的挂载设备是/dev/fuse, 和 /dev/block/vold/179:17 。 好吧,我晕了,等等,会不会 最后两条信息才是挂载SDCARD信息呢?我的是手机因为没有外插SDCARD,所以最后一条才是挂载SDCARD信息,有外插SDCARD的,最后两条是挂载SDCARD信息。这是规律?好吧,不是规律,我又借了部手机,mount了下,发现这个猜想纯属扯淡。 利用mount命令来获取SDCARD路径的方法, 参考: 和

方案四:

  • android常见的SD卡存储位置

/storage/emulated/0//storage/extSdCard/mnt/external_sd//mnt/sdcard2//mnt/sdcard/external_sd//mnt/sdcard-ext//mnt/sdcard//storage/sdcard0//mnt/extSdCard//mnt/extsd//mnt/emmc//mnt/extern_sd//mnt/ext_sd//mnt/ext_card//mnt/_ExternalSD//sdcard2//sdcard//sdcard/sd//sdcard/external_sd//mnt/sd//mnt//storage//mnt/sdcard/sd//mnt/exsdcard//mnt/sdcard/extStorages/SdCard//ext_card//storage/extSdCard

3.0以上可以通过反射获取:

StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);

// 获取sdcard的路径:外置和内置

String[] paths = (String[]) sm.getClass().getMethod("getVolumePaths", null).invoke(sm, null);

可以获得所有mount的SD卡,难道我要一条一条路径去遍历?就算遍历到了,我也不知道哪条是内置存储,哪条是外置存储。而且以后哪个深井冰产商又整出一条路径出来,不就没完没了了嘛。

然而很郁闷,到底怎么弄才有一套最佳方案? 搜索了好久

目前最佳方案代码

/**     * 获取外置SD卡路径     *     * @return     */    public static List<String> getSDCardPaths() {
       List<String> sdcardPaths = new ArrayList<String>();  
            String cmd = "cat /proc/mounts";      
              Runtime run = Runtime.getRuntime();// 返回与当前 Java 应用程序相关的运行时对象        try {            Process p = run.exec(cmd);// 启动另一个进程来执行命令            BufferedInputStream in = new BufferedInputStream(p.getInputStream());            BufferedReader inBr = new BufferedReader(new InputStreamReader(in));            String lineStr;            while ((lineStr = inBr.readLine()) != null) {                // 获得命令执行后在控制台的输出信息          
                   LogUtil.i("CommonUtil:getSDCardPath", lineStr);                String[] temp = TextUtils.split(lineStr, " ");                // 得到的输出的第二个空格后面是路径          
                         String result = temp[1];        
                            File file = new File(result);                if (file.isDirectory() && file.canRead() && file.canWrite()) {                    LogUtil.d("directory can read can write:",                         file.getAbsolutePath());                    // 可读可写的文件夹未必是sdcard,我的手机的sdcard下的Android/obb文件夹也可以得到                    sdcardPaths.add(result);                }                // 检查命令是否执行失败。            
                   if (p.waitFor() != 0 && p.exitValue() == 1) {                    // p.exitValue()==0表示正常结束,1:非正常结束                    LogUtil.e("CommonUtil:getSDCardPath", "命令执行失败!");                }            }            inBr.close();        
            in.close();        } catch (Exception e) {        
               LogUtil.e("CommonUtil:getSDCardPath", e.toString());            sdcardPaths.add(Environment.getExternalStorageDirectory()                    .getAbsolutePath());        }        optimize(sdcardPaths);    
          for (Iterator iterator = sdcardPaths.iterator();
              iterator.hasNext();) {            
              String string = (String) iterator.next();    
               Log.e("清除过后", string);        }      return sdcardPaths;    }    private static void optimize(List<String> sdcaredPaths) {        if (sdcaredPaths.size() == 0) {            return;        }        int index = 0;    
                 while (true) {          
                 if (index >= sdcaredPaths.size() - 1) {                String lastItem = sdcaredPaths.get(sdcaredPaths.size() - 1);                for (int i = sdcaredPaths.size() - 2; i >= 0; i--) {                    if (sdcaredPaths.get(i).contains(lastItem)) {                        sdcaredPaths.remove(i);                    }                }                
                 return;            }            
                   String containsItem = sdcaredPaths.get(index);            for (int i = index + 1; i < sdcaredPaths.size(); i++) {                if (sdcaredPaths.get(i).contains(containsItem)) {                   sdcaredPaths.remove(i);                   i--;                }            }            index++;        }    }
private static String getSecTFPath() {        
          String tfPath = new String();        
          try {            Runtime runtime = Runtime.getRuntime();            Process proc = runtime.exec("mount");            InputStream is = proc.getInputStream();            InputStreamReader isr = new InputStreamReader(is);            String line;            BufferedReader br = new BufferedReader(isr);            while ((line = br.readLine()) != null) {                LogUtil.i("getSecTFPath--line====" + line);                if (line.contains("secure"))                    continue;                if (line.contains("asec"))                    continue;                if (line.contains("internal"))                    continue;                // E人E本 T7                if (line.contains("mydoc"))                    
                   continue;                
               if (line.contains("firmware"))                    
                    continue;                // end                if (line.contains("fat")) {                    LogUtil.i("getSecTFPath--fat====" + line);                    String columns[] = line.split(" ");                    if (columns != null && columns.length > 1) {                        tfPath = columns[1];                    }                }            }        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        return tfPath;    }

写一个广播来监听sdcard是否拔插来获得外置sdcard路径,然后得到外置路径之后将其存储在数据库中

private void redMyExtraSdPathByReceiver(Activity activity) {        IntentFilter intentFilter = new IntentFilter();// sd卡被插入,且已经挂载        intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);        intentFilter.addDataScheme("file");        activity.registerReceiver(new SDcaedReceiver(), intentFilter);// 注册监听函数    }    public class SDcaedReceiver extends BroadcastReceiver {        @Override        public void onReceive(Context context, Intent intent) {            intent.getData().getPath();//外置设备路径        }    }

系统角度想到的解决办法

private void redMyExtraSdPath() {        Runtime runtime = Runtime.getRuntime();        Process proc = null;        try {            proc = runtime.exec("mount");            InputStream is = proc.getInputStream();            InputStreamReader isr = new InputStreamReader(is);            String line;            String mount = new String();            BufferedReader br = new BufferedReader(isr);            while ((line = br.readLine()) != null) {                if (line.contains("secure")) continue;                if (line.contains("asec")) continue;                if (line.contains("fat")) {                    String columns[] = line.split(" ");                    if (columns != null && columns.length > 1) {                        mount = mount.concat("*" + columns[1] + "\n");                    }                } else if (line.contains("fuse")) {                    String columns[] = line.split(" ");                    if (columns != null && columns.length > 1) {                        mount = mount.concat(columns[1] + "\n");                    }                }            }        } catch (IOException e) {            e.printStackTrace();        }    }

注意: Android 4.4 KitKat 限制第三方应用的 SD 卡读写权限

至于为什么要限制SD 卡读写权限请参考 

安卓6.0除了在manifest中声明权限,还需要在运行时动态申请存储权限。

android6.0运行时权限完美封装

androidstarjack的博客地址:

http://blog.csdn.net/androidstarjack/article/details/57524045

如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809 微信公众号:终端研发部 

(欢迎关注学习和交流)


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存