请细读文章后半部分,结合实例,亲笔写的总结。

Android为程序的搜索功能提供了统一的搜索接口,searchdialog和search widget。

searchdialog只能为于activity窗口的上方,search widget可以位于任何位置。

searchdialog和searchwidget的其他属性如下:

A:声音搜索。

B:根据最近的搜索结果,给出搜索建议。

C:根据我们程序的实际搜索结果,给出搜索建议。

注1:search widget在Android 3.0或更高版本才可用

 

searchdialog和search widget的搜索功能特性都一样,但是他们还有微小区别:

A,search dialog是一个被系统控制的UI组件。但他被用户激活的时候,它总是出现在activity的上方。

B,Android系统负责处理search dialog上所有的事件,当用户提交了查询,系统会把这个查询请求传输到我们的searchable activity,让searchable activity在处理真正的查询。C,search widget是SearchView的一个实例,你可以把它放在你的布局的任何地方。

D,默认的,search widget和一个标准的EditText widget一样,不能做任何事情。但是你可以配置它,让android系统处理所有的按键事件,把查询请求传输给合适的activity,可以配置它让它像search dialog一样提供search suggestions。

E,search widget在 Android 3.0或更高版本才可用.search dialog没有此项限制

 

   当用户在search dialog或search widget中执行一个搜索的时候,系统会创建一个Intent,并把查询关键字保存在里面,

然后启动我们在AndroidManifest.xml中声明好的searchable activity,并把Intent传送给它。

 

实现一个可以搜索的程序,主要需要以下几个部份:

(1)search dialog or widget的配置文件。

配置一个XML文件用于配置search dialog 或widget的设置。对于search dialog,该配置文件的名字一般约定为searchable.xml

(2)searchable activity。

searchable activity用于接收搜索关键字,并进行数据搜索和显示搜索结果。

(3)搜索条。search dialog 或search widget

* The search dialog:默认search dialog是隐藏。当我们按下了SEARCH键或在程序中调用onSearchRequested(),它将出现在屏幕的上方。

* SearchView widget:使用search widget的时候,你可以把该搜索框放在我们activity的任何地方,通常把它作为Action Bar的一个菜单而不是放到Layout的xml里面,对于用户来说会显得更加方便。

 

具体实现步骤(以SDK自带的Sample  SearchableDictionary工程分析举例):

(1)   创建配置文件searchable.xml,存放于res/xml文件夹下

<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"   
android:hint="@string/search_hint"
android:searchSettingsDescription="@string/settings_description"
//搜索功能描述        android:searchSuggestAuthority="com.example.android.searchabledict.DictionaryProvider"  //Content Provider的主机名,它必须和你的content provider的mainfest中的               android:authorities一致
android:searchSuggestIntentAction="android.intent.action.VIEW"   
//可选项,定义当用户选择了suggestion后发送给activity的Intent的Action,默认为Intent.ACTION_SEARCH
android:searchSuggestIntentData="content://com.example.android.searchabledict.DictionaryProvider/dictionary"
//定义当用户选择了suggestion后的发送给activity的intent的data,比如:
“content://com.example.android.searchabledict.DictionaryProvider/dictionary/1”(abbey这个单词的Uri)
android:searchSuggestSelection=" ?"
//它就作为selection参数传入到你的suggetion content provider的query函数中,固定为空格 +?
android:searchSuggestThreshold="1"
android:includeInGlobalSearch="true">  //是否支持全局搜索,就是从桌面搜索
</searchable>

关于searchable.xml更多内容请参考(官方英文文档):

http://developer.android.com/guide/topics/search/searchable-config.html

或(中文翻译):

http://hubingforever.blog.163.com/blog/static/171040579201142911524541/

 

(2)   创建SearchableActivity

searchable activity根据搜索关键字进行搜索,并显示搜索结果

1). MENIFEST.XML声明searchable activity

<activity
android:name=".SearchableDictionary"
android:launchMode="singleInstance" >
      <intent-filter>
ion android:name="android.intent.action.MAIN"/>
android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <intent-filter>
ionandroid:name="android.intent.action.SEARCH" />
       </intent-filter>
ta
          android:name="android.app.searchable"
         android:resource="@xml/searchable"/>
</activity>
<meta-data
android:name="android.app.default_searchable"
android:value=".SearchableDictionary" />
//可有可无,这个主要是想让app的任何地方都可以实施搜索并向该activity发送Intent

2) 创建Searchable Activity

SearchableDictionary.java:

