Проблемы с реализацией DocumentsProvider для облака на Android

Я сделал пользовательскую реализацию провайдера документов, где файлы фактически хранятся на сервере.

Я следовал документации, поэтому провайдер работает большую часть времени, но у меня есть проблемы с некоторыми приложениями, а именно:

1) Gmail, прикрепляющий файл от моего провайдера: Первоначально мой публичный ParcelFileDescriptor openDocument был примерно таким:

ParcelFileDescriptor[] pipe=null;
pipe=ParcelFileDescriptor.createPipe();
OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);
new TransferThread(file_id,out).start();
return pipe[0];

но это никогда не работало с Gmail, я получил ошибку сломанной трубы. Это работает, если я сначала скачиваю файл тем же способом (блокировка).

Оригинал работал с большинством других приложений

2) Blackberry Hub - я все равно не могу заставить его работать, так как кажется, что он вызывает методы провайдера в главном потоке

Я бы сдался, но я вижу, что провайдер dropbox работал со всеми приложениями, включая BB-концентратор.

Еще хуже:) кажется, что dropbox может показывать собственный интерфейс при загрузке, например.

Есть идеи, как это можно сделать?

Обратите внимание, что я проверял документацию и несколько примеров много раз, но все еще не могу заставить провайдера работать со всеми приложениями, я знаю, что некоторые приложения могут работать неправильно, но dropbox доказывает, что можно встретить все приложения

1 ответ

Скорее всего, ваша проблема с 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 делает несколько шагов, чтобы сделать это менее очевидным:

  1. Приоритет вашего потока устанавливается как приоритет потока, который выполняет вызов (включая повышение до приоритета пользовательского интерфейса, если вас вызывают из потока пользовательского интерфейса).
  2. Android передает текущие настройки строгого режима (то, что приводит к сбою приложений при работе в сети в главном потоке) из вызывающего приложения в ваш поток. После завершения вызова все нарушения строгого режима собираются и записываются в Parcel, который отправляется обратно в вызывающее приложение.

Даже если операции ContentProvider выполняются в пуле связывателей, они ведут себя почти так, как если бы между процессами не было границ - включая плохие-плохие вещи, происходящие, когда кто-то пытается загрузить файлы из потока пользовательского интерфейса.

Вы должны быть в состоянии избавиться от этой отвратительной "помощи" с помощью android.os.StrictMode, но это не сработает, если рассматриваемые файлы слишком велики (ANR может все еще происходить в вызывающем процессе). Вместо того, чтобы загружать файлы в канал, возвращайтесь из openDocument ParcelFileDescriptor для сокета.

Почему Dropbox не страдает от этой проблемы? Поскольку Dropbox Core написан на C++, а Android Strict Mode в настоящее время является конструкцией только для Java, он не привязывается к нативному коду. Если вы записываете на диск или загружаете из сети в главном потоке с помощью вызовов библиотеки C, ваше приложение не получит никаких последствий (кроме ANR, который запускается в потоке пользовательского интерфейса независимо от строгого режима).

Другие вопросы по тегам