0%

Settings 搜索框架调研

主要调研下Settings对应的通用搜索框架的实现.

1. 自定义fragment支持search扩展

需要继承com.android.settings.search.Indexable接口, 定义SearchIndexProvider的实例, 并需要将自己加到SearchIndexableResources的sProviders中

1.1. 接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 嵌套接口的用法
public interface Indexable {

interface SearchIndexProvider {
/**
* Return a list of references for indexing.
*
* See {@link android.provider.SearchIndexableResource}
*
*
* @param context the context.
* @param enabled hint telling if the data needs to be considered into the search results
* or not.
* @return a list of {@link android.provider.SearchIndexableResource} references.
* Can be null.
*/
List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled);

/**
* Return a list of raw data for indexing. See {@link SearchIndexableRaw}
*
* @param context the context.
* @param enabled hint telling if the data needs to be considered into the search results
* or not.
* @return a list of {@link SearchIndexableRaw} references. Can be null.
*/
List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled);

/**
* Return a list of data keys that cannot be indexed. See {@link SearchIndexableRaw}
*
* @param context the context.
* @return a list of {@link SearchIndexableRaw} references. Can be null.
*/
List<String> getNonIndexableKeys(Context context);

/**
* @param context
* @return a list of {@link AbstractPreferenceController} for ResultPayload data during
* Indexing.
*/
List<AbstractPreferenceController> getPreferenceControllers(Context context);
}
}

1.2. 接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义SearchIndexProvider的实例
public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.tether_prefs;
return Arrays.asList(sir);
}
};
public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider
// 加到sResMap中, 后面遍历时用打
addIndex(TetherSettings.class, NO_DATA_RES_ID, 0);

定义了通用类, BaseSearchIndexProvider, 实现接口的通用方法, 如果有具体的定制需求, 需要自定义BaseSearchIndexProvider的子类覆盖对应方法.

1.3. 结构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@startuml
interface Indexable {
interface SearchIndexProvider
}
interface SearchIndexProvider {
getXmlResourcesToIndex(...)
getRawDataToIndex()
getNonIndexableKeys()
}
Indexable *-- SearchIndexProvider
class BaseSearchIndexProvider implements SearchIndexProvider {
getXmlResourcesToIndex(...)
getRawDataToIndex()
getNonIndexableKeys()
}
class DemoFragment implements Indexable {
SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER
}

DemoFragment o-- BaseSearchIndexProvider
@enduml

DemoFragment中的SEARCH_INDEX_DATA_PROVIDER为具体类, 需要根据实际情况覆写 Indexable.SearchIndexProvider的方法.

2. Settings 搜索实现

2.1. Search 入口分析,

先看入口SettingsActivity, 工厂模式, 反射方式加载具体工厂 FeatureFactoryImpl, 进而找到 SearchFeatureProviderImpl代理.
SearchFeatureProviderImpl类为SearchFeatureProvider的代理

1
2
3
4
5
6
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this, toolbar);
final String clsName = context.getString(R.string.config_featureFactory);
sFactory = (FeatureFactory) context.getClassLoader().loadClass(clsName).newInstance();

public class FeatureFactoryImpl extends FeatureFactory

FeatureFactory为抽象类, 实现类为FeatureFactoryImpl(代理类), 实现了 getSearchFeatureProvider

1
2
3
4
5
6
7
8
9
10
11
public abstract class FeatureFactory {
public abstract SearchFeatureProvider getSearchFeatureProvider();
}

// FeatureFactoryImpl类
public SearchFeatureProvider getSearchFeatureProvider() {
if (mSearchFeatureProvider == null) {
mSearchFeatureProvider = new SearchFeatureProviderImpl();
}
return mSearchFeatureProvider;
}

SearchFeatureProvider为interface, 含默认方法initSearchToolbar. Java中的接口可以有默认方法
initSearchToolbar中指定了Settings的搜索toolbar点击对应的动作.
启动SEARCH_UI_INTENT即SEARCH对应的fragment “com.android.settings.action.SETTINGS_SEARCH”
对应SearchActivity.
同时通过绑定的getSlicesFeatureProvider初始化数据库.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface SearchFeatureProvider {
default void initSearchToolbar(Activity activity, Toolbar toolbar) {
if (activity == null || toolbar == null) {
return;
}
toolbar.setOnClickListener(tb -> {
final Intent intent = SEARCH_UI_INTENT;
intent.setPackage(getSettingsIntelligencePkgName());

FeatureFactory.getFactory(
activity.getApplicationContext()).getSlicesFeatureProvider()
.indexSliceDataAsync(activity.getApplicationContext());
activity.startActivityForResult(intent, 0 /* requestCode */);
});
}
}

