簡體   English   中英

在 Android 上寫入外部 SD 卡的通用方法

[英]Universal way to write to external SD card on Android

在我的應用程序中,我需要在設備存儲中存儲大量圖像。 此類文件往往用於設備存儲,我希望允許用戶能夠選擇外部 SD 卡作為目標文件夾。

我到處都讀到Android不允許用戶寫入外部SD卡,SD卡是指外部和可安裝的SD卡而不是外部存儲,但文件管理器應用程序設法在所有Android版本上寫入外部SD。

在不同的 API 級別(Pre-KitKat、KitKat、Lollipop+)授予對外部 SD 卡的讀/寫訪問權限的更好方法是什么?

更新 1

我嘗試了 Doomknight 的答案中的方法 1,但無濟於事:正如您所看到的,在嘗試寫入 SD 之前,我正在運行時檢查權限:

HashSet<String> extDirs = getStorageDirectories();
for(String dir: extDirs) {
    Log.e("SD",dir);
    File f = new File(new File(dir),"TEST.TXT");
    try {
        if(ActivityCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)==PackageManager.PERMISSION_GRANTED) {
            f.createNewFile();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

但是我收到訪問錯誤,在兩種不同的設備上嘗試過:HTC10 和 Shield K1。

10-22 14:52:57.329 30280-30280/? E/SD: /mnt/media_rw/F38E-14F8
10-22 14:52:57.329 30280-30280/? W/System.err: java.io.IOException: open failed: EACCES (Permission denied)
10-22 14:52:57.329 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:939)
10-22 14:52:57.329 30280-30280/? W/System.err:     at com.myapp.activities.TestActivity.onResume(TestActivity.java:167)
10-22 14:52:57.329 30280-30280/? W/System.err:     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1326)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.Activity.performResume(Activity.java:6338)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3336)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3384)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2574)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.access$900(ActivityThread.java:150)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1399)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.os.Looper.loop(Looper.java:168)
10-22 14:52:57.330 30280-30280/? W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5885)
10-22 14:52:57.330 30280-30280/? W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:819)
10-22 14:52:57.330 30280-30280/? W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:709)
10-22 14:52:57.330 30280-30280/? W/System.err: Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.Posix.open(Native Method)
10-22 14:52:57.330 30280-30280/? W/System.err:     at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
10-22 14:52:57.330 30280-30280/? W/System.err:     at java.io.File.createNewFile(File.java:932)
10-22 14:52:57.330 30280-30280/? W/System.err:  ... 14 more

總結

您可以在不同的 api 級別運行時 API23+授予對外部 SD 卡的讀/寫訪問權限

由於 KitKat,如果您使用特定於應用程序的目錄,則不需要權限, 否則需要權限。

通用方式:

歷史說沒有通用的方式來寫入外部 SD 卡, 但繼續......

這些設備的外部存儲配置示例證明了這一事實

基於API的方式:

KitKat之前嘗試使用Doomsknight 方法 1,否則使用方法 2。

在清單(Api < 23) 和運行時(Api >= 23) 中請求權限。

推薦方式:

ContextCompat.getExternalFilesDirs解決不需要共享文件時的訪問錯誤

共享它的安全方式是使用內容提供者新的存儲訪問框架

隱私意識方式:

從 Android Q Beta 4 開始,默認情況下,面向 Android 9(API 級別 28)或更低版本的應用沒有任何變化。

默認情況下(或選擇加入)以 Android Q 為目標的應用程序會獲得外部存儲的過濾視圖


  1. 初步答復。

在 Android 上寫入外部 SD 卡的通用方法

由於不斷變化, 在 Android 上沒有通用的寫入外部 SD 卡的方法

  • Pre-KitKat:官方Android平台除例外情況外根本不支持SD卡。

  • KitKat:引入了 API,允許應用訪問 SD 卡上應用特定目錄中的文件。

  • Lollipop:添加 API 以允許應用請求訪問其他提供商擁有的文件夾。

  • Nougat:提供了一個簡化的 API 來訪問常見的外部存儲目錄。

  • ... Android Q 隱私更改:應用范圍和媒體范圍的存儲

在不同的 API 級別授予對外部 SD 卡的讀/寫訪問權限的更好方法是什么

