搜索栏在设置页面,那么第一想法,肯定是在Settings模块中,而且在Settings模块中也找到了search的相关文件夹与代码,如果你也跟我一样的话,那么你也会在这上面浪费一天甚至更多的时间,停下来先看看这篇文章,会让你节省出这宝贵的时间。
在点击搜索栏后,可以通过adb命令查看顶层Activity的包名可知
(写一下查询顶层Activity的命令吧:adb shell dumpsys activity | grep -i run
)
其实搜索功能是在SettingsIntelligence模块中。
从业务入手,看源码逻辑也能追根溯源:
在Settings模块里,初始化搜索框是在SettingsActivity类。在函数中,会监听Toolbar,点击Toolbar后触发初始化。
在点击监听中,能找到函数 indexSliceDataAsync ,也是对数据库进行操作的,会初始化数据库。但是在第二步的搜索数据过程中发现,这里操作的数据库与查找的数据库不是同一个。
在SettingsIntelligence模块中,找到SearchActivity,初始化添加了碎片SearchFragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_main);
// Keeps layouts in-place when keyboard opens.
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
if (fragment == null) {
fragmentManager.beginTransaction()
.add(R.id.main_content, new SearchFragment())
.commit();
}
}
关键类SearchFragment中,在onCreate时初始化了数据库:
mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);
在DatabaseIndexingManager中开启一个异步任务,去执行初始化操作
public void performIndexing() {
final long startTime = System.currentTimeMillis();
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
final List providers =
mContext.getPackageManager().queryIntentContentProviders(intent, 0);
final String localeStr = Locale.getDefault().toString();
final String fingerprint = Build.FINGERPRINT;
final String providerVersionedNames =
IndexDatabaseHelper.buildProviderVersionedNames(providers);
final boolean isFullIndex = isFullIndex(mContext, localeStr, fingerprint,
providerVersionedNames);
if (isFullIndex) {
rebuildDatabase();
}
PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);
final long updateDatabaseStartTime = System.currentTimeMillis();
updateDatabase(indexData, isFullIndex);
if (SettingsSearchIndexablesProvider.DEBUG) {
final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;
Log.d(LOG_TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);
}
//TODO(63922686): Setting indexed should be a single method, not 3 separate setters.
IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr);
IndexDatabaseHelper.setBuildIndexed(mContext, fingerprint);
IndexDatabaseHelper.setProvidersIndexed(mContext, providerVersionedNames);
if (SettingsSearchIndexablesProvider.DEBUG) {
final long indexingTime = System.currentTimeMillis() - startTime;
Log.d(LOG_TAG, "performIndexing took time: " + indexingTime
+ "ms. Full index? " + isFullIndex);
}
}
获取到的数据会保存到数据库中
updateDatabase(indexData, isFullIndex);
@VisibleForTesting
void insertIndexData(SQLiteDatabase database, List indexData) {
ContentValues values;
for (IndexData dataRow : indexData) {
if (TextUtils.isEmpty(dataRow.normalizedTitle)) {
continue;
}
values = new ContentValues();
values.put(IndexDatabaseHelper.IndexColumns.DOCID, dataRow.getDocId());
values.put(LOCALE, dataRow.locale);
values.put(DATA_TITLE, dataRow.updatedTitle);
values.put(DATA_TITLE_NORMALIZED, dataRow.normalizedTitle);
values.put(DATA_SUMMARY_ON, dataRow.updatedSummaryOn);
values.put(DATA_SUMMARY_ON_NORMALIZED, dataRow.normalizedSummaryOn);
values.put(DATA_ENTRIES, dataRow.entries);
values.put(DATA_KEYWORDS, dataRow.spaceDelimitedKeywords);
values.put(CLASS_NAME, dataRow.className);
values.put(SCREEN_TITLE, dataRow.screenTitle);
values.put(INTENT_ACTION, dataRow.intentAction);
values.put(INTENT_TARGET_PACKAGE, dataRow.intentTargetPackage);
values.put(INTENT_TARGET_CLASS, dataRow.intentTargetClass);
values.put(ICON, dataRow.iconResId);
values.put(ENABLED, dataRow.enabled);
values.put(DATA_KEY_REF, dataRow.key);
values.put(USER_ID, dataRow.userId);
values.put(PAYLOAD_TYPE, dataRow.payloadType);
values.put(PAYLOAD, dataRow.payload);
database.replaceOrThrow(TABLE_PREFS_INDEX, null, values);
if (!TextUtils.isEmpty(dataRow.className)
&& !TextUtils.isEmpty(dataRow.childClassName)) {
final ContentValues siteMapPair = new ContentValues();
siteMapPair.put(SiteMapColumns.PARENT_CLASS, dataRow.className);
siteMapPair.put(SiteMapColumns.PARENT_TITLE, dataRow.screenTitle);
siteMapPair.put(SiteMapColumns.CHILD_CLASS, dataRow.childClassName);
siteMapPair.put(SiteMapColumns.CHILD_TITLE, dataRow.updatedTitle);
database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP,
null /* nullColumnHack */, siteMapPair);
}
}
}
修改代码需要关注的是数据最后的整理和使用,在SearchResultAggregator类中,函数mergeSearchResults整合了所有查询到的数据并整理为一个list集合
// TODO (b/68255021) scale the dynamic search results ranks
private List mergeSearchResults(
Map> taskResults) {
final List searchResults = new ArrayList<>();
// First add db results as a special case
searchResults.addAll(taskResults.remove(DatabaseResultTask.QUERY_WORKER_ID));
// Merge the rest into result list: add everything to heap then pop them out one by one.
final PriorityQueue heap = new PriorityQueue<>();
for (List taskResult : taskResults.values()) {
heap.addAll(taskResult);
}
while (!heap.isEmpty()) {
searchResults.add(heap.poll());
}
return searchResults;
}
最终的最终,还是回到了SearchFragment类来,onLoadFinished函数接收了最终的数据,并使用adapter来适配展示数据:
@Override
public void onLoadFinished(Loader> loader,
List data) {
mSearchAdapter.postSearchResults(searchResults);
}
现在业务逻辑一目了然,如果要对展示的数据做处理的话,在给adapter设置数据前对这个list数据做修改就可以了,数据模型SearchResult查看源码可知:
/**
* Data class as an interface for all Search Results.
*/
public class SearchResult implements Comparable {
private static final String TAG = "SearchResult";
/**
* Defines the lowest rank for a search result to be considered as ranked. Results with ranks
* higher than this have no guarantee for sorting order.
*/
public static final int BOTTOM_RANK = 10;
/**
* Defines the highest rank for a search result. Used for special search results only.
*/
public static final int TOP_RANK = 0;
/**
* The title of the result and main text displayed.
* Intent Results: Displays as the primary
*/
public final CharSequence title;
/**
* Summary / subtitle text
* Intent Results: Displays the text underneath the title
*/
final public CharSequence summary;
/**
* An ordered list of the information hierarchy.
* Intent Results: Displayed a hierarchy of selections to reach the setting from the home screen
*/
public final List breadcrumbs;
/**
* A suggestion for the ranking of the result.
* Based on Settings Rank:
* 1 is a near perfect match
* 9 is the weakest match
* TODO subject to change
*/
public final int rank;
/**
* Identifier for the recycler view adapter.
*/
@ResultPayload.PayloadType
public final int viewType;
/**
* Metadata for the specific result types.
*/
public final ResultPayload payload;
/**
* Result's icon.
*/
public final Drawable icon;
/**
* A unique key for this object.
*/
public final String dataKey;
protected SearchResult(Builder builder) {
dataKey = builder.mDataKey;
title = builder.mTitle;
summary = builder.mSummary;
breadcrumbs = builder.mBreadcrumbs;
rank = builder.mRank;
icon = builder.mIcon;
payload = builder.mResultPayload;
viewType = payload.getType();
}
@Override
public int compareTo(SearchResult searchResult) {
if (searchResult == null) {
return -1;
}
return this.rank - searchResult.rank;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (!(that instanceof SearchResult)) {
return false;
}
return TextUtils.equals(dataKey, ((SearchResult) that).dataKey);
}
@Override
public int hashCode() {
return dataKey.hashCode();
}
public static class Builder {
private CharSequence mTitle;
private CharSequence mSummary;
private List mBreadcrumbs;
private int mRank = 42;
private ResultPayload mResultPayload;
private Drawable mIcon;
private String mDataKey;
public Builder setTitle(CharSequence title) {
mTitle = title;
return this;
}
public Builder setSummary(CharSequence summary) {
mSummary = summary;
return this;
}
public Builder addBreadcrumbs(List breadcrumbs) {
mBreadcrumbs = breadcrumbs;
return this;
}
public Builder setRank(int rank) {
if (rank >= 0 && rank <= 9) {
mRank = rank;
}
return this;
}
public Builder setIcon(Drawable icon) {
mIcon = icon;
return this;
}
public Builder setPayload(ResultPayload payload) {
mResultPayload = payload;
return this;
}
public Builder setDataKey(String key) {
mDataKey = key;
return this;
}
public SearchResult build() {
// Check that all of the mandatory fields are set.
if (TextUtils.isEmpty(mTitle)) {
throw new IllegalStateException("SearchResult missing title argument");
} else if (TextUtils.isEmpty(mDataKey)) {
Log.v(TAG, "No data key on SearchResult with title: " + mTitle);
throw new IllegalStateException("SearchResult missing stableId argument");
} else if (mResultPayload == null) {
throw new IllegalStateException("SearchResult missing Payload argument");
}
return new SearchResult(this);
}
}
}
其中有public final ResultPayload payload;对象需要注意,此对象中添加有Intent意图,显然每个要跳转的功能的意图都是不一样的,以此来做数据删选是比较合理的,那么onLoadFinished函数里,可以做如下修改:
@Override
public void onLoadFinished(Loader> loader,
List data) {
List searchResults = new ArrayList<>();
if (data != null) {
for (int i = 0; i < data.size(); i++) {
SearchResult st = (SearchResult) data.get(i);
Intent intent = st.payload.getIntent();
for (int j = 0; j < dataSize.length; j++) {
if (!intent.getExtras().toString().contains("=" + dataSize[j] + "]")) {
searchResults.add(st);
}
}
}
}
mSearchAdapter.postSearchResults(searchResults);
}
//这就是你要剔除的功能选项
private final String[] dataSize = new String[]{"376"};
那么问题来了我是如何知道哪个功能的dataSize是什么呢,framework又不能debug调试,对的,虽然不能调试,但是我们可以另辟蹊径,将每个功能的dataSize直接显示在列表上,就能直观的看到对应关系了,这个我就不给你一步一步说明了,直接,SearchViewHolder类是搜索时的列表的Holder类,改这里吧,在onBind(SearchFragment fragment, SearchResult result)函数内,修改titleView.setText(result.title);
为:
Intent intent = result.payload.getIntent();
titleView.setText(result.title+"cs:"+intent.getExtras().toString());
就能直观的在UI上看到对应关系,然后修改集合就行了。