2.1.1. FeatureFactory的相关类图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@startuml
abstract class FeatureFactory {
+abstract SearchFeatureProvider getSearchFeatureProvider()
+static FeatureFactory getFactory()
}

class FeatureFactoryImpl extends FeatureFactory {
+SearchFeatureProvider getSearchFeatureProvider()
}

SearchFeatureProvider -d-o FeatureFactory
SearchFeatureProvider ..o FeatureFactoryImpl: getSearchFeatureProvider

class SearchFeatureProviderImpl implements SearchFeatureProvider{
+updateIndex(Context context)
+DatabaseIndexingManager getIndexingManager(Context context)
}

interface SearchFeatureProvider {
default void initSearchToolbar(Activity activity, Toolbar toolbar)
}
@enduml

2.1.2. getSlicesFeatureProvider 初始化数据

通过FeatureFactory的getSlicesFeatureProvider方法找到SlicesFeatureProvider, 对应的实现代理为SlicesFeatureProviderImpl, 类的关系同SearchFeatureProvider
这里主要看下indexSliceDataAsync的实现. 是SlicesFeatureProvider接口的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void indexSliceDataAsync(Context context) {
SlicesIndexer indexer = getSliceIndexer(context);
1. ThreadUtils.postOnBackgroundThread(indexer);
}
// 这里的get方法通通为单例模式
mSlicesIndexer = new SlicesIndexer(context);
class SlicesIndexer implements Runnable {
public SlicesIndexer(Context context) {
// getInstance 单例
mHelper = SlicesDatabaseHelper.getInstance(mContext);
}

public void run() {
1. indexSliceData();
}
}


// 找到数据库类
public class SlicesDatabaseHelper extends SQLiteOpenHelper {
private SlicesDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null /* CursorFactor */, DATABASE_VERSION);
mContext = context;
}
}

通过getSliceIndexer实例化SlicesIndexer(单例), 执行ThreadUtils.postOnBackgroundThread方法.
ThreadUtils为Thread工具类, 保存了ExecutorService对象(单例), 启动线程池执行Runnable的run方法, 即SlicesIndexer的run方法

1
2
3
4
5
6
public static void postOnBackgroundThread(Runnable runnable) {
if (sSingleThreadExecutor == null) {
sSingleThreadExecutor = Executors.newSingleThreadExecutor();
}
sSingleThreadExecutor.execute(runnable);
}

接下来重点看下indexSliceData的执行流程

2.1.3. indexSliceData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

protected void indexSliceData() {
// 这里有个不重复加载的机制, 跟语言项有关, 注意替换apk的方式可能需要清空数据, 否则这里不再重新index. 与setIndexedState调用有关
if (mHelper.isSliceDataIndexed()) {
Log.d(TAG, "Slices already indexed - returning.");
return;
}

SQLiteDatabase database = mHelper.getWritableDatabase();

try {

database.beginTransaction();

mHelper.reconstruct(mHelper.getWritableDatabase());
// 数据的获得通过 getSliceData
1.1 List<SliceData> indexData = getSliceData();
// 插入数据库中
insertSliceData(database, indexData);

// 只初始化一次? mHelper里也没有实现增删改的方法, 只有onUpgrade.
mHelper.setIndexedState();
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}

1.1 -> // 获取数据,
List<SliceData> getSliceData() {
// 又见到 FeatureFactory, 同样方式拿到SliceDataConverter, 通过SlicesFeatureProviderImpl代理类拿到
return FeatureFactory.getFactory(mContext)
.getSlicesFeatureProvider()
.getSliceDataConverter(mContext)
.getSliceData();
}

public SliceDataConverter getSliceDataConverter(Context context) {
if(mSliceDataConverter == null) {
mSliceDataConverter = new SliceDataConverter(context.getApplicationContext());
}
return mSliceDataConverter;
}

class SliceDataConverter {
public List<SliceData> getSliceData() {
if (!mSliceData.isEmpty()) {
return mSliceData;
}

// 找到代理SearchFeatureProviderImpl的resource对象.
1.2 final Collection<Class> indexableClasses = FeatureFactory.getFactory(mContext)
.getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();

for (Class clazz : indexableClasses) {
final String fragmentName = clazz.getName();

1.3 final SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
clazz);

// CodeInspection test guards against the null check. Keep check in case of bad actors.
if (provider == null) {
Log.e(TAG, fragmentName + " dose not implement Search Index Provider");
continue;
}

1.4 final List<SliceData> providerSliceData = getSliceDataFromProvider(provider,
fragmentName);
mSliceData.addAll(providerSliceData);
}

return mSliceData;
}
}

