SettingsIntelligence

SettingsIntelligence

八归少年 524 2023-10-23

Android Settings 系列文章:

首语

Android Settings中搜索功能帮助我们可以快速访问设置项,进行自定义设置,以得到更佳的使用体验。Android Settings搜索的实现实际不在Settings模块里,而是存在一个单独的模块—SettingsIntelligence,它里面实现了Settings的核心搜索功能,因此,学习SettingsIntelligence搜索实现可以让我们更多了解Settings模块。

搜索实现流程

本文以Android 13 SettingsIntelligence模块源码进行分析。

首先搜索栏的跳转实现在SearchFeatureProvider的initSearchToolbar中,initSearchToolbar在Android Settings解析文章分析过,在SettingsHomepageActivity的initSearchBarView方法中调用。最终跳转到包名为com.android.settings.intelligence,action为android.settings.APP_SEARCH_SETTINGS的页面中。

public interface SearchFeatureProvider {
    default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {
        ...
         final Intent intent = buildSearchIntent(context, pageId)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        ...
        toolbar.setOnClickListener(tb -> {
            FeatureFactory.getFactory(context).getSlicesFeatureProvider()
                    .indexSliceDataAsync(context);

            FeatureFactory.getFactory(context).getMetricsFeatureProvider()
                    .logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId);

            final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();
            activity.startActivity(intent, bundle);
        });
    }
}

它对应的模块为SettingsIntelligence,模块路径:packages/apps/SettingsIntelligence。从AndroidManifest.xml可以看到,Settings跳转搜索的页面为SearchActivity,SearchActivity添加SearchFragment,在SearchFragment中实现了搜索的核心逻辑。

查看onCreate方法,进行了一些变量的初始化,onCreateView方法中进行view初始化,设置布局为search_panel,我们只需要关注搜索框控件SearchView,设置查询字符串为mQuery,即输入搜索的内容。

设置查询监听,重写onQueryTextSubmit和onQueryTextChange方法。当搜索框文本改变时,通过restartLoaders方法调用LoadManager开启加载数据流程。当Loader创建成功时,回调onCreateLoader方法,调用getSearchResultLoader方法来SearchResultLoader实例。

public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener,
        LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
       ...	
        mSearchView = toolbar.findViewById(R.id.search_view);
        mSearchView.setQuery(mQuery, false /* submitQuery */);
        mSearchView.setOnQueryTextListener(this);
        mSearchView.requestFocus();

        return view;
    }  
    @Override
    public boolean onQueryTextChange(String query) {
		...
        if (isEmptyQuery) {
            final LoaderManager loaderManager = getLoaderManager();
            loaderManager.destroyLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT);
            mShowingSavedQuery = true;
            mSavedQueryController.loadSavedQueries();
            mSearchFeatureProvider.hideFeedbackButton(getView());
        } else {
          mMetricsFeatureProvider.logEvent(SettingsIntelligenceEvent.PERFORM_SEARCH);
            restartLoaders();
        }
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        // Save submitted query.
        mSavedQueryController.saveQuery(mQuery);
        hideKeyboard();
        return true;
    }
    private void restartLoaders() {
        mShowingSavedQuery = false;
        final LoaderManager loaderManager = getLoaderManager();
        loaderManager.restartLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT,
                null /* args */, this /* callback */);
    }
    @Override
    public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
        final Activity activity = getActivity();
        switch (id) {
            case SearchCommon.SearchLoaderId.SEARCH_RESULT:
                return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
            default:
                return null;
        }
    }
}

在SearchFeatureProvider实现类SearchFeatureProviderImpl中创建了SearchResultLoader实例,SearchResultLoader在子线程进行数据查找。

public class SearchResultLoader extends AsyncLoader<List<? extends SearchResult>> {

    private final String mQuery;

    public SearchResultLoader(Context context, String query) {
        super(context);
        mQuery = query;
    }

    @Override
    public List<? extends SearchResult> loadInBackground() {
        SearchResultAggregator aggregator = SearchResultAggregator.getInstance();
        return aggregator.fetchResults(getContext(), mQuery);
    }
}

fetchResults方法进行数据查找,并创建了一个tasks集合,然后变量tasks,保存到taskResults中。

