主要调研下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 { List<SearchIndexableResource> getXmlResourcesToIndex (Context context, boolean enabled) ; List<SearchIndexableRaw> getRawDataToIndex (Context context, boolean enabled) ; List<String> getNonIndexableKeys (Context context) ; List<AbstractPreferenceController> getPreferenceControllers (Context context) ; } }
1.2. 接口实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 .SearchIndexProvideraddIndex(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 () ; } 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 ); }); } }
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); } mSlicesIndexer = new SlicesIndexer (context); class SlicesIndexer implements Runnable { public SlicesIndexer (Context context) { mHelper = SlicesDatabaseHelper.getInstance(mContext); } public void run () { 1. indexSliceData(); } } public class SlicesDatabaseHelper extends SQLiteOpenHelper { private SlicesDatabaseHelper (Context context) { super (context, DATABASE_NAME, null , 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 () { if (mHelper.isSliceDataIndexed()) { Log.d(TAG, "Slices already indexed - returning." ); return ; } SQLiteDatabase database = mHelper.getWritableDatabase(); try { database.beginTransaction(); mHelper.reconstruct(mHelper.getWritableDatabase()); 1.1 List<SliceData> indexData = getSliceData(); insertSliceData(database, indexData); mHelper.setIndexedState(); database.setTransactionSuccessful(); } finally { database.endTransaction(); } } 1.1 -> List<SliceData> getSliceData () { 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; } 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); 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 -> @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 <>(); final List<SearchIndexableResource> resList = provider.getXmlResourcesToIndex(mContext, true ); 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) { } String nodeName = parser.getName(); ... final AttributeSet attrs = Xml.asAttributeSet(parser); final String screenTitle = PreferenceXmlParserUtils.getDataTitle(mContext, attrs); 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) { 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); 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); setContentView(R.layout.search_main); 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的生命周期中, 这里又使用到了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 public class SearchFragment extends Fragment implements SearchView .OnQueryTextListener, LoaderManager.LoaderCallbacks<List<? extends SearchResult >>, IndexingCallback { public void onAttach (Context context) { super .onAttach(context); mSearchFeatureProvider = FeatureFactory.get(context).searchFeatureProvider(); } public void onCreate (Bundle savedInstanceState) { final LoaderManager loaderManager = getLoaderManager(); mSearchAdapter = new SearchResultsAdapter (this ); mSavedQueryController = new SavedQueryController ( getContext(), loaderManager, mSearchAdapter); 1. mSearchFeatureProvider.updateIndexAsync(getContext(), this ); } @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 ); 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); mSearchView.setQuery(mQuery, false ); 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. -> 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; } 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 public void onIndexingFinished () { if (getActivity() == null ) { return ; } if (mShowingSavedQuery) { mSavedQueryController.loadSavedQueries(); } else { final LoaderManager loaderManager = getLoaderManager(); loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null , this ); } requery(); } 1.1 -> public void performIndexing () { 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) { 1.1 .1 rebuildDatabase(); } 1.1 .2 PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex); final long updateDatabaseStartTime = System.currentTimeMillis(); 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 -> private void rebuildDatabase () { final SQLiteDatabase db = getWritableDatabase(); IndexDatabaseHelper.getInstance(mContext).reconstruct(db); } public void reconstruct (SQLiteDatabase db) { mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE) .edit() .clear() .commit(); dropTables(db); bootstrapDB(db); } 1.1 .2 -> 1.1 .2 PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex); PreIndexData getIndexDataFromProviders (List<ResolveInfo> providers, boolean isFullIndex) { if (mCollector == null ) { 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 -> private boolean addIndexablesFromRemoteProvider (String packageName, String authority) { try { final Context context = BASE_AUTHORITY.equals(authority) ? mContext : mContext.createPackageContext(packageName, 0 ); final Uri uriForResources = buildUriForXmlResources(authority); mIndexData.dataToUpdate.addAll(getIndexablesForXmlResourceUri(context, packageName, uriForResources, SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS)); final Uri uriForRawData = buildUriForRawData(authority); mIndexData.dataToUpdate.addAll(getIndexablesForRawDataUri(context, packageName, uriForRawData, SearchIndexablesContract.INDEXABLES_RAW_COLUMNS)); return true ; } } 1.1 .2 .2 -> private void addNonIndexablesKeysFromRemoteProvider (String packageName, String authority) { final List<String> keys = getNonIndexablesKeysFromRemoteProvider(packageName, authority); if (keys != null && !keys.isEmpty()) { 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(); 1.1 .3 .1 List<IndexData> indexData = getIndexData(preIndexData); 1.1 .3 .2 insertIndexData(database, indexData); if (!needsReindexing) { 1.1 .3 .3 updateDataInDatabase(database, nonIndexableKeys); } database.setTransactionSuccessful(); } finally { database.endTransaction(); } } 1.1 .3 .1 -> 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(); final List<SearchIndexableData> indexableData = preIndexData.dataToUpdate; 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); 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 -> 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 , siteMapPair); } } } 1.1 .3 .3 -> * 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 -> public void onIndexingFinished () { if (getActivity() == null ) { return ; } if (mShowingSavedQuery) { 1.2 .1 mSavedQueryController.loadSavedQueries(); } else { final LoaderManager loaderManager = getLoaderManager(); loaderManager.initLoader(SearchLoaderId.SEARCH_RESULT, null , this ); } 1.2 .2 requery(); } 1.2 .1 -> public void loadSavedQueries () { mLoaderManager.restartLoader(SearchFragment.SearchLoaderId.SAVED_QUERIES, null , this ); }
调用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 public boolean onQueryTextChange (String query) { if (TextUtils.equals(query, mQuery)) { return true ; } final boolean isEmptyQuery = TextUtils.isEmpty(query); if (mQuery != null && mNoResultsView.getVisibility() == View.VISIBLE && query.length() < mQuery.length()) { mNoResultsView.setVisibility(View.GONE); } mNeverEnteredQuery = false ; mQuery = query; 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 , this ); } 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)); } 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(); 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; } for (SearchQueryTask task : tasks) { 2.2 executorService.execute(task); } final Map<Integer, List<? extends SearchResult >> taskResults = new ArrayMap <>(); for (SearchQueryTask task : tasks) { final int taskId = task.getTaskId(); try { taskResults.put(taskId, task.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS)); } } 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 -> 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 <>(); } FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask( mContext, mQuery); if (rankerTask != null ) { ExecutorService executorService = mFeatureProvider.getExecutorService(); executorService.execute(rankerTask); } final Set<SearchResult> resultSet = new HashSet <>(); 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 ])); if (rankerTask != null ) { ... } List<SearchResult> resultList = new ArrayList <>(resultSet); Collections.sort(resultList); return resultList; } 3.1 -> private Set<SearchResult> firstWordQuery (String[] matchColumns, int baseRank) { 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 )) { return mConverter.convertCursor(resultCursor, baseRank, mSiteMapManager); } } 2.3 -> private List<? extends SearchResult > mergeSearchResults( Map<Integer, List<? extends SearchResult >> taskResults) { 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; }
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) { 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(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) { fragment.onSearchResultClicked(viewHolder, result); final Intent intent = result.payload.getIntent(); 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 ); if (info != null && !info.isEmpty()) { fragment.startActivityForResult(intent, REQUEST_CODE_NO_OP); } } } }); } }