1.2 -> // SearchFeatureProviderImpl的resource对象.
@Override
public SearchIndexableResources getSearchIndexableResources() {
if (mSearchIndexableResources == null) {
// 单例 + 代理
mSearchIndexableResources = new SearchIndexableResourcesImpl();
}
return mSearchIndexableResources;
}

通过上述方法终于找到了SearchIndexableResourcesImpl类, 这个存放了所有支持搜索的fragment的class, 通过其提供的
getProviderValues方法获取集合. 这个地方与自定义fragment支持search扩展 呼应

1
2
3
4
5
6
void addIndex(Class indexClass) {
sProviders.add(indexClass);
}
public Collection<Class> getProviderValues() {
return sProviders;
}

2.1.4. 怎样找到资源的

工具类 DatabaseIndexingUtils, 遍历支持搜索的fragment的class的类名, 通过反射找到其SEARCH_INDEX_DATA_PROVIDER字段(如果支持搜索, 必须定义该字段)
反射出类 SearchIndexProvider(Indexable接口中的子接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

final SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(clazz);
public static Indexable.SearchIndexProvider getSearchIndexProvider(final Class<?> clazz) {
try {
final Field f = clazz.getField(FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
return (Indexable.SearchIndexProvider) f.get(null);
}
return null;
}


1.4 -> 从provider中获取数据, 即支持搜索的fragment需要覆写方法提供数据
final List<SliceData> providerSliceData = getSliceDataFromProvider(provider,
fragmentName);

private List<SliceData> getSliceDataFromProvider(SearchIndexProvider provider,
String fragmentName) {
final List<SliceData> sliceData = new ArrayList<>();
// 需要覆写 getXmlResourcesToIndex方法, 提供xml 资源id
final List<SearchIndexableResource> resList =
provider.getXmlResourcesToIndex(mContext, true /* enabled */);

for (SearchIndexableResource resource : resList) {
int xmlResId = resource.xmlResId;
if (xmlResId == 0) {
Log.e(TAG, fragmentName + " provides invalid XML (0) in search provider.");
continue;
}

1.4.1 List<SliceData> xmlSliceData = getSliceDataFromXML(xmlResId, fragmentName);
sliceData.addAll(xmlSliceData);
}

return sliceData;
}

1.4.1 -> 通过xml id解析出元素填充到SliceData中
private List<SliceData> getSliceDataFromXML(int xmlResId, String fragmentName) {
XmlResourceParser parser = null;

final List<SliceData> xmlSliceData = new ArrayList<>();

try {
parser = mContext.getResources().getXml(xmlResId);

int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& type != XmlPullParser.START_TAG) {
// Parse next until start tag is found
}

String nodeName = parser.getName();
...
final AttributeSet attrs = Xml.asAttributeSet(parser);
final String screenTitle = PreferenceXmlParserUtils.getDataTitle(mContext, attrs);

// TODO (b/67996923) Investigate if we need headers for Slices, since they never
// correspond to an actual setting.

final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext,
xmlResId,
MetadataFlag.FLAG_NEED_KEY
| MetadataFlag.FLAG_NEED_PREF_CONTROLLER
| MetadataFlag.FLAG_NEED_PREF_TYPE
| MetadataFlag.FLAG_NEED_PREF_TITLE
| MetadataFlag.FLAG_NEED_PREF_ICON
| MetadataFlag.FLAG_NEED_PREF_SUMMARY
| MetadataFlag.FLAG_NEED_PLATFORM_SLICE_FLAG);

for (Bundle bundle : metadata) {
// TODO (b/67996923) Non-controller Slices should become intent-only slices.
// Note that without a controller, dynamic summaries are impossible.
final String controllerClassName = bundle.getString(METADATA_CONTROLLER);
if (TextUtils.isEmpty(controllerClassName)) {
continue;
}
final String key = bundle.getString(METADATA_KEY);
final String title = bundle.getString(METADATA_TITLE);
final String summary = bundle.getString(METADATA_SUMMARY);
final int iconResId = bundle.getInt(METADATA_ICON);
final int sliceType = SliceBuilderUtils.getSliceType(mContext, controllerClassName,
key);
final boolean isPlatformSlice = bundle.getBoolean(METADATA_PLATFORM_SLICE_FLAG);
// Build模式
final SliceData xmlSlice = new SliceData.Builder()
.setKey(key)
.setTitle(title)
.setSummary(summary)
.setIcon(iconResId)
.setScreenTitle(screenTitle)
.setPreferenceControllerClassName(controllerClassName)
.setFragmentName(fragmentName)
.setSliceType(sliceType)
.setPlatformDefined(isPlatformSlice)
.build();

xmlSliceData.add(xmlSlice);
}
}
...
return xmlSliceData;
}

