Android Settings搜索Search方案分析

2024-09-06 08:58

本文主要是介绍Android Settings搜索Search方案分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android开发会遇到一些自写界面需要允许被搜索,或者三方应用挂靠在Settings,用户也希望能被搜索。
在知道怎么添加之前,得先了解下整个框架,才能更好地加入我们自己的代码。

 

这里稍微整理了下整个search database数据如何索引加载流程。

Android Settings Search seq

Settings搜索界面是由SearchFragment展现,当用户在Settings主页中点击搜索图标,会启动到SearchActivity。

       <activity android:name=".search.SearchActivity"android:label="@string/search_settings"android:icon="@drawable/ic_search_24dp"android:parentActivityName="Settings"android:theme="@style/Theme.Settings.NoActionBar"><intent-filter><action android:name="com.android.settings.action.SETTINGS_SEARCH" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>

首次启动Settings时,并不会主动加载数据库,而是在第一次发生搜索时,异步进行。

//com/android/settings/search/SearchFragment.java@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);long startTime = System.currentTimeMillis();setHasOptionsMenu(true);Log.d(TAG, "onCreate: ");
......final Activity activity = getActivity();// Run the Index update only if we have some spaceif (!Utils.isLowStorage(activity)) {mSearchFeatureProvider.updateIndexAsync(activity, this /* indexingCallback */);  // 创建数据库,并建立索引} else {Log.w(TAG, "Cannot update the Indexer as we are running low on storage space!");}if (SettingsSearchIndexablesProvider.DEBUG) {Log.d(TAG, "onCreate spent " + (System.currentTimeMillis() - startTime) + " ms");}}

最后走到一个核心类DatabaseIndexingManager,它负责Settings关联所有的索引。

Settings Search Indexing Manager

