Settings中主界面加载流程(一级菜单 动态加载)

2023-05-16

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进行数据绑定;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Settings中主界面加载流程(一级菜单 动态加载) 的相关文章

  • UTM虚拟机-首款iOS虚拟机

    utm虚拟机 xff1b 非越狱安装方法 utm虚拟机是一款ipa为后缀的文件 xff0c 需要爱思助手安装 越狱安装方法 使用uncover越狱后在安装ipa文件 utm介绍 他跟bochs limbo qemo apq等app一样 xf
  • 所有小米机型 解BT+刷Magisk并ROOT+躲避应用ROOT环境检查教程

    废话章节 xff0c 可以不看 时隔一年又回来了 上一篇文章还是在2021年更新的 xff0c 因为学业问题我这是1年1更显然不行 xff0c 那我这次为啥不更新iApp了 xff1f 因为忘得差不多了 我也没想到我有一天回过头来看自己的文
  • 【Minecraft】【ModPC】【我的世界】 我的世界电脑版如何进入网络游戏?

    我的世界电脑版如何进入网络游戏 xff1f 须知 看看就好 xff0c 不要频繁使用modpc xff0c 破坏游戏玩家体验 xff01 不知道为什么Win11会用着用着就会闪退 降级到Win10就什么事也没有 下载 ModPC下载 包含普
  • WindwosServer系统一些设置【网卡驱动修复】【安装UWP应用】【服务器管理取消开机自启动】

    WindwosServer系统一些设置 这里以2022为例 xff1a 第一 网卡驱动丢失修复 此教程只针对I219 V LM网卡 xff01 小知识 xff1a 当电脑没网时 xff0c 将手机和电脑用USB数据线连接 打开设置 xff1
  • dp最长不上升子序列 二分upper lower+贪心

    题意 找出最长不上升子序列长度 再找出最长不下降子序列最大长度 写法运用了指针 减少了代码量 include lt iostream gt include lt algorithm gt using namespace std const
  • 小米平板5ProWIFI(elish)刷ArrowOS

    文章目录 警告下载奇兔刷机系统本体及Recovery 清除数据刷入AospRec开始刷入警告 完成设置输入法 变砖头了qwq又是警告 芝士截图Root方法结尾 警告 此文章只针对 小米平板5Pro Wifi版本 xff08 elish xf
  • 【宝塔】【Windows】【Blessing-Skin】【我的世界】用宝塔Windows搭建皮肤站

    文章目录 前言所需环境相关链接安装宝塔安装步骤访问宝塔同意协议 安装环境安装WNMP添加站点 开始安装皮肤站配置网站配置Nginx URL重写规则 xff08 即 伪静态 xff09 配置PHP 安装皮肤站 一些小调整安装插件常见问题 插件
  • ping的详细过程学习笔记

    pc1 ping pc2 也就是pc1 xff1a 192 168 1 1 ping pc2 xff1a 192 168 1 2 属于同一网段的ping过程 步骤1 ping开始 即后台运行192 168 1 1 ping 192 168
  • FTPClient上传文件内容为空/损坏/缺失

    项目场景 xff1a 项目场景 xff1a 本地项目联调OA系统的时候 xff0c 在发送审批时会传送相关附件 xff0c 该附件由本地项目上传至FTP xff0c OA系统会根据我们提供的路径和文件名去FTP中找到该文件 问题描述 xff
  • Debian9桌面设置

    本文由荒原之梦原创 xff0c 原文链接 xff1a http zhaokaifeng com p 61 665 新安装的Debian9桌面上啥都没有 xff0c 就像这样 xff1a 图 1 虽然很简洁 xff0c 但是用着不是很方便 x
  • 爬虫遇到Cloudflare问题

    网址 xff1a https opensea io rankings sortBy 61 seven day volume 返回代码 xff1a 403 遇到的问题 xff1a Access denied api opensea io us
  • java servlet写的网页猜数小游戏

    几年前 xff0c 用java servlet 写了个猜数的网页小游戏 xff1b 今天看了觉得有点意思 xff0c 贴出来怀旧一下 xff1a 1 代码如下 xff1a package cn wzb import java io impo
  • 安卓-system.img镜像文件过大问题

    3126 5 1SDK预置过多apk时导致编译otapackage时报错处理 xff1a 1 修改prebuilts python linux x86 2 7 5 lib python2 7 zipfile py文件中为ZIP64 LIMI
  • 使用Tesseract-OCR识别图片中的文字并生成双层PDF

    识别图片中的文字并不是很困难 如果自己训练一个文字识别的深度学习程序去识别也是可以 xff0c 但是太费劲 Tesseract OCR是一个开源的文字识别引擎 xff0c 并且支持包括中文在内的多国语言 只要将语言配置上去 xff0c 就可
  • iptables(三)iptables命令详解

    一 语法规则 iptables t table COMMAND chain CONDITION j ACTION t table 是指 39 操作的表 39 filter nat mangle或raw 39 默认使用filter 39 CO
  • 单调栈lllll

    单调栈 xff0c 就是一个栈 xff0c 不过栈内元素保证单调性 即 xff0c 栈内元素要么从小到大 xff0c 要么从大到小 而单调栈维护的就是一个数前 后第一个大于 小于他的数 例题 xff1a P5788 模板 单调栈 例题就是一
  • cmake(六)Cmake添加工程子目录

    重点 xff1a 39 cmake3 39 和 39 make 39 命令 39 输出 39 的 39 深刻解读 39 备注 xff1a 当前阶段暂时不使用 39 IDE 39 工具 先 39 熟悉各指令 39 一 ADD SUBDIREC
  • nginx(二十七)长连接和短连接

    一 长连接和短连接 概念 1 39 HTTP 39 的长连接和短连接 39 本质 39 上是 39 TCP 39 长连接和短连接 2 在 39 HTTP 1 0 39 中默认使用 39 短 39 连接 解读 xff1a 客户端和服务器 39
  • nginx(七十四)nginx与跨域细节探究

    一 nginx配置跨域 知识铺垫 强调 xff1a 跨域是 39 浏览器 39 行为 39 不是 39 服务器行为 43 43 43 43 43 43 43 43 43 43 43 43 43 43 34 跨域的两种解决手段 34 43 4
  • HTTP1.1(一)HTTP协议

    一 HTTP协议定义 RFC7230定义 说明 xff1a 关注 39 红色关键字 39 无状态 解读 xff1a 连续的 39 两个 39 请求 后续的请求 39 不能依赖 39 前一个请求 各个请求是 39 相互独立 39 基于请求 相

