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


76

У моїй програмі мені потрібно зберігати багато зображень у сховищі пристрою. Такі файли, як правило, відповідають пам'яті пристрою, і я хочу дозволити користувачам вибрати зовнішню 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

системні програми можуть повністю отримати доступ до зовнішнього сховища SD-карти, але інші програми не можуть, якщо ОС та програма не мають кореневого доступу
Pavneet_Singh

4
@PavneetSingh це неправда, всі програми провідника файлів мають доступ до зовнішньої SD-карти, навіть без root.
Vektor88

про який провідник файлів ви говорите? тому що якийсь відомий, використовує скрипти для вкорінення для доступу до sd-карти
Pavneet_Singh

1
вони використовують метод, який я тобі сказав, для тесту просто встановіть kitkat OS (un-root) і встановіть ES і спробуйте видалити з нього файл, ви отримаєте попередження (це може зробити ваш телефон цеглиною) з проханням застосувати root процес на ваш власний ризик
Pavneet_Singh

1
рядок із посилання, я настійно рекомендую вам НІКОЛИ не покладатися на цей код , як я вже сказав, що ваш додаток не може цього зробити, але провайдер медіа - це системний додаток, щоб ви могли використовувати його, щоб робити те, що можете
Pavneet_Singh

Відповіді:


121

Резюме

Ви можете надати доступ для читання / запису на зовнішню 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 як посилання.

Покращити евристику, щоб визначити, що являє собою знімні носії на основі різних рівнів APIandroid.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 .. 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дозволу.

Прочитайте пояснення Марка Мерфі і рекомендує Дайан Hackborn і Dave Smith повідомлень

  • До Android 4.4 офіційна підтримка знімних носіїв в Android не існувала. Починаючи з KitKat, в API FMW з’являється концепція „первинного” та „вторинного” зовнішнього сховища.
  • Попередні програми просто покладаються на індексацію MediaStore, постачають разом із апаратним забезпеченням або вивчають точки монтування та застосовують деякі евристики для визначення того, що представляє собою знімний носій.

4. Android 4.4 KitKat представляє Framework Access Storage Framework (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>

Також прочитайте пояснення Паоло Ровеллі та спробуйте використати рішення Джеффа Шаркі починаючи з 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-картою. У більшості областей вони вміють лише читати, але не писати.
  • мультимедійні програми: ви більше не можете повторно позначати / реорганізовувати свою колекцію медіа, оскільки ці програми не можуть писати на неї.
  • офісні програми: майже те саме

Єдине місце, де додатки третьої сторони дозволяють писати на зовнішній картці - це "власні каталоги" (тобто /sdcard/Android/data/<package_name_of_the_app>).

Єдині способи по-справжньому виправити, що вимагає або виробник (деякі з них виправили це, наприклад, Huawei з оновленням Kitkat для P6) - або root ... (пояснення Іззі продовжується тут)


5. Android 5.0 представив зміни та допоміжний клас DocumentFile .

getStorageStateДодано в API 19, застаріле в API 21, використанняgetExternalStorageState(File)

Ось чудовий підручник для взаємодії з Framework Storage Access у 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 для доступу до зовнішніх папок сховища.

Обмежений доступ до каталогів У Android 7.0 програми можуть використовувати нові API для запиту доступу до певного зовнішнього сховища каталогів , включаючи каталоги на знімних носіях, таких як SD-карти ...

Для отримання додаткової інформації дивіться навчальний посібник Scoped Directory Access .

Прочитайте публікації Марка Мерфі: Будьте обережні з обмеженим доступом до каталогів . Це застаріло в Android Q :

Зверніть увагу, що доданий у 7.0 доступ до каталогів із обмеженими можливостями застарілий в Android Q.

Зокрема, createAccessIntent()метод StorageVolume застарілий.

Вони додали, createOpenDocumentTreeIntent()що можна використовувати як альтернативу.


8. Android 8.0 Oreo .. Зміни бета-версії Android Q.

Починаючи з Android O , Storage Access Framework дозволяє постачальникам користувацьких документів створювати доступні дескриптори файлів для файлів, що перебувають у віддаленому джерелі даних ...

Права доступу , до Android O , якщо додаток запросив дозвіл під час виконання і дозвіл було отримано, система також неправильно надав АРР , інші дозволи, які належали до тієї ж групи дозволів, і які були зареєстровані в маніфесті.

Для програм, націлених на Android O , цю поведінку виправлено. Додаток отримує лише ті дозволи, які він прямо просив. Однак, як тільки користувач надає дозвіл додатку, усі подальші запити на отримання дозволів у цій групі дозволів автоматично надаються.

Наприклад, READ_EXTERNAL_STORAGEі WRITE_EXTERNAL_STORAGE...

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

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

Примітка: права доступу до конкретних медіаколекції , які були введені в попередніх бета releases- 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 (Framework Access Framework)

SAFFAQ: Поширені запитання про Framework Access Framework


10. Пов’язані помилки та проблеми.

Помилка: На Android 6 під час використання getExternalFilesDirs це не дозволить створювати нові файли в його результатах

Запис у каталог, повернутий getExternalCacheDir () на Lollipop, неможливий без дозволу на запис


2
Дуже глибокий аналіз. +1. Хоча я повинен зазначити, я не писав цього методу. Це з іншого джерела пару років тому :) Сподіваюся, це йому допоможе.
IAmGroot

