• SettingsIntelligence


    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);
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    它对应的模块为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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    在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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    而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);
                }
            }
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们以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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

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

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

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

    通过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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    可以发现,在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    以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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    SearchIndexableResourcesMobile继承于SearchIndexableResourcesBase,

    public class SearchFeatureProviderImpl implements SearchFeatureProvider {
        @Override
        public SearchIndexableResources getSearchIndexableResources() {
            if (mSearchIndexableResources == null) {
                mSearchIndexableResources = new SearchIndexableResourcesMobile();
            }
            return mSearchIndexableResources;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    实例化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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结一下,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;
                    }
                };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

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

    总结

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

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

  • 相关阅读:
    java基于ssm的洗衣店管理系统
    分布式锁,redis锁,执行的过程
    C++ 类和对象篇(六) 拷贝构造函数
    Flink SQL ---Top-N ,Window Top-N
    conan入门(二十七):因profile [env]字段废弃导致的boost/1.81.0 在aarch64-linux-gnu下交叉编译失败
    【工作笔记】缓存里的几种模式
    【代码随想录】算法训练计划21、22
    Jmeter(五):json提取器元件及jsonpath介绍,响应断言元件
    计算机毕业设计(附源码)python志愿者招募系统
    java计算机毕业设计天津城建大学校友录管理系统源程序+mysql+系统+lw文档+远程调试
  • 原文地址:https://blog.csdn.net/yang_study_first/article/details/134000150