传递读取的 FD parcelable:何时关闭?

Posted

技术标签:

【中文标题】传递读取的 FD parcelable:何时关闭?【英文标题】:Passing a read FD parcelable: when to close? 【发布时间】:2019-05-30 10:08:51 【问题描述】:

为了通过 binder 传递大量数据,我们创建了一个管道,然后将管道的读取端作为 ParcelFileDescriptor 传递到 binder,并启动一个线程将数据写入管道的写入端。基本上是这样的:

  public void writeToParcel(Parcel out, int flags) 
    ParcelFileDescriptor[] fds;
    try 
      fds = ParcelFileDescriptor.createPipe();
     catch (IOException e) 
      throw new RuntimeException(e);
    
    out.writeParcelable(fds[0], 0);
    byte[] bytes = ...; // Marshall object data to bytes
    write(bytes, fds[1]); // Starts a thread to write the data
  

接收端从管道的读取端读取数据。它看起来像这样:

ParcelFileDescriptor readFd = in.readFileDescriptor();

FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(readFd);
ByteArrayOutputStream out = new ByteArrayOutputStream();

byte[] b = new byte[16 * 1024];
int n;

try 
  while ((n = fis.read(b)) != -1) 
    out.write(b, 0, n);
  
 catch (IOException e) 
  throw new RuntimeException(e);
 finally 
  try 
    Log.i(TAG, "Closing read file descriptor..."); // I see this
    fis.close();
    Log.i(TAG, "Closed read file descriptor"); // And I see this
   catch (IOException e) 
    e.printStackTrace();
  

这可行,但是当启用严格模式时,我们会崩溃:

 01-03 14:26:48.099 E/StrictMode(25346): A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks. 
 01-03 14:26:48.099 E/StrictMode(25346): java.lang.Throwable: Explicit termination method 'close' not called 
 01-03 14:26:48.099 E/StrictMode(25346):    at dalvik.system.CloseGuard.open(CloseGuard.java:223) 
 01-03 14:26:48.099 E/StrictMode(25346):    at android.os.ParcelFileDescriptor.<init>(ParcelFileDescriptor.java:192) 
 01-03 14:26:48.099 E/StrictMode(25346):    at android.os.ParcelFileDescriptor.<init>(ParcelFileDescriptor.java:181) 
 01-03 14:26:48.099 E/StrictMode(25346):    at android.os.ParcelFileDescriptor.createPipe(ParcelFileDescriptor.java:425) 
 01-03 14:26:48.099 E/StrictMode(25346):    at com.clover.sdk.FdParcelable.writeToParcel(FdParcelable.java:118) 

第 118 行是管道的创建 (ParcelFileDescriptor.createPipe())。

所以看来发送者需要关闭读端和写端。我的问题是我不知道什么时候可以关闭阅读端,因为我不知道阅读器什么时候会读完。

我错过了什么?

【问题讨论】:

【参考方案1】:

    写完后立即关闭输出流。

    消费者始终负责在完成读取后立即关闭其输入流。这不是您的责任(除非您也是消费者)。

您所描述的类似于我打开 FileOutputStream(我正在使用 API)并期望运行时为我关闭它,只是因为我在使用完它时没有明确关闭它。

因此,将 FD 发送到 Parcel,使用它的人负责关闭它。他们可以使用这样的东西:

val fd = parcel.readFileDescriptor()
val input = ParcelFileDescriptor.AutoCloseInputStream(fd)
// Use input. When #close() is called it will also close the FD.

见:https://developer.android.com/reference/android/os/ParcelFileDescriptor.AutoCloseInputStream

您可以在客户端 SDK 库中隐藏此实现。无论如何,为消费者做好记录。

【讨论】:

谢谢。是的,我正在这样做。我仍然在服务端遇到严格模式异常。我将更新我的答案以显示接收方代码。 另外,我正在查看 ParcelFileDescriptor 的 impl。 closeguard 在该类的终结器中触发。它不会对另一侧关闭进行任何检查。它只是检查是否有人在 ParcelFileDescriptor 的实例上明确调用了 close() 所以服务器端的引用被垃圾收集,而实际的文件描述符仍在使用中并且在客户端有效。您需要保留对 Parcel 中传递的 FD 的强引用。我们又回到了不知道读取何时完成的问题。如果底层 FD 有效,您需要定期在服务器端检查。至少在我的书中,这还不够好。 在服务未绑定时删除 FD 引用应该是安全的……实际上,此时您无论如何都应该关闭所有 FD。所以维护一组交付的 FD,在onUnbind 中将它们全部关闭并删除,并可能在每个onBind 中通过getFileDescriptor().valid() 过滤集合。 这是我发现的:根据c pipe example 一个进程应该关闭不必要的 FD。这意味着您的服务应该在写入数据后关闭正在写入的 FD,在将其写入包裹并将该包裹交付给其他进程后关闭读取 FD,并且您的客户端应该在读取后关闭读取的 FD .你能进一步检查吗?

以上是关于传递读取的 FD parcelable:何时关闭?的主要内容,如果未能解决你的问题,请参考以下文章

Linux简单的文件读取

Android Parcelable 转换错误

如何传递包含另一个 Parcelable 类列表的 Parcelable 类?

Android - 将 Parcelable 数据保存到文件中

Intent使用Parcelable传递对象

序列化与 Parcelable Android