設(shè)置項(xiàng)列表的內(nèi)容通過靜態(tài)配置+動(dòng)態(tài)添加的方式獲取嚼松。本文以MTK平臺(tái)的快霸為例探討如何進(jìn)行動(dòng)態(tài)加載嫡良。
一,解析動(dòng)態(tài)加載項(xiàng)
每個(gè)頁面都有自己特定的動(dòng)態(tài)加載項(xiàng)献酗,那么每個(gè)頁面如果獲取動(dòng)態(tài)加載項(xiàng)呢寝受?
以TopLevelSettings
為例繼承自DashboardFragment
public abstract class DashboardFragment extends SettingsPreferenceFragment
implements SettingsBaseActivity.CategoryListener, Indexable,
PreferenceGroup.OnExpandButtonClickListener,
BasePreferenceController.UiBlockListener {
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
...
// 1,刷新所有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.
// 展示靜態(tài)配置的Preference項(xiàng)
displayResourceTiles();
// 2罕偎,展示動(dòng)態(tài)加載項(xiàng)
refreshDashboardTiles(tag);
...
}
/**
* Refresh preference items backed by DashboardCategory.
*/
private void refreshDashboardTiles(final String tag) {
final PreferenceScreen screen = getPreferenceScreen();
// 3很澄,獲取動(dòng)態(tài)加載項(xiàng)
final DashboardCategory category =
mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
...
}
}
動(dòng)態(tài)加載項(xiàng)通過mDashboardFeatureProvider.getTilesForCategory()
來獲取,需要傳入一個(gè)key值作為參數(shù)锨亏。
1.1 key的來歷
首先看一下這個(gè)key是怎么來的呢痴怨?通過調(diào)用getCategoryKey()
獲得
public String getCategoryKey() {
return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
}
DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP
是一個(gè)靜態(tài)變量忙干,在類的靜態(tài)代碼塊中添加了數(shù)據(jù):
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);
...
}
再來看看CategoryKey.CATEGORY_HOMEPAGE
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";
...
}
因此TopLevelSettings
類查找的是com.android.settings.category.ia.homepage
器予。
在來看看快霸中的manifest:
<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>
其中配置了key為com.android.settings.category
,value為com.android.settings.category.ia.homepage
的meta-data
捐迫。因此在首頁動(dòng)態(tài)加載時(shí)會(huì)加載快霸乾翔。
1.2 根據(jù)key值動(dòng)態(tài)加載
DashboardFeatureProvider
的實(shí)現(xiàn)類是DashboardFeatureProviderImpl
, 它實(shí)現(xiàn)了getTilesForCategory()
, 最終還是通過CategoryManager來獲取Category。
public class DashboardFeatureProviderImpl implements DashboardFeatureProvider {
...
private final CategoryManager mCategoryManager;
...
public DashboardFeatureProviderImpl(Context context) {
...
// CategoryManager是一個(gè)單例
mCategoryManager = CategoryManager.get(context);
...
}
@Override
public DashboardCategory getTilesForCategory(String key) {
// 查找動(dòng)態(tài)項(xiàng)
return mCategoryManager.getTilesByCategory(mContext, key);
}
在CategoryManager中首先進(jìn)行加載施戴,然后保存在mCategoryByKeyMap
中反浓,最后通過key值獲取對(duì)應(yīng)的加載項(xiàng)
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
// 1,加載
tryInitCategories(context);
// 2赞哗,讀取
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) {
// 根據(jù)不同的key保存在map中
mCategoryByKeyMap.put(category.key, category);
}
backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
// 排序
sortCategories(context, mCategoryByKeyMap);
// 過濾重復(fù)
filterDuplicateTiles(mCategoryByKeyMap);
}
}
其關(guān)鍵代碼為TileUtils.getCategories(context, mTileByComponentCache)
雷则。
1.2.1
其中TileUtils
位于SettingsLib中,包名為:com.android.settingslib.drawer.TileUtils
肪笋,代碼如下:
/**
* 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;
// 保存加載到的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 for this user.
// 各種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<>();
// 遍歷加載到的tile
for (Tile 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) {
// 排序
category.sortTiles();
}
if (DEBUG_TIMING) {
Log.d(LOG_TAG, "getCategories took "
+ (System.currentTimeMillis() - startTime) + " ms");
}
return categories;
}
注意上面定義的final ArrayList<Tile> tiles = new ArrayList<>();
這個(gè)List被傳到后面的幾個(gè)方法中月劈,最終將加載到的內(nèi)容放入其中度迂。
1.2.2
根據(jù)不同action進(jìn)行加載
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);
}
1.2.3
根據(jù)PackageManager 獲取應(yīng)用的配置文件信息。
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;
// 加載tile
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) {
// 加載tile
loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
providerInfo);
}
}
}
1.2.4
加載到的結(jié)果放入outTiles中猜揪,也就是前面定義的List中惭墓。
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);
}
}
到此動(dòng)態(tài)項(xiàng)查找及獲取已經(jīng)完成,那么查找到后怎么顯示出來的呢而姐?
此處只是DashboardFragment中進(jìn)行動(dòng)態(tài)加載的邏輯腊凶,在繼承自SettingsActivity的Activity中也會(huì)進(jìn)行動(dòng)態(tài)加載,核心邏輯是相同的拴念,這里不做深入钧萍。
二,顯示動(dòng)態(tài)加載項(xiàng)
繼續(xù)看refreshDashboardTiles()
/**
* Refresh preference items backed by DashboardCategory.
*/
private void refreshDashboardTiles(final String tag) {
final PreferenceScreen screen = getPreferenceScreen();
// 加載數(shù)據(jù)
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();
// 遍歷加載項(xiàng)
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政鼠,創(chuàng)建Preference
final Preference pref = createPreference(tile);
final List<DynamicDataObserver> observers =
mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(),
forceRoundedIcons, getMetricsCategory(), pref, tile, key,
mPlaceholderPreferenceController.getOrder());
// 2划煮,添加到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());
}
}
獲取加載到的數(shù)據(jù)后進(jìn)行遍歷,判斷當(dāng)前列表中是否存在該設(shè)置項(xiàng)缔俄,不存在的話就創(chuàng)建
通過createPreference()
創(chuàng)建Preference如下弛秋。
Preference createPreference(Tile tile) {
return tile instanceof ProviderTile
? new SwitchPreference(getPrefContext())
: tile.hasSwitch()
? new MasterSwitchPreference(getPrefContext())
: new Preference(getPrefContext());
}
最終動(dòng)態(tài)添加到screen中:
screen.addPreference(pref);