搜索框架使得应用变得可搜索。要注意,搜索框架只是一个UI框架,并没有提供对真正搜索逻辑的支撑。相反地,它提供了UI部分,支持用户搜索查询并执行它。这又可以调用指定的搜索逻辑,并返回相关结果。为了说明构建搜索逻辑和搜索界面的基础方面,我们将探索一个搜索应用实例,它支持用户通过莎士比亚的“sonnet(十四行诗)”进行搜索。
搜索基础
搜索对应用有些前提要求。首先,它需要真正的逻辑,能够返回搜索结果。它还需要可搜索的配置,建立关于当初始化搜索UI时显示哪些以及如何执行的一些规范。最后,启动可搜索的活动,接收查询,调用搜索逻辑后,显示结果。
搜索逻辑
有很多种方式可以为生成搜索结果创建真正的搜索逻辑。以下我们将探索两个选项:基础的基于索引的搜索以及SQLite数据库支持的搜索,即android.database.sqlite搜索。在任何一种情况下,我们将从基础的构建分块开始:数据对象和SearchLogic接口。
对于数据对象,该实例包含一个主类对象以及一个子类对象。因为用过sonnet进行搜索,我们先定义了一个Sonnet类,它包含标题、sonnet的编号及其行数。当需要在sonnet内检索特定行而不是整个sonnet时,需要用到子对象Sonnet Fragment。该子对象主要用于显示查找结果。
public class Sonnet { public int num; public String title; public String lines;}public class SonnetFragment { public int num; public String line;}
当真正通过UI查找或检索sonnet时,需要为SearchLogic逻辑实现两个方法并调用它们:一是search方法,它接收查询字符串,返回SonnetFragment对象的有序数组;二是getSonnet方法,它根据序号返回特定的Sonnet对象。
public interface SearchLogicInterface { SonnetFragment search(String query); Sonnet getSonnet(int i);}
基于索引的搜索逻辑。在准备工作中,Sonnet会被写到原始文件中,并对每行进行解析。每构成sonnet的几行会作为一个Sonnet对象进行处理,每行的每个词会作为key整合到大索引库中。对Sonnet值进行设置,每个SonnetRef对象都包含sonnet ID,它包含sonnet所包含的单词以及这些单词的序号列表。当然,也可以使用其他更高级的技术,比如基于单词的定位跟踪(sonnet中每行单词的位置作为一个值),它涉及在sonnet内对单词的含义或上下文进行引用的元数据,计算该sonnet内该单词的权重,为每个sonnet创建单词权重排名系统,以及其他方法处理更准确的搜索查询或生成更具体的查询结果。然而,在本节中,我们将简要介绍最基础的索引搜索系统。
当索引构建完成并且应用可以使用时,当查询指定了特定的搜索单词,会很快返回结果,因为需要处理的逻辑仅仅是在索引和引用的sonnet列表和行列表中查找单词,该单词即索引中的值。在以下实例中,我们通过HashMap存储索引,单词作为关键字以及多个包含sonnet号和行号作为值的SonnetRef对象。
// the indexprivate HashMap<String, HashSet<SonnetRef>> termindex;...// adding the term to the indexHashSet<SonnetRef> set = null;if(index.containsKey(word)) { set = index.get(word);} else set = new HashSet<SonnetRef>;set.add(new SonnetRef(sons.size - 1, i));...
为了处理多个单词项,这段逻辑获取所有的SonnetRef对象,通过多个对象的交集查找sonnet编号。交集即最终的返回值。
基于数据库的搜索逻辑。在这段实现中,搜索的核心是一个SQL查询,它查找存储在数据库中的sonnet。当读取sonnet数据时,每个sonnet会添加到数据库中。数据库的列包含引用的sonnet编号、sonnet标题、行号以及行本身。
搜索查询可以通过LIKE语句执行。
可搜索的配置文件
一旦建立了搜索逻辑,就需要理解搜索框架。首先,创建可搜索的配置文件(searchable configuration),它是在res/xml目录下的XML文件,通常命名成searchable.xml。可搜索的配置文件包含特定的属性,这些属性最终构成SearchableInfo对象的配置,系统启动时会对该对象进行初始化。
可搜索的XML配置文件必须包含可搜索的元素作为根节点,而且必须包含android:label属性。
<?xml version=/"1.0/" encoding=/"utf-8/"?><searchable xmlns:android=/"http://schemas.android.com/apk/res/android/" android:label=/"@string/app_label/" ></searchable>
完整的可搜索配置文件语法如下:
为了获取更多关于可搜索配置的信息,请查看Android开发者指南的Search Configuration一节(http://developer.android.com/guide/topics/search/searchable-config.html#searchable-element)。
可搜索的活动
定义了可搜索的配置后,必须创建可搜索的活动。该活动最终会调用搜索逻辑并显示结果。当在Search Dialog或Search Widget中执行搜索时,系统会启动ACTION_SEARCH操作的intent来启动该活动。查询是通过intent中的SearchManager.QUERY字符串值来表示的。活动可以通过查询字符串来调用搜索逻辑。该逻辑返回结果,并显示给用户。在前面给出的例子中,查询字符串会传递给搜索逻辑的search方法,并返回SonnetFragment对象数组。该活动后续的逻辑会负责把结果显示给用户。
第一步是在manifest文件中声明可搜索的活动,向系统指定搜索查找应该在该活动上执行。这是通过把android.intent.action.SEARCH操作添加到intent过滤器中,并执行可搜索的配置来完成的。
<application ... > <activity android:name=/".searchdemo.SearchActivity/" > <intent-filter> <action android:name=/"android.intent.action.SEARCH/" /> </intent-filter> <meta-data android:name=/"android.app.searchable/" android:resource=/"@xml/searchable/"/> </activity> ...</application>
第二步是决定如何显示搜索结果。通常建议采用表格形式显示结果。在这个例子中,我们使用ListView显示搜索结果。为了简单,该活动扩展了ListActivity,因为ListActivity提供了默认的布局方式ListView,通过getListView方法和setListAdapter方法可以很方便地访问。
在这段代码中,处理系统传递给该活动的ACTION_SEARCH intent这段逻辑很重要。从intent中抽取了SearchManager.QUERY字符串的其他信息(通过getStringExtra方法)后,查询就作为搜索逻辑的输入并返回结果。在这个例子中,把SonnetFragment对象的数组放到ArrayAdapter,该ArrayAdapter被设置成List的适配器,对结果进行显示。
以上是最基础的UI工作。下面,我们将涵盖一些UI组件,用户访问这些组件来执行搜索:即Search Dialog和Search Widget。如果你的目标设备运行在Android 3.0(蜂巢Honeycomb/API 11)或更新的版本上,应该使用Search Widget。
Search Dialog
Search Dialog是个用户界面,它支持用户输入文本并执行搜索。当初始化后,该组件出现在屏幕的最上方。Android系统控制Search Dialog中的所有事件。当用户输入查询并提交时,系统会向manifest文件中指定的活动发送ACTION_SEARCH intent。
为了支持Search Dialog从特定的活动给声明的可搜索的活动发送查询,<meta-data>元素必须包含android:value属性,它指定可搜索的活动的类名称。它还必须包含manifest指定的该活动的Search Dialog和android:name属性,后者的值为“android.app.default_searchable”。
<application ... > <activity android:label=/"@string/app_name/" android:name=/".searchdemo.MainActivity/" > <meta-data android:name=/"android.app.default_searchable/" android: /> </activity> </activity> ...</application>
有了元数据引用之后,当用户在前端按下设备的该活动的Search按钮(如果设备有该按钮),或者当活动调用onSearchRequested方法时,会激活Search Dialog。
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findViewById(R.id.search).setOnClickListener(new OnClickListener { @Override public void onClick(View v) { onSearchRequested; // activates the Search Dialog } });}
Search Dialog会悬浮在屏幕的上方。它不会给活动栈带来任何变化。因此,当出现Search Dialog时,不会调用生命周期方法onPause。活动会失去Search Dialog的输入光标。如果用户按下Back按钮取消搜索,Search Dialog会关闭,活动会重新获取到输入光标。
Search Widget
Search Widget(具体地说即SearchView类)只在Android 3.0(蜂巢Honeycomb/API 11)或更新的版本中提供。建议在Action Bar中使用SearchView作为action视图,可以对菜单项进行折叠,而不是把Search Widget放到活动布局中。要在ActionBar中使用它,首先创建一个定制的菜单XML文件(在这个例子中名为search_menu.xml),在其中一项上引用android:actionView Class=“android.widget.SearchView”,把该XML文件放到res/menu目录中。
<?xml version=/"1.0/" encoding=/"utf-8/"?><menu xmlns:android=/"http://schemas.android.com/apk/res/android/"> <item android:id=/"@+id/menu_search/" android:icon=/"@android:drawable/ic_menu_search/" android:title=/"@string/search/" android:showAsAction=/"ifRoom|withText/" android:actionViewClass=/"android.widget.SearchView/" /></menu>
有了菜单XML文件后,可以在活动的onCreateOptionsMenu方法中设置。通过SearchableInfo引用SearchView并设置setSearchableInfo方法,表示可搜索的配置。
@Overridepublic boolean onCreateOptionsMenu(Menu menu) { // Inflate the options menu from XML MenuInflater inflater = getMenuInflater; // inflate the search_menu.xml inflater.inflate(R.menu.search_menu, menu); // Get the SearchView and set the searchable configuration SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView; searchView.setSearchableInfo( searchManager.getSearchableInfo(getComponentName)); // Do not iconify the widget; expand it by default searchView.setIconifiedByDefault(false); // turn on submit button searchView.setSubmitButtonEnabled(true); // enable query selection refinement searchView.setQueryRefinementEnabled(true); return true;}