java.io.IOException:无法更改文件

时间:2016-06-18 05:46:03

标签: android jaudiotagger

我正在使用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)at at   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)at   android.os.Looper.loop(Looper.java:148)at   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)at   com.android.internal.os.ZygoteInit $ MethodAndArgsCaller.run(ZygoteInit.java:726)   在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)引起   by:java.io.IOException:无法对文件Saibo.mp3进行更改   org.jaudiotagger.audio.mp3.MP3File.precheck(MP3File.java:824)at at   org.jaudiotagger.audio.mp3.MP3File.save(MP3File.java:850)at at   org.jaudiotagger.audio.mp3.MP3File.save(MP3File.java:783)at at   org.jaudiotagger.audio.mp3.MP3File.commit(MP3File.java:795)

我在Android 6上运行此代码,而我的应用程序针对的是SDK 22.我还在清单中提到了以下权限。

  

android.permission.WRITE_EXTERNAL_STORAGE

我仍然无法写入SD卡。请帮我。提前谢谢。

5 个答案:

答案 0 :(得分:1)

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

  1. 首先,我们需要让用户提供我们想要访问的文件夹的URI。如果我们想要访问整个SD卡,用户需要提供SD卡的根文件夹的URI。 例如,当用户点击“编辑”按钮时,我们必须先显示提示对话框,要求用户在SD卡中选择我们要访问的所需目录。您可以在提示对话框中显示以下图像,要求用户选择SD卡的根目录:You can use this image in hint dialog box to ask user to select SD Card root directory

  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");
    }
    
  3. 一旦你获得用户选择目录的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);
        }
    }
    

答案 1 :(得分:0)

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

使用日志检查路径文件。

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

答案 2 :(得分:0)

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

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

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

答案 3 :(得分:0)

您是否已宣布该许可 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

答案 4 :(得分:0)

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

  • 提及SAF的答案是正确的,但它不会帮助您,因为SAF将提供 DocumentFile ,而不是 File
  • 您可以尝试根据需要修改JAudioTagger,将 File 替换为 DocumentFile ,但后者并没有您需要的所有功能。
  • InputStream OutputStream 也无济于事,因为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方法写入。

BTW:我目前正在修改JAudioTagger的较旧版本,以透明地同时使用 File DocumentFile ,但是更改非常巨大,风险很高,需要一些帮助课,但工作尚未完成。

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