我正在使用搜索建议创建SearchActivity。当您开始在SearchView中输入单词时,建议会按预期显示,但是当您单击其中一个建议时,我最终会出现此错误:
没有这样的专栏:产品
这似乎与我的searchable.xml条目有关...
android:searchSuggestIntentData="content://com.example.dbcontentprovider/products
...因为传递给我的DbContentProvider类的query()
方法的URI是......
内容://com.example.dbcontentprovider/products
......然而,我认为它应该是......
内容://com.example.dbcontentprovider/products/(id_of_selected_product)
我认为问题可能是我没有正确使用投影地图中的SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
数据,但我已经尝试了我能想到的每一个组合,但我无处可去。
Android文档没有任何帮助,我工作的Reto Meier书并没有提到任何问题,所以任何人都可以提出解决方案。
我的相关代码位是......
DbContentProvider.java:
package com.example;
import java.util.HashMap;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class DbContentProvider extends ContentProvider {
private static final String LOG_TAG = SQLiteOpenHelper.class.getSimpleName();
private static final String CONTENT_URI_BASE = "com.example.dbcontentprovider";
public static final Uri CONTENT_URI = Uri.parse("content://"
+ CONTENT_URI_BASE + "/products");
// The index (key) column name for use in where clauses.
public static final String KEY_ID = "_id";
public static final String KEY_ID_AS = SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID;
public static final String KEY_BRAND_NAME = "brand_name";
public static final String KEY_BRAND_NAME_AS = SearchManager.SUGGEST_COLUMN_TEXT_1;
public static final String KEY_PARENT_COMPANY_ID = "parent_company_id";
public static final String KEY_LAST_MODIFIED_ON = "last_modified_on";
private static final HashMap<String, String> SEARCH_SUGGEST_PROJECTION_MAP;
static {
SEARCH_SUGGEST_PROJECTION_MAP = new HashMap<String, String>();
SEARCH_SUGGEST_PROJECTION_MAP.put(KEY_ID, KEY_ID + " AS " + KEY_ID);
SEARCH_SUGGEST_PROJECTION_MAP.put(KEY_BRAND_NAME_AS, KEY_BRAND_NAME + " AS " + KEY_BRAND_NAME_AS);
SEARCH_SUGGEST_PROJECTION_MAP.put(KEY_PARENT_COMPANY_ID, KEY_PARENT_COMPANY_ID + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_2);
SEARCH_SUGGEST_PROJECTION_MAP.put(KEY_LAST_MODIFIED_ON, KEY_LAST_MODIFIED_ON);
SEARCH_SUGGEST_PROJECTION_MAP.put(KEY_ID_AS, KEY_ID + " AS " + KEY_ID);
}
private static final int ALL_ROWS = 1;
private static final int SINGLE_ROW = 2;
private static final int SEARCH_SUGGEST = 3;
/*
* Create the UrIMatcher object, where a URI ending in 'products' will
* correspond to a request for all items, and 'products/[rowID]' represents
* a single row.
*/
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(CONTENT_URI_BASE, "products", ALL_ROWS);
uriMatcher.addURI(CONTENT_URI_BASE, "products/#", SINGLE_ROW);
uriMatcher.addURI(CONTENT_URI_BASE, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
uriMatcher.addURI(CONTENT_URI_BASE, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
uriMatcher.addURI(CONTENT_URI_BASE, SearchManager.SUGGEST_URI_PATH_SHORTCUT, SEARCH_SUGGEST);
uriMatcher.addURI(CONTENT_URI_BASE, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", SEARCH_SUGGEST);
}
private DbHelper dbHelper;
@Override
public boolean onCreate() {
dbHelper = new DbHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.d(LOG_TAG, "query()");
Log.d(LOG_TAG, " projection: " + Utilities.toString(projection));
Log.d(LOG_TAG, " selection: " + selection);
Log.d(LOG_TAG, " selectionArgs: " + Utilities.toString(selectionArgs));
Log.d(LOG_TAG, " sortOrder: " + sortOrder);
// Open the database
SQLiteDatabase db = dbHelper.getReadableDatabase();
String groupBy = null;
String having = null;
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Specify the table on which to perform the query. This can be a
// specific table or a join as required.
queryBuilder.setTables(DbHelper.BRAND_NAMES_TABLE);
// If this is a row query, limit the result set to the passed in row
switch (uriMatcher.match(uri)) {
case SINGLE_ROW:
Log.d(LOG_TAG, "query (single): " + uri);
String rowID = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_ID + " = " + rowID);
break;
case SEARCH_SUGGEST:
Log.d(LOG_TAG, "query (suggestion): " + uri);
String query = uri.getPathSegments().get(1);
queryBuilder.appendWhere(KEY_BRAND_NAME + " LIKE \"%" + query + "%\"");
queryBuilder.setProjectionMap(SEARCH_SUGGEST_PROJECTION_MAP);
break;
case ALL_ROWS:
//Don't modify
Log.d(LOG_TAG, "query (all rows): " + uri);
break;
default:
Log.d(LOG_TAG, "query (default): " + uri);
break;
}
String sql = queryBuilder.buildQuery(projection, selection, groupBy, having, sortOrder, null);
Log.d(LOG_TAG, "sql: " + sql);
// Execute the query
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder);
Log.d(LOG_TAG, "cursor count: " + cursor.getCount());
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = dbHelper.getWritableDatabase();
// Insert the values into the table
long id = db.insert(DbHelper.BRAND_NAMES_TABLE, null, values);
// Construct and return the URI of the newly inserted row.
if (id > -1) {
// Construct and return the URI of the newly inserted row.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI, id);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(insertedId, null);
return insertedId;
} else {
return null;
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = dbHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW:
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + " = " + rowID + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
break;
default:
break;
}
// Perform the update.
int updateCount = db.update(DbHelper.BRAND_NAMES_TABLE, values,
selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Open a read / write database to support the transaction.
SQLiteDatabase db = dbHelper.getWritableDatabase();
// If this is a row URI, limit the deletion to the specified row.
switch (uriMatcher.match(uri)) {
case SINGLE_ROW :
String rowID = uri.getPathSegments().get(1);
selection = KEY_ID + " = " + rowID+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
break;
default:
break;
}
// To return the number of deleted items you must specify a where
// clause. To delete all rows and return a value pass in "1".
if (selection == null)
selection = "1";
// Perform the deletion.
int deleteCount = db.delete(DbHelper.BRAND_NAMES_TABLE,
selection, selectionArgs);
// Notify any observers of the change in the data set.
getContext().getContentResolver().notifyChange(uri, null);
// Return the number of deleted items.
return deleteCount;
}
@Override
public String getType(Uri uri) {
// Return a string that identifies the MIME type
// for a Content Provider URI
switch (uriMatcher.match(uri)) {
case ALL_ROWS:
return "vnd.android.cursor.dir/vnd.mobilewebexpert.elemental";
case SINGLE_ROW:
return "vnd.android.cursor.item/vnd.mobilewebexpert.elemental";
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
private class DbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "products.db";
private static final String BRAND_NAMES_TABLE = "brand_names";
private static final int DATABASE_VERSION = 1;
// SQL Statement to create a new database.
private static final String DATABASE_CREATE_SQL = "CREATE TABLE " +
BRAND_NAMES_TABLE + " (" +
KEY_ID + " integer PRIMARY KEY AUTOINCREMENT, " +
KEY_BRAND_NAME + " text NOT NULL, " +
KEY_PARENT_COMPANY_ID + " integer NOT NULL DEFAULT '0', " +
KEY_LAST_MODIFIED_ON + " datetime NOT NULL DEFAULT CURRENT_TIMESTAMP);";
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/*
* Called when no database exists in disk and the help class needs to create a new
* one.
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE_SQL);
}
/*
* Called when there is a database version mismatch meaning that the version of the
* database on disk needs to be upgraded to the current version.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(LOG_TAG, "Upgrading database from from version " + oldVersion + " to " + newVersion);
//Drop the old table
db.execSQL("DROP TABLE IF EXISTS " + BRAND_NAMES_TABLE);
//Create new one
onCreate(db);
}
}
}
SearchActivity.java:
package com.example;
import android.app.Activity;
import android.app.LoaderManager;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.SearchView;
import android.widget.Toast;
public class SearchActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String LOG_TAG = SearchActivity.class.getSimpleName();
private static final int LOADER_ID = 0;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] resultColumns = {DbContentProvider.KEY_ID, DbContentProvider.KEY_BRAND_NAME};
String where;
if (args.getString(DbContentProvider.KEY_BRAND_NAME) != null) {
where = DbContentProvider.KEY_BRAND_NAME + " LIKE \"%" + args.getString(DbContentProvider.KEY_BRAND_NAME) + "%\""; //todo - use fulltext search
}
else {
where = DbContentProvider.KEY_ID + " = " + args.getString(DbContentProvider.KEY_ID);
}
String[] whereArgs = null;
String order = null; //todo
return new CursorLoader(SearchActivity.this, DbContentProvider.CONTENT_URI, resultColumns, where, whereArgs, order);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Replace the result Cursor displayed by the Cursor Adapter with
// the new result set.
try {
int idColumnIndex = cursor.getColumnIndexOrThrow(DbContentProvider.KEY_ID);
int brandNameColumnIndex = cursor.getColumnIndexOrThrow(DbContentProvider.KEY_BRAND_NAME);
if (cursor.getCount() > 0) {
while(cursor.moveToNext()) {
final String id = cursor.getString(idColumnIndex);
final String brandName = cursor.getString(brandNameColumnIndex);
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(SearchActivity.this, "Search result: " + id + ". " + brandName, Toast.LENGTH_SHORT).show();
}
});
}
}
else {
Toast.makeText(SearchActivity.this, "No results found.", Toast.LENGTH_SHORT).show();
}
}
catch(Exception e) {
Log.e(LOG_TAG, "Search exception", e);
}
finally {
try {
cursor.close();
}
catch(Exception ignore) {
}
}
// This handler is not synchonrized with the UI thread, so you
// will need to synchronize it before modiyfing any UI elements
// directly.
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Remove the existing result Cursor from the List Adapter.
//adapter.swapCursor(null);
// This handler is not synchonrized with the UI thread, so you
// will need to synchronize it before modiyfing any UI elements
// directly.
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
// Use the Search Manager to find the SearchableInfo related to this Activity
SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
// Bind the Activity's SearchableInfo to the Search View
final SearchView searchView = (SearchView)findViewById(R.id.searchView);
searchView.setSearchableInfo(searchableInfo);
searchView.setIconifiedByDefault(false);
searchView.setSubmitButtonEnabled(true);
//searchView.setQueryRefinementEnabled(true);
/*
* If this Activity was launched with a search intent, then see to it
*/
parseIntent(getIntent());
}
@Override
public void onNewIntent(Intent intent){
super.onNewIntent(intent);
parseIntent(intent);
}
private void parseIntent(Intent intent) {
/*
* If the activity was started to service a Search request, extract the query
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) { //Normal search submitted
String searchQuery = intent.getStringExtra(SearchManager.QUERY);
performSearch(DbContentProvider.KEY_BRAND_NAME, searchQuery);
}
else if (Intent.ACTION_VIEW.equals(intent.getAction())) { //Suggestion was clicked
Log.d(LOG_TAG, "intent: " + intent);
String query = intent.getExtras().get(SearchManager.USER_QUERY).toString();
Log.d(LOG_TAG, "query: " + query);
Uri detailUri = intent.getData();
Log.d(LOG_TAG, "detailUri: " + detailUri);
String id = detailUri.getLastPathSegment();
Log.d(LOG_TAG, "id: " + id);
performSearch(DbContentProvider.KEY_ID, id);
}
}
private void performSearch(String searchKey, String searchValue) {
//Toast.makeText(this, "Search query: \"" + searchQuery + "\"", Toast.LENGTH_SHORT).show();
Bundle args = new Bundle();
args.putString(searchKey, searchValue);
getLoaderManager().restartLoader(LOADER_ID, args, this);
}
}
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:searchSuggestAuthority="com.example.dbcontentprovider"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSuggestIntentData="content://com.example.dbcontentprovider/products"
android:searchSuggestThreshold="3"
android:includeInGlobalSearch="true"
android:searchSettingsDescription="@string/search_settings_description"
android:queryAfterZeroResults="true"
android:voiceSearchMode="showVoiceSearchButton" >
</searchable>
的AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="19" />
<uses-permission
android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".SplashActivity"
android:label="@string/app_name_short"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".RegisterActivity"
android:label="@string/title_activity_register" >
</activity>
<activity
android:name=".LoginActivity"
android:label="@string/title_activity_login" >
</activity>
<activity
android:name=".SearchActivity"
android:label="@string/app_name"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<meta-data
android:name="android.app.default_searchable"
android:value=".SearchActivity" />
</activity>
<provider
android:name=".DbContentProvider"
android:authorities="com.example.dbcontentprovider"
android:exported="true" />
</application>
</manifest>
并且(在一分钱......),点击建议后,这里是上述代码的LogCat输出...
09-30 21:01:39.720:D / AbsListView(4571):onDetachedFromWindow 09-30 21:01:39.720:D / AbsListView(4571):调用unregisterIRListener() 09-30 21:01:39.770:D / SearchActivity(4571):intent:Intent {act = android.intent.action.VIEW dat = content://com.example.dbcontentprovider/products flg = 0x10000000 cmp = com.example /.SearchActivity(有额外的)} 09-30 21:01:39.770:D / SearchActivity(4571):查询:hei 09-30 21:01:39.770:D / SearchActivity(4571):detailUri:content://com.example.dbcontentprovider/products 09-30 21:01:39.770:D / SearchActivity(4571):id:products 09-30 21:01:39.780:D / SQLiteOpenHelper(4571):query() 09-30 21:01:39.780:D / SQLiteOpenHelper(4571):projection:{&#34; _id&#34;,&#34; brand_name&#34;} 09-30 21:01:39.780:D / SQLiteOpenHelper(4571):选择:_id =产品 09-30 21:01:39.780:D / SQLiteOpenHelper(4571):selectionArgs:null 09-30 21:01:39.780:D / SQLiteOpenHelper(4571):sortOrder:null 09-30 21:01:39.780:D / SQLiteOpenHelper(4571):query(所有行):content://com.example.dbcontentprovider/products 09-30 21:01:39.780:D / SQLiteOpenHelper(4571):sql:SELECT _id,brand_name FROM brand_names WHERE(_id = products) 09-30 21:01:39.780:E / SQLiteLog(4571):( 1)没有这样的栏目:产品 09-30 21:01:39.780:W / dalvikvm(4571):threadid = 16:线程退出,未捕获异常(group = 0x41ebcda0) 09-30 21:01:39.780:E / AndroidRuntime(4571):致命异常:AsyncTask#3 09-30 21:01:39.780:E / AndroidRuntime(4571):处理:com.example,PID:4571 09-30 21:01:39.780:E / AndroidRuntime(4571):java.lang.RuntimeException:执行doInBackground()时发生错误 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.os.AsyncTask $ 3.done(AsyncTask.java:300) 09-30 21:01:39.780:E / AndroidRuntime(4571):at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355) 09-30 21:01:39.780:E / AndroidRuntime(4571):at java.util.concurrent.FutureTask.setException(FutureTask.java:222) 09-30 21:01:39.780:E / AndroidRuntime(4571):at java.util.concurrent.FutureTask.run(FutureTask.java:242) 09-30 21:01:39.780:E / AndroidRuntime(4571):at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 09-30 21:01:39.780:E / AndroidRuntime(4571):at java.util.concurrent.ThreadPoolExecutor $ Worker.run(ThreadPoolExecutor.java:587) 09-30 21:01:39.780:E / AndroidRuntime(4571):at java.lang.Thread.run(Thread.java:841) 09-30 21:01:39.780:E / AndroidRuntime(4571):引起:android.database.sqlite.SQLiteException:没有这样的列:产品(代码1):,同时编译:SELECT _id,brand_name FROM brand_names WHERE(_id =产品) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1121) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:694) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteProgram。(SQLiteProgram.java:58) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteQuery。(SQLiteQuery.java:37) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:44) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1436) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:400) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:294) 09-30 21:01:39.780:E / AndroidRuntime(4571):at com.example.DbContentProvider.query(DbContentProvider.java:124) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.ContentProvider.query(ContentProvider.java:857) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.ContentProvider $ Transport.query(ContentProvider.java:200) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.ContentResolver.query(ContentResolver.java:464) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.CursorLoader.loadInBackground(CursorLoader.java:65) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.CursorLoader.loadInBackground(CursorLoader.java:43) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:312) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.AsyncTaskLoader $ LoadTask.doInBackground(AsyncTaskLoader.java:69) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.content.AsyncTaskLoader $ LoadTask.doInBackground(AsyncTaskLoader.java:57) 09-30 21:01:39.780:E / AndroidRuntime(4571):在android.os.AsyncTask $ 2.call(AsyncTask.java:288) 09-30 21:01:39.780:E / AndroidRuntime(4571):at java.util.concurrent.FutureTask.run(FutureTask.java:237) 09-30 21:01:39.780:E / AndroidRuntime(4571):... 3更多