Читаем/записываем файл в Андроиде без запроса пермишинсов
Я решил написать еще одну статью об элементарных для опытного разработчика вещах, но вызывающих проблемы у новичков. Если погуглить вопрос как прочитать файл, то в основном попадуться старые статьи с советом, что Вам нужно запросить разрешение.
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");
на гитхабе в качестве примера выложен Очень Простой Редактор:)