Подтвердить что ты не робот

Универсальный способ записи на внешнюю SD-карту на Android

В моем приложении мне нужно хранить много изображений в памяти устройства. Такие файлы обычно занимают место на устройстве, и я хочу, чтобы пользователи могли выбирать внешнюю SD-карту в качестве папки назначения.

Я везде читал, что Android не позволяет пользователям записывать на внешнюю SD-карту, под SD-картой я имею в виду внешнюю и монтируемую SD-карту, а не внешнее хранилище, но приложениям файлового менеджера удается записывать на External SD во всех версиях Android.

Как лучше предоставить доступ для чтения/записи к внешней SD-карте на разных уровнях API (Pre-KitKat, KitKat, Lollipop+)?

Обновление 1

Я попробовал метод 1 из ответа Doomknight, но безрезультатно: Как вы видите, я проверяю разрешения во время выполнения перед попыткой записи на 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
4b9b3361

Ответ 1

Резюме

Вы можете предоставить доступ на чтение/запись к внешней SD-карте на разных уровнях API (API23+ во время выполнения).

Начиная с KitKat, разрешения не требуются, если вы используете специфичные для приложения каталоги, иначе требуется.

Универсальный способ:

История говорит, что не существует универсального способа записи на внешнюю SD-карту, но продолжается...

Этот факт подтверждается этими примерами конфигураций внешних хранилищ для устройств.

Основанный на API способ:

До KitKat попробуйте использовать метод Doomsknight 1, метод 2 в противном случае.

Запросите разрешения в манифесте (Api <23) и во время выполнения (Api> = 23).

Рекомендуемый способ:

ContextCompat.getExternalFilesDirs устраняет ошибку доступа, когда вам не нужно обмениваться файлами.

Безопасный способ поделиться им - использовать поставщика контента или новую платформу хранилища доступа.

Способ обеспечения конфиденциальности:

Начиная с Android Q Beta 4, приложения, ориентированные на Android 9 (уровень API 28) или ниже, по умолчанию не изменяются.

Приложениям, ориентированным на Android Q по умолчанию (или включенным в него), предоставляется фильтрованное представление во внешнем хранилище.


1. Первоначальный ответ.

Универсальный способ записи на внешнюю SD-карту на Android

Универсального способа записи на внешнюю SD-карту в Android не существует из-за постоянных изменений:

  • Pre-KitKat: официальная платформа Android вообще не поддерживает SD-карты, кроме исключений.

  • KitKat: представлены API, которые позволяют приложениям получать доступ к файлам в каталогах приложений на SD-картах.

  • Lollipop: добавлены API, позволяющие приложениям запрашивать доступ к папкам, принадлежащим другим поставщикам.

  • Нуга: предоставил упрощенный API для доступа к общим каталогам внешнего хранилища.

  • ... Изменение конфиденциальности Android Q: хранилище на уровне приложения и носителя

Как лучше предоставить доступ для чтения/записи к внешней SD-карте на разных уровнях API?

Основываясь на ответе Doomsknight и моем, а также на блоге Дейва Смита и Марка Мерфи: 1, 2, 3:


2. Обновленный ответ.

Обновление 1. Я попробовал метод 1 из ответа Doomknight, но безрезультатно:

Как вы можете видеть, я проверяю разрешения во время выполнения, прежде чем писать на SD...

Я бы использовал каталоги, специфичные для приложения, чтобы избежать проблемы вашего обновленного вопроса, и ContextCompat.getExternalFilesDirs() используя документацию getExternalFilesDir в качестве ссылки.

Улучшите эвристику, чтобы определить, что представляет собой съемный носитель, на основе различных уровней API, таких как android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT

... но я получаю ошибку доступа, пробовал на двух разных устройствах: HTC10 и Shield K1.

Помните, что Android 6.0 поддерживает переносные устройства хранения, и сторонние приложения должны проходить через Storage Access Framework. Ваши устройства HTC10 и Shield 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 решает проблему, если вам не нужен доступ к другим папкам.


3. Android 1.0.. Предварительно 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.

Прочитайте объяснение Марка Мерфи и порекомендуйте сообщения Дайан Хэкборн и Дейва Смита

  • До Android 4.4 не было официальной поддержки сменных носителей в Android. Начиная с KitKat, в FMW API появилась концепция "первичного" и "вторичного" внешнего хранилища.
  • Предыдущие приложения просто полагаются на индексацию MediaStore, поставляются с оборудованием или проверяют точки монтирования и применяют некоторую эвристику, чтобы определить, что представляет собой съемный носитель.