public class SearchResultAggregator {
    @NonNull
    public synchronized List<? extends SearchResult> fetchResults(Context context, String query) {
        final SearchFeatureProvider mFeatureProvider = FeatureFactory.get(context)
                .searchFeatureProvider();
        final ExecutorService executorService = mFeatureProvider.getExecutorService();

        final List<SearchQueryTask> tasks =
                mFeatureProvider.getSearchQueryTasks(context, query);
        // Start tasks
        for (SearchQueryTask task : tasks) {
            executorService.execute(task);
        }
        // Collect results
        final Map<Integer, List<? extends SearchResult>> taskResults = new ArrayMap<>();
        final long allTasksStart = System.currentTimeMillis();
        for (SearchQueryTask task : tasks) {
            final int taskId = task.getTaskId();
            try {
                taskResults.put(taskId,task.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
            } catch (TimeoutException | InterruptedException | ExecutionException e) {
                Log.d(TAG, "Could not retrieve result in time: " + taskId, e);
                taskResults.put(taskId, Collections.EMPTY_LIST);
            }
        }
        // Merge results
        final List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);
        return mergedResults;
    }
}

getSearchQueryTasks中构建了各种类型的task,如DatabaseResultTask/InstalledAppResultTask等等。这些task都继承于SearchQueryTask.QueryWorker。

public class SearchFeatureProviderImpl implements SearchFeatureProvider {
    @Override
    public List<SearchQueryTask> getSearchQueryTasks(Context context, String query) {
        final List<SearchQueryTask> tasks = new ArrayList<>();
        final String cleanQuery = cleanQuery(query);
        tasks.add(DatabaseResultTask.newTask(context, getSiteMapManager(), cleanQuery));
        tasks.add(InstalledAppResultTask.newTask(context, getSiteMapManager(), cleanQuery));
        tasks.add(AccessibilityServiceResultTask.newTask(context, getSiteMapManager(), cleanQuery));
        tasks.add(InputDeviceResultTask.newTask(context, getSiteMapManager(), cleanQuery));
        return tasks;
    }
}

而SearchQueryTask又继承于FutureTask,call方法去处理任务,完成后返回结果。

public class SearchQueryTask extends FutureTask<List<? extends SearchResult>> {
     public static abstract class QueryWorker implements Callable<List<? extends SearchResult>> {
          @Override
        public List<? extends SearchResult> call() throws Exception {
            final long startTime = System.currentTimeMillis();
            try {
                return query();
            } finally {
                final long endTime = System.currentTimeMillis();
                FeatureFactory.get(mContext).metricsFeatureProvider(mContext)
                        .logEvent(getQueryWorkerId(), endTime - startTime);
            }
        }
     }
}

我们以DatabaseResultTask为例,查看它实现的query方法。query方法通过一系列的查询方法将数据添加到resultSet中,可以看到query方法中获取SQLite数据库实例,IndexDatabaseHelper中初始化数据库,可以看到数据库名为search_index.db,表名和表字段。最后通过query方法查询数据。

public class DatabaseResultTask extends SearchQueryTask.QueryWorker {
    public static SearchQueryTask newTask(Context context, SiteMapManager siteMapManager,
            String query) {
        return new SearchQueryTask(new DatabaseResultTask(context, siteMapManager, query));
    }
    @Override
    protected List<? extends SearchResult> query() {
        if (mQuery == null || mQuery.isEmpty()) {
            return new ArrayList<>();
        }
        // Start a Future to get search result scores.
        FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask(
                mContext, mQuery);

        if (rankerTask != null) {
            ExecutorService executorService = mFeatureProvider.getExecutorService();
            executorService.execute(rankerTask);
        }

        final Set<SearchResult> resultSet = new HashSet<>();

        resultSet.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
        resultSet.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
        resultSet.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
        resultSet.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));

        // Try to retrieve the scores in time. Otherwise use static ranking.
        if (rankerTask != null) {
            try {
                final long timeoutMs = mFeatureProvider.smartSearchRankingTimeoutMs(mContext);
                List<Pair<String, Float>> searchRankScores = rankerTask.get(timeoutMs,
                        TimeUnit.MILLISECONDS);
                return getDynamicRankedResults(resultSet, searchRankScores);
            } catch (TimeoutException | InterruptedException | ExecutionException e) {
                Log.d(TAG, "Error waiting for result scores: " + e);
            }
        }

        List<SearchResult> resultList = new ArrayList<>(resultSet);
        Collections.sort(resultList);
        return resultList;
    }
    private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
        final String whereClause = buildSingleWordWhereClause(matchColumns);
        final String query = mQuery + "%";
        final String[] selection = buildSingleWordSelection(query, matchColumns.length);

        return query(whereClause, selection, baseRank);
    }
    private Set<SearchResult> query(String whereClause, String[] selection, int baseRank) {
        final SQLiteDatabase database =
                IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
        //查询搜索数据
        try (Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS,
                whereClause,
                selection, null, null, null)) {
            return mConverter.convertCursor(resultCursor, baseRank, mSiteMapManager);
        }
    }
}