拿到数据后, 即List 后, 通过insertSliceData(database, indexData) 插入到数据库中.

2.1.5. 小节

Settings入口分析结束, 其中看到了统一的工厂, 抽象工厂, 具体工厂, 通过工厂管理各种工种, 成员为抽象的, 调用工厂的方法找到实际的工种代理.
统一管理起来. 每个代理都是单例的, 其中又涉及到了builder模式, 工具类的使用等等.
抽象 + 具体的模式使用非常普遍, 保存成员时都是保存的抽象.

上述过程主要涉及到元数据的索引, provider和database并不是直接的联系, 而是通过了几层的桥接. 数据类为SliceData

2.2. 搜索过程

通过输入关键词找到关键词对应的fragment. 对应的ui是SearchActivity, 需要回想之前的fragment的生命周期.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 绑定layout 为 search_main
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);
// 绑定布局, 关联到SearchFragment
if (fragment == null) {
fragmentManager.beginTransaction()
.add(R.id.main_content, new SearchFragment())
.commit();
}
}

进入SearchFragment的生命周期中, 这里又使用到了Loader

2.2.1. SearchFragment的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 继承LoaderCallbacks
public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener,
LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
public void onAttach(Context context) {
super.onAttach(context);
// 关联SearchFeatureProvider
mSearchFeatureProvider = FeatureFactory.get(context).searchFeatureProvider();
}

public void onCreate(Bundle savedInstanceState) {
final LoaderManager loaderManager = getLoaderManager();
// 对应的Adapter
mSearchAdapter = new SearchResultsAdapter(this /* fragment */);
mSavedQueryController = new SavedQueryController(
getContext(), loaderManager, mSearchAdapter);
// 更新元数据索引, 又更新一轮
1. mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final Activity activity = getActivity();
final View view = inflater.inflate(R.layout.search_panel, container, false);
// 这里又使用到了 RecyclerView, item元素的布局为list_results
mResultsRecyclerView = view.findViewById(R.id.list_results);
mResultsRecyclerView.setAdapter(mSearchAdapter);
// 设定线性布局管理
mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
mResultsRecyclerView.addOnScrollListener(mScrollListener);

mNoResultsView = view.findViewById(R.id.no_results_layout);

final Toolbar toolbar = view.findViewById(R.id.search_toolbar);
activity.setActionBar(toolbar);
activity.getActionBar().setDisplayHomeAsUpEnabled(true);

mSearchView = toolbar.findViewById(R.id.search_view);
// Query数据保存到 mQuery中. 触发时机为onQueryTextSubmit 和 onQueryTextChange时
// 封装了SearchView, 为Search Toolbar对应的布局.
mSearchView.setQuery(mQuery, false /* submitQuery */);
mSearchView.setOnQueryTextListener(this);
mSearchView.requestFocus();

return view;
}
}

2.2.2. provider.updateIndexAsync

这个地方在SearchFragment进入时, 又更新了一遍搜索数据库index, 需要重点看下, 整个过程为启动AsyncTask, 检索数据, 对数据进行处理, 放在数据库中.
在AsyncTask中执行, 检索完成后调用callback. 回调SearchFragment.onIndexingFinished方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
1. -> // 1. mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);
// 数据的更新直接参与者都是provider, 只不过这里的provider更加抽象, 前面查到数据后往数据库里写的操作
// callback为本身, 加载完成后通过AsyncTask的onPostExecute回调onIndexingFinished
public void updateIndexAsync(Context context, IndexingCallback callback) {
getIndexingManager(context).indexDatabase(callback);
}

@Override
public DatabaseIndexingManager getIndexingManager(Context context) {
if (mDatabaseIndexingManager == null) {
// 关联到 数据库索引管理类, 单例
mDatabaseIndexingManager = new DatabaseIndexingManager(context.getApplicationContext());
}
return mDatabaseIndexingManager;
}
// 启动AsyncTask
public void indexDatabase(IndexingCallback callback) {
IndexingTask task = new IndexingTask(callback);
task.execute();
}

public class IndexingTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... voids) {
1.1 performIndexing();
return null;
}
}

