Android内核学习——SearchManager分析

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;
这些方法的实现都在SearchManagerService中,我们到这个类中看看这四个方法都提供什么样的功能。
SearchManagerService类中有个Searchables类型的属性mSearchables,它会在getSearchables方法使用的时候进行初始化。
    // 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;
    }
 mSearchables这个对象记录了系统中可以被搜索到的组件的信息,SearchManagerService的四个方法的实现都是来自于mSearchables这对象。通过代码注释可以看到,这个对象在延迟初始化之后就不会再修改,其实这个对象还是会修改的,在mSearchables初始化时,会向MyPackageMonitor进行注册,这个类的功能就是当有程序添加进系统或者被删除时,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);
        }
    }
 mSearchables里面信息的填充,是调用了buildSearchableList()方法,下面我们深入到Searchables中,看看它是如何获取“被搜索”组件的信息的。
Searchables内有五个属性来保存“被搜索”组件信息的,它们都是在buildSearchableList()方法中被实例化的。
    private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
    private ArrayList<SearchableInfo> mSearchablesList = null;
    private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
    private ComponentName mGlobalSearchActivity = null;
    private ComponentName mWebSearchActivity = null;
在buildSearchableList方法中,我们可以看到,通过PackageManager的queryIntentActivities方法获取到了ACTION_SEARCH和ACTION_WEB_SEARCH的组件。
       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对象
和 mSearchablesInGlobalSearchList对象。
        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);
                        }
                    }
                }
            }
        }
 那么Searchables五个属性中到底在系统启动的时候存了那些信息呢,我们可以通过在编写一个Android小应用,利用反射查看一些这些信息是什么。通过测试,获得这些属性的包含的值。
mSearchablesList 中存储的是系统中可以"被搜索"到的应用程序的主Activity,当向系统中添加应用时,如果应用在AndroidManifest.xml中被表明是可以被搜索到的,在mSearchablesList 就会有此应用。
mSearchablesMap是mSearchablesList 的Map形式,便于搜索查询相关应用的主Activity。
mGlobalSearchActivity 对应的类是:com.android.quicksearchbox.SearchActivity。
mWebSearchActivity 对应的类是:com.android.quicksearchbox.google.GoogleSearch。
mSearchablesInGlobalSearchList 中存储的对象类型有如下:
com.android.providers.applications.ApplicationLauncher
com.android.mms.ui.SearchActivity
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) 
客户端可以通过调用startSearch方法就可以在当前的显示组件上打开搜索框。我们看看这段代码都做了什么。
    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);
    }
 在startSearch,它调用了startGlobalSearch方法开启了搜索服务;在ensureSearchDialog中初始化了mSearchDialog对象,设置了它的取消和消失事件;打开了搜索框。
    /* 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) 
triggerSearch与startSearch方法类似,只是selectInitialQuery与globalSearch设置为false
  • public void stopSearch() 
相当于触发了SearchManager的cancel事件,将dialog取消掉了
  • 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

猜你喜欢

转载自willsunforjava.iteye.com/blog/1674817