4. Android 4.4 KitKat представляет платформу хранилища доступа (SAF).

Проигнорируйте следующую заметку из-за ошибок, но попробуйте использовать ContextCompat.getExternalFilesDirs():

  • Начиная с Android 4.2, от Google поступил запрос от производителей устройств заблокировать съемные носители для обеспечения безопасности (многопользовательская поддержка), и в 4.4 были добавлены новые тесты.
  • Поскольку KitKat getExternalFilesDirs() и другие методы были добавлены для возврата пригодного пути ко всем доступным томам хранения (первый возвращаемый элемент - это основной том).
  • В таблице ниже указано, что разработчик может попытаться сделать, и как KitKat ответит: enter image description here

Примечание. Начиная с Android 4.4, эти разрешения не требуются, если вы читаете или пишете только файлы, которые являются частными для вашего приложения. Для получения дополнительной информации... см. Сохранение файлов, которые являются частными для приложения.

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

Также прочтите объяснение Паоло Ровелли и попробуйте использовать решение Джеффа Шарки начиная с KitKat:

В KitKat теперь есть открытый API для взаимодействия с этими вторичными общими устройствами хранения.

Новые Context.getExternalFilesDirs() и Context.getExternalCacheDirs() могут возвращать несколько путей, включая как первичные, так и вторичные устройства.

Затем вы можете перебрать их и проверить Environment.getStorageState() и File.getFreeSpace() чтобы определить лучшее место для хранения ваших файлов.

Эти методы также доступны в ContextCompat в библиотеке support-v4.

Начиная с Android 4.4, владелец, группа и режимы файлов на внешних устройствах хранения теперь синтезируются на основе структуры каталогов. Это позволяет приложениям управлять своими каталогами, специфичными для пакета, во внешнем хранилище, не требуя от них широкого разрешения WRITE_EXTERNAL_STORAGE. Например, приложение с именем пакета com.example.foo теперь может свободно обращаться к Android/data/com.example.foo/ на внешних устройствах хранения без каких-либо разрешений. Эти синтезированные разрешения выполняются путем оборачивания необработанных устройств хранения в демон FUSE.

С KitKat ваши шансы на "полное решение" без рутирования практически равны нулю:

Проект Android определенно облажался здесь. Никакие приложения не получают полный доступ к внешним SD-картам:

  • файловые менеджеры: вы не можете использовать их для управления вашей внешней SD-картой. В большинстве областей они могут только читать, но не писать.
  • мультимедийные приложения: вы больше не можете пометить/реорганизовать свою мультимедийную коллекцию, так как эти приложения не могут писать в нее.
  • офисные приложения: почти то же самое

Единственные приложения место 3 - й партии разрешается писать на ваши внешних картах являются "своими собственными каталоги" (т.е. /sdcard/Android/data/<package_name_of_the_app>).

Единственные способы действительно исправить это требуют либо производителя (некоторые из них исправили это, например, Huawei с их обновлением Kitkat для P6) - либо root... (объяснение Иззи здесь продолжается)


5. В Android 5.0 внесены изменения и класс помощника DocumentFile.

getStorageState Добавлен в API 19, устарел в API 21, используйте getExternalStorageState(File)

Здесь отличный учебник для взаимодействия с Storage Access Framework в KitKat.

Взаимодействие с новыми API в Lollipop очень похоже (объяснение Джеффа Шарки).


6. 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

7. Android 7.0 предоставляет упрощенный API для доступа к директориям внешнего хранилища.

Доступ к каталогам в Scoped В Android 7.0 приложения могут использовать новые API для запроса доступа к определенным каталогам внешнего хранилища, включая каталоги на съемных носителях, таких как SD-карты...

Для получения дополнительной информации см. Тренинг по Доступу к каталогу Scoped.

Прочитайте сообщения Марка Мерфи: " Будь осторожен с доступом к директории в рамках" Это устарело в Android Q:

Обратите внимание, что доступ к каталогу в области, добавленный в 7.0, устарел в Android Q.

В частности, метод createAccessIntent() в StorageVolume устарел.

Они добавили createOpenDocumentTreeIntent() который можно использовать в качестве альтернативы.


8. Android 8.0 Oreo. Изменения в Android Q Beta.

