Вызов API в контент-провайдере для глобального поиска

Мы пытаемся подключить наше приложение AndroidTV, чтобы добавлять результаты в глобальный поиск. Я столкнулся с проблемой, когда я не могу сделать вызов API для получения результатов, потому что система вызывает моего поставщика контента в основном потоке.

@Override
public Cursor query(Uri uri, String[] projection, String search, String[] selectionArgs, String searchOrder) {

    ... Logic here that calls the API using RxJava / Retrofit

    return cursor;
}


<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/foo"
android:searchSettingsDescription="@string/foo_results"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority="com.foo.search.provider"
android:searchSuggestIntentAction="android.intent.action.VIEW" />

<provider
   android:authorities="com.foo.search.provider"
   android:name=".search.GlobalSearchProvider"
   android:exported="true"/>

Когда я выполняю глобальный поиск, я вижу, что вызывается запрос ContentProvider#. Если я попытаюсь выполнить вызов API в текущем потоке, я получу исключение networkonmainthread.

Я попытался уведомить курсор, через который были изменены данные, но безуспешно.

getContext().getContentResolver().notifyChange(Uri.parse("content://com.foo.test"), null);
...
cursor.setNotificationUri(getContext().getContentResolver(), Uri.parse("content://com.foo.test"));

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

Спасибо


person Darussian    schedule 01.10.2014    source источник


Ответы (4)


Одним из решений может быть установка процесса поставщика контента

android:process:":androidtv"

и установите для ThreadPolicy значение LAX непосредственно перед выполнением сетевого вызова

ThreadPolicy tp = ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);

Запустив contentprovider в другом процессе, даже если запрос выполняется в основном потоке, это не повлияет на ваши операции с пользовательским интерфейсом.

person nandeesh    schedule 01.10.2014
comment
Не могли бы вы взглянуть на мой ответ ниже? - person Sebastiano; 14.10.2014
comment
@dextor Я думаю, вы можете попробовать изменить порядок, в котором ваша информация для поиска отображается в списке SearchManager.getSearchablesInGlobalSearch(). Я думаю, вы можете сделать это, изменив имя действия для поиска. Так что поиск вашего приложения будет выполняться поисковым приложением в последнюю очередь, но я не уверен, что это сработает. Кроме того, смысл создания отдельного процесса заключается в том, чтобы запрос не выполнялся в основном потоке, если вы просто установите политику lax, запрос все равно будет выполняться в основном потоке, и операции пользовательского интерфейса могут быть задержаны, или вы можете получить ANR - person nandeesh; 14.10.2014
comment
Мои результаты поиска уже отображаются как последний результат, так что это не годится. Я знаю, что запуск в отдельном процессе решает проблему, но я заметил, что результирующая задержка намного выше, чем при работе в том же процессе. И я не могу понять, почему. - person Sebastiano; 14.10.2014
comment
Задержка связана с тем, что вы блокируете поток пользовательского интерфейса, пока ваши результаты не вернутся. Запуск отдельного процесса ничего не делает, поскольку основной поток пользовательского интерфейса будет ждать результатов, прежде чем продолжить, независимо от того, в каком процессе выполняется запрос с вашей стороны. - person Cachapa; 17.04.2015
comment
Отличное решение. Я создал ContentProvider, который запрашивает результат из сети и возвращает его в виде курсора. Затем я использовал загрузчик Android для отображения данных в RecyclerView. Но я получал исключение NetworkOnMainThreadException. Я вызвал StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX); в моем методе запроса ContentProvider перед сетевым вызовом, и он работал как Champ. Большое спасибо @nandeesh - person Pioneer; 24.09.2017

Я также боролся с этим, так как не нашел принятый в настоящее время ответ на блокировку пользовательского интерфейса приемлемым.

Однако, по словам Марка Бэхингера из команды Google TV, это проблема только эмулятора. В более поздних сборках (например, в доступном в настоящее время оборудовании) поставщики поиска вызываются в фоновом потоке, что полностью устраняет проблему.

Я смог протестировать его на Nexus Player и могу подтвердить, что он работает правильно.

Источник: https://plus.google.com/+DanielCachapa/posts/dbNMoyoRGEi