核心方法如下:

    public void indexDatabase(IndexingCallback callback) {IndexingTask task = new IndexingTask(callback);task.execute();}/*** Accumulate all data and non-indexable keys from each of the content-providers.* Only the first indexing for the default language gets static search results - subsequent* calls will only gather non-indexable keys.*/public void performIndexing() {final long startTime = System.currentTimeMillis();// 遍历查询设备中所有声明了android.content.action.SEARCH_INDEXABLES_PROVIDER action的ContentProvider。final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);final List<ResolveInfo> providers =mContext.getPackageManager().queryIntentContentProviders(intent, 0);final String localeStr = Locale.getDefault().toString();final String fingerprint = Build.FINGERPRINT;final String providerVersionedNames =IndexDatabaseHelper.buildProviderVersionedNames(providers);final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, localeStr,fingerprint, providerVersionedNames);if (isFullIndex) {rebuildDatabase();}//遍历所有自定义的自写activity/fragment对应的SearchIndexableProvider提供的可搜索和不可搜索的KEY,并保存至数据结构UpdateData中。for (final ResolveInfo info : providers) {if (!DatabaseIndexingUtils.isWellKnownProvider(info, mContext)) {continue;}final String authority = info.providerInfo.authority;final String packageName = info.providerInfo.packageName;Log.d(LOG_TAG, "knealq performIndexing: authority:"  + authority  + ",packageName:" + packageName + ",isFullIndex:" + isFullIndex + ", resolverInfo:" + info);if (isFullIndex) {//查询可搜索的所有Provider( ? extens SearchIndexableProvider)对应所有可搜索KEY,并保存到数据结构:UpdateData.dataToUpdate。addIndexablesFromRemoteProvider(packageName, authority);}final long nonIndexableStartTime = System.currentTimeMillis();//查询可搜索的所有Provider( ? extens SearchIndexableProvider)对应所有不可搜索(黑名单)KEY,并保存到数据结构:UpdateData.nonIndexableKeys。addNonIndexablesKeysFromRemoteProvider(packageName, authority);if (SettingsSearchIndexablesProvider.DEBUG) {final long nonIndextableTime = System.currentTimeMillis() - nonIndexableStartTime;Log.d(LOG_TAG, "performIndexing update non-indexable for package " + packageName+ " took time: " + nonIndextableTime);}}final long updateDatabaseStartTime = System.currentTimeMillis();// 遍历providers后,将相关索引转化成SearchIndexableData,并保存到database(/data/user_de/0/com.android.settings/databases/search_index.db)中。updateDatabase(isFullIndex, localeStr);if (SettingsSearchIndexablesProvider.DEBUG) {final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;Log.d(LOG_TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);}//TODO(63922686): Setting indexed should be a single method, not 3 separate setters.IndexDatabaseHelper.setLocaleIndexed(mContext, localeStr);IndexDatabaseHelper.setBuildIndexed(mContext, fingerprint);IndexDatabaseHelper.setProvidersIndexed(mContext, providerVersionedNames);if (SettingsSearchIndexablesProvider.DEBUG) {final long indexingTime = System.currentTimeMillis() - startTime;Log.d(LOG_TAG, "performIndexing took time: " + indexingTime+ "ms. Full index? " + isFullIndex);}}

方法中首先,看到的是通过PackageManager扫描查询注册了指定Action的ContentProvider, 并将其转换放置赋给一个String。
一共搜索到如下app有注册。

我们重点关注com.android.settings,

com.android.cellbroadcastreceiver:29,
com.android.emergency:29,
com.android.settings:29,
com.android.traceur:2,
com.google.android.apps.messaging:54087046,
com.google.android.apps.wellbeing:131132,
com.google.android.gms:200414038,
com.google.android.googlequicksearchbox:301068684,
com.google.android.inputmethod.latin:26881014,
com.google.android.permissioncontroller:291900801,
com.google.android.permissioncontroller:291900801,

再来就是检查是否fullIndex, 是否fullIndex需要满足三个条件,
即当前build fingerprint,locale环境,以及当前扫描到的ContentProvider均未被索引过。

这个在应用内部有个sharedpreference保存相关记录,举个栗子。

AOSP:/data/user_de/0/com.android.settings/shared_prefs # cat index.xml                                                                                               
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map><string name="indexed_providers">com.android.cellbroadcastreceiver:29,com.android.emergency:29,com.android.settings:29,com.android.traceur:2,com.google.android.apps.messaging:54087046,com.google.android.apps.wellbeing:131132,com.google.android.gms:200414038,com.google.android.googlequicksearchbox:301068684,com.google.android.inputmethod.latin:26881014,com.google.android.permissioncontroller:291900801,com.google.android.permissioncontroller:291900801,</string><boolean name="en_US" value="true" /><boolean name="Google/XXXXXXXX/AOSP:10/QP1A.190711.020/XXXXX:userdebug/release-keys" value="true" />
</map>
AOSP:/data/user_de/0/com.android.settings/shared_prefs # 

说白了就是判断这个index.xml里边的内容是否已经存在,或者是否有差异。

当fullindex为true,需要创建database和表,具体由IndexDatabaseHelper类来完成。

    /*** Reconstruct the database in the following cases:* - Language has changed* - Build has changed*/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) {dropTables(db);bootstrapDB(db);}private void bootstrapDB(SQLiteDatabase db) {db.execSQL(CREATE_INDEX_TABLE);db.execSQL(CREATE_META_TABLE);db.execSQL(CREATE_SAVED_QUERIES_TABLE);db.execSQL(CREATE_SITE_MAP_TABLE);db.execSQL(INSERT_BUILD_VERSION);Log.i(TAG, "Bootstrapped database");}private void dropTables(SQLiteDatabase db) {clearCachedIndexed(mContext);db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP);}

数据库创建后,首先遍历所有可搜和不可搜的KEY,并保存。
这些数据哪里来,我们暂且称之为“总入口”SearchIndexableResources.sResMap。