1.2 // 加载完成回调SearchFragment.onIndexingFinished
public void onIndexingFinished() {
if (getActivity() == null) {
return;
}
if (mShowingSavedQuery) {
mSavedQueryController.loadSavedQueries();
} else {
final LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null /* args */,
this /* callback */);
}

requery();
}


1.1 -> // performIndexing

public void performIndexing() {
// 通过intent 匹配 action provider, 关联到了 SettingsSearchIndexablesProvider
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) {
// 第一次进来, 会进入里面, 实际操作数据的是IndexDatabaseHelper类
1.1.1 rebuildDatabase();
}

// 获取数据
1.1.2 PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);

final long updateDatabaseStartTime = System.currentTimeMillis();
// setIndex后, 会往sharePreference里写, isFullIndex就变为false, 首次的话 isFullIndex 为 true
1.1.3 updateDatabase(indexData, isFullIndex);
IndexDatabaseHelper.setIndexed(mContext, providers);
}


1
2
3
4
5
6
7
8
9
10
11
12
<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>

先看下数据库重建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
1.1.1 -> // 重建数据库表, 清空sharePreference
private void rebuildDatabase() {
// Drop the database when the locale or build has changed. This eliminates rows which are
// dynamically inserted in the old language, or deprecated settings.
final SQLiteDatabase db = getWritableDatabase();
IndexDatabaseHelper.getInstance(mContext).reconstruct(db);
}

public void reconstruct(SQLiteDatabase db) {
// 清空sharePreference
mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
.edit()
.clear()
.commit();
//清空数据库的table
dropTables(db);
// 重建数据库table
bootstrapDB(db);
}

1.1.2 -> //获取数据 这里的跟之前又有不同
1.1.2 PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);

PreIndexData getIndexDataFromProviders(List<ResolveInfo> providers, boolean isFullIndex) {
if (mCollector == null) {
// 单例关联到 PreIndexDataCollector类, 找到的provider为SettingsSearchIndexablesProvider
mCollector = new PreIndexDataCollector(mContext);
}
return mCollector.collectIndexableData(providers, isFullIndex);
}

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) {
1.1.2.1 addIndexablesFromRemoteProvider(packageName, authority);
}
1.1.2.2 addNonIndexablesKeysFromRemoteProvider(packageName, authority);

}

return mIndexData;
}

1.1.2.1 -> // 构建查询uri, 查询SettingsSearchIndexablesProvider获取数据
private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
try {
final Context context = BASE_AUTHORITY.equals(authority) ?
mContext : mContext.createPackageContext(packageName, 0);

// 构建xml的查询数据uri
final Uri uriForResources = buildUriForXmlResources(authority);
mIndexData.dataToUpdate.addAll(getIndexablesForXmlResourceUri(context, packageName,
uriForResources, SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS));
// 构建rawdata的查询数据uri
final Uri uriForRawData = buildUriForRawData(authority);
// 查询完后, 统一放在mIndexData.dataToUpdate中
mIndexData.dataToUpdate.addAll(getIndexablesForRawDataUri(context, packageName,
uriForRawData, SearchIndexablesContract.INDEXABLES_RAW_COLUMNS));
return true;
}
}

1.1.2.2 ->// 构建查询uri, 查询查询SettingsSearchIndexablesProvider获取数据

private void addNonIndexablesKeysFromRemoteProvider(String packageName,
String authority) {
// 构建NonIndexableKeys的查询uri
final List<String> keys =
getNonIndexablesKeysFromRemoteProvider(packageName, authority);

if (keys != null && !keys.isEmpty()) {
// 查询到后统一放入mIndexData.nonIndexableKeys map中
mIndexData.nonIndexableKeys.put(authority, new ArraySet<>(keys));
}
}

1.1.3 -> // 填充数据

