QuickSearchBox
(1)framework层搜索管理器(SearchManager):主要功能是对Android系统全局搜索提供支持,当Android系统启动完成后会发出系统启动完成(BOOT_COMPLETED)广播,SearchManager接收到此广播后会通过PackageManager搜索系统中支持全局搜索的应用程序,SearchManager解析搜索源的配置信息并将这些配置信息封装成对象保存在List列表中供全局搜索应用程序使用。
(2)全局搜索AppWidget:AppWidget是搜索的入口,当用户需要使用全局搜索时会单击已经添加到桌面的全局搜索AppWidget,这时会打开全局搜索应用。
(3)全局搜索应用程序(QuickSearchBox):主要职能是接受用户的输入请求,启动异步搜索,将搜索到的结果显示。
(4)支持全局搜索的应用程序:每一个支持全局搜索的应用程序都需要实现一个ContentProvider,通过这个ContentProvider向外提供数据,全局搜索应用(QuickSearchBox)会调用这些ContentProvider获取数据。
系统启动后,在system_process进程中,会启动SearchManagerService服务(SearchManagerSerive.java),在初始化函数中针对Intent.ACTION_BOOT_COMPLETED注册了一个BroadcastReceiver,其run()函数中调用getSearchables()函数建立手机可搜索资源,Searchables的类函数buildSearchableList为真正建立搜索列表资源函数。
Searchables.java文件buildSearchableList()函数,跟踪searchList的创建。
final Intent intent = new Intent(Intent.ACTION_SEARCH);
searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
queryIntentActivities的实现函数见PackageManagerService.java文件,
esolveInfo info = (ii < search_count) ? searchList.get(ii)
: webSearchInfoList.get(ii - search_count);
ActivityInfo ai = info.activityInfo;
if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
if (searchable != null) {
newSearchablesList.add(searchable);
newSearchablesMap.put(searchable.getSearchActivity(), searchable);
}
}
SearchableInfo类函数getActivityMetaData(),从代码中可以看到先loadXmlMetaData()加载xml文件,然后getActivityMetaData()获得searchable。
XmlResourceParser xml =
activityInfo.loadXmlMetaData(context.getPackageManager(), MD_LABEL_SEARCHABLE);
SearchableInfo searchable = getActivityMetaData(context, xml, cName);
getActivityMetaData()的关键语句:
result = new SearchableInfo(activityContext, attr, cName);
可以看出从xml文件中获得了SearchableInfo各成员变量。
TypedArray a = activityContext.obtainStyledAttributes(attr,
com.android.internal.R.styleable.Searchable);
mSearchMode= a.getInt(com.android.internal.R.styleable.Searchable_searchMode, 0);
mSuggestThreshold=a.getInt(com.android.internal.R.styleable.
Searchable_searchSuggestThreshold, 0);
ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority, 0);
if (pi != null) {
suggestProviderPackage = pi.packageName;
}
上述提到的xml文件为相应应用的/res/xml/searchable.xml。
当用户在搜索栏中输入内容时,触发mQueryTextView的SearchTextWatcher()的listener函数运行,函数调用updateSuggestionsBuffered()。在函数updateSuggestionsBuffered()里,将runnable变量mUpdateSuggestionsTask加入到message queue,并延迟100ms运行。在runnable的run中调用updateSuggestions(),后者的重要执行语句:
Suggestions suggestions = getSuggestionsProvider().getSuggestions(
query, mCorpus, getMaxSuggestions());
该语句的前一部分getSuggestionsProvider()获得SuggestionsProvider变量,由于SuggestionsProvider为虚类,其实现类SuggestionsProviderImpl,所以语句后半部分运行的是SuggestionsProviderImpl.java里的getSuggestions()函数。
getSuggestions()有两个部分,第一部分根据query(查询内容)获得可搜索源列表,实现函数getCorporaToQuery();第二部分给每个搜索源创建一个QueryTask(本质为runnable)。函数getCorporaToQuery(),
orderedCorpora = mCorpusRanker.getRankedCorpora();系统中所有可搜索源列表
corporaToQuery函数返回值,基于query,根据判断条件,得到的相应缩小到搜索源列表,shouldQueryCorpus()为判断函数,
if (query.length() == 0 && !corpus.isWebCorpus()) { return false; }
if (query.length() >= corpus.getQueryThreshold()) {
if (!corpus.queryAfterZeroResults() && mEmptyCorpora.containsKey(corpus)) {
return false;
}
return true;
}
返回true表示将此搜索源添加到corporaToQuery中,反之则不添加。corpus.getQueryThreshold()为开始搜索的query起始长度。
第二部分,QueryTask的startQueries()函数中,给每个corpus创建QueryTask。
for (SuggestionCursorProvider<C> provider : providers) {
QueryTask<C> task = new QueryTask<C>(query, maxResultsPerProvider, provider, handler,consumer, onlyOneProvider);
executor.execute(task);
}
之后,搜索会在QueryTask的run()函数中运行。
final C cursor = mProvider.getSuggestions(mQuery, mQueryLimit, mTheOnlyOne);// QueryTask.java
public CorpusResult getSuggestions(String query, int queryLimit, boolean onlyCorpus);//MultiSourceCorpus.java
private static Cursor getSuggestions(Context context, SearchableInfo searchable,
String query,int queryLimit);// SearchableSource.java
context.getContentResolver().query(uri, null, selection, selArgs, null);// 真正搜索db
applications、contacts、music三个搜索源在各自应用中的搜索入口:
ApplicationsProvider.java query()函数case SEARCH_SUGGEST
ContactsProvider2.java query()函数case SEARCH_SUGGESTIONS
MediaProvider.java query()函数 case AUDIO_SEARCH_BASIC
如果想让某个应用程序支持全局搜索,必须对这个应用程序进行一系列配置,并实现可被外界访问的内容提供者向搜索应用程序(QuickSearchBox)提供搜索结果,根据配置信息,应用程序可被搜索框架识别为搜索源,搜索应用程序(QuickSearchBox)也可以通过解析配置信息组拼成URI请求应用的ContentProvider获取搜索结果。
配置实现:
1. 应用程序中应当存在一个Activity,这个Activity在AndroidManifest.xml中的基本配置,如下:
<activity android:name="SearchResultsActivity"
android:theme="@style/TallTitleBarTheme"
android:label="@string/contactsList"
android:excludeFromRecents="true"
>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"
/>
</activity>
Activity的作用:
第一,这样一个Activity在应用程序中是必须存在的,因为配置了上面代码后,这个Activity可以被识别为搜索源, 应用就支持全局搜索了。
第二,当搜索出结果信息后单击某一个结果项后会打开这个Activity显示搜索出的内容。
2. searchable.xml配置:
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:icon="@drawable/ic_tab_contacts"
android:label="@string/contactsList"
android:hint="@string/searchHint"
android:searchMode="queryRewriteFromText"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority="com.android.contacts"
android:searchSuggestIntentAction="android.provider.Contacts.SEARCH_SUGGESTION_CLICKED"
android:searchSuggestIntentData="content://com.android.contacts/contacts/lookup"
android:searchSettingsDescription="@string/search_settings_description"
>
<actionkey
android:keycode="KEYCODE_CALL"
android:queryActionMsg="call"
android:suggestActionMsg="call"
/>
</searchable>