基於Doomsknight的回答我的,和戴夫·史密斯馬克·墨菲博客文章:123


  1. 更新了答案。

更新 1 我嘗試了 Doomknight 答案中的方法 1,但無濟於事:

如您所見,在嘗試寫入 SD 之前,我正在運行時檢查權限...

我將使用特定應用程序的目錄來避免您更新的問題和ContextCompat.getExternalFilesDirs()使用getExternalFilesDir 文檔作為參考的問題。

改進啟發式方法,根據不同的 api 級別確定什么代表可移動媒體,例如android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

...但我收到訪問錯誤,在兩種不同的設備上嘗試過:HTC10 和 Shield K1。

請記住, Android 6.0 支持便攜式存儲設備,第三方應用程序必須通過存儲訪問框架 您的設備HTC10Shield K1可能是 API 23。

您的日志顯示訪問/mnt/media_rw的權限被拒絕異常,例如 API 19+ 的此修復程序

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_r" />
<group gid="sdcard_rw" />
<group gid="media_rw" /> // this line is added via root in the link to fix it.
</permission>

我從來沒有嘗試過,所以我不能共享代碼,但我會避免for試圖在所有返回目錄的寫,尋找最佳的可用存儲目錄寫入到基於剩余空間

也許Gizm0 替代getStorageDirectories()方法是一個很好的起點。

如果您不需要訪問其他文件夾, ContextCompat.getExternalFilesDirs可以解決此問題


  1. Android 1.0 .. Pre-KitKat。

在 KitKat 之前嘗試使用Doomsknight 方法 1或閱讀 Gnathonic 的此回復

