Android 11 Settings動(dòng)態(tài)加載之快霸是如何被加載的

設(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.homepagemeta-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);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市俐载,隨后出現(xiàn)的幾起案子蟹略,更是在濱河造成了極大的恐慌,老刑警劉巖遏佣,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挖炬,死亡現(xiàn)場離奇詭異,居然都是意外死亡状婶,警方通過查閱死者的電腦和手機(jī)意敛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膛虫,“玉大人草姻,你說我怎么就攤上這事∩缘叮” “怎么了撩独?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長账月。 經(jīng)常有香客問我综膀,道長,這世上最難降的妖魔是什么局齿? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任剧劝,我火速辦了婚禮,結(jié)果婚禮上抓歼,老公的妹妹穿的比我還像新娘讥此。我一直安慰自己示绊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布暂论。 她就那樣靜靜地躺著面褐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪取胎。 梳的紋絲不亂的頭發(fā)上展哭,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音闻蛀,去河邊找鬼匪傍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛觉痛,可吹牛的內(nèi)容都是我干的役衡。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼薪棒,長吁一口氣:“原來是場噩夢啊……” “哼手蝎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起俐芯,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤棵介,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吧史,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邮辽,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年贸营,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吨述。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钞脂,死狀恐怖揣云,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芳肌,我是刑警寧澤灵再,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站亿笤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏栋猖。R本人自食惡果不足惜净薛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒲拉。 院中可真熱鬧肃拜,春花似錦痴腌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至猛蔽,卻和暖如春剥悟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曼库。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國打工区岗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毁枯。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓慈缔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親种玛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子藐鹤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容