void updateDatabase(PreIndexData preIndexData, boolean needsReindexing) {
final Map<String, Set<String>> nonIndexableKeys = preIndexData.nonIndexableKeys;

final SQLiteDatabase database = getWritableDatabase();
try {
database.beginTransaction();

// Convert all Pre-index data to Index data.
// 重新获取元数据, 对元数据进行处理
1.1.3.1 List<IndexData> indexData = getIndexData(preIndexData);
// 获取元数据后, 插入数据库相应字段中 跟 insertSliceData 对比
1.1.3.2 insertIndexData(database, indexData);

// Only check for non-indexable key updates after initial index.
// Enabled state with non-indexable keys is checked when items are first inserted.
// 当前场景为首次, needsReindexing为true, 下面流程不走
if (!needsReindexing) {
1.1.3.3 updateDataInDatabase(database, nonIndexableKeys);
}

database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}

1.1.3.1-> // List<IndexData> indexData = getIndexData(preIndexData);
List<IndexData> getIndexData(PreIndexData data) {
if (mConverter == null) {
mConverter = new IndexDataConverter(mContext);
}
return mConverter.convertPreIndexDataToIndexData(data);
}

public List<IndexData> convertPreIndexDataToIndexData(PreIndexData preIndexData) {
final long current = System.currentTimeMillis();
// 取出从xml资源和rawdata数据
final List<SearchIndexableData> indexableData = preIndexData.dataToUpdate;
// 取出nonIndexableKeys获得的数据
final Map<String, Set<String>> nonIndexableKeys = preIndexData.nonIndexableKeys;
final List<IndexData> indexData = new ArrayList<>();

for (SearchIndexableData data : indexableData) {
if (data instanceof SearchIndexableRaw) {
final SearchIndexableRaw rawData = (SearchIndexableRaw) data;
final Set<String> rawNonIndexableKeys = nonIndexableKeys.get(
rawData.intentTargetPackage);
// builder模式, nonIndexableKeys最终作用到了IndexData的enabled字段, 最终放在了values.put(ENABLED, dataRow.enabled);里, 可能后面数据库查询时会用到
final IndexData.Builder builder = convertRaw(rawData, rawNonIndexableKeys);

if (builder != null) {
indexData.add(builder.build(mContext));
}
} else if (data instanceof SearchIndexableResource) {
final SearchIndexableResource sir = (SearchIndexableResource) data;
final Set<String> resourceNonIndexableKeys =
getNonIndexableKeysForResource(nonIndexableKeys, sir.packageName);
final List<IndexData> resourceData = convertResource(sir, resourceNonIndexableKeys);
indexData.addAll(resourceData);
}
}
return indexData;
}

1.1.3.2 -> // 更新数据库字段, 注意跟SlicesIndexer.insertSliceData的不同, 替换的是TABLE_SLICES_INDEX表
void insertIndexData(SQLiteDatabase database, List<IndexData> 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);
}
}
}

1.1.3.3 -> // updateDataInDatabase(database, nonIndexableKeys); 这个方法是转换往数据库中更新enable的值.
* All rows which are enabled but are now flagged with non-indexable keys will become disabled.
* All rows which are disabled but no longer a non-indexable key will become enabled.

获取数据的PreIndexDataCollector类为客户端, 匹配到Authority的远端的provider为SettingsSearchIndexablesProvider
该类在内部通过DatabaseIndexingUtils工具类遍历注册的Indexable.SEARCH_INDEX_DATA_PROVIDER, 找到子类覆写的getXmlResourcesToIndex/getRawDataToIndex/getNonIndexableKeys/getPreferenceControllers方法, 如果没有覆写, 则使用其基类BaseSearchIndexProvider的方法
检索元数据.

在检索元数据,并将数据放入数据库中后, 检索的AsyncTask走完, 会回调

2.2.3. 检索完数据, 回调onIndexingFinished方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.2 -> // onIndexingFinished

public void onIndexingFinished() {
if (getActivity() == null) {
return;
}
// mShowingSavedQuery首次加载是为true.
if (mShowingSavedQuery) {
1.2.1 mSavedQueryController.loadSavedQueries();
} else {
final LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null /* args */,
this /* callback */);
}

1.2.2 requery();
}
1.2.1 -> // Loader restart
public void loadSavedQueries() {
mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVED_QUERIES, null /* args */,
this /* callback */);
}

调用restartLoader后, Loader的生命周期就开启了

2.2.4. 进入Loader流程

缓存Loader的控制在SavedQueryController中, SAVED_QUERIES应该是保存的查询项的缓存, 缓存结果保存在了saved_queries表中.
真正查询的地方在id为 SEARCH_RESULT的SearchResultLoader中, 是由TextChange事件触发的.

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public Loader onCreateLoader(int id, Bundle args) {
switch (id) {
case SearchFragment.SearchLoaderId.SAVE_QUERY_TASK:
return new SavedQueryRecorder(mContext, args.getString(ARG_QUERY));
case SearchFragment.SearchLoaderId.REMOVE_QUERY_TASK:
return new SavedQueryRemover(mContext);
case SearchFragment.SearchLoaderId.SAVED_QUERIES:
return mSearchFeatureProvider.getSavedQueryLoader(mContext);
}
return null;
}

在onQueryTextChange时会触发

2.2.5. 触发查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
    // 查询条件为 query