public static HashSet<String> getExternalMounts() {
    final HashSet<String> out = new HashSet<String>();
    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
    String s = "";
    try {
        final Process process = new ProcessBuilder().command("mount")
                .redirectErrorStream(true).start();
        process.waitFor();
        final InputStream is = process.getInputStream();
        final byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            s = s + new String(buffer);
        }
        is.close();
    } catch (final Exception e) {
        e.printStackTrace();
    }

    // parse output
    final String[] lines = s.split("\n");
    for (String line : lines) {
        if (!line.toLowerCase(Locale.US).contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase(Locale.US).contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

將下一個代碼添加到您的AndroidManifest.xml並閱讀獲取對外部存儲的訪問權限

對外部存儲的訪問受到各種 Android 權限的保護。

從 Android 1.0 開始,寫訪問受WRITE_EXTERNAL_STORAGE權限保護。

從 Android 4.1 開始,讀取訪問權限受READ_EXTERNAL_STORAGE權限保護。

為了……在外部存儲上寫入文件,您的應用程序必須獲得……系統權限:

 <manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> </manifest>

如果兩者都需要...,則只需要請求WRITE_EXTERNAL_STORAGE權限。

閱讀Mark Murphy 的解釋推薦Dianne HackbornDave Smith 的帖子

  • 直到 Android 4.4,Android 還沒有官方支持可移動媒體,從 KitKat 開始,FMW API 中出現了“主要”和“次要”外部存儲的概念。
  • 以前的應用程序僅依賴於 MediaStore 索引、隨硬件一起提供或檢查掛載點並應用一些啟發式方法來確定什么代表可移動媒體。

  1. Android 4.4 KitKat 引入了存儲訪問框架 (SAF)

由於錯誤而忽略下一個注釋,但嘗試使用ContextCompat.getExternalFilesDirs()

  • 自 Android 4.2 以來,Google 一直要求設備制造商鎖定可移動媒體以確保安全(多用戶支持),並且在 4.4 中添加了新的測試。
  • 由於添加了 KitKat getExternalFilesDirs()和其他方法來返回所有可用存儲卷上的可用路徑(返回的第一項是主卷)。
  • 下表說明了開發人員可能會嘗試做什么以及 KitKat 將如何響應: 在此處輸入圖片說明

注意:從 Android 4.4 開始,如果您只讀取或寫入應用程序私有的文件,則不需要這些權限。 有關更多信息...,請參閱保存應用程序私有的文件

 <manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> </manifest>

另請閱讀Paolo Rovelli 的解釋並嘗試使用自 KitKat 以來Jeff Sharkey 的解決方案

在 KitKat 中,現在有一個公共 API 用於與這些輔助共享存儲設備進行交互。

新的Context.getExternalFilesDirs()Context.getExternalCacheDirs()方法可以返回多個路徑,包括主要和次要設備。

然后您可以迭代它們並檢查Environment.getStorageState()File.getFreeSpace()以確定存儲文件的最佳位置。

這些方法也可在 support-v4 庫中的ContextCompat上使用。

從 Android 4.4 開始,外部存儲設備上文件的所有者、組和模式現在基於目錄結構進行合成。 這使應用程序能夠在外部存儲上管理其特定於包的目錄,而無需持有廣泛的WRITE_EXTERNAL_STORAGE權限。 例如,包名為com.example.foo的應用程序現在可以在沒有權限的情況下自由訪問外部存儲設備上的Android/data/com.example.foo/ 這些合成權限是通過將原始存儲設備包裝在 FUSE 守護程序中來實現的。

使用 KitKat,您無需生根即可獲得“完整解決方案”的機會幾乎為零:

Android 項目肯定在這里搞砸了。 沒有應用程序可以完全訪問外部 SD 卡:

  • 文件管理器:您不能使用它們來管理您的外部 SD 卡。 在大多數地區,他們只能讀不能寫。
  • 媒體應用程序:您不能再重新標記/重新組織您的媒體收藏,因為這些應用程序無法寫入其中。
  • 辦公應用:幾乎一樣

唯一允許 3 rd方應用程序在您的外部卡上寫入的地方是“他們自己的目錄”(即/sdcard/Android/data/<package_name_of_the_app> )。

真正解決這個問題的唯一方法需要制造商(其中一些人修復它,例如華為通過他們的 P6 Kitkat 更新) – 或 root ...... (Izzy 的解釋在這里繼續)


  1. Android 5.0 引入了更改和DocumentFile助手類。

getStorageState在 API 19 中添加,在 API 21 中棄用, 使用getExternalStorageState(File)

這是與 KitKat 中的存儲訪問框架交互的很棒的教程

與 Lollipop 中的新 API 交互非常相似(Jeff Sharkey 的解釋)


  1. Android 6.0 Marshmallow 引入了新的 運行時權限模型。

請求權限在運行時,如果API級別23+和閱讀在運行時請求權限

從 Android 6.0(API 級別 23)開始,用戶在應用程序運行時授予應用程序權限,而不是在安裝應用程序...或更新應用程序時...用戶可以撤銷權限。

 // Assume thisActivity is the current activity int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

Android 6.0 引入了一種新的 運行時權限模型,應用程序可以在運行時根據需要請求功能。 由於新模型包含READ/WRITE_EXTERNAL_STORAGE權限,平台需要動態授予存儲訪問權限,而無需終止或重新啟動已運行的應用程序。 它通過維護所有已安裝存儲設備的三個不同視圖來實現此目的:

  • /mnt/runtime/default 顯示給沒有特殊存儲權限的應用程序...
  • /mnt/runtime/read 顯示給帶有 READ_EXTERNAL_STORAGE 的應用程序
  • /mnt/runtime/write 顯示給具有 WRITE_EXTERNAL_STORAGE 的應用程序

  1. Android 7.0 提供了一個簡化的 API 來訪問外部存儲目錄。

范圍目錄訪問在 Android 7.0 中,應用程序可以使用新的 API 來請求訪問特定的外部存儲目錄,包括可移動媒體(如 SD 卡)上的目錄...

有關詳細信息,請參閱范圍目錄訪問培訓

閱讀 Mark Murphy 的帖子: 小心 Scoped Directory Access 它在 Android Q 中已被棄用

請注意,7.0 中添加的范圍目錄訪問在 Android Q 中已棄用。

具體來說,不推薦使用StorageVolume上的createAccessIntent()方法。

他們添加了一個createOpenDocumentTreeIntent()可以用作替代。


  1. Android 8.0 Oreo .. Android Q Beta 更改。

從 Android O 開始,存儲訪問框架允許自定義文檔提供程序為駐留在遠程數據源中的文件創建可查找的文件描述符...

權限在 Android O 之前,如果應用程序在運行時請求權限並且該權限被授予,系統也會錯誤地授予該應用程序屬於同一權限組並在清單中注冊的其余權限。

對於面向 Android O 的應用程序,此行為已得到糾正。 該應用程序僅被授予它明確請求的權限。 但是,一旦用戶向應用授予權限,該權限組中的所有后續權限請求都會自動授予。

例如, READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE ...

更新: Android Q 早期測試版暫時用更細粒度的特定媒體權限替換了READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE權限。

注意: Google 在 Beta 1 中引入了角色,並在 Beta 2 之前從文檔中刪除了它們...

注意:在早期 Beta 版本中引入的特定於媒體收藏的權限 - READ_MEDIA_IMAGESREAD_MEDIA_AUDIOREAD_MEDIA_VIDEO -現在已過時 更多信息:

Mark Murphy 的 Q Beta 4(最終 API)評論: 外部存儲的死亡:傳奇的終結(?)

“死亡比生命更普遍。每個人都會死,但不是每個人都活着。” —— 安德魯·薩克斯


  1. 相關問題和推薦答案。

如何獲取適用於 Android 4.0+ 的外部 SD 卡路徑?

mkdir() 可以在內部閃存中工作,但不能在 SD 卡中工作?

getExternalFilesDir 和 getExternalStorageDirectory() 之間的差異

為什么 getExternalFilesDirs() 在某些設備上不起作用?

如何使用為 Android 5.0 (Lollipop) 提供的新 SD 卡訪問 API

在 Android 5.0 及更高版本中寫入外部 SD 卡

使用 SAF(存儲訪問框架)的 Android SD 卡寫入權限

SAFFAQ:存儲訪問框架常見問題


  1. 相關的錯誤和問題。

錯誤:在 Android 6 上,當使用 getExternalFilesDirs 時,它不會讓您在其結果中創建新文件

在沒有寫入權限的情況下,寫入 Lollipop 上的 getExternalCacheDir() 返回的目錄失敗

我相信有兩種方法可以實現這一目標:

方法1:(上工作6.0及以上,由於權限更改)

我多年來一直在許多設備版本上使用這種方法,沒有任何問題。 歸功於原始來源,因為它不是我寫的。

它將在字符串目錄位置列表中返回所有已安裝的媒體(包括真正的 SD 卡) 有了這個列表,你就可以詢問用戶在哪里保存,等等。

您可以使用以下命令調用它:

 HashSet<String> extDirs = getStorageDirectories();

方法:

/**
 * Returns all the possible SDCard directories
 */
public static HashSet<String> getStorageDirectories() {
    final HashSet<String> out = new HashSet<String>();
    String reg = "(?i).*vold.*(vfat|ntfs|exfat|fat32|ext3|ext4).*rw.*";
    String s = "";
    try {
        final Process process = new ProcessBuilder().command("mount")
                .redirectErrorStream(true).start();
        process.waitFor();
        final InputStream is = process.getInputStream();
        final byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            s = s + new String(buffer);
        }
        is.close();
    } catch (final Exception e) {
        e.printStackTrace();
    }

    // parse output
    final String[] lines = s.split("\n");
    for (String line : lines) {
        if (!line.toLowerCase().contains("asec")) {
            if (line.matches(reg)) {
                String[] parts = line.split(" ");
                for (String part : parts) {
                    if (part.startsWith("/"))
                        if (!part.toLowerCase().contains("vold"))
                            out.add(part);
                }
            }
        }
    }
    return out;
}

方法二:

使用 v4 支持庫

import android.support.v4.content.ContextCompat;

只需調用以下命令即可獲取存儲File位置列表。

 File[] list = ContextCompat.getExternalFilesDirs(myContext, null);

但是,這些位置的用法不同。

返回所有外部存儲設備上應用程序特定目錄的絕對路徑,應用程序可以在其中放置它擁有的持久文件。 這些文件在應用程序內部,通常作為媒體對用戶不可見。

此處返回的外部存儲設備被視為設備的永久部分,包括模擬外部存儲和物理媒體插槽,例如電池倉中的 SD 卡。 返回的路徑不包括臨時設備,例如 USB 閃存驅動器。

應用程序可以將數據存儲在任何或所有返回的設備上。 例如,應用程序可能會選擇在可用空間最大的設備上存儲大文件

有關 ContextCompat 的更多信息

它們就像應用程序特定的文件。 對其他應用程序隱藏。

只是另一個答案。 此答案僅顯示 5.0+,因為我相信此處發布的 Doomknight 的答案是適用於 Android 4.4 及以下版本的最佳方法。

這最初發布在這里( 有沒有辦法在 Android 中獲取 SD 卡大小? )由我在 Android 5.0+ 上獲取外部 SD 卡的大小

將外部 SD 卡作為File

public File getExternalSdCard() {
    File externalStorage = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        File storage = new File("/storage");

        if(storage.exists()) {
            File[] files = storage.listFiles();

            for (File file : files) {
                if (file.exists()) {
                    try {
                        if (Environment.isExternalStorageRemovable(file)) {
                            externalStorage = file;
                            break;
                        }
                    } catch (Exception e) {
                        Log.e("TAG", e.toString());
                    }
                }
            }
        }
    } else {
        // do one of many old methods
        // I believe Doomsknight's method is the best option here
    }

    return externalStorage;
}

