很多网站没有后台,在线免费crm黑白配,网站可以增加关键词吗,推广策划公司Android Settings 系列文章#xff1a;
Android Settings解析SettingsIntelligenceSettingsProvider
首语
Android Settings中搜索功能帮助我们可以快速访问设置项#xff0c;进行自定义设置#xff0c;以得到更佳的使用体验。Android Settings搜索的实现实际不在Setting…Android Settings 系列文章
Android Settings解析SettingsIntelligenceSettingsProvider
首语
Android Settings中搜索功能帮助我们可以快速访问设置项进行自定义设置以得到更佳的使用体验。Android Settings搜索的实现实际不在Settings模块里而是存在一个单独的模块—SettingsIntelligence它里面实现了Settings的核心搜索功能因此学习SettingsIntelligence搜索实现可以让我们更多了解Settings模块。
搜索实现流程
本文以Android 13 SettingsIntelligence模块源码进行分析。
首先搜索栏的跳转实现在SearchFeatureProvider的initSearchToolbar中initSearchToolbar在Android Settings解析文章分析过在SettingsHomepageActivity的initSearchBarView方法中调用。最终跳转到包名为com.android.settings.intelligenceaction为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跳转搜索的页面为SearchActivitySearchActivity添加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.LoaderCallbacksList? extends SearchResult, IndexingCallback {Overridepublic 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;} Overridepublic 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;}Overridepublic 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 */);}Overridepublic LoaderList? 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 AsyncLoaderList? extends SearchResult {private final String mQuery;public SearchResultLoader(Context context, String query) {super(context);mQuery query;}Overridepublic List? extends SearchResult loadInBackground() {SearchResultAggregator aggregator SearchResultAggregator.getInstance();return aggregator.fetchResults(getContext(), mQuery);}
}fetchResults方法进行数据查找并创建了一个tasks集合然后变量tasks保存到taskResults中。
public class SearchResultAggregator {NonNullpublic synchronized List? extends SearchResult fetchResults(Context context, String query) {final SearchFeatureProvider mFeatureProvider FeatureFactory.get(context).searchFeatureProvider();final ExecutorService executorService mFeatureProvider.getExecutorService();final ListSearchQueryTask tasks mFeatureProvider.getSearchQueryTasks(context, query);// Start tasksfor (SearchQueryTask task : tasks) {executorService.execute(task);}// Collect resultsfinal MapInteger, 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 resultsfinal List? extends SearchResult mergedResults mergeSearchResults(taskResults);return mergedResults;}
}getSearchQueryTasks中构建了各种类型的task如DatabaseResultTask/InstalledAppResultTask等等。这些task都继承于SearchQueryTask.QueryWorker。
public class SearchFeatureProviderImpl implements SearchFeatureProvider {Overridepublic ListSearchQueryTask getSearchQueryTasks(Context context, String query) {final ListSearchQueryTask 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又继承于FutureTaskcall方法去处理任务完成后返回结果。
public class SearchQueryTask extends FutureTaskList? extends SearchResult {public static abstract class QueryWorker implements CallableList? extends SearchResult {Overridepublic 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));}Overrideprotected List? extends SearchResult query() {if (mQuery null || mQuery.isEmpty()) {return new ArrayList();}// Start a Future to get search result scores.FutureTaskListPairString, Float rankerTask mFeatureProvider.getRankerTask(mContext, mQuery);if (rankerTask ! null) {ExecutorService executorService mFeatureProvider.getExecutorService();executorService.execute(rankerTask);}final SetSearchResult 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);ListPairString, 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);}}ListSearchResult resultList new ArrayList(resultSet);Collections.sort(resultList);return resultList;}private SetSearchResult 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 SetSearchResult 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.LoaderCallbacksList? extends SearchResult, IndexingCallback {...mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);
}通过indexDatabase方法更新数据。
public class SearchFeatureProviderImpl implements SearchFeatureProvider {Overridepublic 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 AsyncTaskVoid, Void, Void {VisibleForTestingIndexingCallback mCallback;private long mIndexStartTime;public IndexingTask(IndexingCallback callback) {mCallback callback;}Overrideprotected void onPreExecute() {mIndexStartTime System.currentTimeMillis();mIsIndexingComplete.set(false);}Overrideprotected Void doInBackground(Void... voids) {performIndexing();return null;}Overrideprotected 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 ListResolveInfo 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。
providerandroid:name.search.SettingsSearchIndexablesProviderandroid:authoritiescom.android.settingsandroid:multiprocessfalseandroid:grantUriPermissionstrueandroid:permissionandroid.permission.READ_SEARCH_INDEXABLESandroid:exportedtrueintent-filteraction android:nameandroid.content.action.SEARCH_INDEXABLES_PROVIDER //intent-filter/providerSettingsSearchIndexablesProvider继承于SearchIndexablesProviderSearchIndexablesProvider继承于ContentProvider, query方法进行了分类查询插入删除更新均不支持通过final修饰和抛出UnsupportedOperationException屏蔽了。
public abstract class SearchIndexablesProvider extends ContentProvider {Overridepublic 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;}}Overridepublic final Uri insert(Uri uri, ContentValues values) {throw new UnsupportedOperationException(Insert not supported);}
}以queryXmlResources为例通过getSearchIndexableResourcesFromProvider方法获取数据集合并保存到cursor中。bundles里一个class类型的集合。
public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {Overridepublic Cursor queryXmlResources(String[] projection) {final MatrixCursor cursor new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);final ListSearchIndexableResource 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 classcursor.addRow(ref);}return cursor;}private ListSearchIndexableResource getSearchIndexableResourcesFromProvider(Context context) {final CollectionSearchIndexableData bundles FeatureFactory.getFactory(context).getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();ListSearchIndexableResource resourceList new ArrayList();for (SearchIndexableData bundle : bundles) {Indexable.SearchIndexProvider provider bundle.getSearchIndexProvider();final ListSearchIndexableResource 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 {Overridepublic SearchIndexableResources getSearchIndexableResources() {if (mSearchIndexableResources null) {mSearchIndexableResources new SearchIndexableResourcesMobile();}return mSearchIndexableResources;}
}SearchIndexableResourcesMobile类生成在IndexableProcessor中IndexableProcessor设置的注解为SearchIndexableSearchIndexable注解可以指定target(ALL/MOBILE/TV/WEAR/AUTO/ARC)对应不同平台。通过JavaPoet库来addCode实例化SearchIndexableDatagetProviderValues方法返回的是带有SearchIndexable注解的所有类集合。
SupportedSourceVersion(SourceVersion.RELEASE_9)
SupportedAnnotationTypes({com.android.settingslib.search.SearchIndexable})
public class IndexableProcessor extends AbstractProcessor {Overridepublic boolean process(Set? extends TypeElement annotations,for (Element element : roundEnvironment.getElementsAnnotatedWith(SearchIndexable.class)) {if (element.getKind().isClass()) {Name className element.accept(new SimpleElementVisitor8Name, Void() {Overridepublic 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;}//实例化SearchIndexableDatabuilder.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();
}实例化SearchIndexableDatamTargetClass为className.classmSearchIndexProvider为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_PROVIDERSettings封装了一个基础的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) {Overrideprotected 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;}//返回SearchIndexableResourceOverridepublic ListSearchIndexableResource getXmlResourcesToIndex(Context context, boolean enabled) {if (mXmlRes ! 0) {final SearchIndexableResource sir new SearchIndexableResource(context);sir.xmlResId mXmlRes;return Arrays.asList(sir);}return null;}//返回SearchIndexableRaw集合Overridepublic ListSearchIndexableRaw getRawDataToIndex(Context context, boolean enabled) {return null;}//返回动态数据集合Overridepublic ListSearchIndexableRaw getRawDataToIndex(Context context, boolean enabled) {return null;}//无法搜索的集合OverrideCallSuperpublic ListString getNonIndexableKeys(Context context) {...}//页面是否启用搜索protected boolean isPageSearchEnabled(Context context) {return true;}//获取xml设置禁用搜索的集合VisibleForTesting(otherwise VisibleForTesting.PROTECTED)public ListString getNonIndexableKeysFromXml(Context context, XmlRes int xmlResId,boolean suppressAllPage) {return getKeysFromXml(context, xmlResId, suppressAllPage);}
}以上就是Settings的搜索逻辑。要测试新菜单的可搜索性需要先清除Settings数据让数据库重新添加数据。
总结
Settings菜单如果想要支持搜索首先对应页面需要添加SearchIndexable注解其次在本页面创建一个常量SEARCH_INDEX_DATA_PROVIDER然后根据需要重写需要的实现方法。这样这个菜单就支持搜索了。
SettingsIntelligence会扫描这些添加SearchIndexable注解的页面将这些页面的菜单添加到数据库中查询时根据关键词进行匹配查询。