那么问题来了,Settings搜索数据存储在SQLite数据库中,我们分析了它的查询流程,那么它是如何存储的呢?

其实在SearchFragment的onCreate就有实现,通过updateIndexAsync刷新数据。

public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener,
        LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
    ...
	mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);            
}

通过indexDatabase方法更新数据。

public class SearchFeatureProviderImpl implements SearchFeatureProvider {
    @Override
    public void updateIndexAsync(Context context, IndexingCallback callback) {
        if (DEBUG) {
            Log.d(TAG, "updating index async");
        }
        getIndexingManager(context).indexDatabase(callback);
    }
}

IndexingTask继承于AsyncTask。异步执行performIndexing方法,通过queryIntentContentProviders方法获取ContentProvider,然后根据provider查找数据,更新到数据库中。看下intent指定的action PROVIDER_INTERFACE为"android.content.action.SEARCH_INDEXABLES_PROVIDER",在Settings查找是否有定义此action的ContentProvider。

public class DatabaseIndexingManager {
    public void indexDatabase(IndexingCallback callback) {
        IndexingTask task = new IndexingTask(callback);
        task.execute();
    }
    public class IndexingTask extends AsyncTask<Void, Void, Void> {
        @VisibleForTesting
        IndexingCallback mCallback;
        private long mIndexStartTime;

        public IndexingTask(IndexingCallback callback) {
            mCallback = callback;
        }

        @Override
        protected void onPreExecute() {
            mIndexStartTime = System.currentTimeMillis();
            mIsIndexingComplete.set(false);
        }

        @Override
        protected Void doInBackground(Void... voids) {
            performIndexing();
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            int indexingTime = (int) (System.currentTimeMillis() - mIndexStartTime);
            FeatureFactory.get(mContext).metricsFeatureProvider(mContext).logEvent(
                    SettingsIntelligenceLogProto.SettingsIntelligenceEvent.INDEX_SEARCH,
                    indexingTime);

            mIsIndexingComplete.set(true);
            if (mCallback != null) {
                mCallback.onIndexingFinished();
            }
        }
    }
    public void performIndexing() {
        final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
        final List<ResolveInfo> providers =
                mContext.getPackageManager().queryIntentContentProviders(intent, 0);

        final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, providers);

        if (isFullIndex) {
            rebuildDatabase();
        }

        PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);

        final long updateDatabaseStartTime = System.currentTimeMillis();
        updateDatabase(indexData, isFullIndex);
        IndexDatabaseHelper.setIndexed(mContext, providers);
        if (DEBUG) {
            final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;
            Log.d(TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);
        }
    }
}

可以发现,在Settings的AndroidManifest.xml中指定一个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" />
            </intent-filter>
        </provider>

SettingsSearchIndexablesProvider继承于SearchIndexablesProvider,SearchIndexablesProvider继承于ContentProvider, query方法进行了分类查询,插入,删除,更新均不支持,通过final修饰和抛出UnsupportedOperationException屏蔽了。

