The contents of the list can be obtained through static configuration and dynamic addition. This paper takes kuaiba of MTK platform as an example to discuss how to carry out dynamic loading.

First, parse the dynamic add-in

Each page has its own specific dynamic add-in, so how to get the dynamic add-in for each page?

The TopLevelSettings example is inherited from the DashboardFragment

public abstract class DashboardFragment extends SettingsPreferenceFragment implements SettingsBaseActivity.CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener, BasePreferenceController.UiBlockListener { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { ... // 1, refresh all Preference refreshAllPreferences(getLogTag()); . } /** * Refresh all preference items, including both static prefs from xml, and dynamic items from * DashboardCategory. */ private void refreshAllPreferences(final String tag) { ... // Add resource based tiles. // Display statically configured Preference displayResourceTiles(); // 2, display the dynamic add-on function refreshDashboardTiles(tag); . } /** * Refresh preference items backed by DashboardCategory. */ private void refreshDashboardTiles(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); / / 3, the final item for dynamic loading DashboardCategory category. = mDashboardFeatureProvider getTilesForCategory (getCategoryKey ()); . }}Copy the code

Dynamic loading item by mDashboardFeatureProvider. GetTilesForCategory () to obtain, need to pass in a key value as a parameter.

1.1 History of Key

So first of all, where does this key come from? Get this by calling getCategoryKey()

public String getCategoryKey() {
    return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
}
Copy the code

DashboardFragmentRegistry PARENT_TO_CATEGORY_KEY_MAP is a static variable, in the class of the static block of code to add the data:

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; . static { PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>(); PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(), CategoryKey.CATEGORY_HOMEPAGE); . }Copy the code

Take a look at categoryKey.category_homepage again

public final class CategoryKey { // Activities in this category shows up in Settings homepage. public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage"; . }Copy the code

Therefore TopLevelSettings class lookup is com. Android. Settings. The category. Ia. The homepage.

Take a look at the Manifest in Quaiba:

<activity android:name=".DuraSpeedMainActivity" android:configChanges="orientation|keyboardHidden|screenSize|mcc|mnc|navigation" android:label="@string/app_name" android:launchMode="singleTask" android:permission="com.mediatek.duraspeed.START_DURASPEED_APP"> <intent-filter> <action  android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.INFO" /> </intent-filter> <intent-filter android:priority="5"> <action android:name="com.android.settings.action.EXTRA_SETTINGS" /> </intent-filter> <meta-data android:name="com.android.settings.category" android:value="com.android.settings.category.ia.homepage" /> <meta-data android:name="com.android.settings.icon" android:resource="@drawable/ic_settings_rb2" /> </activity>Copy the code

Configuration of the key for the com. Android. Settings. The category, the value for the com. Android. Settings. The category. Ia. The homepage of the meta data. Therefore in the home page dynamic loading will load fast bully.

1.2 Dynamic loading based on key values

DashboardFeatureProvider implementation class is DashboardFeatureProviderImpl, it implements the getTilesForCategory (), eventually get the Category by CategoryManager.

public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { ... private final CategoryManager mCategoryManager; . public DashboardFeatureProviderImpl(Context context) { ... // CategoryManager is a singleton mCategoryManager = categoryManager.get (context); . } @override public DashboardCategory getTilesForCategory(String key) {// Search for dynamic entries return mCategoryManager.getTilesByCategory(mContext, key); }Copy the code

It is first loaded in CategoryManager, then saved in mCategoryByKeyMap, and finally gets the corresponding add-in by key value

Public synchronized DashboardCategory getTilesByCategory(Context Context, String categoryKey) {// 1, Loading tryInitCategories (context); // 2, read 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(); // load mCategories = TileUtils. GetCategories (context, mTileByComponentCache); For (DashboardCategory category: mCategories) {// McAtegorybykeymap. put(category.key, category); } backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap); // sortCategories(context, mCategoryByKeyMap); // Filter repeated filterDuplicateTiles(mCategoryByKeyMap); }}Copy the code

The key code is tileUtils.getCategories (Context, mTileByComponentCache).

1.2.1

Including TileUtils in SettingsLib, package called: com. Android. SettingsLib. Drawer. TileUtils, code is as follows:

/** * Build a list of DashboardCategory. */ public static List<DashboardCategory> getCategories(Context context, Map<Pair<String, String>, Tile> cache) { final long startTime = System.currentTimeMillis(); final boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) ! = 0; // Save the loaded tile 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 // Load 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<>(); // Loop over the loaded tile for (tile: tiles) {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()); For (DashboardCategory category: categories) {// Sort category.sorttiles (); } if (DEBUG_TIMING) { Log.d(LOG_TAG, "getCategories took " + (System.currentTimeMillis() - startTime) + " ms"); } return categories; }Copy the code

Note that final ArrayList

tiles = new ArrayList<>(); This List is passed to the following methods, which eventually put the loaded content into it.

1.2.2

Load depending on the action

static 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); }Copy the code
1.2.3

Obtain application configuration file information from the PackageManager.

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 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo); } } private static void loadProviderTiles(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.queryIntentContentProvidersAsUser(intent, 0 /* flags */, user.getIdentifier()); for (ResolveInfo resolved : results) { if (! resolved.system) { // Do not allow any app to add to settings, only system ones. continue; } final ProviderInfo providerInfo = resolved.providerInfo; final List<Bundle> switchData = getSwitchDataFromProvider(context, providerInfo.authority); if (switchData == null || switchData.isEmpty()) { continue; } for (Bundle metaData : SwitchData) {// loadTile loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, providerInfo); }}}Copy the code

Check in loadActivityTiles() and skip it if it’s not a system application. Otherwise, any third-party application can be added to the system Settings, which Is definitely not allowed by Google.

1.

The loaded results go into outTiles, the List defined earlier.

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); }}Copy the code