Дякую за повну відповідь. Мені здається, що проблема тут полягає в тому, що я повинен використовувати SAF, щоб писати в кореневій частині моєї SD-карти, замість того, щоб використовувати @Doomsknight метод 1, оскільки він не буде працювати на більш високих рівнях API, тоді як нижче KitKat я можу його використовувати.
Vektor88

Np, якщо вам потрібно написати за межами вашої програми, це правильний спосіб використання офіційного API . Здається, тут вирішено подібну проблему, я перевірю це іншим днем.
Альбоделу

Як ES File Explorer у 4.4.2 телефонів, що створюють папки? @albodelu Чи можете ви пояснити
karanatwal.github.io

У пункті 3 є посилання, де пояснюється, як це зробити для кореневих пристроїв. У цьому додатку є можливість вимкнути обмеження, додані в kitKat: Інструмент> Root Explorer> Mount R / W
albodelu

10

Я вважаю, що для досягнення цього існує два методи:

МЕТОД 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;
}

МЕТОД 2:

Використовуйте бібліотеку підтримки v4

import android.support.v4.content.ContextCompat;

Просто зателефонуйте нижче, щоб отримати список Fileмісць зберігання.

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

Однак місця використання відрізняються між собою.

Повертає абсолютні шляхи до специфічних каталогів на всіх зовнішніх пристроях зберігання, де програма може розміщувати постійні файли, якими вона володіє. Ці файли є внутрішніми для програми та зазвичай не видно користувачеві як носій.

Повернуті сюди зовнішні пристрої зберігання даних вважаються постійною частиною пристрою, включаючи як емульовані зовнішні накопичувачі, так і слоти для фізичних носіїв, такі як SD-карти в батарейному відсіку. Повернуті шляхи не включають перехідні пристрої, такі як флеш-накопичувачі USB.

Програма може зберігати дані на будь-якому або всіх повернутих пристроях. Наприклад, програма може вибрати зберігання великих файлів на пристрої з найбільшим доступним простором

Більше інформації про ContextCompat

Вони схожі на певні файли додатків. Приховано від інших програм.


1
Будь ласка, поділіться посиланням, звідки ви отримали код методом 1. Кредит, де він належить. Дякую.
Євген Печанець

2
@EugenPechanec Я повністю згоден. однак це було багато років тому, я взяв цей код із фантастичного джерела. Буду шукати
IAmGroot

1
Мені незрозуміло, що мені робити з цими шляхами: Якщо пристрій не викорінено, я не отримаю доступ до запису на SDCard, вказавши її абсолютний шлях. Я помиляюся? Можливо, я можу зберегти деякі дані у спеціальній папці додатків у /external_sd/data/data/com.myapp ..., але я не зможу писати в / external_sd / MyApp / Images
Vektor88

1
@ vektor88 Ви можете отримати до нього чудовий доступ без укорінення. Я і мої клієнти ніколи не користуємось корінними пристроями. Яку помилку ви отримуєте? Ви додали дозвіл. І як зазначено нижче, ви націлюєтесь на останню версію та вимагаєте дозволу під час виконання. Змініть ціль на 20 і перевірте, чи вона працює, перш ніж вирішувати запит на отримання дозволу на час виконання. В іншому випадку повідомте про помилку.
IAmGroot,

1
@ Vektor88 Ця концепція суто як тест, щоб перевірити, чи зараз у вас виникли проблеми з дозволами. Якщо ви націлюєтесь на api 23, тоді це ставить питання, чи ви запитували дозволи на час запуску. Хоча це окреме питання про бажання отримати місце для зберігання на картці SDCard (як спочатку ставили під сумнів). developer.android.com/training/permissions/requesting.html
IAmGroot

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's method is the best option here
    }

    return externalStorage;
}

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


Дякую @ th3pat3l isExternalStorageRemovablereference developer.android.com/reference/android/os/…
albodelu

1
Це працює лише для доступу до підкаталогу зовнішньої SD-карти на основі вашої програми: наприклад, на моєму телефоні LG він дає мені: /storage/0403-0201/Android/data/com.your.application/files - якщо ви хочете додати файл до каталогу DCIM - навіть якщо у вас є дозволи у вашому маніфесті, і ви просите їх під час виконання, і ви заходите у свою програму-> дозволи під налаштуванням та вмикаєте сховище ВСЕ ЩО ОТРИМАЄТЕ СУМІСТЬ (такого файлу чи каталогу немає) ) помилка, якщо ви намагаєтеся створити та відкрити там файл. Принаймні так відбувається на моєму LG Aristo SDK 23. Єдине, що я бачу, що працює, це SAF.
MarkJoel60,

isExternalStorageRemovableце лише для> = 21, чи можна визначити, чи є накопичувач знімним (карта microsd) на <= 19 пристроях API, реалізуючи якось подібний метод?
user924

3

На додаток до всіх інших приємних відповідей, я міг би додати ще трохи до цього питання, щоб воно могло надати ширше охоплення читачам. У своїй відповіді тут я використав би 2 лічильні ресурси для презентації зовнішнього сховища.

Перший ресурс - з Android Programming, The Big Nerd Ranch Guide 2nd edition , глава 16, сторінка 294.

У книзі описані основні та зовнішні методи файлів та каталогів. Я спробую скласти резюме того, що може мати відношення до вашого запитання.

Наступна частина з книги:

Зовнішнє сховище

Вашій фотографії потрібно більше, ніж місце на екрані. Повнорозмірні зображення занадто великі, щоб їх можна було залишити всередині бази даних SQLite, а тим більше - Intent. Їм знадобиться місце для проживання у файловій системі вашого пристрою. Зазвичай ви зберігаєте їх у своєму приватному сховищі. Згадайте, що ви використовували приватне сховище для збереження бази даних SQLite. З такими методами, як Context.getFileStreamPath(String)і 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 Ranch Guide з дозволу авторів. Щоб отримати додаткову інформацію про цю книгу або придбати її примірник, відвідайте bignerdranch.com.


0

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

По суті, він читає доступні точки монтування, фільтрує недійсні, перевіряє решту, чи є вони доступними, і додає їх, якщо всі умови виконані.

Звичайно, необхідні дозволи повинні бути надані перед викликом коду.

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

-1

Ось спосіб створення нового файлу у зовнішньому сховищі (якщо 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)


-3

Для версій нижче Marshmallow ви можете безпосередньо надати дозволи в маніфесті.

Але для пристроїв із Зефіром і вище потрібно надати дозволи на час роботи.

З допомогою

Environment.getExternalStorageDirectory();

Ви можете безпосередньо отримати доступ до зовнішньої SD-карти (вмонтованої). Сподіваюся, це допоможе.


Environment.getExternalStorageDirectory();надасть вам шлях до внутрішньої SD-карти пристрою.
Vektor88

Тепер від Android Lollipop ви отримуєте два варіанти: використовувати зовнішню SD-карту як внутрішню пам’ять або як типову SD-карту, це також залежить від цього вибору.
Geet Choubey,


2
Цей фрагмент коду дає мені шлях, де встановлена ​​зовнішня SD-карта. Що тоді? Я все ще не можу на ньому писати.
Vektor88

Яку помилку ви отримуєте при спробі записати на зовнішню SD-карту?
Geet Choubey,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.