版权声明:本文为博主原创文章,未经博主允许不得转载。
- 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();
- }
- });
- }