person Cachapa    schedule 23.04.2015

ОТРЕДАКТИРОВАННЫЙ ОТВЕТ

Я сам столкнулся с этой проблемой, и мне пришлось полагаться на предложенное решение принятого ответа. Однако я заметил заметную задержку при вводе текста в поле "Глобальный поиск". Это отставание:

  1. Вызвано приложением, так как его удаление приводит к исчезновению лагов
  2. Скорее всего, из-за синхронного ожидания запрашиваемых приложений — поскольку наше приложение выполняет два сетевых запроса, методу query() требуется время для завершения, что приводит к этой задержке.

Я обнаружил, что отдельный процесс (:androidtv) не нужен. Установив конфигурацию ThreadPolicy.LAX, сетевой запрос все равно будет выполняться без выдачи ошибки NetworkOnMainThreadException.

Я до сих пор не понимаю, почему такое отставание.


ИСХОДНЫЙ ОТВЕТ

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

После вызова метода query() вы должны создать новый поток/задачу/задание для выполнения сетевого вызова (поэтому избегая NetworkOnMainThreadException), который обновит адаптер, как только он получит нужные данные.

Есть разные способы сделать это. Вы можете использовать обратный вызов или шину событий (например, Otto). Это метод, который я вызываю для обновления адаптера:

public void updateSearchResult(ArrayList<Data> result) {
    mListRowAdapter.clear();
    mListRowAdapter.addAll(0, result);
    HeaderItem header = new HeaderItem(0, "Search results", null);
    mRowsAdapter.add(new ListRow(header, mListRowAdapter));
}
person Sebastiano    schedule 04.10.2014
comment
Я не могу запустить отдельный поток, потому что я не могу сообщить платформе AndroidTV, что данные в курсоре были обновлены. - person Darussian; 04.10.2014
comment
Почему нет? (может я что-то упускаю) - person Sebastiano; 04.10.2014
comment
Я не контролирую то, что делает фреймворк, и они не добавили никакой логики для моего общения с ним. - person Darussian; 05.10.2014
comment
Я все еще упускаю суть. Это потому, что вы используете RxJava/Retrofit? Или потому что у вас есть свой провайдер? Потому что в моем телевизионном приложении я делаю то, что написал в своем ответе, и оно работает так, как ожидалось. - person Sebastiano; 05.10.2014
comment
Чтобы предоставить AndroidTV результаты поиска из нашего приложения, нам нужно передать ему данные через курсор, который предоставляется через ContentProvider, который зарегистрирован для предоставления Global Result. Чтобы получить результаты поиска, мне нужно запросить наш API, который должен быть либо в отдельном потоке, либо с помощью ThreadPolicy.LAX. Ваш ответ работает, если вы представляете результаты поиска в приложении, потому что у вас есть прямой доступ к адаптеру, который показывает данные. Для глобального поиска все, что я могу контролировать, это курсор, который передается в структуру, которая обрабатывает представление данных. - person Darussian; 05.10.2014
comment
Я пришел, чтобы столкнуться с вашей точно такой же проблемой. Решение, опубликованное выше, работает, но вызывает отставание в интерфейсе поиска (которого нет, если я удаляю приложение). Скорее всего это из-за сетевого звонка. Это то же самое для вас? - person Sebastiano; 13.10.2014
comment
Я тоже заметил небольшое отставание. Я считаю, что в настоящее время это единственный способ сделать это, пока Google не добавит ловушки для уведомления об изменении данных. - person Darussian; 14.10.2014

Чтобы решить для отображения результата в глобальном поиске с использованием API в методе запроса, я в основном сделал задержку между получением результата API и запросом результатов в БД для возврата курсора.

Вы можете сделать это через

private Cursor getSuggestions(final String query) {
    Cursor cursor;
    cursor = getCursor(query);
    if (cursor==null || cursor.getCount() == 0) {
    //apiCall
      try {
        Thread.sleep(X millis);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      cursor = getCursor(query);
    }
    return cursor;
  }

Будем продолжать искать, сможем ли мы получить какой-нибудь крючок, чтобы снова прикрепить его без задержки.

person Ankit Khare    schedule 17.10.2018