To this dynamic item search and obtain has been completed, so how to find after the display?

This is only the dynamic loading logic in DashboardFragment. It is also used in activities that inherit from SettingsActivity. The core logic is the same.

Second, display dynamic add-ons

Continue to see refreshDashboardTiles ()

/** * Refresh preference items backed by DashboardCategory. */ private void refreshDashboardTiles(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); / / load the data final DashboardCategory category. = mDashboardFeatureProvider getTilesForCategory (getCategoryKey ()); if (category == null) { Log.d(tag, "NO dashboard tiles for " + tag); return; } final List<Tile> tiles = category.getTiles(); if (tiles == null) { Log.d(tag, "tile list is empty, skipping category " + category.key); return; } // Create a list to track which tiles are to be removed. final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys); // Install dashboard tiles. final boolean forceRoundedIcons = shouldForceRoundedIcon(); / / traverse the add-in for (Tile Tile: tiles) {final String key = mDashboardFeatureProvider. GetDashboardKeyForTile (Tile); if (TextUtils.isEmpty(key)) { Log.d(tag, "tile does not contain a key, skipping " + tile); continue; } if (! 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. // 1, createPreference final Preference pref = createPreference(tile); final List<DynamicDataObserver> observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),  forceRoundedIcons, getMetricsCategory(), pref, tile, key, mPlaceholderPreferenceController.getOrder()); // 2, add to screen screen.addpreference (pref); registerDynamicDataObservers(observers); mDashboardTilePrefKeys.put(key, observers); } remove.remove(key); } // Finally remove tiles that are gone. for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) { final String key = entry.getKey(); mDashboardTilePrefKeys.remove(key); final Preference preference = screen.findPreference(key); if (preference ! = null) { screen.removePreference(preference); } unregisterDynamicDataObservers(entry.getValue()); }}Copy the code

Get the loaded data and iterate to determine whether the item exists in the current list. If not, create the item

CreatePreference with createPreference() as follows.

Preference createPreference(Tile tile) {
    return tile instanceof ProviderTile
            ? new SwitchPreference(getPrefContext())
            : tile.hasSwitch()
                    ? new MasterSwitchPreference(getPrefContext())
                    : new Preference(getPrefContext());
}
Copy the code

Finally add to Screen dynamically:

screen.addPreference(pref);
Copy the code