如上一篇文章介绍(
http://blog.youkuaiyun.com/zhanglianyu00/article/details/61196900 ),安卓search框架支持通过实现接口,将第三方App作为外部数据源接入到系统搜索。这篇文章看一下系统侧如何管理这些searchable info。
安卓框架层有一个SearchManager作为Search管理者的对外的接口。官方文档:
https://developer.android.com/reference/android/app/SearchManager.html 。对应的系统服务是SearchManagerService.java。代码路径:frameworks/base/services/core/java/com/android/server/search。可以看到,是frameworks/base/services/core/java/com/android/server的一个子路径,可见SearchManagerService是系统核心服务之一,与PackageManagerService、ActivityManagerService地位等同。
在frameworks/base/services/core/java/com/android/server/search下只有两个文件:
Searchables.java SearchManagerService.java
SearchManagerService是系统服务类,实现了ISearchManager.stub接口。
Searchables定义了所有Searchable Activity的信息。
以某个App被安装/卸载的场景为例,看看SearchManagerService是怎么样维护Searchable info的。内部类MyPackageMonitor继承PackageMonitor:
/**
* Refreshes the "searchables" list when packages are added/removed.
*/
class MyPackageMonitor extends PackageMonitor {
@Override
public void onSomePackagesChanged() {
updateSearchables();
}
@Override
public void onPackageModified(String pkg) {
updateSearchables();
}
private void updateSearchables() {
final int changingUserId = getChangingUserId();
synchronized (mSearchables) {
// Update list of searchable activities
for (int i = 0; i < mSearchables.size(); i++) {
if (changingUserId == mSearchables.keyAt(i)) {
getSearchables(mSearchables.keyAt(i)).buildSearchableList();
break;
}
}
}
// Inform all listeners that the list of searchables has been updated.
Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
}
}
在SearchManagerService的构造方法中注册,这里看到会侦听所有User的Package Changed:
new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
PackageMonitor.updateSearchables()里面做两件事:
(1)更新一遍Searchable info;
(2)向发生了packages changed的user发SEARCHABLES_CHANGED广播。
(1)里面主要的逻辑是Searchables.buildSearchableList():
/**
* Builds an entire list (suitable for display) of
* activities that are searchable, by iterating the entire set of
* ACTION_SEARCH & ACTION_WEB_SEARCH intents.
*
* Also clears the hash of all activities -> searches which will
* refill as the user clicks "search".
*
* This should only be done at startup and again if we know that the
* list has changed.
*
* TODO: every activity that provides a ACTION_SEARCH intent should
* also provide searchability meta-data. There are a bunch of checks here
* that, if data is not found, silently skip to the next activity. This
* won't help a developer trying to figure out why their activity isn't
* showing up in the list, but an exception here is too rough. I would
* like to find a better notification mechanism.
*
* TODO: sort the list somehow? UI choice.
*/
public void buildSearchableList() {
// These will become the new values at the end of the method
HashMap<ComponentName, SearchableInfo> newSearchablesMap
= new HashMap<ComponentName, SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesList
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
// Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
List<ResolveInfo> searchList;
final Intent intent = new Intent(Intent.ACTION_SEARCH);
long ident = Binder.clearCallingIdentity();
try {
searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);
List<ResolveInfo> webSearchInfoList;
final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
// analyze each one, generate a Searchables record, and record
if (searchList != null || webSearchInfoList != null) {
int search_count = (searchList == null ? 0 : searchList.size());
int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
int count = search_count + web_search_count;
for (int ii = 0; ii < count; ii++) {
// for each component, try to find metadata
ResolveInfo info = (ii < search_count)
? searchList.get(ii)
: webSearchInfoList.get(ii - search_count);
ActivityInfo ai = info.activityInfo;
// Check first to avoid duplicate entries.
if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai,
mUserId);
if (searchable != null) {
newSearchablesList.add(searchable);
newSearchablesMap.put(searchable.getSearchActivity(), searchable);
if (searchable.shouldIncludeInGlobalSearch()) {
newSearchablesInGlobalSearchList.add(searchable);
}
}
}
}
}
List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities();
// Find the global search activity
ComponentName newGlobalSearchActivity = findGlobalSearchActivity(
newGlobalSearchActivities);
// Find the web search activity
ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
// Store a consistent set of new values
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
mGlobalSearchActivities = newGlobalSearchActivities;
mCurrentGlobalSearchActivity = newGlobalSearchActivity;
mWebSearchActivity = newWebSearchActivity;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
看到两点信息: (1)这部分逻辑不含对Search Setting的处理。 (2)对于应用安装/卸载的处理是粗线条的。也就是说即使被安装/卸载的App并没有实现Search接口,也会全量刷新整个Searchable info。