注意:我只得到“第一個”外部 sd 卡,但是您可以修改它並返回ArrayList<File>而不是File並讓循環繼續而不是在找到第一個后調用break

除了所有其他不錯的答案之外,我還可以為這個問題添加更多內容,以便為讀者提供更廣泛的覆蓋范圍。 在我的回答中,我將使用 2 個可數的資源來呈現外部存儲。

第一個資源來自Android Programming,The Big Nerd Ranch Guide 2nd edition ,第 16 章,第 294 頁。

本書介紹了基本的和外部的文件和目錄方法。 我將嘗試制作一份可能與您的問題相關的簡歷。

書中的以下部分:

外部存儲

您的照片不僅需要在屏幕上占據一席之地。 全尺寸圖片太大而無法保存在 SQLite 數據庫中,更不用說Intent 他們需要在你設備的文件系統上有一個地方。 通常,您會將它們放在您的私人存儲中。 回想一下,您使用私有存儲來保存 SQLite 數據庫。 使用Context.getFileStreamPath(String)Context.getFilesDir() ,您也可以對常規文件執行相同的操作(這些文件將位於與 SQLite 數據庫所在的數據庫子文件夾相鄰的子文件夾中)

Context 中的基本文件和目錄方法

| Method                                                                                |
|---------------------------------------------------------------------------------------|
|File getFilesDir()                                                                      |
| - Returns a handle to the directory for private application files.                    |
|                                                                                       |
|FileInputStream openFileInput(String name)                                             |
| - Opens an existing file for input (relative to the files directory).                 |
|                                                                                       |
|FileOutputStream openFileOutput(String name, int mode)                                 |
| - Opens a file for output, possibly creating it (relative to the files directory).    |
|                                                                                       |
|File getDir(String name, int mode)                                                     |
| - Gets (and possibly creates) a subdirectory within the files directory.              |
|                                                                                       |
|String[] fileList()                                                                    |
| - Gets a list of file names in the main files directory, such as for use with         |
|   openFileInput(String).                                                              |
|                                                                                       |
|File getCacheDir()                                                                     |
| - Returns a handle to a directory you can use specifically for storing cache files.   |
|   You should take care to keep this directory tidy and use as little space as possible|