public boolean onQueryTextChange(String query) {
// 上次缓存的条件为 mQuery
if (TextUtils.equals(query, mQuery)) {
return true;
}

final boolean isEmptyQuery = TextUtils.isEmpty(query);

// Hide no-results-view when the new query is not a super-string of the previous
if (mQuery != null
&& mNoResultsView.getVisibility() == View.VISIBLE
&& query.length() < mQuery.length()) {
mNoResultsView.setVisibility(View.GONE);
}

mNeverEnteredQuery = false;
mQuery = query;

// If indexing is not finished, register the query text, but don't search.
// index 索引未完成的情况下, 先保存查询条件, 待index结束后, 回调callback, 执行requery查询
if (!mSearchFeatureProvider.isIndexingComplete(getActivity())) {
return true;
}
// 查询条件为空, 取出缓存的查询项结果
if (isEmptyQuery) {
final LoaderManager loaderManager = getLoaderManager();
loaderManager.destroyLoader(SearchLoaderId.SEARCH_RESULT);
mShowingSavedQuery = true;
mSavedQueryController.loadSavedQueries();
mSearchFeatureProvider.hideFeedbackButton(getView());
} else {
// 开始查询
2. restartLoaders();
}
return true;
}


private void restartLoaders() {
mShowingSavedQuery = false;
final LoaderManager loaderManager = getLoaderManager();
loaderManager.restartLoader(
SearchLoaderId.SEARCH_RESULT, null /* args */, this /* callback */);
}

public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
final Activity activity = getActivity();

switch (id) {
case SearchLoaderId.SEARCH_RESULT:
return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
default:
return null;
}
}

public SearchResultLoader getSearchResultLoader(Context context, String query) {
return new SearchResultLoader(context, cleanQuery(query));
}
// 异步执行AsyncLoader的loadInBackground, 由构造函数传入了查询条件mQuery
public class SearchResultLoader extends AsyncLoader<List<? extends SearchResult>> {
public List<? extends SearchResult> loadInBackground() {
SearchResultAggregator aggregator = SearchResultAggregator.getInstance();
2.1 return aggregator.fetchResults(getContext(), mQuery);
}
}

public synchronized List<? extends SearchResult> fetchResults(Context context, String query) {
final SearchFeatureProvider mFeatureProvider = FeatureFactory.get(context)
.searchFeatureProvider();
final ExecutorService executorService = mFeatureProvider.getExecutorService();

// 这里封装了四个Task, 但最终索要结果的只有一个Task.
final List<SearchQueryTask> tasks =
mFeatureProvider.getSearchQueryTasks(context, query);


public 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;
}

// Start tasks, 执行task
for (SearchQueryTask task : tasks) {
2.2 executorService.execute(task);
}

// Collect results
final Map<Integer, List<? extends SearchResult>> taskResults = new ArrayMap<>();
for (SearchQueryTask task : tasks) {
final int taskId = task.getTaskId();
try {
// 以taskid为键值, 存入taskResults中, task.get会阻塞, 未执行完不会返回
taskResults.put(taskId,
task.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
}

// Merge results
2.3 final List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);

return mergedResults;
}

2.2.5.1. DatabaseResultTask执行过程

DatabaseResultTask并不是AsyncTask, 而是java中的FutureTask机制, 这里简单看一下执行过程.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2.2 -> // Add and execute
tasks.add(DatabaseResultTask.newTask(context, getSiteMapManager(), cleanQuery));

public class DatabaseResultTask extends SearchQueryTask.QueryWorker {
public static SearchQueryTask newTask(Context context, SiteMapManager siteMapManager,
String query) {
return new SearchQueryTask(new DatabaseResultTask(context, siteMapManager, query));
}

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);
}
}
}


public class SearchQueryTask extends FutureTask<List<? extends SearchResult>> {

public SearchQueryTask(@NonNull QueryWorker queryWorker) {
super(queryWorker);
mId = queryWorker.getQueryWorkerId();
}

public static abstract class QueryWorker implements Callable<List<? extends SearchResult>> {
public List<? extends SearchResult> call() throws Exception {
final long startTime = System.currentTimeMillis();
try {
return query();
} finally {
}
}
}

}

相关类图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@startuml

class FutureTask
package SearchQueryTask {
class SearchQueryTask extends FutureTask {
QueryWorker queryWorker
}

abstract class QueryWorker implements Callable {
<b>call()
query()
}

QueryWorker -d-o SearchQueryTask
}

class DatabaseResultTask extends QueryWorker {
<b>query()
}
@enduml

