1. 程式人生 > >獲取手機SD卡路徑之爬坑解決方案

獲取手機SD卡路徑之爬坑解決方案

  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路徑下的檔案結構為:Markdown

  從這個檔案結構可以看出,真正有內容的應該是emulated/legacy和sdcard0才對,再從後面的連線來看,最後這兩個目錄都應該是指向/mnt/shell/emulated/0。接著開啟/mnt/shell/emulated/0來看看,果然是我的sdcard目錄Markdown

這讓我很疑惑,這樣的話,讀取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 來看下會得到什麼

Markdown

  我借來的這部手機有外插SDCARD。可以看到最後兩條應該是掛載SDCARD資訊了。不過它的掛載裝置是/dev/fuse, 和 /dev/block/vold/179:17 。 好吧,我暈了,等等,會不會 最後兩條資訊才是掛載SDCARD資訊呢?我的是手機因為沒有外插SDCARD,所以最後一條才是掛載SDCARD資訊,有外插SDCARD的,最後兩條是掛載SDCARD資訊。這是規律?好吧,不是規律,我又借了部手機,mount了下,發現這個猜想純屬扯淡。
利用mount命令來獲取SDCARD路徑的方法,
參考:http://my.eoe.cn/1028320/archive/4718.htmlhttp://www.eoeandroid.com/thread-275560-1-1.html

方案四:


  • 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 卡讀寫許可權

安卓6.0除了在manifest中宣告許可權,還需要在執行時動態申請儲存許可權。

如果你覺得此文對您有所幫助,歡迎入群 QQ交流群 :232203809
微信公眾號:終端研發部
Markdown

(歡迎關注學習和交流)