随机推荐

  • nginx(七十五)nginx与Vary响应头细节探讨

    一 Vary nginx与Vary有关联的地方 nginx源码分析处理Vary响应头的逻辑 CORS和缓存 gzip vary 1 gzip vary on 如果设置为 39 开启 39 2 服务器 39 返回数据 39 时会在头部带上 3
  • JDK1.8之Lambada表达式一

    一 lambada表达式简介 我们知道对于Java变量可以赋给其一个值 而如果想将 34 一块代码 一个完整的方法 34 赋给一个Java变量 如下所示 xff0c 怎么做呢 xff1f 你可能认为 就是下面的方式来实现 很显然 xff0c
  • Oracle(三)

    一 概述 1 DML xff08 data manipulation language 数据操作语言 xff09 insert update delete 2 DDL data definition language 数据定义语言 crea
  • 项目中权限控制系统的设计

    RBAC 权限 xff1a 权利 能做的 和限制 不能做的 xff0c 在权限范围内做好自己的事情 xff0c 不该看的不看 机密 xff0c 不该做的不做 xff01 最开始真正有权限的概念是在Linux上关于文件和目录的权限 xff0c
  • 每天一个Linux命令之(read)

    一 概述 read命令特点 xff1a 1 接收 39 标准输入 键盘 39 的输入 或其它 39 文件 描述符 39 的输入 2 得到输入后 然后将数据 39 保存 39 一个 39 变量 39 中 核心点 xff1a 39 数据源 39
  • LInux shell之(for in 用法总结)

    一 语法 for 变量名 in 列表 do 程序段 command done 注意1 xff1a 是变量名 而不是 变量 xff01 注意2 xff1a 列表 可以做文章 xff01 二 应用 第一类 xff1a 数字性循环 gt seq
  • 一次性将所有变成 long long

    include lt bits stdc 43 43 h gt using namespace std const int N 61 100000 43 100 define int long long define fir i a b f
  • Linux基础命令(二十一)Linux中的磁盘管理(终)

    一 逻辑卷管理器 Logical Volume Manager 需求引入 xff1a 最初规划主机的时候 xff0c 只给了 home 100G的 xff0c 但是随着业务量的增大 xff0c 导致用户的增多 xff0c 这个文件系统不够
  • 【机器学习】DBSCAN聚类算法(含Python实现)

    文章目录 一 算法介绍二 例子三 Python实现3 1 例13 2 算法参数详解3 3 鸢尾花数据集 一 算法介绍 DBSCAN xff08 Density Based Spatial Clustering of Applications
  • Zookeeper深度解析(概念、原理机制、应用场景)

    1 Zookeeper是什么 xff1f 分布 开源的应用程序协调服务 它是集群的管理者 监视着集群中各个节点的状态 xff0c 根据节点的反馈进行下一步合理操作 主要解决分布式应用经常遇到的数据管理问题 如 xff1a 统一命名服务 状态
  • 计算机操作系统知识梳理

    1 进程和线程以及它们的区别 xff08 1 xff09 进程是对 运行时程序的封装 是系统进行资源 调度和分配的 基本单位 实现操作系统 的 并发 xff08 2 xff09 线程是进程的 子任务 是CPU调度和分派的基本单位 用于保证程
  • 数据库知识梳理

    概述 xff1a 对数据库索引 数据库锁 数据库事务 MySql优化等基础知识梳理 1 数据库范式 xff08 1 xff09 第一范式 xff1a 列不可分 eg 联系人 xff08 姓名 xff0c 性别 xff0c 电话 xff09
  • JAVA重要知识点梳理(一)

    1 Struts2和SpringMVC的区别 xff08 1 xff09 设计理念 xff1a 前者为 有状态的 Action 均为多例 xff0c Action对象属性字段 承载请求 响应 xff0c 后者一般为无状态的 Controll
  • Shell脚本中:#!/bin/bash和#!/bin/sh是什么意思以及区别?

    意思 xff1a bin sh是指此脚本使用 bin sh来解释执行 xff0c 是特殊的表示符 xff0c 其后面跟的是解释此脚本的shell的路径 其实第一句的 是对脚本的解释器程序路径 xff0c 脚本的内容是由解释器解释的 xff0
  • Jar包与war包文件区别?

    Jar文件 xff1a xff08 Java Archive xff0c Java 归档文件 xff09 JAR 文件格式以流行的 ZIP 文件格式为基础 与 ZIP 文件不同的是 xff0c JAR 文件不仅用于压缩和发布 xff0c 而
  • SpringCloud开发框架入门知识

    1 分布式开发简介 分布式开发的思考点 xff1a 如何可以让代码更安全 xff1b 如何有效的通讯 xff1b 在进行分布式处理的时候如何进行程序功能划分 xff1b web集群 xff1a 考虑多用户并发访问的处理速度 业务中心 xff
  • slf4j Failed to load class “org.slf4j.impl.StaticLoggerBinder“ 错误 源码解析

    基本描述 slf4j 是日志的api 门面模式 xff0c 引入slf4j api就行 xff0c 但是打印不出日志的 xff0c 因为没有具体的实现类 logback实现包 span class token tag span class
  • 考试 2022 5 20 代码

    倒数第一 include lt bits stdc 43 43 h gt using namespace std define INF 0x3f3f3f3f const int N 61 1e6 43 10 define ios ios s
  • shiro+cas认证登录,出现Authentication failed for token submission(认证失败错误)

    项目场景 xff1a 项目采用spring boot 43 shiro 43 cas配置 xff0c 由于数据库原因 xff0c 登录时认证失败 xff0c 记录一下解决过程 问题描述 xff1a cas验证用户成功后 xff0c 到shi
  • Settings中主界面加载流程(一级菜单 动态加载)

    Settings中主界面加载流程 xff08 一级菜单 动态加载 xff09 DashboardFragment中的refreshAllPreferences 这个方法中加载了refreshDashboardTiles tag 方法 xff