Читаем/записываем файл в Андроиде без запроса пермишинсов

d80e9288a893d831eff86d342c75e07a.png

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

Manifest.permission.READ_EXTERNAL_STORAGE

Этот подход нормально работал до появления Андроид 11. Потом с помощью специального костыля можно было какое то время жить по старому.

Совсем упертые могут попробывать доказать модерации, что Вам нужен

android.permission.MANAGE_EXTERNAL_STORAGE

Для тех кому сразу интересен результат https://github.com/Muraveiko/EditorExample/blob/main/app/src/main/java/ru/a402d/verysimpleeditor/MainActivity.java#L73 для остальных продолжу по шагам.

Ребят я скажу только одну фразу, после которой все станет на свои места

В андроиде нет файлов

Загорелось меня поправить ? Готовы привести кучу примеров почему я не прав. Подождите. Поясню.

1. Забудьте о том как это работает в Windows/Unix (имя и путь к файлу). Фактически нам не нужно для чтения данных фактическое месторасположение файла и его наименование.

static String readTxtFile(InputStream inputStream) {
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        String line;
        try {
            br = new BufferedReader(new InputStreamReader(inputStream));
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
}

Данные мы читаем из потока, который получаем

getContentResolver().openInputStream(p)

где p — Uri — Универсальный идентификатор ресурса. Файл — это частный случай uri.

2. В адроиде отказались от схемы file: . Взамен ее используется content:

При взаимодействие программ между собой в андроиде используются контент провадеры.

Современный подход получить нужный uri очень просто

mGetContent.launch("*/*");

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

private final ActivityResultLauncher mGetContent = 
  registerForActivityResult(
            new ActivityResultContracts.GetContent(),
            this::fileSelected
 );

Это рекомендуемая сейчас замена для activityStartForResult (депрекейтед)

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

После неявного вызова ACTION_GET_CONTENT, мы получили URI контента вместе с разрешением его прочитать.

Аналогично для записи

private final ActivityResultLauncher mSaveContent = 
  registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            this::saveSelected
);

только нужный интент проще собрать ручками

Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TITLE, cTime+".txt");

на гитхабе в качестве примера выложен Очень Простой Редактор:)

© Habrahabr.ru