2.2.5.2. 查询过程

真正的查询是通过FutureTask的 execute执行call方法, 最终走到子类DatabaseResultTask的query中执行的查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

protected List<? extends SearchResult> query() {
if (mQuery == null || mQuery.isEmpty()) {
return new ArrayList<>();
}
// Start a Future to get search result scores.
// 出现几个Task了, Task嵌套Task再嵌套Task
FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask(
mContext, mQuery);
// 这里并没有指定RankerTask, 忽略
if (rankerTask != null) {
ExecutorService executorService = mFeatureProvider.getExecutorService();
executorService.execute(rankerTask);
}

final Set<SearchResult> resultSet = new HashSet<>();
// 这里分别根据几个字段分别查, 最终结果保存到resultSet. 查询条件中都有AND enabled = 1, 与前面数据库enabled字段相匹配
// 第二个参数为评分分数, 与结果的展示有关, resultSet有去重功能
3.1 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.
// rankerTask为空, 忽略, 排名相关
if (rankerTask != null) {
...
}

List<SearchResult> resultList = new ArrayList<>(resultSet);
// 采用默认排序
Collections.sort(resultList);
return resultList;
}

// 这里只看第一个查询条件即可
3.1 -> // firstWordQuery
private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
// whereClause是匹配字段, selection是通过mQuery定的匹配条件
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)) {
// 查询结果由CursorToSearchResultConverter 转化为 SearchResult的集合
return mConverter.convertCursor(resultCursor, baseRank, mSiteMapManager);
}
}

2.3 -> //合并结果, 只取了DatabaseResultTask的结果, 其他的task执行并未索取

private List<? extends SearchResult> mergeSearchResults(
Map<Integer, List<? extends SearchResult>> taskResults) {

final List<SearchResult> 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<SearchResult> heap = new PriorityQueue<>();
for (List<? extends SearchResult> taskResult : taskResults.values()) {
heap.addAll(taskResult);
}
while (!heap.isEmpty()) {
searchResults.add(heap.poll());
}
return searchResults;
}

2.2.6. 搜索结果显示

在走完AsyncLoader的loadInBackground后会进入LoaderCallbacks的onLoadFinished方法中, SearchResultsAdapter为RecyclerView, 保存单项的显示结果
响应顺序:
->getItemViewType(拿到viewType)
->onCreateViewHolder(根据viewType设置自定义holder)
->onBindViewHolder(响应自定义holder的onBind方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
List<? extends SearchResult> data) {
mSearchAdapter.postSearchResults(data);
}

public int getItemViewType(int position) {
return mSearchResults.get(position).viewType;
}

public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final Context context = parent.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
final View view;
switch (viewType) {
// RecyleView 可以随意更换item的布局, viewType来自getItemViewType方法
// IntentSearchViewHolder中定义了item的点击事件
case ResultPayload.PayloadType.INTENT:
view = inflater.inflate(R.layout.search_intent_item, parent, false);
return new IntentSearchViewHolder(view);
case ResultPayload.PayloadType.INLINE_SWITCH:
view = inflater.inflate(R.layout.search_intent_item, parent, false);
return new IntentSearchViewHolder(view);
case ResultPayload.PayloadType.INLINE_LIST:
view = inflater.inflate(R.layout.search_intent_item, parent, false);
return new IntentSearchViewHolder(view);
case ResultPayload.PayloadType.SAVED_QUERY:
view = inflater.inflate(R.layout.search_saved_query_item, parent, false);
return new SavedQueryViewHolder(view);
default:
return null;
}
}

public void onBindViewHolder(SearchViewHolder holder, int position) {
// 响应自定义的holder的onBind
holder.onBind(mFragment, mSearchResults.get(position));
}

public class IntentSearchViewHolder extends SearchViewHolder {
public void onBind(final SearchFragment fragment, final SearchResult result) {
super.onBind(fragment, result);
final SearchViewHolder viewHolder = this;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 点击跳转到对应的intent
fragment.onSearchResultClicked(viewHolder, result);
final Intent intent = result.payload.getIntent();
// Use app user id to support work profile use case.
if (result instanceof AppSearchResult) {
AppSearchResult appResult = (AppSearchResult) result;
fragment.getActivity().startActivity(intent);
} else {
final PackageManager pm = fragment.getActivity().getPackageManager();
final List<ResolveInfo> info = pm.queryIntentActivities(intent, 0 /* flags */);
if (info != null && !info.isEmpty()) {
fragment.startActivityForResult(intent, REQUEST_CODE_NO_OP);
}
}
}
});
}
}