【问题标题】:java.io.IOException: Cannot make changes to filejava.io.IOException:无法更改文件
【发布时间】:2016-06-18 05:46:02
【问题描述】:

我正在使用 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) 在 android.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(Native Method) 在 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 卡。请帮我。提前致谢。

【问题讨论】:

  • 嗨!我有同样的问题(阅读下面的评论)!如果你解决了请告诉我!!

标签: android jaudiotagger


【解决方案1】:

您必须使用存储访问框架 (SAF) 从 API 19 (Kitkat) 开始访问 SD 卡。

  1. 首先我们需要让用户提供我们想要访问的文件夹的 URI。如果我们想访问整个 SD 卡,用户需要提供 SD 卡根文件夹的 URI。 例如,当用户点击编辑按钮时,我们必须首先显示提示对话框,要求用户选择我们想要访问的 SD 卡中所需的目录。您可以在提示对话框中显示以下图像,要求用户选择 SD 卡的根目录:

  2. 当用户关闭提示对话框时,需要触发存储访问框架:

    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);
    }
}

【讨论】:

    【解决方案2】:

    可能是您指向不存在的文件。

    使用 Log 检查您的路径文件。

    Log.d("Activity", "path = " + path);
    

    【讨论】:

    • 我在上面的代码中提到了路径示例。它看起来像 /storage/3932-3434/Music/xyz.mp3。我通过文件资源管理器检查了该文件及其那里。
    • 我也有同样的问题!我的路径是/storage/6332-6461/Music/xyz.mp3,我认为问题出在数字上。也许jaudiotagger不支持?如果你的问题解决了,请告诉我!
    【解决方案3】:

    Android-M 或 API 23 引入了运行时权限,以减少 android 设备中的安全漏洞。

    要使用 Google Play 服务更新您的应用以处理 Android 6.0 权限,最好管理用户在设置运行时可能需要的权限时的期望。以下link 将帮助您避免潜在问题。

    https://developer.android.com/training/permissions/requesting.html

    【讨论】:

    • 但是 AFAIK,由于我的应用程序针对 API 22,因此不需要运行时权限,并且在安装应用程序时授予 WRITE_EXTERNAL_STORAGE 权限时应用程序应该可以正常运行。
    【解决方案4】:

    您是否声明了权限 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>?

    【讨论】:

    • 我已经在我的问题中提到我已经声明了上述许可。
    【解决方案5】:

    我看到您已经在 J​​AudioTagger GitHub 存储库中创建了一个问题,这是可取的,但从未得到通用的解决方案。到目前为止我的发现:

    • 提到 SAF 的答案是正确的,但它对您没有帮助,因为 SAF 将提供 DocumentFile,而不是 File
    • 您可以尝试根据需要修改 JAudioTagger,将 File 替换为 DocumentFile,但后者并不具备您需要的所有功能。
    • 另外,InputStreamOutputStream 对你没有帮助,因为 JAudioTagger 需要 File 并且在内部大量使用 RandomAccessFile 这也不可用。
    • Google“忘记”提供一些 getRandomAccessFileFromUri(),这让事情变得更糟(是的,有一些黑客使用 Java 反射来解决这个限制......)。
    • “/proc/self/fd”方法 (How to handle SAF when I can only handle File or file-path?) 也不会立即起作用,因为 JAudioTagger 需要复制和重命名功能,这些功能不适用于此类文件。特别是 JAudioTagger 将找不到合适的文件扩展名,如“.m4a”。当然,您可以尝试相应地更改 JAudioTagger。
    • 您可以按照建议将文件复制到您的个人存储中,然后对其应用 JAudioTagger,最后将其复制回 SD 卡,但是:
    • 如果您想使用 JAudioTagger 从 SD 卡中读取,正如 Google 所宣布的,这将在 Android 10 上失败。从该版本开始,您甚至无法通过 文件读取 SD 卡 界面。
    • 此外,文件界面让您可以读取运行 Android 9 及更低版本的 SD 卡,但不能读取其他 SAF 设备,例如 USB OTG 内存或 SMB 共享等。
    • 当然,您也可以复制每个文件以读取其元数据,但这会非常慢,并且如果您有多个文件,则不适合。

    所以我目前的建议是:

    • 尝试“/proc/self/fd”方法并相应地修改 JAudioTagger。
    • 如果改动太大,使用fd方式读取标签,使用copy方式写入。

    顺便说一句:我目前正在修改旧版本的 JAudioTagger 以透明地使用 FileDocumentFile,但是更改是巨大的,承担着高风险,需要一些帮助上课了,工作还没有完成。

    BTSW:与 File 函数相比,DocumentFile 函数速度非常慢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-28
      • 2018-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-02
      • 1970-01-01
      相关资源
      最近更新 更多