Search Indexable resource

Settings对应Provider是SettingsSearchIndexableProvider,其声明了queryXmlResources等接口,而真正访问的数据是来自SearchIndexableResources类里边静态初始化的一个sResMap,里边保存了SearchIndexableData信息。

UpdateData数据结构.png

queryXmlResources直接拿seMap里边的信息即可。
queryNonIndexableKeys则通过sResMap指定的className(前边提到的extends Indexable的fragment、activity),通过DatabaseIndexUtils类找到fragment/Activity内部静态声明定义的BaseSearchIndexProvider。找到后,调用其给出的getNonIndexableKeys(context)方法,它返回的是List<String>。

 

以FingerprintSettingsFragment为例,它声明了FingerprintSearchIndexProvider, 其继承自BaseSearchIndexProvider,实现了Indexable.SearchIndexProvider接口。并静态实例化了一个被命名为SEARCH_INDEX_DATA_PROVIDER的FingerprintSearchIndexProvider。

FingerprintSettingsFragment


注意:

划重点,这里将用法。

【1】
自写fragment、activity中必须按照如下框架写。

// 第一步
public class SearchDefinedExt implements Indexable {// 往sResMap注册的类,可以是Activity、Fragment,还可以是其它类似工具类,不限定。public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER= new BaseSearchIndexProvider() {  //注意命名,必须是这个, Override几个你需要的方法,DataIndexingUtils工具类就是按照这个名称来找SearchIndexProvider的。@Overridepublic List<SearchIndexableRaw> getRawDataToIndex(Context context,boolean enabled) {List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();//省略若干return indexables;}@Overridepublic List<String> getNonIndexableKeys(Context context) {List<String> keys = super.getNonIndexableKeys(context);final ArrayList<String> result = new ArrayList<String>();return result;}};}//第2步
//往sResMap总入口添加声明,方便别人能找,可类比于一本书的目录。public final class SearchIndexableResources {static {Log.d("SearchIndexableResources", "static initializer: ");//添加自己所需要的addIndex(SearchDefinedExt.class, NO_DATA_RES_ID, R.drawable.ic_settings_wireless);}}

这里需要着重说明的是,如果第2步不加,将无法索引到SearchDefinedExt里边定义的SearchIndexProvider。
这个“目录”索引添加很重要。

 

【2】

三方挂靠Settings的应用怎么办?
Settings并不能直接获取到另外一个app资源xmlResId。

1) 实现自己的ContentProvider,且要符合Search规则,比如注册:android.content.action.SEARCH_INDEXABLES_PROVIDER action。
2) 使用Settings扩展出来的SearchDefinedExt,在getRawDataToIndex()里边添加自己应用的intentAction, intentTargetPackage, intentTargetClass封装成SearchIndexableRaw的形式传递出去。

 


回归到SearchFragment,我们看到调用updateIndexAsync时有传入this本类(本类实现了IndexingCallback),即当DatabaseIndexingManager索引完成database创建和数据插入(performIndexing)后。
将通过callback回传消息,告诉SearchFragment,然后让其更新UI,Ui通过SearchResultsAdapter负责加载SearchResult。

Settings SearchViewHolder

SearchResultsAdapter onCreateViewHolder时,将根据情况生成IntentSearchViewHolder或者SavedQueryViewHolder。
其中ViewHolder在onBind()时将注册OnClickListener点击事件,一旦点击,跳转到我们索引的界面。


 

这篇关于Android Settings搜索Search方案分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1141572

相关文章

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

解决docker目录内存不足扩容处理方案

《解决docker目录内存不足扩容处理方案》文章介绍了Docker存储目录迁移方法:因系统盘空间不足,需将Docker数据迁移到更大磁盘(如/home/docker),通过修改daemon.json配... 目录1、查看服务器所有磁盘的使用情况2、查看docker镜像和容器存储目录的空间大小3、停止dock

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe