Settings搜索框搜索数据

0. 一些基本属性

# 数据库 - search_index.db
# 数据表 - prefs_index 

1. 数据库初始化

数据库的初始化起初以为是在Settings中进行的,在Settings模块中也找到了数据库初始化代码。但是通过第二步搜索数据步骤发现,搜索数据查询的数据库和在Settings模块中初始化的数据库不是同一个。
数据库的初始化操作其实是在SettingsIntelligence模块中进行的。

1.1 流程图如下

Settings搜索框数据初始化流程

1.2 具体代码

首先是Settings模块,在SettingsActivity中对搜索框进行初始化。在初始化方法中,会对Toolbar设置点击监听,只有在Toolbar点击之后才会触发初始化以及后续操作。
在点击监听中,有一个疑似方法 indexSliceDataAsync() ,通过代码追踪发现也是对数据库进行操作的,会有初始化数据库的动作。但是在第二步的搜索数据过程中发现,这里操作的数据库与查找的数据库不是同一个。所以这里是比较具有迷惑性的。
具体的数据库初始化是在点击事件触发后,跳转界面完成的。

SettingsActivity#onCreate()
	final Toolbar toolbar = findViewById(R.id.search_action_bar);
	FeatureFactory.getFactory(this).getSearchFeatureProvider()
	        .initSearchToolbar(this, toolbar);
	setActionBar(toolbar);
SearchFeatureProvider#initSearchToolbar()
	// 为Toolbar设置点击事件
	toolbar.setOnClickListener(tb -> {
		// new Intent("com.android.settings.action.SETTINGS_SEARCH");
	    final Intent intent = SEARCH_UI_INTENT;
	    // com.android.settings.intelligence
	    intent.setPackage(getSettingsIntelligencePkgName());
	
	    FeatureFactory.getFactory(
	            activity.getApplicationContext()).getSlicesFeatureProvider()
	            .indexSliceDataAsync(activity.getApplicationContext());
	    activity.startActivityForResult(intent, 0 /* requestCode */);
	});

点击Toolbar之后,会进行跳转。进入到搜索页面,这个页面属于SettingsIntelligence模块,从Intent中可以找到对应的Activity.

SearchActivity#onCreate()
	fragmentManager.beginTransaction()
                    .add(R.id.main_content, new SearchFragment())
                    .commit();
SearchFragment#onCreate()
	mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);

updateIndexAsync是去初始化数据库的操作。

SearchFeatureProviderImpl#updateIndexAsync()
	getIndexingManager(context).indexDatabase(callback);
DatabaseIndexingManager#indexDatabase()
	IndexingTask task = new IndexingTask(callback);
    task.execute();

DatabaseIndexingManager中开启一个异步任务,去执行初始化操作。IndexingTask是DatabaseIndexingManager中的内部类。

IndexingTask#doInBackground()
	performIndexing();
DatabaseIndexingManager#performIndexing()
	// action:android.content.action.SEARCH_INDEXABLES_PROVIDER
	final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);
	// 获得所需的ContentProvider
    final List<ResolveInfo> providers =
              mContext.getPackageManager().queryIntentContentProviders(intent, 0);
    final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, providers);
    if (isFullIndex) {
        rebuildDatabase();
    }

	// 从Provider中解析数据
    PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);

    final long updateDatabaseStartTime = System.currentTimeMillis();
   	// 将解析出来的数据存入到数据库中
    updateDatabase(indexData, isFullIndex);
    IndexDatabaseHelper.setIndexed(mContext, providers);

performIndexing方法中,通过Intent获得所需的providers,之后遍历providers获得要存入到数据的数据。得到数据后,将数据存入到数据库。

Settings中对应的ContentProvider

Settings#AndroidManifest.xml
        <provider
            android: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>

从ContentProvider中解析数据

DatabaseIndexingManager#getIndexDataFromProviders()
    if (mCollector == null) {
        mCollector = new PreIndexDataCollector(mContext);
    }
    return mCollector.collectIndexableData(providers, isFullIndex);
PreIndexDataCollector#collectIndexableData()
    mIndexData = new PreIndexData();

    for (final ResolveInfo info : providers) {
        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;

将解析出的数据保存到数据库

DatabaseIndexingManager#updateDatabase()
	// 从PreIndexData中解析出要存入到数据库中样式的数据
	final List<IndexData> indexData = getIndexData(preIndexData);
    insertIndexData(database, indexData);
    insertSiteMapData(database, getSiteMapPairs(indexData, preIndexData.getSiteMapPairs()));
    if (!isFullIndex) {
        updateDataInDatabase(database, nonIndexableKeys);
    }
insertIndexData()
	for (IndexData dataRow : indexData) {
	    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(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);
	
	    database.replaceOrThrow(TABLE_PREFS_INDEX, null, values);
	}
insertSiteMapData()
	for (SiteMapPair pair : siteMapPairs) {
       database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP,
                    null /* nullColumnHack */, pair.toContentValue());
    }
updateDataInDatabase()
	database.update(TABLE_PREFS_INDEX, enabledToDisabledValue, whereClause, null);
	database.update(TABLE_PREFS_INDEX, disabledToEnabledValue, whereClause, null);

