Ваша проблема с Gmail, скорее всего, связана с жизненным циклом ContentProvider.
ContentProvider на самом деле просто IPC Binder, очень похожи на те, которые обычно возвращаются из Service#onBind
. Но вместо привязки к вашему приложению клиенты получают его косвенно через ContentResolver каждый раз, когда они делают запрос. Обычно нет явной отмены привязки, система Android кэширует поставщика в течение короткого времени после завершения каждого запроса IPC.
К сожалению, скрытый характер неявной привязки ContentProvider означает, что нет возможности немедленно освободить поставщика. Хуже того, нет способа справиться с ошибками в багги-провайдерах - , если ваш ContentProvider сработает перед возвратом Cursor или ParcelFileDescriptor, вызывающее приложение немедленно спустится с вами! Google, очевидно, знает об этом, поэтому они создали другой API для взаимодействия с неверно доверенными сторонними ContentProviders - 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 занимает несколько шагов, чтобы сделать это менее очевидно:
- Приоритет вашей нити установлен приоритет нити, что делает вызов (в том числе повышение приоритету пользовательского интерфейса, если вы получаете вызванный из потока пользовательского интерфейса).
- Android passes Текущие настройки Strict Mode (вещи, которые приводят к сбою приложений при работе в основном потоке) из вызывающего приложения в ваш поток. По завершении вызова все нарушения Strict Mode собираются, и written to Parcel отправляется обратно в вызывающее приложение.
Даже если операции ContentProvider выполняются в пуле связующего, они ведут себя так же, как если бы между процессами не было границ - в том числе плохо-плохие вещи, когда кто-то пытается загрузить файлы из потока пользовательского интерфейса.
Вы должны быть в состоянии избавиться от этой неприятной «помощи», используя android.os.StrictMode, но это не будет сделано, если файлы, о которых идет речь, слишком велики (ANR может все еще произойти в вызывающем процессе). Вместо того, чтобы загружать файлы в канал, возвращайтесь с openDocument
a ParcelFileDescriptor for socket.
Почему Dropbox не страдает от этой проблемы? Поскольку Dropbox Core написан на C++, а Android Strict Mode в настоящее время является конструкцией только для Java, он не вписывается в собственный код. Если вы пишете на диск или загружаетесь из сети в основной поток, используя вызовы библиотеки C, ваше приложение не будет получать никаких последствий (кроме ANR, который запускается в потоке пользовательского интерфейса независимо от Strict Mode).
попробовал 'ContentProvider # openPipeHelper'? это упростит – pskink
или отправьте код 'TransferThread', если вы не хотите использовать' openPipeHelper' – pskink