目录
1.SettingsHomepageActivity.java
3、SearchFeatureProviderImpl.java
6.SettingsIntelligence-SearchFeatureProviderImpl.java
7.DatabaseIndexingManager.java
概要
在Android系统中,Setting菜单非常多,有些菜单很难找到,因此Google支持搜索菜单功能。搜索的主要逻辑在packages/apps/SettingsIntelligence模块中。模块包名为com.android.settings.intelligence。
该模块会检索所有继承自SearchIndexablesProvider的ContentProvider,将所有数据保存至数据库
/data/data/com.android.settings.intelligence/databases/search_index.db。然后在SearchFragment中通过访问该数据库来实现搜索菜单的功能。本文重点探讨保存数据库这步流程
代码流程图
第一次从设置首页进入搜索时,会跳转到SettingsIntelligence加载数据库,以下为这个过程的流程图
代码流程分析
接下来,我会按照流程图的顺序,逐步分析相关代码,所有代码示例皆来自Android14
1.SettingsHomepageActivity.java
这个为设置首页,从这个界面顶端进入搜索
SettingsHomepageActivity.java:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initSearchBarView(); //1.搜索相关的初始化
}
private void initSearchBarView() {
final Toolbar toolbar = findViewById(R.id.search_action_bar); //获取search控件
//通过Factory模式,将搜索初始化放在SearchFeatureProvider中实现
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbar,
SettingsEnums.SETTINGS_HOMEPAGE);
if (mIsEmbeddingActivityEnabled) { //支持分栏的设备会走这里
final Toolbar toolbarTwoPaneVersion = findViewById(R.id.search_action_bar_two_pane);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbarTwoPaneVersion,
SettingsEnums.SETTINGS_HOMEPAGE);
}
}
2.SearchFeatureProvider.java
主要处理Setting Search相关逻辑,具体实现类为SearchFeatureProviderImpl
SearchFeatureProvider.java
default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {
final Context context = activity.getApplicationContext();
//2.获取SearchActivity的Intent,用于跳转
final Intent intent = buildSearchIntent(context, pageId)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//增加OnClickListener,点击搜索控件时,会回调onClick跳转到SearchActivity
toolbar.setOnClickListener(tb -> startSearchActivity(context, activity, pageId, intent));
}
private static void startSearchActivity(
Context context, FragmentActivity activity, int pageId, Intent intent) {
FeatureFactory.getFactory(context).getSlicesFeatureProvider()
.indexSliceDataAsync(context);
FeatureFactory.getFactory(context).getMetricsFeatureProvider()
.logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId);
final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();
//3.start SearchActivity
activity.startActivity(intent, bundle);
}
3、SearchFeatureProviderImpl.java
从这里跳转到SearchActivity
SearchFeatureProviderImpl.java
@Override
public Intent buildSearchIntent(Context context, int pageId) {
return new Intent(Settings.ACTION_APP_SEARCH_SETTINGS) //获取SearchActivity action
.setPackage(getSettingsIntelligencePkgName(context)) //获取包名
.putExtra(Intent.EXTRA_REFERRER, buildReferrer(context, pageId));
}
//获取的包名为com.android.settings.intelligence
default String getSettingsIntelligencePkgName(Context context) {
return context.getString(R.string.config_settingsintelligence_package_name);
}
Intent.java
//SearchActivity会配置该action
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
4、SearchActivity.java
Search界面
SettingsIntelligence AndroidManifest.xml
<activity
android:name=".search.SearchActivity"
android:exported="true"
android:theme="@style/Theme.Settings.NoActionBar"
android:windowSoftInputMode="adjustResize">
<intent-filter priority="-1">
<!--通过匹配这个action来跳转SearchActivity-->
<action android:name="com.android.settings.action.SETTINGS_SEARCH" />
<action android:name="android.settings.APP_SEARCH_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
public class SearchActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_main);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
if (fragment == null) {
fragmentManager.beginTransaction()
.add(R.id.main_content, new SearchFragment()) //4.嵌入SearchFragment,主要逻辑在Fragment里面
.commit();
}
}
@Override
public boolean onNavigateUp() {
finish();
return true;
}
}
5.SearchFragment.java
搜索界面,搜索逻辑从这里开始调用
SearchFragment.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long startTime = System.currentTimeMillis();
setHasOptionsMenu(true);
final LoaderManager loaderManager = getLoaderManager();
//给搜索结果使用的数据
mSearchAdapter = new SearchResultsAdapter(this /* fragment */);
mSavedQueryController = new SavedQueryController(
getContext(), loaderManager, mSearchAdapter);
mSearchFeatureProvider.initFeedbackButton();
if (savedInstanceState != null) {
//保存搜索结果
mQuery = savedInstanceState.getString(SearchCommon.STATE_QUERY);
mNeverEnteredQuery = savedInstanceState.getBoolean(SearchCommon.STATE_NEVER_ENTERED_QUERY);
mShowingSavedQuery = savedInstanceState.getBoolean(SearchCommon.STATE_SHOWING_SAVED_QUERY);
} else {
mShowingSavedQuery = true;
}
5.加载搜索数据库逻辑从这里开始
mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */); //load database 1
if (SearchFeatureProvider.DEBUG) {
Log.d(TAG, "onCreate spent " + (System.currentTimeMillis() - startTime) + " ms");
}
}
//IndexingTask数据处理完成后onPostExecute会通过mCallback.onIndexingFinished()进行回调返回这里
@Override
public void onIndexingFinished() { //load database 完成后回调
if (getActivity() == null) {
return;
}
if (mShowingSavedQuery) {
mSavedQueryController.loadSavedQueries();
} else {
final LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT, null /* args */,
this /* callback */);
}
requery();
}
6.SettingsIntelligence-SearchFeatureProviderImpl.java
此处为SettingsIntelligence中的类,和前面的不是同一个
/**
* FeatureProvider for the refactored search code.
*/
SearchFeatureProviderImpl.java
@Override
public void updateIndexAsync(Context context, IndexingCallback callback) { //load database 2
if (DEBUG) {
Log.d(TAG, "updating index async");
}
//6.此处就是一个衔接的作用,最终要调用DatabaseIndexingManager中进行处理
getIndexingManager(context).indexDatabase(callback);
}
7.DatabaseIndexingManager.java
DatabaseIndexingManager.IndexingTask
* Consumes the SearchIndexableProvider content providers. * Updates the Resource, Raw Data and non-indexable data for Search.
该文件主要是检索分析所有SearchIndexableProvider的之类,将数据分类保存到数据库/data/data/com.android.settings.intelligence/databases
IndexingTask内部类则用于异步处理保存数据库的耗时操作
DatabaseIndexingManager.java
/**
* Consumes the SearchIndexableProvider content providers.
* Updates the Resource, Raw Data and non-indexable data for Search.
*/
//7.将数据库加载的耗时操作放入IndexingTask
public void indexDatabase(IndexingCallback callback) {
IndexingTask task = new IndexingTask(callback);
task.execute();
}
public void performIndexing() { //load database 5 关键代码
final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE); //查询所有search数据的intent,该intent为"android.content.action.SEARCH_INDEXABLES_PROVIDER"
final List<ResolveInfo> providers =
mContext.getPackageManager().queryIntentContentProviders(intent, 0); //查询所有该intent匹配的Provider
//检查是否要全部更新,如修改语言,OTA后
final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, providers);
if (isFullIndex) {
rebuildDatabase(); //若要全部更新,则删除数据库Table,重建Table
}
//9.收集数据,返回格式为indexData
PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);
final long updateDatabaseStartTime = System.currentTimeMillis();
updateDatabase(indexData, isFullIndex); //load database 7 更新数据库
//设一些标志位,可能会用于判断isFullIndex,结果保存在indexing_manager.xml,格式为
SharedPreferences,包括语言,包括搜索数据的应用包名和fingerprint
IndexDatabaseHelper.setIndexed(mContext, providers);
if (DEBUG) {
final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;
Log.d(TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);
}
}
//9.搜集搜索数据,返回格式为PreIndexData
@VisibleForTesting
PreIndexData getIndexDataFromProviders(List<ResolveInfo> providers, boolean isFullIndex) {
if (mCollector == null) {
mCollector = new PreIndexDataCollector(mContext);
}
//9.搜集搜索数据,返回格式为PreIndexData
return mCollector.collectIndexableData(providers, isFullIndex);
}
//数据返回后,更新数据库
@VisibleForTesting
void updateDatabase(PreIndexData preIndexData, boolean isFullIndex) {
final SQLiteDatabase database = getWritableDatabase(); //IndexDatabaseHelper用于管理数据库
.
.
.
insertIndexData(database, indexData); //10.插入数据库,插入成功后,则数据库更新完成
}
private void insertIndexData(SQLiteDatabase database, List<IndexData> indexData) { //load database 7.3
ContentValues values;
for (IndexData dataRow : indexData) {
if (TextUtils.isEmpty(dataRow.normalizedTitle)) {
continue;
}
values = new ContentValues();
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(DATA_PACKAGE, dataRow.packageName);
values.put(DATA_AUTHORITY, dataRow.authority);
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(PAYLOAD_TYPE, dataRow.payloadType);
values.put(PAYLOAD, dataRow.payload);
values.put(TOP_LEVEL_MENU_KEY, dataRow.topLevelMenuKey);
//database类为IndexDatabaseHelper
database.replaceOrThrow(TABLE_PREFS_INDEX, null, values); //写入数据库
}
}
IndexingTask class
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);
}
//8.马上进入真正的保存数据库操作
@Override
protected Void doInBackground(Void... voids) {
performIndexing(); //load database 4
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(); //执行完成后,此处将结果回调给SearchFragment
}
}
}
8.PreIndexDataCollector.java
在此类中处理数据
PreIndexDataCollector.java
//9.处理返回数据
public PreIndexData collectIndexableData(List<ResolveInfo> providers, boolean isFullIndex) {
mIndexData = new PreIndexData();
for (final ResolveInfo info : providers) {
if (!isWellKnownProvider(info)) {
continue;
}
final String authority = info.providerInfo.authority;
final String packageName = info.providerInfo.packageName;
if (isFullIndex) {
addIndexablesFromRemoteProvider(packageName, authority); //加载数据
}
final long nonIndexableStartTime = System.currentTimeMillis();
addNonIndexablesKeysFromRemoteProvider(packageName, authority); //剔除不要的数据
if (SearchFeatureProvider.DEBUG) {
final long nonIndexableTime = System.currentTimeMillis() - nonIndexableStartTime;
Log.d(TAG, "performIndexing update non-indexable for package " + packageName
+ " took time: " + nonIndexableTime);
}
}
return mIndexData; //返回结果
}
9.IndexDatabaseHelper.java
数据库创建读写操作都在这里
IndexDatabaseHelper.java
public class IndexDatabaseHelper extends SQLiteOpenHelper {
//这些Table名称
public interface Tables {
String TABLE_PREFS_INDEX = "prefs_index";
String TABLE_SITE_MAP = "site_map";
String TABLE_META_INDEX = "meta_index";
String TABLE_SAVED_QUERIES = "saved_queries";
}
//数据库中保存的数据都在这里,包括title,Summary,package,class name,key,icon等
public interface IndexColumns {
String DATA_TITLE = "data_title";
String DATA_TITLE_NORMALIZED = "data_title_normalized";
String DATA_SUMMARY_ON = "data_summary_on";
String DATA_SUMMARY_ON_NORMALIZED = "data_summary_on_normalized";
String DATA_SUMMARY_OFF = "data_summary_off";
String DATA_SUMMARY_OFF_NORMALIZED = "data_summary_off_normalized";
String DATA_ENTRIES = "data_entries";
String DATA_KEYWORDS = "data_keywords";
String DATA_PACKAGE = "package";
String DATA_AUTHORITY = "authority";
String CLASS_NAME = "class_name";
String SCREEN_TITLE = "screen_title";
String INTENT_ACTION = "intent_action";
String INTENT_TARGET_PACKAGE = "intent_target_package";
String INTENT_TARGET_CLASS = "intent_target_class";
String ICON = "icon";
String ENABLED = "enabled";
String DATA_KEY_REF = "data_key_reference";
String PAYLOAD_TYPE = "payload_type";
String PAYLOAD = "payload";
String TOP_LEVEL_MENU_KEY = "top_level_menu_key";
}
@Override
public void onCreate(SQLiteDatabase db) {
bootstrapDB(db);
}
//会创建以下几个Table
private void bootstrapDB(SQLiteDatabase db) {
db.execSQL(CREATE_INDEX_TABLE);
db.execSQL(CREATE_META_TABLE);
db.execSQL(CREATE_SAVED_QUERIES_TABLE);
db.execSQL(CREATE_SITE_MAP_TABLE);
db.execSQL(INSERT_BUILD_VERSION);
Log.i(TAG, "Bootstrapped database");
}
public void reconstruct(SQLiteDatabase db) { //load database 5.2
mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
.edit()
.clear()
.commit();
dropTables(db); //删除数据库的表table
bootstrapDB(db); //新建数据库的表
}
}
小结
Settings属于Android原生比较大的模块了,代码也非常多,这只是其中搜索功能的一部分。其他部分将在其他文章中继续探讨