Начиная с Android O, Storage Access Framework позволяет провайдерам пользовательских документов создавать доступные для поиска файловые дескрипторы для файлов, находящихся в удаленном источнике данных...

Разрешения, до Android O, если приложение запрашивало разрешение во время выполнения, и разрешение было предоставлено, система также неправильно предоставила приложению остальные разрешения, которые принадлежали к той же группе разрешений и были зарегистрированы в манифесте.

В приложениях, ориентированных на Android O, это поведение было исправлено. Приложению предоставляются только те разрешения, которые оно явно запросило. Однако, как только пользователь предоставит разрешение приложению, все последующие запросы на разрешения в этой группе разрешений будут автоматически предоставлены.

Например, READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE...

Обновление: более ранняя бета-версия Android Q временно заменила разрешения READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE на более детальные разрешения для конкретного носителя.

Примечание: Google представил роли в бета-версии 1 и удалил их из документации до бета-версии 2...

Примечание. Разрешения, относящиеся к коллекциям носителей, которые были представлены в более ранних бета-версиях - READ_MEDIA_IMAGES, READ_MEDIA_AUDIO и READ_MEDIA_VIDEO - теперь устарели. Больше информации:

Q Beta 4 (финальные API) обзор Марка Мерфи: Смерть внешнего хранилища: конец саги (?)

"Смерть более универсальна, чем жизнь. Все умирают, но не все живут". - Эндрю Сакс


9. Связанные вопросы и рекомендуемые ответы.

Как я могу получить путь к внешней SD-карте для Android 4. 0+?

mkdir() работает во внутренней флеш-памяти, а не на SD-карте?

Различие между getExternalFilesDir и getExternalStorageDirectory()

Почему getExternalFilesDirs() не работает на некоторых устройствах?

Как использовать новый API доступа к SD-карте, представленный для Android 5.0 (Lollipop)

Запись на внешнюю SD-карту в Android 5.0 и выше

Разрешение записи SD-карты Android с использованием SAF (Storage Access Framework)

SAFFAQ: часто задаваемые вопросы по структуре хранилища доступа


10. Связанные ошибки и проблемы.

Ошибка: в Android 6 при использовании getExternalFilesDirs он не позволяет создавать новые файлы в своих результатах.

Запись в каталог, возвращаемый getExternalCacheDir() в Lollipop, завершается неудачно без разрешения на запись

Ответ 2

Я считаю, что для этого есть два способа:

МЕТОД 1: (работает НЕ в 6.0 и выше, из-за изменений разрешений)

Я использую этот метод в течение многих лет на многих версиях устройств без проблем. Кредит связан с оригинальным источником, так как я не написал его.

Он вернет все смонтированные носители (включая Real SD Cards) в списке местоположений каталога строк. Со списком вы можете затем спросить пользователя о том, где сохранить и т.д.

Вы можете вызвать его со следующим:

 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;
}

МЕТОД 2:

Используйте библиотеку поддержки v4

import android.support.v4.content.ContextCompat;

Просто вызовите следующее, чтобы получить список мест хранения File.

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

Однако местоположения различаются в использовании.

Возвращает абсолютные пути к каталогам конкретных приложений на всех     внешние устройства хранения, в которых приложение может размещать постоянные файлы     он владеет. Эти файлы являются внутренними для приложения, и не типично     видимый пользователю как носитель.

Внешние запоминающие устройства, возвращаемые сюда, считаются постоянной частью      устройство, включая как эмулированное внешнее хранилище, так и физические носители      слотов, таких как SD-карты в батарейном отсеке. Возвращенные пути       не включают переходные устройства, такие как USB-накопители.

Приложение может хранить данные на любом или всех возвращенных устройствах. Для       Например, приложение может выбрать сохранение больших файлов на устройстве с помощью       наиболее доступное пространство

Дополнительная информация о ContextCompat

Они похожи на файлы приложений. Скрыт от других приложений.

Ответ 3

Еще один ответ. Этот ответ показывает только 5.0+, потому что я верю, что ответ Doomknight, представленный здесь, - лучший способ сделать для Android 4.4 и ниже.

Это изначально размещено здесь (У меня есть способ получить размер SD-карты в Android?), чтобы получить внешний размер SD-карты на Android 5.0+

Чтобы получить внешнюю 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 method is the best option here
    }

    return externalStorage;
}

