Android java.io.IOException: 写入失败:EBADF(错误的文件编号)

9

我已经阅读了几篇关于EBADF错误的文章,但没有解决我的问题。我的情况独特之处在于,我正在尝试写入getFilesDir,这应该是应用程序可写的。

当执行encrypt方法时,我遇到了EBADF错误。文件参数是使用以下方式创建的:

new File(mContext.getFilesDir(), "file.dat")

加密方法列表在哪里:

public static void encrypt(File file, String password, List<Object> objects) {

    byte[] salt = generateSalt();
    byte[] iv = generateIV();
    Cipher c = createCipher(password, salt, Cipher.ENCRYPT_MODE, iv);

    try (FileOutputStream fos = new FileOutputStream(file)) {
        fos.write(salt);
        fos.write(iv);
        try (CipherOutputStream cos = new CipherOutputStream(fos, c);
        ObjectOutputStream oos = new ObjectOutputStream(cos)) {
            for (Object o : objects) {
                oos.writeObject(o);
            }
        }

    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

有人能看出我为什么会得到这个异常吗?

这是异常日志:

 Caused by: java.lang.RuntimeException: java.io.IOException: write failed: EBADF (Bad file number)
    at za.co.lot24media.password.util.EncryptUtil.encrypt(EncryptUtil.java:69)
    at za.co.lot24media.password.store.Store.save(Store.java:94)
    at za.co.lot24media.password.store.Store.createSamples(Store.java:179)
    at za.co.lot24media.password.store.Store.load(Store.java:76)
    at za.co.lot24media.password.activity.login.LoginAction$2.doInBackground(LoginAction.java:62)
    at za.co.lot24media.password.activity.login.LoginAction$2.doInBackground(LoginAction.java:55)
    at android.os.AsyncTask$2.call(AsyncTask.java:292)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
    at java.lang.Thread.run(Thread.java:818) 
 Caused by: java.io.IOException: write failed: EBADF (Bad file number)
    at libcore.io.IoBridge.write(IoBridge.java:502)
    at java.io.FileOutputStream.write(FileOutputStream.java:186)
    at java.io.OutputStream.write(OutputStream.java:82)
    at javax.crypto.CipherOutputStream.close(CipherOutputStream.java:129)
    at za.co.lot24media.password.util.EncryptUtil.encrypt(EncryptUtil.java:64)

问题似乎出在ObjectOutputStream写入CipherOutputStream时。当我从encrypt()方法中删除ObjectOutputStream时,该方法成功执行。以下代码有效:

    public static void encrypt(File file, String password, StoreDataRecord storeDataRecord) {

    byte[] salt = generateSalt();
    byte[] iv = generateIV();
    Cipher c = createCipher(password, salt, Cipher.ENCRYPT_MODE, iv);

    try (FileOutputStream fos = new FileOutputStream(file)) {
        fos.write(salt);
        fos.write(iv);

        try (CipherOutputStream cos = new CipherOutputStream(fos, c)) {
            cos.write(new byte[10]);
        }

    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

我使用byte [10]只是为了将任意数据写入流中。
** 编辑2 **
以下解决方案也有效,首先将数据写入ByteArrayOutputStream:
public static void encrypt(File file, String password, StoreDataRecord storeDataRecord) {

    byte[] salt = generateSalt();
    byte[] iv = generateIV();
    Cipher c = createCipher(password, salt, Cipher.ENCRYPT_MODE, iv);

    try (FileOutputStream fos = new FileOutputStream(file)) {
        fos.write(salt);
        fos.write(iv);

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bos);
             CipherOutputStream cos = new CipherOutputStream(fos, c)) {
            oos.writeObject(storeDataRecord.getVersion());
            oos.writeObject(storeDataRecord.getItems());
            cos.write(bos.toByteArray());
        }
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

发布异常日志。 - Themelis
1
你不需要遍历对象。写出List即可。 - user207421
最内层的try-with-resources是ObjectOutputStream,当块退出时将被关闭,这将关闭底层流和文件,这将关闭FileOutputStream,当需要刷新CipherOutputStream时,它将在关闭时引发此异常,显然它确实需要刷新。在(冗余的)循环之后添加oos.flush()以确保在关闭时CipherOutputStream不需要刷新。 - user207421
也许不是。尝试添加调试代码以显式检查并显示权限实际情况。如果 POSIX write() 调用 失败并设置了 errnoEBADF,只有一个原因:"EBADF fildes 参数未打开写入的有效文件描述符。使用 Java 很难获得完全无效的文件描述符 - 甚至不表示已打开的文件的值。因此,您可能没有文件的写入权限。它已经存在了吗? - Andrew Henle
在 za.co.lot24media.password.util.EncryptUtil.encrypt(EncryptUtil.java:69) 中执行了什么操作? - Hans Schreuder
显示剩余5条评论
1个回答

0

我看到你在doInBackground中调用了encrypt方法,这可能会导致一些复杂的情况,比如移动到另一个片段或创建两个片段实例,这会让Android感到困惑。最好的方法是当onDestroy()被调用时忽略encrypt方法。

并确保在使用流之前没有关闭它们。

希望能有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接