Я решил написать еще одну статью об элементарных для опытного разработчика вещах, но вызывающих проблемы у новичков. Если погуглить вопрос как прочитать файл, то в основном попадуться старые статьи с советом, что Вам нужно запросить разрешение.
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<String> mGetContent = registerForActivityResult( new ActivityResultContracts.GetContent(), this::fileSelected );
Это рекомендуемая сейчас замена для activityStartForResult (депрекейтед).
Таким образом для чтения любого файла нам не потребавалось никаких дополнительных разрешений. Доступ к файлу нам предоставил файловый менеджер смартфона.
После неявного вызова ACTION_GET_CONTENT, мы получили URI контента вместе с разрешением его прочитать.
Аналогично для записи:
private final ActivityResultLauncher<Intent> 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");
На гитхабе в качестве примера выложен Очень Простой Редактор :)