Примечание. Я получаю только "первую" внешнюю SD-карту, но вы можете ее изменить и вернуть ArrayList<File> вместо File и продолжить цикл вместо вызова break после того, как первый найден.

Ответ 4

В дополнение ко всем другим приятным ответам я мог бы добавить немного больше к этому вопросу, чтобы он мог дать более широкий охват для читателей. В своем ответе я бы использовал 2 счетных ресурса для представления внешнего хранилища.

Первый ресурс из Android Programming, The Big Nerd Ranch Guide, 2-е издание, глава 16, стр. 294.

В книге описаны основные и внешние методы работы с файлами и каталогами. Я постараюсь составить резюме того, что может иметь отношение к вашему вопросу.

Следующая часть из книги:

Внешнее хранилище

Ваша фотография нуждается больше, чем место на экране. Полноразмерные изображения слишком велики, чтобы их можно было вставить в базу данных SQLite, а тем более Intent. Им понадобится место для жизни на файловой системе вашего устройства. Обычно вы помещаете их в свое личное хранилище. Напомним, что вы использовали свое личное хранилище для сохранения базы данных SQLite. С помощью таких методов, как Context.getFileStreamPath(String) и Context.getFilesDir(), вы можете делать то же самое и с обычными файлами (которые будут Context.getFilesDir() баз данных, в которой находится ваша база данных SQLite)

Основные методы файлов и каталогов в контексте

| 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-карта, но в настоящее время она чаще интегрируется в само устройство. Некоторые устройства могут иметь дополнительное внешнее хранилище. Это подпадает под "все остальное".

Контекст также предоставляет несколько способов получения доступа к внешнему хранилищу. Эти методы предоставляют простые способы получить доступ к первичному внешнему хранилищу, а также довольно простые способы получить доступ ко всему остальному. Все эти методы также хранят файлы в общедоступных местах, поэтому будьте осторожны с ними.

Внешние методы файлов и каталогов в контексте

| 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 делает так, чтобы ваше приложение запрашивало это разрешение только для версий Android, которые старше, чем API 19, Android KitKat. Обратите внимание, что вы просите только прочитать внешнее хранилище. Также есть разрешение WRITE_EXTERNAL_STORAGE, но оно вам не нужно. Вы не будете ничего записывать во внешнее хранилище: приложение камеры сделает это за вас

Второй ресурс - эта ссылка, прочитайте все это, но вы также можете перейти к разделу Использование внешнего хранилища.

Ссылка:

Больше материалов для чтения:

Отказ от ответственности: Эта информация была взята из Android Programming: The Big Nerd Guide с разрешения авторов. Для получения дополнительной информации об этой книге или для приобретения копии, пожалуйста, посетите bignerdranch.com.

Ответ 5

Эта тема немного устарела, но я искал решение, и после некоторых исследований я пришел с приведенным ниже кодом, чтобы получить список доступных "внешних" точек монтирования, которые, по моим сведениям, работают на многих разных устройствах.

В основном, он считывает доступные точки монтирования, отфильтровывает недопустимые, проверяет остальное, если они доступны, и добавляет их, если выполняются все условия.

Конечно, требуемые разрешения должны быть предоставлены до вызова кода.

// 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 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 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;
}

Ответ 6

Вот способ создания нового файла во внешнем хранилище (SDCard, если он присутствует в устройстве или устройстве External Storage, если нет). Просто замените "имя папки" на имя нужной папки назначения и "имя файла" с именем файла, который вы сохраняете. Конечно, здесь вы можете увидеть, как сохранить общий файл, теперь вы можете искать, как сохранить изображения возможно здесь или что угодно в файле.

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++;
            }

Это также контролирует, что файл существует и добавляет номер в конце имени нового файла, чтобы сохранить его.

Надеюсь, что это поможет!

EDIT: Извините, я забыл, что вы должны просить <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> в вашем манифесте (или программно, если вы предпочитаете из Зефира)

Ответ 7

 
`Log.d(TAG, System.getenv("SECONDARY_STORAGE"));`

выход:

`D/MainActivity: /storage/extSdCard`

Работа с Note 3 Android 5.0.

Ответ 8

Для версий ниже Marshmallow вы можете напрямую предоставить разрешения в манифесте.

Но для устройств с Marshmallow и выше вам необходимо предоставить разрешения на время выполнения.

Используя

Environment.getExternalStorageDirectory();

вы можете напрямую получить доступ к внешней SD-карте (установленной) Надеюсь, это поможет.