如果您正在存儲只有當前應用程序需要使用的文件,那么這些方法正是您所需要的。

另一方面,如果您需要另一個應用程序來寫入這些文件,那么您就不走運了:雖然您可以將Context.MODE_WORLD_READABLE標志傳遞給openFileOutput(String, int) ,但它已被棄用,並且不完全可靠它對較新設備的影響。 如果您要存儲文件以與其他應用程序共享或從其他應用程序接收文件(如存儲圖片的文件),則需要將它們存儲在外部存儲上。

有兩種外部存儲:主存儲和其他所有存儲。 所有 Android 設備至少有一個外部存儲位置:主位置,位於Environment.getExternalStorageDirectory()返回的文件夾中。 這可能是一個 SD 卡,但現在它更常見地集成到設備本身中。 某些設備可能有額外的外部存儲。 那將屬於“其他一切”。

Context 也提供了很多獲取外部存儲的方法。 這些方法提供了獲取主要外部存儲的簡單方法,以及獲取其他所有內容的有點簡單的方法。 所有這些方法也將文件存儲在公開可用的位置,因此請小心使用它們。

Context 中的外部文件和目錄方法

| Method                                                                                |
| --------------------------------------------------------------------------------------|
|File getExternalCacheDir()                                                             |
| - Returns a handle to a cache folder in primary external storage. Treat it like you do|
|   getCacheDir(), except a little more carefully. Android is even less likely to clean |
|   up this folder than the private storage one.                                        |
|                                                                                       |
|File[] getExternalCacheDirs()                                                          |
| - Returns cache folders for multiple external storage locations.                      |
|                                                                                       |
|File getExternalFilesDir(String)                                                       |
| - Returns a handle to a folder on primary external storage in which to store regular  |
|   files. If you pass in a type String, you can access a specific subfolder dedicated  |
|   to a particular type of content. Type constants are defined in Environment, where   |
|   they are prefixed with DIRECTORY_.                                                  |
|   For example, pictures go in Environment.DIRECTORY_PICTURES.                         |
|                                                                                       |
|File[] getExternalFilesDirs(String)                                                    |
| - Same as getExternalFilesDir(String), but returns all possible file folders for the  |
|   given type.                                                                         |
|                                                                                       |
|File[] getExternalMediaDirs()                                                          |
| - Returns handles to all the external folders Android makes available for storing     |
|   media – pictures, movies, and music. What makes this different from calling         |
|   getExternalFilesDir(Environment.DIRECTORY_PICTURES) is that the media scanner       |
|   automatically scans this folder. The media scanner makes files available to         |
|   applications that play music, or browse movies and photos, so anything that you     |
|   put in a folder returned by getExternalMediaDirs() will automatically appear in     |
|   those apps.                                                                         |

