Settings搜索系统SettingsIntelligence
目录
概要
代码流程图
代码流程分析
1.SettingsHomepageActivity.java
2.SearchFeatureProvider.java
3、SearchFeatureProviderImpl.java
4、SearchActivity.java
5.SearchFragment.java
6.SettingsIntelligence-SearchFeatureProviderImpl.java
7.DatabaseIndexingManager.java
8.PreIndexDataCollector.java
9.IndexDatabaseHelper.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:@Overrideprotected 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.javadefault 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跳转到SearchActivitytoolbar.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 SearchActivityactivity.startActivity(intent, bundle);}
3、SearchFeatureProviderImpl.java
从这里跳转到SearchActivity
SearchFeatureProviderImpl.java@Overridepublic 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<activityandroid: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 {@Overridepublic 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();}}@Overridepublic boolean onNavigateUp() {finish();return true;}
}
5.SearchFragment.java
搜索界面,搜索逻辑从这里开始调用
SearchFragment.java@Overridepublic 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 1if (SearchFeatureProvider.DEBUG) {Log.d(TAG, "onCreate spent " + (System.currentTimeMillis() - startTime) + " ms");}}//IndexingTask数据处理完成后onPostExecute会通过mCallback.onIndexingFinished()进行回调返回这里@Overridepublic 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@Overridepublic void updateIndexAsync(Context context, IndexingCallback callback) { //load database 2if (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.收集数据,返回格式为indexDataPreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex); final long updateDatabaseStartTime = System.currentTimeMillis();updateDatabase(indexData, isFullIndex); //load database 7 更新数据库//设一些标志位,可能会用于判断isFullIndex,结果保存在indexing_manager.xml,格式为
SharedPreferences,包括语言,包括搜索数据的应用包名和fingerprintIndexDatabaseHelper.setIndexed(mContext, providers); if (DEBUG) {final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;Log.d(TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);}}//9.搜集搜索数据,返回格式为PreIndexData
@VisibleForTestingPreIndexData getIndexDataFromProviders(List<ResolveInfo> providers, boolean isFullIndex) {if (mCollector == null) {mCollector = new PreIndexDataCollector(mContext);}
//9.搜集搜索数据,返回格式为PreIndexData return mCollector.collectIndexableData(providers, isFullIndex); }//数据返回后,更新数据库@VisibleForTestingvoid 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.3ContentValues 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类为IndexDatabaseHelperdatabase.replaceOrThrow(TABLE_PREFS_INDEX, null, values); //写入数据库}}IndexingTask classpublic 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);}//8.马上进入真正的保存数据库操作@Overrideprotected Void doInBackground(Void... voids) {performIndexing(); //load database 4return 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(); //执行完成后,此处将结果回调给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.javapublic 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";}@Overridepublic void onCreate(SQLiteDatabase db) {bootstrapDB(db);}//会创建以下几个Tableprivate 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.2mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE).edit().clear().commit();dropTables(db); //删除数据库的表tablebootstrapDB(db); //新建数据库的表}
}
小结
Settings属于Android原生比较大的模块了,代码也非常多,这只是其中搜索功能的一部分。其他部分将在其他文章中继续探讨