时时彩网站开发教程怎么推广自己的店铺
Android Settings 系列文章:
- Android Settings解析
- SettingsIntelligence
- SettingsProvider
首语
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 {@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 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;}@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 List<SearchQueryTask> tasks =mFeatureProvider.getSearchQueryTasks(context, query);// Start tasksfor (SearchQueryTask task : tasks) {executorService.execute(task);}// Collect resultsfinal 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 resultsfinal List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);return mergedResults;}
}
getSearchQueryTasks中构建了各种类型的task,如DatabaseResultTask/InstalledAppResultTask等等。这些task都继承于SearchQueryTask.QueryWorker。
public class SearchFeatureProviderImpl implements SearchFeatureProvider {@Overridepublic 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>> {@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.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 {@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 AsyncTask<Void, 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 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。
<providerandroid: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 {@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 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 classcursor.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 {@Overridepublic 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 {@Overridepublic 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>() {@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();
}
实例化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) {@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;}//返回SearchIndexableResource@Overridepublic 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集合@Overridepublic List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {return null;}//返回动态数据集合@Overridepublic List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {return null;}//无法搜索的集合@Override@CallSuperpublic 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注解的页面,将这些页面的菜单添加到数据库中,查询时根据关键词进行匹配查询。