您的 Gmail 问题很可能来自 ContentProvider 生命周期。
ContentProvider 实际上只是IPC Binder,与那些非常相似,通常从Service#onBind
. 但是客户端每次发出请求时都通过 ContentResolver 间接获取它,而不是绑定到您的应用程序。通常没有显式解除绑定,Android 系统会在每个 IPC 请求完成后将提供程序缓存一小段时间。
不幸的是,隐式 ContentProvider 绑定的隐藏特性意味着无法立即释放提供者。更糟糕的是,没有办法处理有问题的提供程序中的错误——如果您的 ContentProvider 在返回 Cursor 或 ParcelFileDescriptor 之前崩溃,调用应用程序将立即与您一起崩溃!谷歌显然知道这一点,因此他们创建了另一个 API 用于与不信任的第三方 ContentProvider 交互 - ContentProviderClient。请注意, ContentProviderClient 包含处理远程异常和进程死亡的方法以及显式关闭 ContentProvider 的方法。
现在想象一下假设的 Gmail ContentProvider 工作流程:
ParcelFileDescriptor fd = null;
try (ContentProviderClient c = resolver.acquireUnstableContentProviderClient(...)) {
fd = c.openFile(...)
} catch (Exception ohThoseBuggyProviders) {
...
}
// here ContentProvider is already closed
if (fd != null) {
// use the received descriptor to create email attachment
...
}
但是,如果您的 ContentProvider 想要在后台线程中从服务器读取其余文件的时间更长一点怎么办?好吧,无论如何,系统很可能会杀死您的进程,因为它不知道您想要那个。您的进程终止,Gmail 收到“管道损坏”错误。
这就是为什么您不应该创建新线程或使用ContentProvider#openPipeHelper
(为什么该方法甚至存在?),只需在调用线程中完成所有工作。
您问题第二部分的答案也在于 ContentProvider 内部。当您的提供程序从调用应用程序的主线程调用时,您的代码不会在进程的主线程上执行——它像往常一样在 Binder 线程池中执行。但为了让程序员的生活更轻松,Android 采取了几个步骤来让这一点变得不那么明显:
- 您的线程的优先级设置为线程的优先级,这使得调用(包括提高到 UI 优先级,如果您从 UI 线程调用)。
- Android将当前的严格模式设置(当它们在主线程上联网时使应用程序崩溃的东西)从调用应用程序传递到您的线程。呼叫完成后,将收集所有严格模式违规并将其写入 Parcel发送回呼叫应用程序。
即使 ContentProvider 操作在绑定池中执行,它们的行为也几乎就像进程之间没有界限一样——包括当有人试图从 UI 线程下载文件时发生的坏-坏-坏事情。
您应该能够通过使用android.os.StrictMode摆脱那个令人讨厌的“帮助” ,但是如果有问题的文件太大(ANR 可能仍然在调用过程中发生),那将不会这样做。而不是从 ParcelFileDescriptor 下载文件以通过管道openDocument
返回socket。
为什么 Dropbox 没有遇到这个问题?因为 Dropbox Core 是用 C++ 编写的,而 Android Strict Mode 目前是仅限 Java 的构造,所以它不会挂钩到本机代码。如果您使用 C 库调用在主线程中写入磁盘或从网络下载,您的应用将不会收到任何影响(除了 ANR,它在独立于严格模式的 UI 线程上触发)。