1. 前言
Android系统提供了的一种搜索服务,利用此服务可以实现对系统中的应用、联系人、SMS等进行搜索,也提供转入浏览器中的搜索。Android Develop Blog中有一篇文章赞美了Android搜索功能的强大快捷——《Introducing Quick Search Box for Android》。
SearchManager是搜索服务的入口,可以通过context.getSystemService(Context.SEARCH_SERVICE)获取SearchManager对象。SearchManager像其他ActivityManager、PackageManager等服务一样,是随系统启动一起启动的服务,并且启动后向ServiceManager注册自己,客户端最终获取搜索服务的途径也是通过binder机制向ServiceManager获取的。
从搜索的角度来看,应用可分为三类: unsearchable 类型应用、Query-Search 类型应用和 Filter-Search 类型应用。大部分应用是属于后两种。不过,即便是第一种类型,应用也仍旧支持对搜索的调用。后两种的区分就在于,Query-Search 类型应用执行 batch-mode 搜索,每一个查询字符串都被转化成结果列表;Filter-Search 类型应用则提供 filter-as-you-type 搜索。通常来讲,对基于网络的数据进行 Query Search,而对本地数据,则需要 Filter Search。
2. 整体类图
从上面的类图可以看出SearchManager提供的搜索服务最终是由ISearchManager这个接口的实现类提供的。同时SearchManager又实现了Dialog相关的两个接口OnDismissListener和OnCancelListener,以及内置一个SearchDialog,这些是为了搜索框实现服务的,因为在Android系统中的搜索服务界面就是一个Quick Search Box。下面我们就来先分析一个ISearchManager这一块提供了什么样的搜索服务以及如何提供搜索服务。
3. SearchManagerService 提供搜索服务
ISearchManager是利用AIDL定义的,因此它的代码利用aapt编译生成的,组织方式使用了代理模式。ISearchManager.Stub的实现类是SearchManagerService,所以真正提供搜索服务的是这个类。
ISearchManager接口中定义搜索相关的基础服务,有如下的方法:
- public SearchableInfo getSearchableInfo(ComponentName launchActivity) throws RemoteException;
- public List<SearchableInfo> getSearchablesInGlobalSearch() throws RemoteException;
- public ComponentName getGlobalSearchActivity() throws RemoteException;
- public ComponentName getWebSearchActivity() throws RemoteException;
// This field is initialized lazily in getSearchables(), and then never modified. private Searchables mSearchables;
private synchronized Searchables getSearchables() { if (mSearchables == null) { Log.i(TAG, "Building list of searchable activities"); new MyPackageMonitor().register(mContext, true); mSearchables = new Searchables(mContext); mSearchables.buildSearchableList(); } return mSearchables; }
class MyPackageMonitor extends PackageMonitor { @Override public void onSomePackagesChanged() { // Update list of searchable activities getSearchables().buildSearchableList(); // Inform all listeners that the list of searchables has been updated. Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); mContext.sendBroadcast(intent); } }
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null; private ArrayList<SearchableInfo> mSearchablesList = null; private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null; private ComponentName mGlobalSearchActivity = null; private ComponentName mWebSearchActivity = null;
final PackageManager pm = mContext.getPackageManager(); // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers. List<ResolveInfo> searchList; final Intent intent = new Intent(Intent.ACTION_SEARCH); searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA); List<ResolveInfo> webSearchInfoList; final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH); webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);然后分别取遍历searchList和webSearchInfoList去填充mSearchablesMap对象、mSearchablesList对象
if (searchList != null || webSearchInfoList != null) { int search_count = (searchList == null ? 0 : searchList.size()); int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size()); int count = search_count + web_search_count; for (int ii = 0; ii < count; ii++) { // for each component, try to find metadata ResolveInfo info = (ii < search_count) ? searchList.get(ii) : webSearchInfoList.get(ii - search_count); ActivityInfo ai = info.activityInfo; // Check first to avoid duplicate entries. 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); if (searchable.shouldIncludeInGlobalSearch()) { newSearchablesInGlobalSearchList.add(searchable); } } } } }
com.android.contacts.SearchResultsActivity
com.android.browser.BookmarkSearch
com.android.music.QueryBrowserActivity
4. SearchManager提供搜索相关操作
SearchManager是搜索服务的入口,客户端获取SearchManager对象可以调用getSystemService方法来获得。
SearchManager searchManager = (SearchManager)this.getSystemService(SEARCH_SERVICE);
可以看到SearchManager内部定义了两个回调接口OnDismissListener和OnCancelListener,并且实现了DialogInterface.OnDismissListener和DialogInterface.OnCancelListener接口,客户端可以通过setOnDismissListener和setOnCancelListener来设置搜索框消失和取消时的事件处理。
SearchManager有个SearchDialog类型的对象mSearchDialog,它就是搜索时显示的搜索框。
看一下SearchManager中的一些重要方法,
- public void startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch)
public void startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch) { if (globalSearch) { startGlobalSearch(initialQuery, selectInitialQuery, appSearchData); return; } ensureSearchDialog(); mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData); }
/* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData) { ComponentName globalSearchActivity = getGlobalSearchActivity(); if (globalSearchActivity == null) { Log.w(TAG, "No global search activity found."); return; } Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(globalSearchActivity); // Make sure that we have a Bundle to put source in if (appSearchData == null) { appSearchData = new Bundle(); } else { appSearchData = new Bundle(appSearchData); } // Set source to package name of app that starts global search, if not set already. if (!appSearchData.containsKey("source")) { appSearchData.putString("source", mContext.getPackageName()); } intent.putExtra(APP_DATA, appSearchData); if (!TextUtils.isEmpty(initialQuery)) { intent.putExtra(QUERY, initialQuery); } if (selectInitialQuery) { intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery); } try { if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0)); mContext.startActivity(intent); } catch (ActivityNotFoundException ex) { Log.e(TAG, "Global search activity not found: " + globalSearchActivity); } }可以看到startGlobalSearch做的事情就是打开globalSearchActivity,上面我分析可以知道globalSearchActivity就是com.android.quicksearchbox.SearchActivity,其实就是打开了QuickSearchBox应用的首界面。
- public void triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData)
- public void stopSearch()
- public Cursor getSuggestions(SearchableInfo searchable, String query, int limit)
这个方法被标为@hide的,是android内部的api。
5. SuggestionsAdapter填充SearchDialog的下拉框数据
6. SearchRecentSuggestions与SearchRecentSuggestionsProvider辅助实现搜索的历史记录
7. 搜索在系统组件中的分布
Activity中有方法onSearchRequested
8. meta-data标签的使用
参考:http://blog.csdn.net/happy_6678/article/details/6556771
9. 与ContentProvider相关
参考:http://www.cnblogs.com/over140/archive/2011/12/28/2304393.html