public abstract class SearchIndexablesProvider extends ContentProvider {
     @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {
        try {
            switch (mMatcher.match(uri)) {
                case MATCH_RES_CODE:
                    return queryXmlResources(null);
                case MATCH_RAW_CODE:
                    return queryRawData(null);
                case MATCH_NON_INDEXABLE_KEYS_CODE:
                    return queryNonIndexableKeys(null);
                case MATCH_SITE_MAP_PAIRS_CODE:
                    return querySiteMapPairs();
                case MATCH_SLICE_URI_PAIRS_CODE:
                    return querySliceUriPairs();
                case MATCH_DYNAMIC_RAW_CODE:
                    return queryDynamicRawData(null);
                default:
                    throw new UnsupportedOperationException("Unknown Uri " + uri);
            }
        } catch (UnsupportedOperationException e) {
            throw e;
        } catch (Exception e) {
            Log.e(TAG, "Provider querying exception:", e);
            return null;
        }
    }
    @Override
    public final Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException("Insert not supported");
    }
}

以queryXmlResources为例,通过getSearchIndexableResourcesFromProvider方法获取数据集合,并保存到cursor中。bundles里一个class类型的集合。

public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {
    @Override
    public Cursor queryXmlResources(String[] projection) {
        final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
        final List<SearchIndexableResource> resources =
                getSearchIndexableResourcesFromProvider(getContext());
        for (SearchIndexableResource val : resources) {
            final Object[] ref = new Object[INDEXABLES_XML_RES_COLUMNS.length];
            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] = val.intentAction;
            ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = val.intentTargetPackage;
            ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class
            cursor.addRow(ref);
        }

        return cursor;
    }
    private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) {
        final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context)
               .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
        List<SearchIndexableResource> resourceList = new ArrayList<>();

        for (SearchIndexableData bundle : bundles) {
            Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();
            final List<SearchIndexableResource> resList =
                    provider.getXmlResourcesToIndex(context, true);
            if (resList == null) {
                continue;
            }
            for (SearchIndexableResource item : resList) {
                item.className = TextUtils.isEmpty(item.className)
                        ? bundle.getTargetClass().getName()
                        : item.className;
            }
            resourceList.addAll(resList);
        }
        return resourceList;
    }
}

SearchIndexableResourcesMobile继承于SearchIndexableResourcesBase,

public class SearchFeatureProviderImpl implements SearchFeatureProvider {
    @Override
    public SearchIndexableResources getSearchIndexableResources() {
        if (mSearchIndexableResources == null) {
            mSearchIndexableResources = new SearchIndexableResourcesMobile();
        }
        return mSearchIndexableResources;
    }
}

SearchIndexableResourcesMobile类生成在IndexableProcessor中,IndexableProcessor设置的注解为SearchIndexable,SearchIndexable注解可以指定target(ALL/MOBILE/TV/WEAR/AUTO/ARC)对应不同平台。通过JavaPoet库来addCode实例化SearchIndexableData,getProviderValues方法返回的是带有SearchIndexable注解的所有类集合。

