为Actionbar SearchView创建异步ContentProvider

6
我在ActionBar中有一个SearchView,它与ContentProvider相连,以提供搜索建议。这些建议不是来自数据库(通常是ContentProvider的情况),而是来自Web服务。这就是为什么我必须异步处理ContentProvider的游标。到目前为止,我的代码可以工作,但是搜索建议总是落后一个字母:
当我输入“the”时,我会得到上一次搜索的所有结果=>“th”
当我输入“they”时,我会得到上一次搜索的所有结果=>“the”
我该如何告诉SearchView游标中有新的结果呢?我查看了ContentObserver和ContentResolver().notifyChange(),但在SearchView的上下文中使用它们并不是很可行。
以下是我的代码。ContentProvider中onResponse回调函数是重点。我创建了一个新的MatrixCursor,并使用它来覆盖成员MatrixCursor。
AutocompleteSuggestionProvider扩展ContentProvider
@Override
public Cursor query(final Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

    String query = selectionArgs[0];

    mNetworkHelper.startAutoCompleteRequest(
        selectionArgs[0],
        SuggestionCategory.EVERYTHING,
        new Response.Listener<AutoCompleteResponse>() {


            /**
             * This is the callback for a successful web service request
             */
            @Override
            public void onResponse(AutoCompleteResponse response) {

                MatrixCursor nCursor = new MatrixCursor(SEARCH_SUGGEST_COLUMNS, 10);
                List<String> suggestions = response.getResults();

                // transfrom suggestions to MatrixCursor
                for (int i = 0; i < suggestions.size() && i < 10; i++) 
                    nCursor.addRow(new String[]{String.valueOf(i), suggestions.get(i)});
                }

                // update cursor
                mAsyncCursor = nCursor;
            }
        }, 

        /**
         * This is the callback for a errornous web service request
         */
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Toast.makeText(getContext(), "Fehler", Toast.LENGTH_SHORT).show();
            }
        }
    );
    return mAsyncCursor;
}

AndroidManifest

<activity
        android:name=".activities.MainActivity"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <intent-filter>
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>

        <meta-data android:name="android.app.default_searchable" android:value=".MainActivity" />
        <meta-data android:name="android.app.searchable" android:resource="@xml/searchable"/>

    </activity>

    <provider
        android:name=".provider.AutocompleteSuggestionProvider"
        android:authorities="my.package.provider.AutocompleteSuggestion"
        android:exported="false" />

searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
        android:label="@string/app_name"
        android:hint="@string/search_hint"
        android:searchSettingsDescription="@string/search_settings"

        android:searchSuggestAuthority="my.package.provider.AutocompleteSuggestion"
        android:searchSuggestIntentAction="android.intent.action.VIEW"
        android:searchSuggestSelection=" ?"
        android:searchSuggestThreshold="2" >
</searchable>
2个回答

6

我找到了解决方案。最重要的是要知道,ContentProvider 的查询方法并不在 UI 线程上运行。因此,我们可以进行同步的 HTTP 调用。由于现在每个明智的人都使用 Volley,所以你必须像这样调用:

String url = NetworkHelper.buildRequestUrl(selectionArgs[0], SuggestionCategory.EVERYTHING, RequestType.AUTOCOMPLETE);
RequestFuture<JSONArray> future = RequestFuture.newFuture();
JsonArrayRequest request = new JsonArrayRequest(url, future, future);

mNetworkHelper.getRequestQueue().add(request);

// this does not run on the UI thread, so we can make a synchronous HTTP request
try {
  JSONArray suggestions = future.get();
  MatrixCursor resultCursor = new MatrixCursor(SEARCH_SUGGEST_COLUMNS, 10);

  for (int i = 0; i < suggestions.length() && i < 10; i++) {
    resultCursor.addRow(new String[]{String.valueOf(i), suggestions.get(i).toString()});
  }

  return resultCursor;

} catch (InterruptedException e) {
  e.printStackTrace();
} catch (ExecutionException e) {
  e.printStackTrace();
} catch (JSONException e) {
  e.printStackTrace();
}

这样一切都能很好地运作。

说真的,我们已经到了2015年,但在Android上进行简单的搜索仍需要大量的样板代码...这让我感到疯狂。感谢您的帖子。 - eVoxmusic

1

不知道人们是否仍需要这个。以防万一,为了未来的搜索者,我找到了一个解决方案。我还使用了Volley作为我的内容提供程序类,它似乎不太适合Android的内容提供程序框架。与muetzenflo的答案相反,我发现我的内容提供程序确实在UI线程中运行。因此,当我在其中同步使用Volley的Future时,它会减慢(阻塞)UI直到请求返回(超时)。除此之外,我在网上找到了有关Volley的Future请求应在其他线程中运行(例如在Async任务中)才能正常工作的信息。因此,它没有解决我的问题,因为如果我必须使用它(在异步任务中),我会首先使用Volley的普通(异步)请求(那时我使用的就是这个)。

  1. In my ContentProvider subclass, I define a listener interface:

    public interface ResultListener {

      void onProviderResult(Cursor mCursor);
    
      void onProviderError(String errorMsg);
    

    }

  2. In my activity (which implemented LoaderCallbacks), I implemented also above interface.

  3. In my singleton application class, I define a static variable which is used as transient data, together with its getter/setter methods:

    private static HashMap transientData = new HashMap();

    public static Object getTransientData(String objectName) {

    return transientData.get(objectName);
    

    }

    public static void setTransientData(String objectName, Object object) {

    transientData.put(objectName, object);
    

    }

  4. now the logic: In the activity, before calling getSupportLoaderManager().initLoader(...), I called MyApplication.setTransientData("requestor", this) :
在我的内容提供者类中,在Volley请求的onResponse回调中,我这样做了:
``` public void onResponse(JSONArray response){ ```
  ...

  ResultListener requestor =   (ResultListener)TheApplication.getTransientData("requestor");

  if (requestor!=null) requestor.onProviderResult(mCursor);

}

当Volley请求返回时,它将触发请求者的回调方法,并将充满响应数据的游标传递给它,然后请求者(即活动)通过调用以下方式通知游标适配器: adapter.swapCursor(c); adapter.notifyDataSetChanged();
希望这能帮助到某些人。 祝福你。

到了2021年,这个解决方案仍然有效。至于我,我是通过意图实现的。 - djdance
@djdance 我已经不在这个话题上了。我应该将其标记为最佳答案吗? - muetzenflo
1
@muetzenflo 我也这么认为。至少它帮助我理解,没有其他选择,而我也不是一个傻瓜试图走同样的路。我找不到其他替代方案。ContentProvider -> http请求 -> singleton -> intent -> swapCursor。 - djdance

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接