Android Settings 快速搜索

Android Settings 快速搜索

标签: androidSettingsSearchIndexablesProv快速搜索
  734人阅读  评论(0)  收藏  举报
[java]  view plain  copy
  1. Settings 之 SearchIndexablesProvider  
  2. 首先需要在清单文件中注册action为"android.content.action.SEARCH_INDEXABLES_PROVIDER"的provider,如下:  
  3.       <provider  
  4.             android:name=".search.SettingsSearchIndexablesProvider"  
  5.             android:authorities="com.android.settings"  
  6.             android:multiprocess="false"  
  7.             android:grantUriPermissions="true"  
  8.             android:permission="android.permission.READ_SEARCH_INDEXABLES"  
  9.             android:exported="true">  
  10.             <intent-filter>  
  11.                 <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" /> //注册此 action  
  12.             </intent-filter>  
  13.         </provider>  
  14.   
  15. 搜索数据库路径:/data/user_de/0/com.android.settings/databases/search_index.db  
  16. //search_index.db 数据库的prefs_index表格中存放的就是搜索的设置选项  
  17. 此数据库的初始化不是在开机阶段,而是在每一次打开settings或者当前切换用户(因为系统为每一个用户维护一个单独的search_index.db),或者是当前的语言发生变化会更新数据库.  
  18.   
  19. 数据库的初始化:  
  20. Index#update  
  21.   
  22.     public void update() {  
  23.         AsyncTask.execute(new Runnable() {  
  24.             @Override  
  25.             public void run() {  
  26.                 // 查找系统中所有的配置了"android.content.action.SEARCH_INDEXABLES_PROVIDER"的Provider  
  27.                 final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);  
  28.                 List<ResolveInfo> list =  
  29.                         mContext.getPackageManager().queryIntentContentProviders(intent, 0);  
  30.   
  31.                 final int size = list.size();  
  32.                 for (int n = 0; n < size; n++) {  
  33.                     final ResolveInfo info = list.get(n);  
  34.                     if (!isWellKnownProvider(info)) {  
  35.                         continue;  
  36.                     }  
  37.                     final String authority = info.providerInfo.authority;  
  38.                     final String packageName = info.providerInfo.packageName;  
  39.                     //打印packageName为:  
  40.                     //01-01 17:38:09.257  5207  5511 E qcdds   : packageName= com.android.cellbroadcastreceiver  
  41.                     //01-01 17:38:09.678  5207  5511 E qcdds   : packageName= com.android.phone  
  42.                     //01-01 17:38:09.777  5207  5511 E qcdds   : packageName= com.android.settings  
  43.   
  44.                     // 添加其他APP的设置项  
  45.                     addIndexablesFromRemoteProvider(packageName, authority); //主要方法  
  46.                     // 添加其他APP中不需要被搜索到的设置项  
  47.                     addNonIndexablesKeysFromRemoteProvider(packageName, authority);  
  48.                 }  
  49.   
  50.                 mDataToProcess.fullIndex = true;  
  51.                 // 上面的addIndexablesFromRemoteProvider会添加设置项到内存中的一个mDataToProcess对象里,updateInternal将该对象更新到数据库中  
  52.                 updateInternal();  
  53.             }  
  54.         });  
  55.     }  
  56.   
  57.   
  58.     private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {  
  59.         try {  
  60.             // rank是按照指定算法计算出的一个值,用来搜索的时候,展示给用户的优先级  
  61.             final int baseRank = Ranking.getBaseRankForAuthority(authority);  
  62.             // mBaseAuthority是com.android.settings,authority是其他APP的包名  
  63.             final Context context = mBaseAuthority.equals(authority) ?  
  64.                     mContext : mContext.createPackageContext(packageName, 0);  
  65.             // 构建搜索的URI  
  66.             final Uri uriForResources = buildUriForXmlResources(authority);  
  67.             // 两种添加到数据库的方式,我们以addIndexablesForXmlResourceUri为例  
  68.             addIndexablesForXmlResourceUri(context, packageName, uriForResources,  
  69.                     SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS, baseRank);  
  70.   
  71.             final Uri uriForRawData = buildUriForRawData(authority);  
  72.             addIndexablesForRawDataUri(context, packageName, uriForRawData,  
  73.                     SearchIndexablesContract.INDEXABLES_RAW_COLUMNS, baseRank);  
  74.             return true;  
  75.         } catch (PackageManager.NameNotFoundException e) {  
  76.             Log.w(LOG_TAG, "Could not create context for " + packageName + ": "  
  77.                     + Log.getStackTraceString(e));  
  78.             return false;  
  79.         }  
  80.      }  
  81.   
  82.   
  83. 上面代码主要做了下面事情:  
  84.   
  85.     根据当前包名创建对应包的context对象。  
  86.     根据当前包名构建指定URI,例如,settings:content://com.android.settings/settings/indexables_xml_res  
  87.     然后通过context对象查找对应的Provider的数据  
  88.     之所以构建出content://com.android.settings/settings/indexables_xml_res 这样的URI是因为所有的需要被搜索到的设置项所在的APP,其Provider都需要继承自SearchIndexablesProvider  
  89.   
  90.   
  91. //SearchIndexablesProvider 继承 ContentProvider  
  92. public abstract class SearchIndexablesProvider extends ContentProvider {  
  93.     ....  
  94.         //定义了查询路径  
  95.         mMatcher = new UriMatcher(UriMatcher.NO_MATCH);  
  96.         mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_XML_RES_PATH,  
  97.                 MATCH_RES_CODE);  
  98.         mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_RAW_PATH,  
  99.                 MATCH_RAW_CODE);  
  100.         mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH,  
  101.                 MATCH_NON_INDEXABLE_KEYS_CODE);  
  102.     ....  
  103.   
  104.     @Override  
  105.     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,  
  106.                         String sortOrder) {  
  107.         switch (mMatcher.match(uri)) {  
  108.             // 匹配不同的Uri进行查找  
  109.             case MATCH_RES_CODE:  
  110.                 return queryXmlResources(null);  
  111.             case MATCH_RAW_CODE:  
  112.                 return queryRawData(null);  
  113.             case MATCH_NON_INDEXABLE_KEYS_CODE:  
  114.                 return queryNonIndexableKeys(null);  
  115.             default:  
  116.                 throw new UnsupportedOperationException("Unknown Uri " + uri);  
  117.         }  
  118.     }  
  119.   
  120.     @Override  
  121.     public String getType(Uri uri) {  
  122.         switch (mMatcher.match(uri)) {  
  123.             case MATCH_RES_CODE:  
  124.                 return SearchIndexablesContract.XmlResource.MIME_TYPE;  
  125.             case MATCH_RAW_CODE:  
  126.                 return SearchIndexablesContract.RawData.MIME_TYPE;  
  127.             case MATCH_NON_INDEXABLE_KEYS_CODE:  
  128.                 return SearchIndexablesContract.NonIndexableKey.MIME_TYPE;  
  129.             default:  
  130.                 throw new IllegalArgumentException("Unknown URI " + uri);  
  131.         }  
  132.     }  
  133.     ....  
  134.   
  135. }  
  136.   
  137.   
  138. //采用 MatrixCursor 构建虚拟的数据表  
  139. public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {  
  140.     private static final String TAG = "SettingsSearchIndexablesProvider";  
  141.   
  142.     @Override  
  143.     public boolean onCreate() {  
  144.         return true;  
  145.     }  
  146.   
  147.     @Override  
  148.     public Cursor queryXmlResources(String[] projection) {  
  149.         MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);  
  150.         //通过 SearchIndexableResources.values() 获取到所有添加到map集合中的 SearchIndexableResource  
  151.         /* 
  152.           SearchIndexableResource的路径为: /frameworks/base/core/java/android/provider/SearchIndexableResource.java 
  153.           其中定义了    this.rank = rank; 
  154.                         this.xmlResId = xmlResId; 
  155.                         this.className = className; 
  156.                         this.iconResId = iconResId; 
  157.           等属性 
  158.         */  
  159.         Collection<SearchIndexableResource> values = SearchIndexableResources.values();   
  160.         for (SearchIndexableResource val : values) {  
  161.             Object[] ref = new Object[7];  
  162.             ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;  
  163.             ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;  
  164.             ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className;  
  165.             ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId;  
  166.             ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = null// intent action  
  167.             ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = null// intent target package  
  168.             ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null// intent target class  
  169.             cursor.addRow(ref);  
  170.         }  
  171.         return cursor;  
  172.     }  
  173.   
  174.     @Override  
  175.     public Cursor queryRawData(String[] projection) {  
  176.         MatrixCursor result = new MatrixCursor(INDEXABLES_RAW_COLUMNS);  
  177.         return result;  
  178.     }  
  179.     // 该方法返回当前布局不想被搜索到的设置项  
  180.     @Override  
  181.     public Cursor queryNonIndexableKeys(String[] projection) {  
  182.         MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);  
  183.         return cursor;  
  184.     }  
  185. }  
  186.   
  187.   
  188.   
  189. 接着  
  190. //Index#addIndexablesForXmlResourceUri  
  191.   
  192. private void addIndexablesForXmlResourceUri(Context packageContext, String packageName,  
  193.             Uri uri, String[] projection, int baseRank) {  
  194.         // 获取指定包对应的ContentResolver  
  195.         final ContentResolver resolver = packageContext.getContentResolver();  
  196.         final Cursor cursor = resolver.query(uri, projection, nullnullnull);  
  197.   
  198.         if (cursor == null) {  
  199.             Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());  
  200.             return;  
  201.         }  
  202.   
  203.         try {  
  204.             final int count = cursor.getCount();  
  205.             if (count > 0) {  
  206.                 while (cursor.moveToNext()) {  
  207.                     final int providerRank = cursor.getInt(COLUMN_INDEX_XML_RES_RANK);  
  208.                     final int rank = (providerRank > 0) ? baseRank + providerRank : baseRank;  
  209.   
  210.                     final int xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID);  
  211.   
  212.                     final String className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME);  
  213.                     final int iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID);  
  214.   
  215.                     final String action = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION);  
  216.                     final String targetPackage = cursor.getString(  
  217.                             COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE);  
  218.                     final String targetClass = cursor.getString(  
  219.                             COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS);  
  220.   
  221.                     SearchIndexableResource sir = new SearchIndexableResource(packageContext);  
  222.                     sir.rank = rank;  
  223.                     sir.xmlResId = xmlResId;  
  224.                     sir.className = className;  
  225.                     sir.packageName = packageName;  
  226.                     sir.iconResId = iconResId;  
  227.                     sir.intentAction = action;  
  228.                     sir.intentTargetPackage = targetPackage;  
  229.                     sir.intentTargetClass = targetClass;  
  230.                     // 解析cursor数据,并且添加到内存UpdateData的dataToUpdate属性上, dataToUpdate属性是一个list集合  
  231.                     addIndexableData(sir);  
  232.                 }  
  233.             }  
  234.         } finally {  
  235.             cursor.close();  
  236.         }  
  237. }  
  238.   
  239.   
  240.   
  241. public void addIndexableData(SearchIndexableData data) {  
  242.         synchronized (mDataToProcess) {  
  243.             mDataToProcess.dataToUpdate.add(data);  
  244.         }  
  245. }  
  246.   
  247. //Index#updateInternal更新到数据库中  
  248. private void updateInternal() {  
  249.         synchronized (mDataToProcess) {  
  250.             final UpdateIndexTask task = new UpdateIndexTask();  
  251.             // 拷贝一个mDataToProcess对象的副本,前面将数据添加到mDataToProcess对象中。  
  252.             UpdateData copy = mDataToProcess.copy();  
  253.             // 执行UpdateIndexTask,UpdateIndexTask会将copy对象保存到数据库里  
  254.             task.execute(copy);  
  255.             mDataToProcess.clear();  
  256.         }  
  257. }  
  258.   
  259.   
  260. //接下来的调用流程为:  
  261. Index$UpdateIndexTask  
  262.   doInBackground -->   
  263.       processDataToUpdate(database, localeStr, dataToUpdate, nonIndexableKeys, forceUpdate) --> // 插入或者更新当前数据库内容     
  264.           indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys) -->  // 继续indexOneSearchIndexableData更新数据库  
  265.   
  266.   
  267.   
  268. private void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr,  
  269.             SearchIndexableData data, Map<String, List<String>> nonIndexableKeys) {  
  270.         //两种方式添加数据库  
  271.         if (data instanceof SearchIndexableResource) {  
  272.             indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys);  
  273.         } else if (data instanceof SearchIndexableRaw) {  
  274.             indexOneRaw(database, localeStr, (SearchIndexableRaw) data);  
  275.         }  
  276. }  
  277.         接下来调用:  
  278.         indexFromResource() -->  //List<SearchIndexableResource> resList = provider.getXmlResourcesToIndex(context, enabled) 获取当前布局不想被搜索到的设置项  
  279.                 indexFromProvider() --> //List<SearchIndexableResource> resList = provider.getXmlResourcesToIndex(context, enabled)  需要解析的布局  
  280.                       indexFromResource() -->  //使用XmlResourceParser解析xml布局  
  281.                             updateOneRowWithFilteredData() -->   
  282.                                   updateOneRow() -->  //此方法最终将解析的数据更新至数据库  
  283.   
  284. //在settings中添加搜索项:  
  285.   
  286. 在SearchIndexableResources 中维护了一个sResMap,其中添加了所有的SearchIndexableResource,每一个子页面对应一个SearchIndexableResource,并且在SearchIndexableResources 提供了一个values()方  
  287. 法,用来返回当前集合中的所有数据,其实SearchIndexableResources.values()是在SettingsSearchIndexablesProvider中用到的,SettingsSearchIndexablesProvider重写了queryXmlResources方法,并且通过SearchIndexableResources.values()会返回setting中所有的子页面,最后封装成一个cursor.  
  288.   
  289. 在SearchIndexableResources的静态代码块中初始化了:  
  290.     static {  
  291.         sResMap.put(WifiSettings.class.getName(),  
  292.                 new SearchIndexableResource(  
  293.                         Ranking.getRankForClassName(WifiSettings.class.getName()),  
  294.                         NO_DATA_RES_ID,  
  295.                         WifiSettings.class.getName(),  
  296.                         R.drawable.ic_settings_wireless));  
  297.       
  298.         sResMap.put(SavedAccessPointsWifiSettings.class.getName(),  
  299.                 new SearchIndexableResource(  
  300.                         Ranking.getRankForClassName(SavedAccessPointsWifiSettings.class.getName()),  
  301.                         R.xml.wifi_display_saved_access_points,  
  302.                         SavedAccessPointsWifiSettings.class.getName(),  
  303.                         R.drawable.ic_settings_wireless));  
  304.   
  305.     }  
  306. 两种方式,一种是直接在new SearchIndexableResource() 时传入布局文件,另一种为"NO_DATA_RES_ID"表示此搜索项匹配没有需要解析的xml文件,此xml的解析在Index.java中,  
  307. 采用第二种时,需要在对应的类中创建一个SEARCH_INDEX_DATA_PROVIDER,类型为SearchIndexProvider,继承BaseSearchIndexProvider并复写其两个方法:   
  308. getXmlResourcesToIndex 和 getNonIndexableKeys.  
  309.   
  310. 以SecuritySettings为例:  
  311.     /** 
  312.      * For Search. Please keep it in sync when updating "createPreferenceHierarchy()" 
  313.      */  
  314.     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new SecuritySearchIndexProvider();  
  315.   
  316.     private static class SecuritySearchIndexProvider extends BaseSearchIndexProvider {  
  317.   
  318.         @Override  
  319.         public List<SearchIndexableResource> getXmlResourcesToIndex(  
  320.                 Context context, boolean enabled) {  
  321.             final List<SearchIndexableResource> index = new ArrayList<SearchIndexableResource>();  
  322.             //返回需要解析的布局  
  323.             index.add(getSearchResource(context, R.xml.security_settings_misc));  
  324.             return index;  
  325.         }  
  326.   
  327.         @Override  
  328.         public List<String> getNonIndexableKeys(Context context) {  
  329.             // 该方法返回当前布局不想被搜索到的设置项  
  330.             final List<String> keys = new ArrayList<String>();  
  331.             LockPatternUtils lockPatternUtils = new LockPatternUtils(context);  
  332.   
  333.             // Do not display SIM lock for devices without an Icc card  
  334.             final UserManager um = UserManager.get(context);  
  335.             final TelephonyManager tm = TelephonyManager.from(context);  
  336.             if (!um.isAdminUser() || !tm.hasIccCard()) {  
  337.                 keys.add(KEY_SIM_LOCK);  
  338.             }  
  339.             if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {  
  340.                 keys.add(KEY_CREDENTIALS_MANAGER);  
  341.             }  
  342.   
  343.             // TrustAgent settings disappear when the user has no primary security.  
  344.             if (!lockPatternUtils.isSecure(MY_USER_ID)) {  
  345.                 // keys.add(KEY_TRUST_AGENT); //Move To LockScreen Settings  
  346.                 keys.add(KEY_MANAGE_TRUST_AGENTS);  
  347.             }  
  348.   
  349.             return keys;  
  350.         }  
  351.   
  352.   
  353.   
  354. 在搜索过程中,会从数据库中查找匹配,点击筛选结果,根据className启动对应的界面,代码实现在SearchResultsSummary类中:  
  355.   
  356.     @Override  
  357.     public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  358.                              Bundle savedInstanceState) {  
  359.   
  360.         final View view = inflater.inflate(R.layout.search_panel, container, false);  
  361.   
  362.         mLayoutSuggestions = (ViewGroup) view.findViewById(R.id.layout_suggestions);  
  363.         mLayoutResults = (ViewGroup) view.findViewById(R.id.layout_results);  
  364.   
  365.         mResultsListView = (ListView) view.findViewById(R.id.list_results);  
  366.         mResultsListView.setAdapter(mResultsAdapter);  
  367.         mResultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { // mResultsListView就是查询的结果列表  
  368.             @Override  
  369.             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
  370.                 // We have a header, so we need to decrement the position by one  
  371.                 position--;  
  372.   
  373.                 // Some Monkeys could create a case where they were probably clicking on the  
  374.                 // List Header and thus the position passed was "0" and then by decrement was "-1"  
  375.                 if (position < 0) {  
  376.                     return;  
  377.                 }  
  378.   
  379.                 final Cursor cursor = mResultsAdapter.mCursor;  
  380.                 cursor.moveToPosition(position);  
  381.   
  382.                 final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME);   
  383.                 final String screenTitle = cursor.getString(Index.COLUMN_INDEX_SCREEN_TITLE);  
  384.                 final String action = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION);  
  385.                 final String key = cursor.getString(Index.COLUMN_INDEX_KEY);  
  386.   
  387.                 final SettingsActivity sa = (SettingsActivity) getActivity();  
  388.                 sa.needToRevertToInitialFragment();  
  389.                 if (TextUtils.isEmpty(action)) {  
  390.                     Bundle args = new Bundle();  
  391.                     args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);  
  392.   
  393.                     Utils.startWithFragment(sa, className, args, null0, -1, screenTitle); // 通过className启动Settings中对应的Fragment界面  
  394.                 } else {  
  395.                     final Intent intent = new Intent(action);  
  396.   
  397.                     final String targetPackage = cursor.getString(  
  398.                             Index.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);  
  399.                     final String targetClass = cursor.getString(  
  400.                             Index.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS);  
  401.                     if (!TextUtils.isEmpty(targetPackage) && !TextUtils.isEmpty(targetClass)) {  
  402.                         final ComponentName component =  
  403.                                 new ComponentName(targetPackage, targetClass);  
  404.                         intent.setComponent(component);  
  405.                     }  
  406.                     intent.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);  
  407.   
  408.                     sa.startActivity(intent);  
  409.                 }  
  410.   
  411.                 saveQueryToDatabase();  
  412.             }  
  413.         });  
  414.   
  415.     }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值