Android Settings 快速搜尋
Settings 之 SearchIndexablesProvider 首先需要在清單檔案中註冊action為"android.content.action.SEARCH_INDEXABLES_PROVIDER"的provider,如下: <provider android:name=".search.SettingsSearchIndexablesProvider" android:authorities="com.android.settings" android:multiprocess="false" android:grantUriPermissions="true" android:permission="android.permission.READ_SEARCH_INDEXABLES" android:exported="true"> <intent-filter> <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" /> //註冊此 action </intent-filter> </provider> 搜尋資料庫路徑:/data/user_de/0/com.android.settings/databases/search_index.db //search_index.db 資料庫的prefs_index表格中存放的就是搜尋的設定選項 此資料庫的初始化不是在開機階段,而是在每一次開啟settings或者當前切換使用者(因為系統為每一個使用者維護一個單獨的search_index.db),或者是當前的語言發生變化會更新資料庫. 資料庫的初始化: Index#update public void update() { AsyncTask.execute(new Runnable() { @Override public void run() { // 查詢系統中所有的配置了"android.content.action.SEARCH_INDEXABLES_PROVIDER"的Provider final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE); List<ResolveInfo> list = mContext.getPackageManager().queryIntentContentProviders(intent, 0); final int size = list.size(); for (int n = 0; n < size; n++) { final ResolveInfo info = list.get(n); if (!isWellKnownProvider(info)) { continue; } final String authority = info.providerInfo.authority; final String packageName = info.providerInfo.packageName; //列印packageName為: //01-01 17:38:09.257 5207 5511 E qcdds : packageName= com.android.cellbroadcastreceiver //01-01 17:38:09.678 5207 5511 E qcdds : packageName= com.android.phone //01-01 17:38:09.777 5207 5511 E qcdds : packageName= com.android.settings // 新增其他APP的設定項 addIndexablesFromRemoteProvider(packageName, authority); //主要方法 // 新增其他APP中不需要被搜尋到的設定項 addNonIndexablesKeysFromRemoteProvider(packageName, authority); } mDataToProcess.fullIndex = true; // 上面的addIndexablesFromRemoteProvider會新增設定項到記憶體中的一個mDataToProcess物件裡,updateInternal將該物件更新到資料庫中 updateInternal(); } }); } private boolean addIndexablesFromRemoteProvider(String packageName, String authority) { try { // rank是按照指定演算法計算出的一個值,用來搜尋的時候,展示給使用者的優先順序 final int baseRank = Ranking.getBaseRankForAuthority(authority); // mBaseAuthority是com.android.settings,authority是其他APP的包名 final Context context = mBaseAuthority.equals(authority) ? mContext : mContext.createPackageContext(packageName, 0); // 構建搜尋的URI final Uri uriForResources = buildUriForXmlResources(authority); // 兩種新增到資料庫的方式,我們以addIndexablesForXmlResourceUri為例 addIndexablesForXmlResourceUri(context, packageName, uriForResources, SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS, baseRank); final Uri uriForRawData = buildUriForRawData(authority); addIndexablesForRawDataUri(context, packageName, uriForRawData, SearchIndexablesContract.INDEXABLES_RAW_COLUMNS, baseRank); return true; } catch (PackageManager.NameNotFoundException e) { Log.w(LOG_TAG, "Could not create context for " + packageName + ": " + Log.getStackTraceString(e)); return false; } } 上面程式碼主要做了下面事情: 根據當前包名建立對應包的context物件。 根據當前包名構建指定URI,例如,settings:content://com.android.settings/settings/indexables_xml_res 然後通過context物件查詢對應的Provider的資料 之所以構建出content://com.android.settings/settings/indexables_xml_res 這樣的URI是因為所有的需要被搜尋到的設定項所在的APP,其Provider都需要繼承自SearchIndexablesProvider //SearchIndexablesProvider 繼承 ContentProvider public abstract class SearchIndexablesProvider extends ContentProvider { .... //定義了查詢路徑 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_XML_RES_PATH, MATCH_RES_CODE); mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_RAW_PATH, MATCH_RAW_CODE); mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH, MATCH_NON_INDEXABLE_KEYS_CODE); .... @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (mMatcher.match(uri)) { // 匹配不同的Uri進行查詢 case MATCH_RES_CODE: return queryXmlResources(null); case MATCH_RAW_CODE: return queryRawData(null); case MATCH_NON_INDEXABLE_KEYS_CODE: return queryNonIndexableKeys(null); default: throw new UnsupportedOperationException("Unknown Uri " + uri); } } @Override public String getType(Uri uri) { switch (mMatcher.match(uri)) { case MATCH_RES_CODE: return SearchIndexablesContract.XmlResource.MIME_TYPE; case MATCH_RAW_CODE: return SearchIndexablesContract.RawData.MIME_TYPE; case MATCH_NON_INDEXABLE_KEYS_CODE: return SearchIndexablesContract.NonIndexableKey.MIME_TYPE; default: throw new IllegalArgumentException("Unknown URI " + uri); } } .... } //採用 MatrixCursor 構建虛擬的資料表 public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { private static final String TAG = "SettingsSearchIndexablesProvider"; @Override public boolean onCreate() { return true; } @Override public Cursor queryXmlResources(String[] projection) { MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS); //通過 SearchIndexableResources.values() 獲取到所有新增到map集合中的 SearchIndexableResource /* SearchIndexableResource的路徑為: /frameworks/base/core/java/android/provider/SearchIndexableResource.java 其中定義了 this.rank = rank; this.xmlResId = xmlResId; this.className = className; this.iconResId = iconResId; 等屬性 */ Collection<SearchIndexableResource> values = SearchIndexableResources.values(); for (SearchIndexableResource val : values) { Object[] ref = new Object[7]; ref[COLUMN_INDEX_XML_RES_RANK] = val.rank; ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId; ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className; ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId; ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = null; // intent action ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = null; // intent target package ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class cursor.addRow(ref); } return cursor; } @Override public Cursor queryRawData(String[] projection) { MatrixCursor result = new MatrixCursor(INDEXABLES_RAW_COLUMNS); return result; } // 該方法返回當前佈局不想被搜尋到的設定項 @Override public Cursor queryNonIndexableKeys(String[] projection) { MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS); return cursor; } } 接著 //Index#addIndexablesForXmlResourceUri private void addIndexablesForXmlResourceUri(Context packageContext, String packageName, Uri uri, String[] projection, int baseRank) { // 獲取指定包對應的ContentResolver final ContentResolver resolver = packageContext.getContentResolver(); final Cursor cursor = resolver.query(uri, projection, null, null, null); if (cursor == null) { Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString()); return; } try { final int count = cursor.getCount(); if (count > 0) { while (cursor.moveToNext()) { final int providerRank = cursor.getInt(COLUMN_INDEX_XML_RES_RANK); final int rank = (providerRank > 0) ? baseRank + providerRank : baseRank; final int xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID); final String className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME); final int iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID); final String action = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION); final String targetPackage = cursor.getString( COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE); final String targetClass = cursor.getString( COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS); SearchIndexableResource sir = new SearchIndexableResource(packageContext); sir.rank = rank; sir.xmlResId = xmlResId; sir.className = className; sir.packageName = packageName; sir.iconResId = iconResId; sir.intentAction = action; sir.intentTargetPackage = targetPackage; sir.intentTargetClass = targetClass; // 解析cursor資料,並且新增到記憶體UpdateData的dataToUpdate屬性上, dataToUpdate屬性是一個list集合 addIndexableData(sir); } } } finally { cursor.close(); } } public void addIndexableData(SearchIndexableData data) { synchronized (mDataToProcess) { mDataToProcess.dataToUpdate.add(data); } } //Index#updateInternal更新到資料庫中 private void updateInternal() { synchronized (mDataToProcess) { final UpdateIndexTask task = new UpdateIndexTask(); // 拷貝一個mDataToProcess物件的副本,前面將資料新增到mDataToProcess物件中。 UpdateData copy = mDataToProcess.copy(); // 執行UpdateIndexTask,UpdateIndexTask會將copy物件儲存到資料庫裡 task.execute(copy); mDataToProcess.clear(); } } //接下來的呼叫流程為: Index$UpdateIndexTask doInBackground --> processDataToUpdate(database, localeStr, dataToUpdate, nonIndexableKeys, forceUpdate) --> // 插入或者更新當前資料庫內容 indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys) --> // 繼續indexOneSearchIndexableData更新資料庫 private void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr, SearchIndexableData data, Map<String, List<String>> nonIndexableKeys) { //兩種方式新增資料庫 if (data instanceof SearchIndexableResource) { indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys); } else if (data instanceof SearchIndexableRaw) { indexOneRaw(database, localeStr, (SearchIndexableRaw) data); } } 接下來呼叫: indexFromResource() --> //List<SearchIndexableResource> resList = provider.getXmlResourcesToIndex(context, enabled) 獲取當前佈局不想被搜尋到的設定項 indexFromProvider() --> //List<SearchIndexableResource> resList = provider.getXmlResourcesToIndex(context, enabled) 需要解析的佈局 indexFromResource() --> //使用XmlResourceParser解析xml佈局 updateOneRowWithFilteredData() --> updateOneRow() --> //此方法最終將解析的資料更新至資料庫 //在settings中新增搜尋項: 在SearchIndexableResources 中維護了一個sResMap,其中添加了所有的SearchIndexableResource,每一個子頁面對應一個SearchIndexableResource,並且在SearchIndexableResources 提供了一個values()方 法,用來返回當前集合中的所有資料,其實SearchIndexableResources.values()是在SettingsSearchIndexablesProvider中用到的,SettingsSearchIndexablesProvider重寫了queryXmlResources方法,並且通過SearchIndexableResources.values()會返回setting中所有的子頁面,最後封裝成一個cursor. 在SearchIndexableResources的靜態程式碼塊中初始化了: static { sResMap.put(WifiSettings.class.getName(), new SearchIndexableResource( Ranking.getRankForClassName(WifiSettings.class.getName()), NO_DATA_RES_ID, WifiSettings.class.getName(), R.drawable.ic_settings_wireless)); sResMap.put(SavedAccessPointsWifiSettings.class.getName(), new SearchIndexableResource( Ranking.getRankForClassName(SavedAccessPointsWifiSettings.class.getName()), R.xml.wifi_display_saved_access_points, SavedAccessPointsWifiSettings.class.getName(), R.drawable.ic_settings_wireless)); } 兩種方式,一種是直接在new SearchIndexableResource() 時傳入佈局檔案,另一種為"NO_DATA_RES_ID"表示此搜尋項匹配沒有需要解析的xml檔案,此xml的解析在Index.java中, 採用第二種時,需要在對應的類中建立一個SEARCH_INDEX_DATA_PROVIDER,型別為SearchIndexProvider,繼承BaseSearchIndexProvider並複寫其兩個方法: getXmlResourcesToIndex 和 getNonIndexableKeys. 以SecuritySettings為例: /** * For Search. Please keep it in sync when updating "createPreferenceHierarchy()" */ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new SecuritySearchIndexProvider(); private static class SecuritySearchIndexProvider extends BaseSearchIndexProvider { @Override public List<SearchIndexableResource> getXmlResourcesToIndex( Context context, boolean enabled) { final List<SearchIndexableResource> index = new ArrayList<SearchIndexableResource>(); //返回需要解析的佈局 index.add(getSearchResource(context, R.xml.security_settings_misc)); return index; } @Override public List<String> getNonIndexableKeys(Context context) { // 該方法返回當前佈局不想被搜尋到的設定項 final List<String> keys = new ArrayList<String>(); LockPatternUtils lockPatternUtils = new LockPatternUtils(context); // Do not display SIM lock for devices without an Icc card final UserManager um = UserManager.get(context); final TelephonyManager tm = TelephonyManager.from(context); if (!um.isAdminUser() || !tm.hasIccCard()) { keys.add(KEY_SIM_LOCK); } if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) { keys.add(KEY_CREDENTIALS_MANAGER); } // TrustAgent settings disappear when the user has no primary security. if (!lockPatternUtils.isSecure(MY_USER_ID)) { // keys.add(KEY_TRUST_AGENT); //Move To LockScreen Settings keys.add(KEY_MANAGE_TRUST_AGENTS); } return keys; } 在搜尋過程中,會從資料庫中查詢匹配,點選篩選結果,根據className啟動對應的介面,程式碼實現在SearchResultsSummary類中: @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.search_panel, container, false); mLayoutSuggestions = (ViewGroup) view.findViewById(R.id.layout_suggestions); mLayoutResults = (ViewGroup) view.findViewById(R.id.layout_results); mResultsListView = (ListView) view.findViewById(R.id.list_results); mResultsListView.setAdapter(mResultsAdapter); mResultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { // mResultsListView就是查詢的結果列表 @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // We have a header, so we need to decrement the position by one position--; // Some Monkeys could create a case where they were probably clicking on the // List Header and thus the position passed was "0" and then by decrement was "-1" if (position < 0) { return; } final Cursor cursor = mResultsAdapter.mCursor; cursor.moveToPosition(position); final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME); final String screenTitle = cursor.getString(Index.COLUMN_INDEX_SCREEN_TITLE); final String action = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION); final String key = cursor.getString(Index.COLUMN_INDEX_KEY); final SettingsActivity sa = (SettingsActivity) getActivity(); sa.needToRevertToInitialFragment(); if (TextUtils.isEmpty(action)) { Bundle args = new Bundle(); args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key); Utils.startWithFragment(sa, className, args, null, 0, -1, screenTitle); // 通過className啟動Settings中對應的Fragment介面 } else { final Intent intent = new Intent(action); final String targetPackage = cursor.getString( Index.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE); final String targetClass = cursor.getString( Index.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS); if (!TextUtils.isEmpty(targetPackage) && !TextUtils.isEmpty(targetClass)) { final ComponentName component = new ComponentName(targetPackage, targetClass); intent.setComponent(component); } intent.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key); sa.startActivity(intent); } saveQueryToDatabase(); } }); }
相關推薦
Android Settings 快速搜尋
Settings 之 SearchIndexablesProvider 首先需要在清單檔案中註冊action為"android.content.action.SEARCH_INDEXABLES_PROVIDER"的provider,如下: <provide
Android 從資料庫中快速搜尋匹配資料並新增監聽事件
如何從資料庫中搜索與我們目標相符的資料呢? 我使用的是List view+cursoradapter。現在應該很少有人使用list view了吧,原來打算換換recyclerview來寫的,但是recyclerview不支援cursor view啊。暫且先記著
start com.android.settings/com.android.settings.SubSettings activity
window content htm track dap method popu -s use 1. get class name: adb shell [email protected]/* */:/mnt/sdcard/books $ dumpsys w
Android中快速實現自定義字體!
sdk true fcm version ttf spa pre ets 怎麽 前言:我們都知道,Android中默認的字體是黑體,而大多數app也都是使用的這種字體,但我們發現,大多數app中,個別地方字體非常好看,例如app的標題欄,菜單欄等地方,那他們是怎麽做到的呢?
Android基礎——快速開發之定制BaseTemplate
temp .net fonts The 成了 抽取 一份 應該 我們 初學者肯定會遇到一個日常任務,那麽就是findViewById,setOnClickListener(暫且把它們稱為日常任務),而且很多人會把他們混在一起,導致項目結構混亂,最主要的是寫多了會煩,不覺得嗎
Android基礎——快速開發之打造萬能適配器
臃腫 log 思想 代碼分析 htm 考試報名 做了 順序 基礎 這裏以ListView作演示,對於ListView我們再熟悉不過了,其步驟分為: 創建ListView的Bean對象 創建ListView的Adapter的ItemView布局 創建ListView的Ada
Android BLE 快速上手指南
原文地址 本文旨在提供一個方便沒接觸過Android上低功耗藍芽(Bluetooth Low Energy)的同學快速上手使用的簡易教程,因此對其中的一些細節不做過分深入的探討,此外,為了讓沒有Ble裝置的同學也能模擬與裝置的互動過程,本文還提供了中央裝置(central)和外圍裝置(periphera
Android實現快速傳送電子郵件
最近有朋友有需求是通過apk傳送郵件,我心想這怎麼可以實現?然後就研究了一番,最後得出結論是可行的! 確實可以自己的手機上定義主題和內容或者附件,然後傳送給對應的郵箱!詳細步驟傾聽我一一道來 我們以A郵箱傳送郵件給B郵箱為例: 1 開啟A郵箱的POP3服務 每個郵箱都有POP3服
android——butterKnife快速生成
gradle: compile 'com.jakewharton:butterknife:8.5.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' 步驟: 下載外掛 file-》settings
BAT大牛親授技能+技巧 Android面試快速充電升級
前往下載 BAT大咖助力 全面升級Android面試 第1章 課程介紹(本課程專為初中級同學面試複習) 本課程專為初中級程度同學面試準備的系統複習指南,本章帶你瞭解面試過程中會遇到的問題,個人應該擺正的心態,以及面試官最為看重你的解決問題的思路。關於框架面試專題課程請移步到:http
消滅黑白屏,實現android app“快速啟動”
進行應用開發時,如果沒有對app的啟動頁做處理,那我們的app冷啟動時就會出現一個白屏或者黑屏的過程,正是這個黑白屏過程的存在會讓使用者感覺app啟動速度慢,本篇部落格中所說的“快速啟動“”也正是針對這個過程進行優化以達到沒有黑白屏的過程; 關於app的冷啟動: 冷啟動是指在程序未建立時,使用者
adb命令列開啟Android settings
adb命令開啟手機設定頁面 設定主頁面 adb shell am start com.android.settings/com.android.settings.Settings 安全 adb shell am start com.android.settings/com.andro
Android annotations快速開發框架使用,Android Studio與Eclipse配置
Androidannotations框架是目前最火的Andorid端快速開發框架,通過註解方式挺高開發效率,減少重複編寫沒有技術含量的程式碼。 使用AndoridAnnotations框架的理由:  
快速搜尋效能問題調研
最近因為專案需要做搜尋,安排我對搜尋的效能這一方面做調研。本文件調研了simhash和es為代表的搜尋方案。用Simhash和ElasticSearch做搜尋各有優缺點,綜合來看可這麼標籤:Simhash是偏計算密集型的搜尋方案代表,但演算法方案複雜;ElasticSea
Android 鍵盤的搜尋按鈕功能
系統鍵盤的搜尋按鈕,預設情況下是被隱藏的,如果要使用必須要手動設定,才可以調用搜索按鍵功能。 具體使用,只需要如下三個步驟: 1:在佈局檔案中的EditText中新增如下三個屬性 android:maxLines=“1” android:singleLine=“true” and
Android藍芽搜尋連線通訊
藍芽( Bluetooth® ):是一種無線技術標準,可實現固定裝置、移動裝置和樓宇個人域網之間的短距離資料交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍芽技術最初由電信巨頭愛立信公司於1994年創制,當時是作為RS232資料線的替代方案。
BAT大牛親授技能 技巧 Android面試快速充電升級
第1章 課程介紹(本課程專為初中級同學面試複習)本課程專為初中級程度同學面試準備的系統複習指南,本章帶你瞭解面試過程中會遇到的問題,個人應該擺正的心態,以及面試官最為看重你的解決問題的思路。關於框架面試專題課程請移步到:http://coding.imooc.com/class/157.html1-1 課程介
【機器人學:運動規劃】快速搜尋隨機樹(RRT---Rapidly-exploring Random Trees)入門及在Matlab中演示
快速搜尋隨機樹(RRT -Rapidly-ExploringRandom Trees),是一種常見的用於機器人路徑(運動)規劃的方法,它本質上是一種隨機生成的資料結構—樹,這種思想自從LaValle在[1]中提出以後已經得到了極大的發展,到現在依然有改進的RRT不斷地被提出來。
Android避免快速雙擊按鈕最簡單好用的方式
oid 方法 nbsp lis lean 按鈕 urn turn true 代碼如下,直接放到工具類中即可。類可以實現Onclicklistener,然後重寫onClick方法,直接將該函數寫在onClick方法中即可,這樣對於所有的點擊事件都將生效。 避免了快速雙擊出現
Android APP 快速開發教程(安卓)
Android APP 快速開發教程(安卓) 前言 本篇部落格從開發的角度來介紹如何開發一個Android App,需要說明一點是,這裡只是提供一個如何開發一個app的思路,並不會介紹很多技術上的細節,從整個大局去把握如何去構思一個app的開發,讓你對獨立開發一款app的時候有個理解