java.io.IOException: невозможно внести изменения в файл
Я использую библиотеку JAudioTagger для чтения и записи тегов для аудиофайла. Я могу читать теги, но не могу их написать.
Я получаю путь к аудиофайлу следующим образом:
private String getSongPath(long songId) {
String path = null;
ContentResolver contentResolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection = {MediaStore.Audio.Media.DATA};
String selection = MediaStore.Audio.Media._ID + " == ?";
String[] selectionArgs = {String.valueOf(songId)};
Cursor cursor = contentResolver.query(uri, projection, selection, selectionArgs, null);
if (cursor != null) {
int pathCol = cursor.getColumnIndexOrThrow(projection[0]);
cursor.moveToFirst();
path = cursor.getString(pathCol);
cursor.close();
}
return path;
}
Затем, чтобы написать теги, используя JAudioTagger:
File songFile = new File(path); // path looks like /storage/3932-3434/Music/xyz.mp3
AudioFile audiofile = = AudioFileIO.read(songFile);
Tag tag = = audiofile.getTag();
tag.setField(FieldKey.TITLE, title);
// some more setField calls for different feilds
audiofile.commit();
Метод commit() дает следующее исключение:
org.jaudiotagger.audio.exceptions.CannotWriteException: java.io.IOException: невозможно внести изменения в файл xyz.mp3 в org.jaudiotagger.audio.mp3.MP3File.commit(MP3File.java:799) в com.techapps.musicplayerplus.MainActivity$17.onClick(MainActivity.java:2125) на android.support.v7.app.AlertController$ButtonHandler.handleMessage(AlertController.java:157) на android.os.Handler.dispatchMessage(Handler.java:102) на андроиде.os.Looper.loop(Looper.java:148) на android.app.ActivityThread.main(ActivityThread.java:5417) 06-18 10:59:48.134 8802-8802/com.techapps.musicplayerplus W/System.err:
на java.lang.reflect.Method.invoke(собственный метод) на com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) на com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) Причина: java.io.IOException: Невозможно внести изменения в файл Saibo.mp3 в org.jaudiotagger.audio.mp3.MP3File.precheck(MP3File.java:824) в org.jaudiotagger.audio.mp3.MP3File.save(MP3File.java:850) в org.jaudiotagger.audio.mp3.MP3File.save(MP3File.java:783) в org.jaudiotagger.audio.mp3.MP3File.commit(MP3File.java:795)
Я запускаю этот код на Android 6, в то время как мое приложение предназначено для SDK 22. Я также упомянул следующее разрешение в манифесте.
android.permission.WRITE_EXTERNAL_STORAGE
Я все еще не могу записать на SD-карту. Пожалуйста, помогите мне. Заранее спасибо.
4 ответа
Вы должны использовать Storage Access Framework (SAF) для доступа к SD-карте с API 19 (Kitkat) и далее.
Сначала нам нужно попросить пользователя указать URI папки, к которой мы хотим получить доступ. Если мы хотим получить доступ ко всей SD-карте, пользователь должен предоставить URI корневой папки SD-карты. Например, когда пользователь нажимает кнопку "Изменить", нам сначала нужно отобразить диалоговое окно с подсказкой, в котором пользователь должен выбрать нужный каталог на SD-карте, к которому мы хотим получить доступ. Вы можете отобразить следующее изображение в диалоговом окне подсказки, чтобы попросить пользователя выбрать корневой каталог SD-карты:
Когда пользователь закрывает диалоговое окно подсказки, необходимо запустить Storage Access Framework:
private void triggerStorageAccessFramework() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCESS); } public final void onActivityResult(final int requestCode, final int resultCode, final Intent resultData) { if (resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_CODE_STORAGE_ACCESS) { Uri treeUri = null; // Get Uri from Storage Access Framework. treeUri = resultData.getData(); pickedDir= DocumentFile.fromTreeUri(this, treeUri); if (!isSDCardRootDirectoryUri(treeUri)) { Toast.makeText(this, "Wrong directory selected. Please select SD Card root directory.", Toast.LENGTH_LONG).show(); createSDCardHintDialog().show(); return; } // Persist URI in shared preference so that you can use it later. SharedPreferences sharedPreferences = getSharedPreferences(App.PREFERENCE_FILENAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString(App.SDCARD_URI_KEY, treeUri.toString()); editor.apply(); // Persist access permissions, so you dont have to ask again final int takeFlags = resultData.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } } private boolean isSDCardRootDirectoryUri(Uri treeUri) { String uriString = treeUri.toString(); return uriString.endsWith("%3A"); }
Как только вы получите Uri выбранного пользователем каталога, вы можете выполнить операцию записи, используя SAF: (creadit: this answer)
public void writeFile(DocumentFile pickedDir) {
try {
DocumentFile file = pickedDir.createFile("image/jpeg", "try2.jpg");
OutputStream out = getContentResolver().openOutputStream(file.getUri());
try {
// write the image content
} finally {
out.close();
}
} catch (IOException e) {
throw new RuntimeException("Something went wrong : " + e.getMessage(), e);
}
}
Вы заявили разрешение <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
?
Возможно, вы указываете на несуществующий файл.
Проверьте ваш путь к файлу с помощью журнала.
Log.d("Activity", "path = " + path);
Android-M или API 23 представили Runtime Permissions для уменьшения уязвимостей в устройстве Android.
Чтобы обновить приложения с помощью сервисов Google Play для обработки разрешений Android 6.0, рекомендуется управлять ожиданиями пользователей при настройке разрешений, которые могут потребоваться во время выполнения. Следующая ссылка поможет вам избежать потенциальных проблем.
https://developer.android.com/training/permissions/requesting.html
Я видел, что вы уже создали проблему в репозитории JAudioTagger GitHub, что было желательно, но так и не было найдено универсального рабочего решения. Мои выводы на данный момент:
- Ответ, в котором упоминается SAF, правильный, но он вам не поможет, поскольку SAF предоставит DocumentFile, а не File.
- Вы можете попробовать изменить JAudioTagger в соответствии с вашими потребностями, заменив File на DocumentFile, но последний имеет не все функции, которые вам понадобятся.
- Также InputStream и OutputStream вам не помогут, поскольку JAudioTagger нуждается в File и внутренне активно использует RandomAccessFile, который также недоступен.
- Google "забыл" предоставить некоторую getRandomAccessFileFromUri(), что еще больше усугубляет ситуацию (да, есть хаки, использующие отражение Java, чтобы обойти это ограничение...).
- Метод "/proc/self/fd" ( как обрабатывать SAF, когда я могу обрабатывать только файл или путь к файлу?) Также не будет работать сразу, поскольку JAudioTagger требует функций копирования и переименования, которые не применимы к файлам такого типа. В частности, JAudioTagger не найдет подходящего расширения имени файла, такого как ".m4a". Конечно, вы можете попробовать изменить JAudioTagger соответствующим образом.
- Вы можете последовать совету, чтобы сделать копию файла в свое личное хранилище, затем применить к нему JAudioTagger и, наконец, скопировать его обратно на SD-карту, но:
- Если вы хотите использовать JAudioTagger для чтения с SD-карты, это, как объявил Google, не удастся с Android 10. Начиная с этой версии, у вас даже не будет доступа для чтения к SD-карте через файловый интерфейс.
- Кроме того, файловый интерфейс дает вам доступ для чтения к SD-картам с Android 9 и ниже, но не к другим устройствам SAF, таким как память USB OTG или общие ресурсы SMB и т. Д.
- Конечно, вы также можете скопировать каждый файл, чтобы прочитать его метаданные, но это будет очень медленно и не подходит, если у вас больше нескольких файлов.
Итак, мои текущие советы:
- Попробуйте использовать метод "/proc/self/fd" и соответствующим образом измените JAudioTagger.
- Если изменения слишком большие, используйте метод fd для чтения тегов и метод копирования для записи.
Кстати: в настоящее время я модифицирую старую версию JAudioTagger для прозрачного использования как File, так и DocumentFile, но изменения огромны, сопряжены с высоким риском, требуются некоторые вспомогательные классы, а работа еще не завершена.
BTSW: Функции DocumentFile мучительно медленны по сравнению с функциями File.