搜索框架使得應用變得可搜索。要注意,搜索框架只是一個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 index private HashMap<String, HashSet<SonnetRef>> termindex; ... // adding the term to the index HashSet<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方法,表示可搜索的配置。
@Override public 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; }