從技術上講,上面提供的外部文件夾可能不可用,因為某些設備使用可移動 SD 卡進行外部存儲。 在實踐中,這很少成為問題,因為幾乎所有現代設備都具有不可移動的內部存儲作為其“外部”存儲。 因此,不值得花大力氣來解釋它。 但是我們確實建議包含簡單的代碼來防止這種可能性,您稍后會這樣做。

外部存儲權限

通常,您需要獲得從外部存儲寫入或讀取的權限。 權限是您使用<uses-permission>標記放入清單中的眾所周知的字符串值。 他們告訴 Android 你想做一些 Android 想讓你征得許可的事情。

在這里,Android 希望您征得許可,因為它想要強制執行某些責任。 你告訴 Android 你需要訪問外部存儲,然后 Android 會告訴用戶這是你的應用程序在嘗試安裝時所做的事情之一。 這樣,當您開始將內容保存到他們的 SD 卡時,沒有人會感到驚訝。

在 Android 4.4 KitKat 中,他們放寬了這一限制。 由於Context.getExternalFilesDir(String)返回一個特定於您的應用程序的文件夾,因此您希望能夠讀取和寫入位於其中的文件是有道理的。 因此,在 Android 4.4 (API 19) 及更高版本上,您不需要此文件夾的此權限。 (但您仍然需要它用於其他類型的外部存儲。)

在清單中添加一行請求讀取外部存儲的權限,但僅限於 API 清單 16.5 請求外部存儲權限 ( AndroidManifest.xml )

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.bignerdranch.android.criminalintent" >
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="18" />

maxSdkVersion 屬性使您的應用程序僅在早於 API 19,Android KitKat 的 Android 版本上請求此權限。 請注意,您只是要求讀取外部存儲。 還有一個WRITE_EXTERNAL_STORAGE權限,但您不需要它。 您不會向外部存儲寫入任何內容:相機應用程序會為您做到這一點

第二個資源是這個鏈接閱讀所有內容,但您也可以跳轉到使用外部存儲部分。

參考:

更多閱讀內容:

免責聲明:此信息取自 Android 編程:經作者許可的大書呆子牧場指南。 有關本書的更多信息或購買副本,請訪問 bignerdranch.com。

這個話題有點老了,但我一直在尋找解決方案,經過一些研究,我使用下面的代碼來檢索可用的“外部”掛載點列表,據我所知,這些掛載點可在許多不同的設備上運行。

基本上,它讀取可用的掛載點,過濾掉無效的掛載點,測試其余的掛載點是否可訪問,並在滿足所有條件時添加它們。

當然,必須在調用代碼之前授予所需的權限。

// Notice: FileSystemDevice is just my own wrapper class. Feel free to replace it with your own. 