它重写了onNewIntent方法来处理新的Intent,如果这个activity是由我们手动点击应用图标启动的,Intent的action为Intent.ACTION_MAIN,不会执行handleIntent里面的处理搜索的代码。另一方面它在菜单里又定义了一个搜索按钮作为actionbar的一部分显示在屏幕顶端了。当我们点击这个按钮时,通过直接调用onSearchRequested();就会在屏幕顶端出现一个在搜索框(Search Dialog),向里面输入英文单词(注意:会有suggestions下拉列表,后面再说它的配置),当你点击了其中一条或者按下了软键盘的右下角的“开始“键,就会立即给SearchableDictionary这个SearchableActivity发送一个Intent,其action根据你刚才的操作(是点了suggestion还是按了“开始”键)分别为:Intent.ACTION_VIEW和Intent.ACTION_SEARCH,其中这个IntentIntent.ACTION_VIEW是在searchable.xml里面自主定义的。接着触发onNewIntent方法。

若是单击suggestion来的,那么intent.getData()可以获取这个suggestion的唯一Uri(比如:

“content://com.example.android.searchabledict.DictionaryProvider/dictionary/1”),然后启动WordActivity查询数据库并显示word这个单词的释义;若是按了“开始”键来的,那么intent.getStringExtra(SearchManager.QUERY);可以获取此次要搜索的关键词,然后通过showResults方法搜索数据库并显示搜索结果,可能不止一条结果,所以开头定义了一个ListView。

 

(3)创建ContentProvider

?       MENIFEST.XML里面声明:
<provider
android:name=".DictionaryProvider"
android:authorities="com.example.android.searchabledict.DictionaryProvider" />
 
?       实现ContentProvider实现Suggestions

DictionaryProvider.java:

里面主要是public Cursor query(…)这个方法,提供显示搜索建议的Cursor,之前里面定义了一个UriMatcher,用于匹配识别搜索类型,当URI匹配时返回相应的匹配码:就是指当搜索框自动访问ContentProvider获取搜索建议时,URI是

“content://com.example.android.searchabledict.DictionaryProvider/search_suggest_query?limit=50”,正好匹配AUTHORITY + SearchManager.SUGGEST_URI_PATH_SHORTCUT,返回匹配码SEARCH_SUGGEST,通过getSuggestions(selectionArgs[0])返回suggestions的cursor.当我们点击suggestions的任意一条后,search dialog消失并会向activity发送Intent,此时这个Intent包含了一条这条suggestion的uri,(比如“content://com.example.android.searchabledict.DictionaryProvider/dictionary/1”)那么activity可以通过这个uri访问ContentProvider来获取cursor并显示结果,因此当UriMatcher匹配到这个uri时返回匹配码GET_WORD,通过调用getWord(uri)获取cursor。

当我们没有点击所给的suggestion而是点击了”开始”键强行搜索时,uri会是

“content://com.example.android.searchabledict.DictionaryProvider/dictionary”,则返回匹配码SEARCH_WORDS,通过search(selectionArgs[0])搜索数据库并返回cursor。

匹配码REFRESH_SHORTCUT目前还用不到。

 

最后说明搜索框获得suggestions的cursor后,如何知道显示那些列的内容,

SearchManager 定义了两个固定列名:SearchManager.SUGGEST_COLUMN_TEXT_1和SearchManager.SUGGEST_COLUMN_TEXT_2,显示suggestions是就是取这两列的内容显示,矛盾的是我们的cursor对应的数据库表的列名几乎不可能都采用这两个固定的列名,解决办法是将查询到的结果表的列名重新命名为这两个固定列名。因此可以在DictionaryDatabase.java中看到定义了一个HashMap,用于映射原列名和改后的列名,这个HashMap应用于SQLiteQueryBuilder对象,就可以完成查询后结果表的列的重命名工作。

前面说过一个URI:

content://com.example.android.searchabledict.DictionaryProvider/dictionary/1,

他是怎么来的?由于前面一部分来自searchable.xml里面的定义android:searchSuggestIntentData,最后的1是id值,那么这个id值它是怎么获得的呢?你以为是取 _id这列的值吗,我们通常android里面数据库的主键通常用_id,但这不是必须的,要能获得id值,android在SearchManager又定义了一个列名:

SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID用来获取id这个值,看它的名字就因该知道它是作为Intent的data的uri最后的id了。因此一般还需要在HashMap映射_id为SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID。

 

附:android系统内置的ContentResolver 查询数据库并重命名列的方法(关键在projection):

Cursor cursor = resolver.query(uri, new String[] {
           MediaStore.Audio.Media._ID + " AS _id",
           MediaStore.Audio.Media._ID+ " AS suggest_intent_data_id",
           MediaStore.Audio.Media.TITLE+ " AS suggest_text_1",
           MediaStore.Audio.Media.ARTIST+ " AS suggest_text_2" },
           "title like'%" + keywd + "%'  or artist like '%" + keywd
                      +"%' ", 
null, 
null);