@SupportedSourceVersion(SourceVersion.RELEASE_9)
@SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
public class IndexableProcessor extends AbstractProcessor {
     @Override
    public boolean process(Set<? extends TypeElement> annotations,
          
        for (Element element : roundEnvironment.getElementsAnnotatedWith(SearchIndexable.class)) {
            if (element.getKind().isClass()) {
                Name className = element.accept(new SimpleElementVisitor8<Name, Void>() {
                    @Override
                    public Name visitType(TypeElement typeElement, Void aVoid) {
                        return typeElement.getQualifiedName();
                    }
                }, null);
                if (className != null) {
                    SearchIndexable searchIndexable = element.getAnnotation(SearchIndexable.class);

                    int forTarget = searchIndexable.forTarget();
                    MethodSpec.Builder builder = baseConstructorBuilder;

                    if (forTarget == SearchIndexable.ALL) {
                        builder = baseConstructorBuilder;
                    } else if ((forTarget & SearchIndexable.MOBILE) != 0) {
                        builder = mobileConstructorBuilder;
                    } else if ((forTarget & SearchIndexable.TV) != 0) {
                        builder = tvConstructorBuilder;
                    } else if ((forTarget & SearchIndexable.WEAR) != 0) {
                        builder = wearConstructorBuilder;
                    } else if ((forTarget & SearchIndexable.AUTO) != 0) {
                        builder = autoConstructorBuilder;
                    } else if ((forTarget & SearchIndexable.ARC) != 0) {
                        builder = arcConstructorBuilder;
                    }
                    //实例化SearchIndexableData
                    builder.addCode(
                            "$N(new SearchIndexableData($L.class, $L"
                                    + ".SEARCH_INDEX_DATA_PROVIDER));\n",
                            addIndex, className, className);
               ...
            }
        }
        inal MethodSpec getProviderValues = MethodSpec.methodBuilder("getProviderValues")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .returns(ParameterizedTypeName.get(
                        ClassName.get(Collection.class),
                        searchIndexableData))
                .addCode("return $N;\n", providers)
                .build();

        final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
                .addField(providers)
                .addMethod(baseConstructorBuilder.build())
                .addMethod(addIndex)
                .addMethod(getProviderValues)
                .build();
        final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();

        final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,
                TypeSpec.classBuilder(CLASS_MOBILE)
                        .addModifiers(Modifier.PUBLIC)
                        .superclass(ClassName.get(PACKAGE, baseClass.name))
                        .addMethod(mobileConstructorBuilder.build())
                        .build())
                .build();
}

实例化SearchIndexableData,mTargetClass为className.class,mSearchIndexProvider为className.SEARCH_INDEX_DATA_PROVIDER,其中的className就是对应添加SearchIndexable注解的类名

public class SearchIndexableData {
	public SearchIndexableData(Class targetClass, Indexable.SearchIndexProvider provider) {
        mTargetClass = targetClass;
        mSearchIndexProvider = provider;
    }
}

总结一下,Settings搜索功能就是在需要被提供的页面添加@SearchIndexable注解,在这页面创建一个常量SEARCH_INDEX_DATA_PROVIDER,这个常量类型必须为Indexable.SearchIndexProvider。以TopLevelSettings为例。添加了@SearchIndexable注解,指定Target为MOBILE,也创建了SEARCH_INDEX_DATA_PROVIDER,Settings封装了一个基础的SearchIndexProvider,不返回任何要索引的数据,类名为BaseSearchIndexProvider。

@SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
            public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
            new BaseSearchIndexProvider(R.xml.top_level_settings) {

                @Override
                protected boolean isPageSearchEnabled(Context context) {
                    // Never searchable, all entries in this page are already indexed elsewhere.
                    return false;
                }
            };
}

SearchIndexProvider和BaseSearchIndexProvider扩展的方法可以让我们准确处理菜单搜索需求。

public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {
    public BaseSearchIndexProvider() {
    }

    public BaseSearchIndexProvider(int xmlRes) {
        mXmlRes = xmlRes;
    }
    //返回SearchIndexableResource
    @Override
    public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) {
        if (mXmlRes != 0) {
            final SearchIndexableResource sir = new SearchIndexableResource(context);
            sir.xmlResId = mXmlRes;
            return Arrays.asList(sir);
        }
        return null;
    }
    //返回SearchIndexableRaw集合
    @Override
    public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
        return null;
    }
    //返回动态数据集合
    @Override
    public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
        return null;
    }
    //无法搜索的集合
    @Override
    @CallSuper
    public List<String> getNonIndexableKeys(Context context) {
        ...
    }
    //页面是否启用搜索
    protected boolean isPageSearchEnabled(Context context) {
        return true;
    }
    //获取xml设置禁用搜索的集合
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    public List<String> getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId,
            boolean suppressAllPage) {
        return getKeysFromXml(context, xmlResId, suppressAllPage);
    }
}

以上就是Settings的搜索逻辑。要测试新菜单的可搜索性,需要先清除Settings数据,让数据库重新添加数据。

总结

Settings菜单如果想要支持搜索,首先对应页面需要添加@SearchIndexable注解,其次在本页面创建一个常量SEARCH_INDEX_DATA_PROVIDER,然后根据需要重写需要的实现方法。这样这个菜单就支持搜索了。

SettingsIntelligence会扫描这些添加@SearchIndexable注解的页面,将这些页面的菜单添加到数据库中,查询时根据关键词进行匹配查询。


Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.yanghujun.com/archives/settingsintelligence