首先会将数据再解析一遍,解析为IndexData的数据集,之后存入到数据库。

getIndexData()
	return mConverter.convertPreIndexDataToIndexData(data);
IndexDataConverter#convertPreIndexDataToIndexData()
	List<SearchIndexableData> indexableData = preIndexData.getDataToUpdate();
	// 遍历集合,根据对象的不同,采取不同的解析方式
	for (SearchIndexableData data : indexableData) {
        if (data instanceof SearchIndexableRaw) {
        	final IndexData convertedRaw = convertRaw(mContext, rawData, rawNonIndexableKeys);
        	indexData.add(convertedRaw);
       	} else if (data instanceof SearchIndexableResource) {
       		// 具体的实现是去解析 sir.xmlResId 这个XML文件
       		final List<IndexData> resourceData = convertResource(sir, resourceNonIndexableKeys);
       		indexData.addAll(resourceData);
      	}
     }

数据初始化到此结束

2. 搜索数据

2.1流程图

Settings搜索数据流程

2.2 代码

搜索数据起点是在点击搜索框,输入数据之后。位置是在SettingsIntelligence模块中。

SearchFragment#onQueryTextChange()
	boolean isEmptyQuery = TextUtils.isEmpty(query);
	if (isEmptyQuery) {
		......
	} else {
		restartLoaders();
	}
restartLoaders()
	final LoaderManager loaderManager = getLoaderManager();
	loaderManager.restartLoader(SearchLoaderId.SEARCH_RESULT, null /* args */, this /* callback */);

LoaderManager的代码在frameworks下,在执行完成后会执行回调onCreateLoader

SearchFragment#onCreateLoader()
	switch (id) {
		case SearchLoaderId.SEARCH_RESULT:
			return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
	}
	
SearchFeatureProviderImpl#getSearchResultLoader()
	return new SearchResultLoader(context, cleanQuery(query));
	
SearchResultLoader#loadInBackground()
	SearchResultAggregator aggregator = SearchResultAggregator.getInstance();
	return aggregator.fetchResults(getContext(), mQuery);

SearchResultAggregator#fetchResults()
	// 开启任务执行查询操作
	final List<SearchQueryTask> tasks =
                mFeatureProvider.getSearchQueryTasks(context, query);
    // Start tasks
    for (SearchQueryTask task : tasks) {
        executorService.execute(task);
    }
	
	// 收集查询的结果
	final Map<Integer, List<? extends SearchResult>> taskResults = new ArrayMap<>();
	for (SearchQueryTask task : tasks) {
		final int taskId = task.getTaskId();
		taskResults.put(taskId, task.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
	}

	// 合并结果集
	final List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);
	return mergedResults;

获取所有的查询任务

SearchFeatureProviderImpl#getSearchQueryTasks()
	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;

这里可以看到为查询任务集合里添加了4个任务,以数据库查询为例。执行查询操作。

SearchQueryTask.QueryWorker#call()
	return query();
DatabaseResultTask#query()
	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]));

	List<SearchResult> resultList = new ArrayList<>(resultSet);
	Collections.sort(resultList);
	return resultList;

在数据库任务查询中,添加了4条查询语句。分别对应的whereClause语句如下

methodwhereCaluseselection
firstWordQuery(data_title like ? OR data_title_normalized like ? ) AND enabled = 1[输入%, 输入%]
secondaryWordQuery(data_title like ? OR data_title_normalized like ? ) AND enabled = 1[% 输入%, % 输入%]
anyWordQuery(data_summary_on like ? OR data_summary_on like ? OR data_summary_on_normalized like ? OR data_summary_on_normalized like ? OR data_summary_off like ? OR data_summary_off like ? OR data_summary_off_normalized like ? OR data_summary_off_normalized like ?) AND enabled = 1[输入%, % 输入%, 输入%, % 输入%, 输入%, % 输入%, 输入%, % 输入%]
anyWordQuerywhereClause = (data_keywords like ? OR data_keywords like ? OR data_entries like ? OR data_entries like ?) AND enabled = 1[输入%, % 输入%, 输入%, % 输入%]

查询结束后,会合并结果集

SearchResultAggregator#mergeSearchResults()
	final List<SearchResult> searchResults = new ArrayList<>();
	searchResults.addAll(taskResults.remove(DatabaseResultTask.QUERY_WORKER_ID));
	final PriorityQueue<SearchResult> heap = new PriorityQueue<>();
    for (List<? extends SearchResult> taskResult : taskResults.values()) {
        heap.addAll(taskResult);
    }
    while (!heap.isEmpty()) {
        searchResults.add(heap.poll());
    }
    return searchResults;

结果集的操作完成之后,会通过LoadManager产生回调onLoadFinished。之后将结果集发送给适配器,由适配器来做数据的展示。

SearchFragment#onLoadFinished()
	mSearchAdapter.postSearchResults(data);

3. 结束

关于Settings的数据初始化和搜索流程分析到此结束。


版权声明:本文为ddss1990原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。