private List<FileSystemDevice> getDevices() {

    List<FileSystemDevice> devices = new ArrayList<>();

    // Add default external storage if available.
    File sdCardFromSystem = null;
    switch(Environment.getExternalStorageState()) {
        case Environment.MEDIA_MOUNTED:
        case Environment.MEDIA_MOUNTED_READ_ONLY:
        case Environment.MEDIA_SHARED:
            sdCardFromSystem = Environment.getExternalStorageDirectory();
            break;
    }

    if (sdCardFromSystem != null) {
        devices.add(new FileSystemDevice(sdCardFromSystem));
    }

    // Read /proc/mounts and add all mount points that are available
    // and are not "special". Also, check if the default external storage
    // is not contained inside the mount point. 
    try {
        FileInputStream fs = new FileInputStream("/proc/mounts");
        String mounts = IOUtils.toString(fs, "UTF-8");
        for(String line : mounts.split("\n")) {
            String[] parts = line.split(" ");

            // parts[0] - mount type
            // parts[1] - mount point
            if (parts.length > 1) {
                try {

                    // Skip "special" mount points and mount points that can be accessed
                    // directly by Android's functions. 
                    if (parts[0].equals("proc")) { continue; }
                    if (parts[0].equals("rootfs")) { continue; }
                    if (parts[0].equals("devpts")) { continue; }
                    if (parts[0].equals("none")) { continue; }
                    if (parts[0].equals("sysfs")) { continue; }
                    if (parts[0].equals("selinuxfs")) { continue; }
                    if (parts[0].equals("debugfs")) { continue; }
                    if (parts[0].equals("tmpfs")) { continue; }
                    if (parts[1].equals(Environment.getRootDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getDataDirectory().getAbsolutePath())) { continue; }
                    if (parts[1].equals(Environment.getExternalStorageDirectory().getAbsolutePath())) { continue; }

                    // Verify that the mount point is accessible by listing its content. 
                    File file = new File(parts[1]);
                    if (file.listFiles() != null) {
                        try {

                            // Get canonical path for case it's just symlink to another mount point.
                            String devPath = file.getCanonicalPath();

                            for(FileSystemDevice device : devices) {

                                if (!devices.contains(devPath)) {                        
                                    devices.add(new FileSystemDevice(new File(devPath)));
                                }

                            }
                        } catch (Exception e) {
                            // Silently skip the exception as it can only occur if the mount point is not valid. 
                            e.printStackTrace();
                        }
                    }
                } catch (Exception e) {
                    // Silently skip the exception as it can only occur if the mount point is not valid. 
                    e.printStackTrace();
                }
            }
        }

        fs.close();
    } catch (FileNotFoundException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable. 
        // Possibly, another detection method can be called here.
        e.printStackTrace();
    } catch (IOException e) {
        // Silently skip the exception as it can only occur if the /proc/mounts file is unavailable.
        // Possibly, another detection method can be called here.
        e.printStackTrace();            
    }

    return devices;
}

這是在外部存儲中創建新文件的方法(如果設備中存在 SDCard,否則設備外部存儲中存在)。 只需將“文件夾名”替換為所需目標文件夾的名稱,將“文件名”替換為要保存的文件名。 當然,在這里您可以看到如何保存通用文件,現在您可以搜索如何在此處或文件中的任何內容保存圖像。

try {
            File dir =  new File(Environment.getExternalStorageDirectory() + "/foldername/");
            if (!dir.exists()){
                dir.mkdirs();
            }
            File sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileName );
            int num = 1;
            String fileNameAux = fileName;
            while (sdCardFile.exists()){
                fileNameAux = fileName+"_"+num;
                sdCardFile = new File(Environment.getExternalStorageDirectory() + "/foldername/" + fileNameAux);
                num++;
            }

這也控制該文件是否存在並在新文件的名稱末尾添加一個數字以保存它。

希望有幫助!

編輯:對不起,我忘了你必須在你的清單中要求<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> (或者如果你喜歡 Marshmallow 以編程方式)

對於低於 Marshmallow 的版本,您可以直接在清單中授予權限。

但是對於帶有 Marshmallow 及以上版本的設備,您需要在運行時授予權限。

通過使用

Environment.getExternalStorageDirectory();

您可以直接訪問外部 SD 卡(已安裝)希望這會有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM