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

Принудительный доступ к внешней съемной карте microSD

Я использую Samsung A3, Android 5.0.2. Я использую эту настройку для компиляции приложений, то есть Android 4.1 Jelly Bean (API 16).

Я точно знаю путь к внешней съемной карте microSD, это /mnt/extSdCard/ (см. также Примечание № 7 ниже).

Проблема: я замечаю, что

File myDir = new File("/mnt/extSdCard/test");
myDir.mkdirs();

не работает: каталог не создается.

также:

File file = new File("/mnt/extSdCard/books/test.txt");   // the folder "books" already exists on the external microSD card, has been created from computer with USB connection
FileOutputStream fos = new FileOutputStream(file);

вызывает эту ошибку:

java.io.FileNotFoundException:/mnt/extSdCard/books/test.txt: open failed: EACCES (Permission denied) в libcore.io.IoBridge.open(...

Как заставить читать + писать записи на внешнюю съемную карту microSD?

Примечания:

  • Environment.getExternalStorageDirectory().toString() дает /storage/emulated/0, который является моим внутренним хранилищем для телефона, то есть не тем, что я хочу.

  • getExternalFilesDir(null) дает /storage/emulated/0/Android/data/com.blahblah.appname/files/ то есть не то, что я хочу. Обратите внимание, что я не могу использовать getExternalFilesDirs с окончательным s, потому что это недоступно в API16. Кроме того, в API16 также не доступны разрешения времени выполнения.

  • У меня уже есть <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />, а также READ_EXTERNAL_STORAGE.

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

  • Я не хочу ACTION_OPEN_DOCUMENT и ACTION_CREATE_DOCUMENT, на самом деле я не хочу никакого GUI-решения.

  • Некоторые приложения, которые у меня есть (Sync Resilio) , могут успешно изменять /mnt/extSdCard/music/, создавать там новые файлы и т.д.

  • Кстати, ls -la /mnt/extSdCard/ дает

    drwxrwx--x root     sdcard_r          2017-10-15 01:21 Android
    drwxrwx--- root     sdcard_r          2017-10-14 00:59 LOST.DIR
    drwxrwx--- root     sdcard_r          2017-12-05 16:44 books
    drwxrwx--- root     sdcard_r          2017-11-21 22:55 music
    
4b9b3361

Ответ 1

помните, что некоторые устройства Android будут иметь другой путь для SD-карты, а некоторые не имеют съемной SD-карты.

Вам не нужно устанавливать путь напрямую!

File myDir = new File("/mnt/extSdCard/test");
myDir.mkdirs();

Сначала вы можете проверить, смогло ли устройство смонтировать съемную SD-карту:

public static boolean isSDCardAvailable(Context context) {
    File[] storages = ContextCompat.getExternalFilesDirs(context, null);
    if (storages.length > 1 && storages[0] != null && storages[1] != null)
        return true;
    else
        return false;
}

Зачем получать внешние каталоги > 1, так как большинство устройств Android имеют внешнее хранилище в качестве основного каталога и съемную SD-карту в качестве второго каталога:

ознакомление с описанием образа жизни

Но вы можете использовать метод, чтобы получить реальный путь к вашей съемной карте microSD:

public static String getRemovableSDCardPath(Context context) {
    File[] storages = ContextCompat.getExternalFilesDirs(context, null);
    if (storages.length > 1 && storages[0] != null && storages[1] != null)
        return storages[1].toString();
    else
        return "";
}

Затем просто сделайте следующее:

File myDir = new File(getRemovableSDCardPath(getApplicationContext()),"test");
if(myDir.mkdirs()){
  Log.i(TAG, "Directory was succesfully create!");
}else{
  Log.i(TAG, "Error creating directory!");
}

Например, используя метод:

   String pathSDCard = getRemovableSDCardPath(getApplicationContext());

В результате у меня есть путь к моей съемной SD-карте (если бы у меня не было сменной SD-карты, мой путь был бы "", поэтому вы можете выполнить проверку, чтобы избежать создания папки):

/storage/extSdCard/Android/data/com.jorgesys.myapplication/files

Теперь создадим новую папку внутри:

    File myDir = new File(getRemovableSDCardPath(getApplicationContext()),"test");
    if(myDir.mkdirs()){
        Log.i(TAG, "Directory was succesfully create!");
    }else{
        Log.i(TAG, "Error creating directory!");
    }

теперь у меня есть каталог /test created:

введите описание изображения здесь

Ответ 2

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

Документы DocumentFile для Android и Отсюдa >

Я тестировал код 5.1.1 и 6.0.1 не на оставшемся устройстве, я его не тестировал, но он должен работать нормально.

В 5.0.2 для записи на внешнем устройстве вам нужно будет спросить разрешения пользователя.

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

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 25);

теперь в onActivityResult сохраните возврат UriTree API, как вам понадобится позже.

 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == 25&&resultCode == RESULT_OK) {
            getContentResolver().takePersistableUriPermission(data.getData(), Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    } 
    super.onActivityResult(requestCode, resultCode, data);
}

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

Чтобы получить документ UriTree, используйте приведенный ниже код.

public static DocumentFile getDocumentFile(final File file) {
        String baseFolder = getExtSdCardFolder(file);
        String relativePath = null;

        if (baseFolder == null) {
            return null;
        }

        try {
            String fullPath = file.getCanonicalPath();
            relativePath = fullPath.substring(baseFolder.length() + 1);
        } catch (IOException e) {
            Logger.log(e.getMessage());
            return null;
        }
        Uri treeUri = Common.getInstance().getContentResolver().getPersistedUriPermissions().get(0).getUri();

        if (treeUri == null) {
            return null;
        }

        // start with root of SD card and then parse through document tree.
        DocumentFile document = DocumentFile.fromTreeUri(Common.getInstance(), treeUri);

        String[] parts = relativePath.split("\\/");

        for (String part : parts) {
            DocumentFile nextDocument = document.findFile(part);
            if (nextDocument != null) {
                document = nextDocument;
            }
        }

        return document;
    }


    public static String getExtSdCardFolder(File file) {
        String[] extSdPaths = getExtSdCardPaths();
        try {
            for (int i = 0; i < extSdPaths.length; i++) {
                if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
                    return extSdPaths[i];
                }
            }
        } catch (IOException e) {
            return null;
        }
        return null;
    }


@TargetApi(Build.VERSION_CODES.KITKAT)
public static String[] getExtSdCardPaths() {
    List<String> paths = new ArrayList<>();
    for (File file : Common.getInstance().getExternalFilesDirs("external")) {

        if (file != null && !file.equals(Common.getInstance().getExternalFilesDir("external"))) {
            int index = file.getAbsolutePath().lastIndexOf("/Android/data");
            if (index < 0) {
                Log.w("asd", "Unexpected external file dir: " + file.getAbsolutePath());
            } else {
                String path = file.getAbsolutePath().substring(0, index);
                try {
                    path = new File(path).getCanonicalPath();
                } catch (IOException e) {
                    // Keep non-canonical path.
                }
                paths.add(path);
            }
        }
    }
    return paths.toArray(new String[paths.size()]);
}

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

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

Надеюсь, что это поможет в случае сомнений, дайте мне знать.

Забыл сказать, что я задал тот же вопрос некоторое время назад Здесь этот вопрос.

Edit: Используя этот код, вы можете проверить, дал ли пользователь разрешение не на

public static boolean hasPermission() {
    List<UriPermission> uriPermission = Common.getInstance().getContentResolver().getPersistedUriPermissions();
    return uriPermission != null && uriPermission.size() > 0;
}

если разрешение отменено, тогда не будет UriTree, поэтому вам придется снова запрашивать разрешение.

Ответ 3

На основе Doomsknight answer и , и Дэйв Смит и Марк Мурфи сообщения в блогах: 1, 2, 3:

Создать мои пакеты Каталог данных Android в порядке для первичных и вторичных хранилищ:

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

Теперь разрешение android.permission.WRITE_EXTERNAL_STORAGE предоставляет членство в sdcard_r И sdcard_rw...

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

ПРИМЕЧАНИЕ. В списках ls -la /mnt/extSdCard/... группа sdcard_r имеет полные + rwx-разрешения, что, очевидно, неверно на практике... потому что демон FUSE является активным участником модификации примененных разрешений для приложений во время выполнения.

Samsung: пример из практики

В Android 4.3 Samsung эмулирует основной объем внешней памяти на внутренней вспышке устройства (НЕ сменная SD-карта). SD карта всегда была помечена как вторичная внешняя носитель информации...

В Android 4.4 основной объем внешней памяти STILL включен внутренняя вспышка.

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

Update:

Как объясняется здесь и отвечая на ваш комментарий об использовании корневого пути для обмена файлами:

Вы можете до Kikat использовать Doomsknight method 1 и if/else код на основе целевой версии или создание нескольких APK.

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

Почему сейчас?

Ответ, в сокращенном виде, CTS.

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

Однако новые тесты были добавлены в CTS для 4.4, подтверждающие правильность наличия вторичного хранилища разрешений только для чтения в каталогах, не относящихся к конкретным приложениям, предположительно из-за новых API-интерфейсов, чтобы, наконец, выявить эти пути для приложения Разработчики. Как только CTS включает эти правила, OEM-производители должны поддерживать чтобы они сохраняли устройства доставки с GMS (Google Play и т.д.) на борту.

Что такое совместное использование файлов?

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

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