Settings中主界面加载流程(一级菜单 动态加载)
DashboardFragment中的refreshAllPreferences
这个方法中加载了refreshDashboardTiles(tag);方法,此方法就是动态加载
private void refreshAllPreferences(final String tag) {??
Log.d("wuzhangxiao", "wuzhangxiao:?? refreshAllPreferences "+tag);???
//这个在oncreate前执行???
、、、、、、、、、、、省略代码
refreshDashboardTiles(tag);
refreshDashboardTiles
private void refreshDashboardTiles(final String tag) {??? Log.d("wuzhangxiao", "data? : refreshDashboardTiles"+tag);??? final PreferenceScreen screen = getPreferenceScreen();??? final DashboardCategory category =??????????? mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
去看DashboardFragmentRegistry
DashboardFragmentRegistry
/**
* A registry to keep track of which page hosts which category.
*/
public class DashboardFragmentRegistry {
/**
* Map from parent fragment to category key. The parent fragment hosts child with
* category_key.
*/
public static final Map<String, String> PARENT_TO_CATEGORY_KEY_MAP;
/**
* Map from category_key to parent. This is a helper to look up which fragment hosts the
* category_key.
*/
public static final Map<String, String> CATEGORY_KEY_TO_PARENT_MAP;
static {
PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
CategoryKey.CATEGORY_HOMEPAGE);
PARENT_TO_CATEGORY_KEY_MAP.put(
NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),
CategoryKey.CATEGORY_CONNECT);
PARENT_TO_CATEGORY_KEY_MAP.put(AdvancedConnectedDeviceDashboardFragment.class.getName(),
CategoryKey.CATEGORY_DEVICE);
PARENT_TO_CATEGORY_KEY_MAP.put(AppAndNotificationDashboardFragment.class.getName(),
CategoryKey.CATEGORY_APPS);
PARENT_TO_CATEGORY_KEY_MAP.put(PowerUsageSummary.class.getName(),
CategoryKey.CATEGORY_BATTERY);
PARENT_TO_CATEGORY_KEY_MAP.put(DisplaySettings.class.getName(),
CategoryKey.CATEGORY_DISPLAY);
PARENT_TO_CATEGORY_KEY_MAP.put(SoundSettings.class.getName(),
CategoryKey.CATEGORY_SOUND);
PARENT_TO_CATEGORY_KEY_MAP.put(StorageDashboardFragment.class.getName(),
CategoryKey.CATEGORY_STORAGE);
PARENT_TO_CATEGORY_KEY_MAP.put(SecuritySettings.class.getName(),
CategoryKey.CATEGORY_SECURITY);
PARENT_TO_CATEGORY_KEY_MAP.put(AccountDetailDashboardFragment.class.getName(),
CategoryKey.CATEGORY_ACCOUNT_DETAIL);
PARENT_TO_CATEGORY_KEY_MAP.put(AccountDashboardFragment.class.getName(),
CategoryKey.CATEGORY_ACCOUNT);
PARENT_TO_CATEGORY_KEY_MAP.put(
SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
PARENT_TO_CATEGORY_KEY_MAP.put(LanguageAndInputSettings.class.getName(),
CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
PARENT_TO_CATEGORY_KEY_MAP.put(DevelopmentSettingsDashboardFragment.class.getName(),
CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
PARENT_TO_CATEGORY_KEY_MAP.put(ConfigureNotificationSettings.class.getName(),
CategoryKey.CATEGORY_NOTIFICATIONS);
PARENT_TO_CATEGORY_KEY_MAP.put(LockscreenDashboardFragment.class.getName(),
CategoryKey.CATEGORY_SECURITY_LOCKSCREEN);
PARENT_TO_CATEGORY_KEY_MAP.put(ZenModeSettings.class.getName(),
CategoryKey.CATEGORY_DO_NOT_DISTURB);
PARENT_TO_CATEGORY_KEY_MAP.put(GestureSettings.class.getName(),
CategoryKey.CATEGORY_GESTURES);
PARENT_TO_CATEGORY_KEY_MAP.put(NightDisplaySettings.class.getName(),
CategoryKey.CATEGORY_NIGHT_DISPLAY);
PARENT_TO_CATEGORY_KEY_MAP.put(PrivacyDashboardFragment.class.getName(),
CategoryKey.CATEGORY_PRIVACY);
PARENT_TO_CATEGORY_KEY_MAP.put(EnterprisePrivacySettings.class.getName(),
CategoryKey.CATEGORY_ENTERPRISE_PRIVACY);
PARENT_TO_CATEGORY_KEY_MAP.put(LegalSettings.class.getName(),
CategoryKey.CATEGORY_ABOUT_LEGAL);
PARENT_TO_CATEGORY_KEY_MAP.put(MyDeviceInfoFragment.class.getName(),
CategoryKey.CATEGORY_MY_DEVICE_INFO);
PARENT_TO_CATEGORY_KEY_MAP.put(BatterySaverSettings.class.getName(),
CategoryKey.CATEGORY_BATTERY_SAVER_SETTINGS);
CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size());
for (Map.Entry<String, String> parentToKey : PARENT_TO_CATEGORY_KEY_MAP.entrySet()) {
CATEGORY_KEY_TO_PARENT_MAP.put(parentToKey.getValue(), parentToKey.getKey());
}
}
}
这个就是一个注册表的作用,注册记录什么界面(fragment)使用哪一个host去进行相应动态索引加载。 我们可以看见主界面的fragment为TopLeverSettings.java,相应的CategoryKey就是
PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
CategoryKey.CATEGORY_HOMEPAGE);
// Activities in this category shows up in Settings homepage.
public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";
1可以看到主界面动态加载关键字应是"com.android.settings.category.ia.homepage"。 再去查看getCategoryKey方法
@VisibleForTestingpublic String getCategoryKey() {??? //返回CategoryKey中的数据key??? return
DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());}
此方法是获取相关fragment的CategoryKey用于动态加载,根据上面分析主界面是TopLevelSettings.java,故而key为"com.android.settings.category.ia.homepage"。
2、继续看getTilesForCategory方法(),具体实现是在DashboardFeatureProviderImpl.java中:
DashboardFeatureProviderImpl
@Override
public DashboardCategory getTilesForCategory(String key) {
return mCategoryManager.getTilesByCategory(mContext, key);
}//
packages/apps/Settings/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java CategoryManager.java的getTilesByCategory()方法:
CategoryManager
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
tryInitCategories(context);
return mCategoryByKeyMap.get(categoryKey);
}
可以看到方法返回值是通过关键字key(“com.android.settings.category.ia.homepage”)去map集合中索引返回DashboardCategory的对象,故tryInitCategories()方法肯定是存在加载然后对map赋值的操作。直接看tryInitCategories()方法:
CategoryManager
public synchronized DashboardCategory getTilesByCategory(Context context, String
categoryKey) {???
tryInitCategories(context);???
return mCategoryByKeyMap.get(categoryKey);}
private synchronized void tryInitCategories(Context context) {???
// Keep cached tiles by default. The cache is only invalidated when
InterestingConfigChange???
// happens.???
tryInitCategories(context, false /* forceClearCache */);}
private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
if (mCategories == null) {
if (forceClearCache) {
mTileByComponentCache.clear();
}
mCategoryByKeyMap.clear();
mCategories = TileUtils.getCategories(context, mTileByComponentCache);
for (DashboardCategory category : mCategories) {
mCategoryByKeyMap.put(category.key, category);
}
backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
sortCategories(context, mCategoryByKeyMap);
filterDuplicateTiles(mCategoryByKeyMap);
}
}
此方法的作用是 1、首先清空mCategoryByKeyMap集合; 2、调用getCategories()方法,去查询构建DashboardCategory的list列表; 3、遍历list填充mCategoryByKeyMap集合; 4、检查是否使用旧的category keys,如果是,使用最新的category keys去替换; 5、排序; 6、去掉category中重复的tiles。 我可以看见private List<DashboardCategory> mCategories;mCategories = TileUtils.getCategories(context, mTileByComponentCache); 这个是获取相关的数据,是读取TileUtils.java,此文件在 frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
/** * Build a list of DashboardCategory. */
public static List<DashboardCategory> getCategories(Context context,??????? Map<Pair<String, String>, Tile> cache) {??
final long startTime = System.currentTimeMillis();??
//global.DEVICE_PROVISIONED是要检索的设置的名称。??
final boolean setup =Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED,
0) != 0;???
final ArrayList<Tile> tiles = new ArrayList<>();???
final UserManager userManager = (UserManager) context.getSystemService(??????????? Context.USER_SERVICE);???
for (UserHandle user : userManager.getUserProfiles()) {???????
// TODO: Needs much optimization, too many PM queries going on here.???????
if (user.getIdentifier() == ActivityManager.getCurrentUser()) {???????????
// Only add Settings for this user.仅为该用户添加设置。??????????? loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles,
true);???????????
loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,??????????????????? OPERATOR_DEFAULT_CATEGORY, tiles, false);???????????
loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,??????????????????? MANUFACTURER_DEFAULT_CATEGORY, tiles, false);???????
}???????
if (setup) {??????????? loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null,
tiles, false);??????????? loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null,
tiles, false);??????? }??? }??? final HashMap<String, DashboardCategory> categoryMap = new HashMap<>();??? int size = tiles.size();??? for (int i = 0; i < size; i++) {??????? Tile tile = tiles.get(i);??????? final String categoryKey = tile.getCategory();??????? DashboardCategory category = categoryMap.get(categoryKey);??????? if (category == null) {??????????? category = new DashboardCategory(categoryKey);??????????? if (category == null) {??????????????? Log.w(LOG_TAG, "Couldn't find category " + categoryKey);??????????????? continue;??????????? }??????????? categoryMap.put(categoryKey, category);??????? }??????? category.addTile(tile);??? }??? final ArrayList<DashboardCategory> categories = new
ArrayList<>(categoryMap.values());??? int categorySize = categories.size();??? for (int i = 0; i < categorySize; i++) {??????? DashboardCategory category = categories.get(i);??????? category.sortTiles();??? }??? if (DEBUG_TIMING) {??????? Log.d(LOG_TAG, "getCategories took "??????????????? + (System.currentTimeMillis() - startTime) + " ms");??? }??? return categories;}
可以看见 1、 boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;此是判断是否完成开机向导设置,setup为true时表明已完成。 2、ArrayList tiles = new ArrayList<>();新建tiles集合。 3、
for (UserHandle user : userManager.getUserProfiles()) {???
// TODO: Needs much optimization, too many PM queries going on here.???
if (user.getIdentifier() == ActivityManager.getCurrentUser()) {????
// Only add Settings for this user.仅为该用户添加设置。??????? loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles,
true);???????
loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,??????????????? OPERATOR_DEFAULT_CATEGORY, tiles, false);???????
loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,??????????????? MANUFACTURER_DEFAULT_CATEGORY, tiles, false);???
}??
if (setup) {??????? loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null,
tiles, false);??????
loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles,
false);??? }}
遍历设备中的所有用户,调用getTilesForAction方法根据相关action获取相关tiles,填充tiles集合;设置中主要通过此action去搜索系统中符合的活动去作为主页面TopLevelSettings的tile,相关的action定义如下:
/**
* Settings will search for system activities of this action and add them as a top level
* settings tile using the following parameters.
*
* <p>A category must be specified in the meta-data for the activity named
* {@link #EXTRA_CATEGORY_KEY}
*
* <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
* otherwise the label for the activity will be used.
*
* <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
* otherwise the icon for the activity will be used.
*
* <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
*/
public static final String EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS";
/**
* @See {@link #EXTRA_SETTINGS_ACTION}.
*/
public static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS";
/**
* Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
*/
private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";
private static final String OPERATOR_SETTINGS =
"com.android.settings.OPERATOR_APPLICATION_SETTING";
private static final String OPERATOR_DEFAULT_CATEGORY =
"com.android.settings.category.wireless";
private static final String MANUFACTURER_SETTINGS =
"com.android.settings.MANUFACTURER_APPLICATION_SETTING";
private static final String MANUFACTURER_DEFAULT_CATEGORY =
"com.android.settings.category.device";
4、新建categoryMap集合, HashMap<String, DashboardCategory> categoryMap,其中map的key为categoryKey; 5、遍历tiles集合,以每个tile的tile.getCategory()的值为构造参数,创建DashboardCategory对象,并将tile添加到此对象中,将此填充到map集合中; 6、将categoryMap的值赋值给ArrayList cagtories以便执行排序算法,遍历新集合利用Collections函数和比较器TILE_COMPARATOR将category.tiles按照priority从大到小排序。 可以看到主要调用loadTilesForAction来获取数据源。
@VisibleForTestingstatic void loadTilesForAction(Context context,???????
UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,??????? String defaultCategory, List<Tile> outTiles, boolean requireSettings) {??
final Intent intent = new Intent(action);???
if (requireSettings) {???????
intent.setPackage(SETTING_PKG);???
}???
loadActivityTiles(context, user, addedCache, defaultCategory, outTiles,
intent);???
loadProviderTiles(context, user, addedCache, defaultCategory, outTiles,
intent);}
private static void loadActivityTiles(Context context,??????
UserHandle user, Map<Pair<String, String>, Tile> addedCache,???????
String defaultCategory, List<Tile> outTiles, Intent intent) {???
final PackageManager pm = context.getPackageManager();???
final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,??????????? PackageManager.GET_META_DATA, user.getIdentifier());???
for (ResolveInfo resolved : results) {???????
if (!resolved.system) {???????????
// Do not allow any app to add to settings, only system ones.???????????
continue;??????? }???????
final ActivityInfo activityInfo = resolved.activityInfo;???????
final Bundle metaData = activityInfo.metaData;??????
loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
activityInfo);??? }}
private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile>
addedCache,???????
String defaultCategory, List<Tile> outTiles, Intent intent, Bundle
metaData,???????
ComponentInfo componentInfo) {???
String categoryKey = defaultCategory;???
// Load category???
if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))???????????
&& categoryKey == null) {???????
Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "???????????????
+ intent + " missing metadata "???????????????
+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));???????
return;??? } else {???????
categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);???
}???
final boolean isProvider = componentInfo instanceof ProviderInfo;??? final Pair<String, String> key = isProvider???????????
? new Pair<>(((ProviderInfo) componentInfo).authority,??????????????????? metaData.getString(META_DATA_PREFERENCE_KEYHINT))???????????
: new Pair<>(componentInfo.packageName, componentInfo.name);???
Tile tile = addedCache.get(key);???
if (tile == null) {???????
tile = isProvider???????????????
? new ProviderTile((ProviderInfo) componentInfo, categoryKey,
metaData)???????????????
: new ActivityTile((ActivityInfo) componentInfo, categoryKey);??????? addedCache.put(key, tile);???
} else {???????
tile.setMetaData(metaData);???
}???
if (!tile.userHandle.contains(user)) {???????
tile.userHandle.add(user);???
}???
if (!outTiles.contains(tile)) {???????
outTiles.add(tile);??? }}
1、通过action来构建intent,根据requireSetting来决定是否指定Settings进程包名:
final Intent intent = new Intent(action);
if (requireSettings) {???
intent.setPackage(SETTING_PKG);}
2、使用PM查询符合相关intent action支持的ResolveInfo集合,每个ResolveInfo对象主要是从AndroidManifest.xml中解析出的
final PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
3、遍历ResolveInfo集合,获取集合中每一个ResolveInfo对象,判断是否是系统进程,是否AndroidManifest.xml配置的meta标签name包含com.android.settings.category并解析其value值,构建tile对象,并将此添加到tiles集合内输出。
可以看到每个Tile对象,都是包含有从AndroidManifest.xml解析出的Resolveinfo对象和解析meta标签name包含com.android.settings.category value的值。
总结: 1、遍历设备所有用户,调getTilesForAction()方法利用PM去检索设备中所有符合传入action的activity ResolveInfo; 2、判断每一个ResolveInfo是否是系统进程,是否AndroidManifest.xml中配置的meta标签name包含"com.android.settings.category"的 value,将符合条件的以此value的值和ResolveInfo对象构建tile对象;并以此构建填充tiles集合。 3、构建HashMap<String, DashboardCategory> categoryMap集合,以AndroidManifest.xml中配置的meta标签name包含"com.android.settings.category"的 value值为参数来构建DashboardCategory对象,遍历tiles集合将符合条件的tile填充DashboardCategory对象(DashboardCategory对象即包含可以显示在界面上的设置项),并以value为key,DashboardCategory对象为value填充categoryMap集合; 4、将categoryMap的值赋值给ArrayList cagtories以便执行排序算法,遍历新集合利用Collections函数和比较器TILE_COMPARATOR将category.tiles按照priority从大到小排序。 5、经过对Categories集合的更新、排序、去重等操作,得到最终所需的mCategoryByKeyMap集合; 6、再根据所传入的TAG(TopLevelSettings),去mCategoryByKeyMap集合检索,最终得出适合在Settings主界面TopLevelSettings中可以显示的DashboardCategory对象。
这整个流程就分析完毕了,重新结合来看:DashboardFragment.refreshDashboardTiles方法
DashboardFragment.refreshDashboardTiles
此方法是刷新由DashboardCategory支持的首选项
private void refreshDashboardTiles(final String tag) {???
Log.d("wuzhangxiao", "data? : refreshDashboardTiles"+tag);???
final PreferenceScreen screen = getPreferenceScreen();???
final DashboardCategory category =??????????? mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());???
if (category == null) {???????
Log.d(tag, "NO dashboard tiles for " + tag);???????
return;??? }
通过getTilesForCategory()方法得到适合在Settings主界面TopLevelSettings中可以显示的DashboardCategory对象。判断对象是否为空,对象内是否包含tiles集合;
// Create a list to track which tiles are to be removed.创建一个列表以跟踪要删除的图块final Map<String, List<DynamicDataObserver>> remove = new
ArrayMap(mDashboardTilePrefKeys);
// Install dashboard tiles.安装tiles。
final boolean forceRoundedIcons = shouldForceRoundedIcon();
1、新建remove集合,跟踪那些tile是需要被转移的
开始遍历适合在Settings主界面TopLevelSettings中可以显示的DashboardCategory对象内的tile集合,每个tile包含从AndroidManifest.xml解析出的resolveinfo对象,此即为初步符合条件可以显示在主界面的动态设置项:
for (int i = 0; i < size; i++) {???
Tile tile = tiles.get(i);???
final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);???
if (TextUtils.isEmpty(key)) {???????
Log.d(tag, "tile does not contain a key, skipping " + tile);???????
continue;???
}???
if (gmsversion &&
key.endsWith(UserBackupSettingsActivity.class.getSimpleName())) {???????
continue;???
}???
f (!displayTile(tile)) {???????
continue;??
}???
if (mDashboardTilePrefKeys.containsKey(key)) {??????
// Have the key already, will rebind.???????
final Preference preference = screen.findPreference(key);???????
mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),??????????????? forceRoundedIcons, getMetricsCategory(), preference, tile, key,??????????????? mPlaceholderPreferenceController.getOrder());???
} else {???????
// Don't have this key, add it.???????
final Preference pref = createPreference(tile);???????
final List<DynamicDataObserver> observers =???????????????
mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),??????????????????????? forceRoundedIcons, getMetricsCategory(), pref, tile, key,??????????????????????? mPlaceholderPreferenceController.getOrder());???????
screen.addPreference(pref);???????
registerDynamicDataObservers(observers);???????
mDashboardTilePrefKeys.put(key, observers);???
}???
remove.remove(key);}
看final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile); 中的getDashboardKeyForTile方法
DashboardFeatureProviderImpl.getDashboardKeyForTile方法
@Override
public String getDashboardKeyForTile(Tile tile) {
if (tile == null) {
return null;
}
if (tile.hasKey()) {
return tile.getKey(mContext);
}
final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
final ComponentName component = tile.getIntent().getComponent();
sb.append(component.getClassName());
return sb.toString();
}
判断AndroidManifest.xml中是否配置了meta标签name为"com.android.settings.keyhint"的属性;如果配置,则获取其value值作为后续显示在界面的preference的key值:
/**
* Optional key to use for this tile.
*/
public String getKey(Context context) {
if (!hasKey()) {
return null;
}
ensureMetadataNotStale(context);
if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
} else {
return mMetaData.getString(META_DATA_PREFERENCE_KEYHINT);
}
}
其中的ensureMetadataNotStale()方法主要是能确保获取最新的mMetaData
/** * Ensures metadata is not stale for this tile. */
private void ensureMetadataNotStale(Context context) {???
final PackageManager pm = context.getApplicationContext().getPackageManager();??? try {???????
final long lastUpdateTime = pm.getPackageInfo(mComponentPackage,??????????????? PackageManager.GET_META_DATA).lastUpdateTime;???????
if (lastUpdateTime == mLastUpdateTime) {???????????
// All good. Do nothing???????????
return;???????
}???????
// App has been updated since we load metadata last time. Reload metadata.??????? mComponentInfo = null;???????
getComponentInfo(context);???????
mLastUpdateTime = lastUpdateTime;???
} catch (PackageManager.NameNotFoundException e) {???????
Log.d(TAG, "Can't find package, probably uninstalled.");???
}}
查询此App最后一次修改的时间与上一次修改时间是否一致,如果不是则重新通过PM查询更新mMetaData属性,确保mMetaData属性是从App内获取到的最新的。
如果未配置meta标签name为"com.android.settings.keyhint"的属性,则通过将activity name拼接处理
DashboardFeatureProviderImpl.getDashboardKeyForTile
final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
final ComponentName component = tile.getIntent().getComponent();
sb.append(component.getClassName());
return sb.toString();
我来以主设置界面的Google设置项为例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVnrOOXO-1658131332512)(en-resource://database/1458:0)]
其拼接处理后即为:“dashboard_tile_pref_com.google.android.gms.app.settings.GoogleSettingsIALink”
1、判断获取到的key是否为空,判断此设置项是否需要被显示; 2、调用bindPreferenceToTile()方法,对preference进行数据绑定; 3、调用setListening()方法,设置监听,以便于后续各个preference后续可以自行根据需要更新summary。
bindPreferenceToTileAndGetObservers()
packages/apps/Settings/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java
@Override
public List<DynamicDataObserver>
bindPreferenceToTileAndGetObservers(FragmentActivity activity,??????
boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile
tile,???????
String key, int baseOrder) {???
if (pref == null) {???????
return null;??? }???
if (!TextUtils.isEmpty(key)) {???????
pref.setKey(key);??? } else {???????
pref.setKey(getDashboardKeyForTile(tile));??? }???
final List<DynamicDataObserver> outObservers = new ArrayList<>();???
DynamicDataObserver observer = bindTitleAndGetObserver(pref, tile);???
if (observer != null) {???????
outObservers.add(observer);??? }???
observer = bindSummaryAndGetObserver(pref, tile);???
if (observer != null) {???????
outObservers.add(observer);??? }???
observer = bindSwitchAndGetObserver(pref, tile);???
if (observer != null) {???????
outObservers.add(observer);??? }???
bindIcon(pref, tile, forceRoundedIcon);???
if (tile instanceof ActivityTile) {???????
final Bundle metadata = tile.getMetaData();???????
String clsName = null;???????
String action = null;???????
if (metadata != null) {???????????
clsName =
metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);???????????
action = metadata.getString(META_DATA_KEY_INTENT_ACTION);??????? }???????
if (!TextUtils.isEmpty(clsName)) {???????????
pref.setFragment(clsName);???????
} else {???????????
final Intent intent = new Intent(tile.getIntent());??????????? intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,??????????????????? sourceMetricsCategory);???????????
if (action != null) {???????????????
intent.setAction(action);???????????
}???????????
pref.setOnPreferenceClickListener(preference -> {???????????????
launchIntentOrSelectProfile(activity, tile, intent,
sourceMetricsCategory);???????????????
return true;???????????
});???????
}???
}???
if (tile.hasOrder()) {???????
final String skipOffsetPackageName = activity.getPackageName();???????
final int order = tile.getOrder();???????
boolean shouldSkipBaseOrderOffset = TextUtils.equals(???????????????
skipOffsetPackageName,
tile.getIntent().getComponent().getPackageName());???????
if (shouldSkipBaseOrderOffset || baseOrder == Preference.DEFAULT_ORDER) {??????????? pref.setOrder(order);???????
} else {???????????
pref.setOrder(order + baseOrder);???????
}??? }???
return outObservers.isEmpty() ? null : outObservers;}
1、设置preference的key,主要还是通过调用getDashboardKeyForTile()方法去获取
if (!TextUtils.isEmpty(key)) {
pref.setKey(key);
} else {
pref.setKey(getDashboardKeyForTile(tile));
}
2、设置preference的summary: observer = bindSummaryAndGetObserver(pref, tile);
private DynamicDataObserver bindSummaryAndGetObserver(Preference preference, Tile
tile) {???
final CharSequence summary = tile.getSummary(mContext);???
if (summary != null) {???????
preference.setSummary(summary);???
} else if (tile.getMetaData() != null&& tile.getMetaData().containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {???????
// Set a placeholder summary before starting to fetch real summary, this is
necessary???????
// to avoid preference height change.???????
// preference.setSummary(R.string.summary_placeholder);???????
/**????????
* 在开始获取真实摘要之前设置占位符摘要,这是避免首选项高度更改所必需的。偏爱setSummary???????? */???????
final Uri uri = TileUtils.getCompleteUri(tile,
META_DATA_PREFERENCE_SUMMARY_URI,???????????????
METHOD_GET_DYNAMIC_SUMMARY);???????
refreshSummary(uri, preference);???????
return createDynamicDataObserver(METHOD_GET_DYNAMIC_SUMMARY, uri,
preference);???
} else {???????
preference.setSummary(R.string.summary_placeholder);???
}???
return null;}
首先判断tile对象是否设置了mSummaryOverride,是,则以此作为preference的summary; 其次再此确保此时tile保存的meta属性是最新的,通过读取"com.android.settings.summary_uri"、"com.android.settings.summary"属性,根据需要取其value作为preference的summary。
3、设置preference的icon,通过读取meta的属性"com.android.settings.icon_uri"、"com.android.settings.icon"的value的值; 4、设置preference的点击跳转界面:
if (tile instanceof ActivityTile) {???
final Bundle metadata = tile.getMetaData();???
String clsName = null;???
String action = null;???
if (metadata != null) {???????
clsName =
metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);???????
action = metadata.getString(META_DATA_KEY_INTENT_ACTION);??? }???
if (!TextUtils.isEmpty(clsName)) {???????
pref.setFragment(clsName);???
} else {???????
final Intent intent = new Intent(tile.getIntent());??????? intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,??????????????? sourceMetricsCategory);???????
if (action != null) {???????????
intent.setAction(action);??????? }???????
pref.setOnPreferenceClickListener(preference -> {???????????
launchIntentOrSelectProfile(activity, tile, intent,
sourceMetricsCategory);???????????
return true;??????? });??? }}
如果设置了"com.android.settings.FRAGMENT_CLASS"属性,则直接设置此value为跳转的fragment;反之,则构建intent,设置点击监听,跳转activity;
5、如果设置了"com.android.settings.order"属性,则根据其value值来设置preference显示前后。order为负时,绝对值越高,界面显示越靠前;order为正时,值越高,显示越靠后。
总结:
1、主要是通过解析tile对象内保存的meta属性去设置preference的title、key、summary、icon、跳转界面、order显示优先级; 2、Android 11.0中设置主界面的设置项除了加载三方应用的,其余设置基本都是top_level_settings.xml定义的。故对于动态AndroidManifest中配置加载,以其它界面的设置配置项为例,示例如下:
<activity
android:name="Settings$DevelopmentSettingsDashboardActivity"
android:label="@string/development_settings_title" <!-- preference 标题 -->
android:icon="@drawable/ic_settings_development" <!-- preference 图标 -->
android:parentActivityName="Settings"
android:enabled="false">
<intent-filter android:priority="1">
<action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
<action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<!-- 可以被Settings搜索到的action,主要逻辑在TileUtils#getTilesForAction()方法 -->
<action android:name="com.android.settings.action.SETTINGS" />
</intent-filter>
<!-- preference order值,列表显示的优先级 -->
<meta-data android:name="com.android.settings.order" android:value="-40"/>
<!-- 类别,定义显示在哪个fragment,这里定义的值代表显示在SystemDashboardFragment中,具体看DashboardFragmentRegistry.java中map集合定义 -->
<meta-data android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.system" />
<!-- preference summary -->
<meta-data android:name="com.android.settings.summary"
android:resource="@string/summary_empty"/>
<!-- preference 点击跳转fragment界面 -->
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
3、refreshDashboardTiles()主要是动态的通过PM从AndroidManifest.xml中读取相关配置来加载可以显示的设置item; 4、getTilesForCategory();通过PM去检索AndroidManifest.xml中符合相关action的可以显示在当前fragment上的设置项; 5、bindPreferenceToTile();解析AndroidManifest.xml中配置的meta属性来对设置项preference进行数据绑定;
|