Android10 Settings源码解析

/packages/apps/Settings/AndroidManifest.xml

<activity android:name=".homepage.SettingsHomepageActivity"
          android:label="@string/settings_label_launcher"
          android:theme="@style/Theme.Settings.Home"
          android:launchMode="singleTask">
    <intent-filter android:priority="1">
        <action android:name="android.settings.SETTINGS" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
               android:value="true" />
</activity>

.................................................................................

<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias android:name="Settings"
        android:label="@string/settings_label_launcher"
        android:launchMode="singleTask"
        android:targetActivity=".homepage.SettingsHomepageActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity-alias>

        activity-alias是Android里为了重复使用Activity而设计的。对于activity-alias标签,它有一个属性叫android:targentActivity,这个属性就是用来为该标签设置目标Activity的,android:targetActivity 该属性指定了目标Activity,即通过activity-alias调起的Activity是哪个。

        通过目标activity我们进入SettingsHomepageActivity

/packages/apps/Settings/src/com/android/settings/homepage/SettingsHomepageActivity.java

package com.android.settings.homepage;

import android.animation.LayoutTransition;
import android.app.ActivityManager;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toolbar;

import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import com.android.settings.R;
import com.android.settings.accounts.AvatarViewMixin;
import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
import com.android.settings.overlay.FeatureFactory;

public class SettingsHomepageActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.settings_homepage_container);
        final View root = findViewById(R.id.settings_homepage_container);
        root.setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

        setHomepageContainerPaddingTop();

        final Toolbar toolbar = findViewById(R.id.search_action_bar);
        FeatureFactory.getFactory(this).getSearchFeatureProvider()
                .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);

        final ImageView avatarView = findViewById(R.id.account_avatar);
        final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(this, avatarView);
        getLifecycle().addObserver(avatarViewMixin);

        if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
            // Only allow contextual feature on high ram devices.
            showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
        }
        showFragment(new TopLevelSettings(), R.id.main_content);
        ((FrameLayout) findViewById(R.id.main_content))
                .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
    }

    private void showFragment(Fragment fragment, int id) {
        final FragmentManager fragmentManager = getSupportFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        final Fragment showFragment = fragmentManager.findFragmentById(id);

        if (showFragment == null) {
            fragmentTransaction.add(id, fragment);
        } else {
            fragmentTransaction.show(showFragment);
        }
        fragmentTransaction.commit();
    }

    @VisibleForTesting
    void setHomepageContainerPaddingTop() {
        final View view = this.findViewById(R.id.homepage_container);

        final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height);
        final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin);

        // The top padding is the height of action bar(48dp) + top/bottom margins(16dp)
        final int paddingTop = searchBarHeight + searchBarMargin * 2;
        view.setPadding(0 /* left */, paddingTop, 0 /* right */, 0 /* bottom */);
    }
}

加载布局文件settings_homepage_container,调用showFragment方法显示TopLevelSettings。

package com.android.settings.homepage;

import static com.android.settings.search.actionbar.SearchMenuController.NEED_SEARCH_ICON_IN_ACTION_BAR;
import static com.android.settingslib.search.SearchIndexable.MOBILE;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.provider.SearchIndexableResource;

import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;

import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.support.SupportPreferenceController;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.search.SearchIndexable;

import java.util.Arrays;
import java.util.List;

@SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {

    private static final String TAG = "TopLevelSettings";

    public TopLevelSettings() {
        final Bundle args = new Bundle();
        // Disable the search icon because this page uses a full search view in actionbar.
        args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false);
        setArguments(args);
    }

    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.top_level_settings;
    }

    @Override
    protected String getLogTag() {
        return TAG;
    }

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.DASHBOARD_SUMMARY;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        use(SupportPreferenceController.class).setActivity(getActivity());
    }

    @Override
    public int getHelpResource() {
        // Disable the help icon because this page uses a full search view in actionbar.
        return 0;
    }

    @Override
    public Fragment getCallbackFragment() {
        return this;
    }

    @Override
    public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
        new SubSettingLauncher(getActivity())
                .setDestination(pref.getFragment())
                .setArguments(pref.getExtras())
                .setSourceMetricsCategory(caller instanceof Instrumentable
                        ? ((Instrumentable) caller).getMetricsCategory()
                        : Instrumentable.METRICS_CATEGORY_UNKNOWN)
                .setTitleRes(-1)
                .launch();
        return true;
    }

     @Override
     protected boolean shouldForceRoundedIcon() {
         return getContext().getResources()
                 .getBoolean(R.bool.config_force_rounded_icon_TopLevelSettings);
     }
 
     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
             new BaseSearchIndexProvider() {
                 @Override
                 public List<SearchIndexableResource> getXmlResourcesToIndex(
                         Context context, boolean enabled) {
                     final SearchIndexableResource sir = new SearchIndexableResource(context);
                     sir.xmlResId = R.xml.top_level_settings;
                     return Arrays.asList(sir);
                 }
 
                 @Override
                 protected boolean isPageSearchEnabled(Context context) {
                     // Never searchable, all entries in this page are already indexed elsewhere.
                     return false;
                 }
             };
}

加载布局文件top_level_settings。

/packages/apps/Settings/res/xml/top_level_settings.xml

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="top_level_settings">

    <Preference
        android:key="top_level_network"
        android:title="@string/network_dashboard_title"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_network"
        android:order="-120"
        android:fragment="com.android.settings.network.NetworkDashboardFragment"
        settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>

    <Preference
        android:key="top_level_connected_devices"
        android:title="@string/connected_devices_dashboard_title"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_connected_device"
        android:order="-110"
        android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment"
        settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/>

    <Preference
        android:key="top_level_apps_and_notifs"
        android:title="@string/app_and_notification_dashboard_title"
        android:summary="@string/app_and_notification_dashboard_summary"
        android:icon="@drawable/ic_homepage_apps"
        android:order="-100"
        android:fragment="com.android.settings.applications.AppAndNotificationDashboardFragment"/>

    <Preference
        android:key="top_level_battery"
        android:title="@string/power_usage_summary_title"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_battery"
        android:fragment="com.android.settings.fuelgauge.PowerUsageSummary"
        android:order="-90"
        settings:controller="com.android.settings.fuelgauge.TopLevelBatteryPreferenceController"/>

    <Preference
        android:key="top_level_display"
        android:title="@string/display_settings"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_display"
        android:order="-80"
        android:fragment="com.android.settings.DisplaySettings"
        settings:controller="com.android.settings.display.TopLevelDisplayPreferenceController"/>

    <Preference
        android:key="top_level_sound"
        android:title="@string/sound_settings"
        android:summary="@string/sound_dashboard_summary"
        android:icon="@drawable/ic_homepage_sound"
        android:order="-70"
        android:fragment="com.android.settings.notification.SoundSettings"/>

    <Preference
        android:key="top_level_storage"
        android:title="@string/storage_settings"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_storage"
        android:order="-60"
        android:fragment="com.android.settings.deviceinfo.StorageSettings"
        settings:controller="com.android.settings.deviceinfo.TopLevelStoragePreferenceController"/>

    <Preference
        android:key="top_level_privacy"
        android:title="@string/privacy_dashboard_title"
        android:summary="@string/privacy_dashboard_summary"
        android:icon="@drawable/ic_homepage_privacy"
        android:order="-55"
        android:fragment="com.android.settings.privacy.PrivacyDashboardFragment"/>

    <Preference
        android:key="top_level_location"
        android:title="@string/location_settings_title"
        android:summary="@string/location_settings_loading_app_permission_stats"
        android:icon="@drawable/ic_homepage_location"
        android:order="-50"
        android:fragment="com.android.settings.location.LocationSettings"
        settings:controller="com.android.settings.location.TopLevelLocationPreferenceController"/>

    <Preference
        android:key="top_level_security"
        android:title="@string/security_settings_title"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_security"
        android:order="-40"
        android:fragment="com.android.settings.security.SecuritySettings"
        settings:controller="com.android.settings.security.TopLevelSecurityEntryPreferenceController"/>

    <Preference
        android:key="top_level_accounts"
        android:title="@string/account_dashboard_title"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_accounts"
        android:order="-30"
        android:fragment="com.android.settings.accounts.AccountDashboardFragment"
        settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/>

    <Preference
        android:key="top_level_accessibility"
        android:title="@string/accessibility_settings"
        android:summary="@string/accessibility_settings_summary"
        android:icon="@drawable/ic_homepage_accessibility"
        android:order="-20"
        android:fragment="com.android.settings.accessibility.AccessibilitySettings"
        settings:controller="com.android.settings.accessibility.TopLevelAccessibilityPreferenceController"/>

    <Preference
        android:key="top_level_system"
        android:title="@string/header_category_system"
        android:summary="@string/system_dashboard_summary"
        android:icon="@drawable/ic_homepage_system_dashboard"
        android:order="10"
        android:fragment="com.android.settings.system.SystemDashboardFragment"/>

    <Preference
        android:key="top_level_about_device"
        android:title="@string/about_settings"
        android:summary="@string/summary_placeholder"
        android:icon="@drawable/ic_homepage_about"
        android:order="20"
        android:fragment="com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment"
        settings:controller="com.android.settings.deviceinfo.aboutphone.TopLevelAboutDevicePreferenceController"/>

    <Preference
        android:key="top_level_support"
        android:summary="@string/support_summary"
        android:title="@string/page_tab_title_support"
        android:icon="@drawable/ic_homepage_support"
        android:order="100"
        settings:controller="com.android.settings.support.SupportPreferenceController"/>

</PreferenceScreen>

        top_level_settings主要配置一些Preference菜单项如网络、已连接的设备、通知、电池、显示、声音等等

attr description
android:key    选项的名称,也是用来存储时唯一的key。
android:title选项的标题
android:summary摘要,配置的简要说明,显示在标题下面。
android:icon指定左侧的图标。
android:order偏好的顺序。如果不指定,默认的顺序将字母。
android:fragment指定fragment
settings:controller控制管理类

TopLevelSettings继承自DashboardFragment.java,进入DashboardFragment.java看看。

package com.android.settings.dashboard;

import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;

import androidx.annotation.CallSuper;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerListHelper;
import com.android.settings.core.SettingsBaseActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.Indexable;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
 */
public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements SettingsBaseActivity.CategoryListener, Indexable,
        SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
    private static final String TAG = "DashboardFragment";

    private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers =
            new ArrayMap<>();
    private final Set<String> mDashboardTilePrefKeys = new ArraySet<>();

    private DashboardFeatureProvider mDashboardFeatureProvider;
    private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
    private boolean mListeningToCategoryChange;
    private SummaryLoader mSummaryLoader;
    private List<String> mSuppressInjectedTileKeys;
    @VisibleForTesting
    UiBlockerController mBlockerController;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
                R.array.config_suppress_injected_tile_keys));
        mDashboardFeatureProvider = FeatureFactory.getFactory(context).
                getDashboardFeatureProvider(context);
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        // Load preference controllers from code
        final List<AbstractPreferenceController> controllersFromCode =
                createPreferenceControllers(context);
        // Load preference controllers from xml definition
        final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
                .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
        // Filter xml-based controllers in case a similar controller is created from code already.
        final List<BasePreferenceController> uniqueControllerFromXml =
                PreferenceControllerListHelper.filterControllers(
                        controllersFromXml, controllersFromCode);

        // Add unique controllers to list.
        if (controllersFromCode != null) {
            controllers.addAll(controllersFromCode);
        }
        controllers.addAll(uniqueControllerFromXml);

         // And wire up with lifecycle.
         final Lifecycle lifecycle = getSettingsLifecycle();
         uniqueControllerFromXml
                 .stream()
                 .filter(controller -> controller instanceof LifecycleObserver)
                 .forEach(
                         controller -> lifecycle.addObserver((LifecycleObserver) controller));
 
         mPlaceholderPreferenceController =
                 new DashboardTilePlaceholderPreferenceController(context);
         controllers.add(mPlaceholderPreferenceController);
         for (AbstractPreferenceController controller : controllers) {
             addPreferenceController(controller);
         }
 
         checkUiBlocker(controllers);
     }
 
     @VisibleForTesting
     void checkUiBlocker(List<AbstractPreferenceController> controllers) {
         final List<String> keys = new ArrayList<>();
         controllers
                 .stream()
                 .filter(controller -> controller instanceof BasePreferenceController.UiBlocker)
                 .forEach(controller -> {
                     ((BasePreferenceController) controller).setUiBlockListener(this);
                     keys.add(controller.getPreferenceKey());
                 });
 
         if (!keys.isEmpty()) {
             mBlockerController = new UiBlockerController(keys);
             mBlockerController.start(()->updatePreferenceVisibility(mPreferenceControllers));
         }
     }
 
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         // Set ComparisonCallback so we get better animation when list changes.
         getPreferenceManager().setPreferenceComparisonCallback(
                 new PreferenceManager.SimplePreferenceComparisonCallback());
         if (icicle != null) {
             // Upon rotation configuration change we need to update preference states before any
             // editing dialog is recreated (that would happen before onResume is called).
             updatePreferenceStates();
         }
     }
 
     @Override
     public void onCategoriesChanged() {
         final DashboardCategory category =
                 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
         if (category == null) {
             return;
         }
         refreshDashboardTiles(getLogTag());
     }
 
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         refreshAllPreferences(getLogTag());
     }
 
     @Override
     public void onStart() {
         super.onStart();
         final DashboardCategory category =
                 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
         if (category == null) {
             return;
         }
         if (mSummaryLoader != null) {
             // SummaryLoader can be null when there is no dynamic tiles.
             mSummaryLoader.setListening(true);
         }
         final Activity activity = getActivity();
         if (activity instanceof SettingsBaseActivity) {
             mListeningToCategoryChange = true;
             ((SettingsBaseActivity) activity).addCategoryListener(this);
         }
     }
 
     @Override
     public void notifySummaryChanged(Tile tile) {
         final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
         final Preference pref = getPreferenceScreen().findPreference(key);
         if (pref == null) {
             Log.d(getLogTag(), String.format(
                     "Can't find pref by key %s, skipping update summary %s",
                     key, tile.getDescription()));
             return;
         }
         pref.setSummary(tile.getSummary(pref.getContext()));
     }
 
     @Override
     public void onResume() {
         super.onResume();
         updatePreferenceStates();
     }
 
     @Override
     public boolean onPreferenceTreeClick(Preference preference) {
         Collection<List<AbstractPreferenceController>> controllers =
                 mPreferenceControllers.values();
         // If preference contains intent, log it before handling.
         mMetricsFeatureProvider.logDashboardStartIntent(
                 getContext(), preference.getIntent(), getMetricsCategory());
         // Give all controllers a chance to handle click.
         for (List<AbstractPreferenceController> controllerList : controllers) {
             for (AbstractPreferenceController controller : controllerList) {
                 if (controller.handlePreferenceTreeClick(preference)) {
                     return true;
                 }
             }
         }
         return super.onPreferenceTreeClick(preference);
     }
 
     @Override
     public void onStop() {
         super.onStop();
         if (mSummaryLoader != null) {
             // SummaryLoader can be null when there is no dynamic tiles.
             mSummaryLoader.setListening(false);
         }
         if (mListeningToCategoryChange) {
             final Activity activity = getActivity();
             if (activity instanceof SettingsBaseActivity) {
                 ((SettingsBaseActivity) activity).remCategoryListener(this);
             }
             mListeningToCategoryChange = false;
         }
     }
 
     @Override
     protected abstract int getPreferenceScreenResId();
 
     @Override
     public void onExpandButtonClick() {
         mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
                 SettingsEnums.ACTION_SETTINGS_ADVANCED_BUTTON_EXPAND,
                 getMetricsCategory(), null, 0);
     }
 
     protected boolean shouldForceRoundedIcon() {
         return false;
     }
 
     protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
         List<AbstractPreferenceController> controllerList = mPreferenceControllers.get(clazz);
         if (controllerList != null) {
             if (controllerList.size() > 1) {
                 Log.w(TAG, "Multiple controllers of Class " + clazz.getSimpleName()
                         + " found, returning first one.");
             }
             return (T) controllerList.get(0);
         }
 
         return null;
     }
 
     protected void addPreferenceController(AbstractPreferenceController controller) {
         if (mPreferenceControllers.get(controller.getClass()) == null) {
             mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
         }
         mPreferenceControllers.get(controller.getClass()).add(controller);
     }
 
     /**
      * Returns the CategoryKey for loading {@link DashboardCategory} for this fragment.
      */
     @VisibleForTesting
     public String getCategoryKey() {
         return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
     }
 
     /**
      * Get the tag string for logging.
      */
     protected abstract String getLogTag();
 
     /**
      * Get a list of {@link AbstractPreferenceController} for this fragment.
      */
     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
         return null;
     }
 
     /**
      * Returns true if this tile should be displayed
      */
     @CallSuper
     protected boolean displayTile(Tile tile) {
         if (mSuppressInjectedTileKeys != null && tile.hasKey()) {
             // For suppressing injected tiles for OEMs.
             return !mSuppressInjectedTileKeys.contains(tile.getKey(getContext()));
         }
         return true;
     }
 
     /**
      * Displays resource based tiles.
      */
     private void displayResourceTiles() {
         final int resId = getPreferenceScreenResId();
         if (resId <= 0) {
             return;
         }
         addPreferencesFromResource(resId);
         final PreferenceScreen screen = getPreferenceScreen();
         screen.setOnExpandButtonClickListener(this);
         mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
                 controller -> controller.displayPreference(screen));
     }
 
     /**
      * Update state of each preference managed by PreferenceController.
      */
     protected void updatePreferenceStates() {
         final PreferenceScreen screen = getPreferenceScreen();
         Collection<List<AbstractPreferenceController>> controllerLists =
                 mPreferenceControllers.values();
         for (List<AbstractPreferenceController> controllerList : controllerLists) {
             for (AbstractPreferenceController controller : controllerList) {
                 if (!controller.isAvailable()) {
                     continue;
                 }
 
                 final String key = controller.getPreferenceKey();
                 if (TextUtils.isEmpty(key)) {
                     Log.d(TAG, String.format("Preference key is %s in Controller %s",
                             key, controller.getClass().getSimpleName()));
                     continue;
                 }
 
                 final Preference preference = screen.findPreference(key);
                 if (preference == null) {
                     Log.d(TAG, String.format("Cannot find preference with key %s in Controller %s",
                             key, controller.getClass().getSimpleName()));
                     continue;
                 }
                 controller.updateState(preference);
             }
         }
     }
 
     /**
      * Refresh all preference items, including both static prefs from xml, and dynamic items from
      * DashboardCategory.
      */
     private void refreshAllPreferences(final String TAG) {
         final PreferenceScreen screen = getPreferenceScreen();
         // First remove old preferences.
         if (screen != null) {
             // Intentionally do not cache PreferenceScreen because it will be recreated later.
             screen.removeAll();
         }
 
         // Add resource based tiles.
         displayResourceTiles();
 
         refreshDashboardTiles(TAG);
 
         final Activity activity = getActivity();
         if (activity != null) {
             Log.d(TAG, "All preferences added, reporting fully drawn");
             activity.reportFullyDrawn();
         }
 
         updatePreferenceVisibility(mPreferenceControllers);
     }
 
     @VisibleForTesting
     void updatePreferenceVisibility(
             Map<Class, List<AbstractPreferenceController>> preferenceControllers) {
         final PreferenceScreen screen = getPreferenceScreen();
         if (screen == null || preferenceControllers == null || mBlockerController == null) {
             return;
         }
 
         final boolean visible = mBlockerController.isBlockerFinished();
         for (List<AbstractPreferenceController> controllerList :
                 preferenceControllers.values()) {
             for (AbstractPreferenceController controller : controllerList) {
                 final String key = controller.getPreferenceKey();
                 final Preference preference = findPreference(key);
                 if (preference != null) {
                     preference.setVisible(visible && controller.isAvailable());
                 }
             }
         }
     }
 
     /**
      * Refresh preference items backed by DashboardCategory.
      */
     @VisibleForTesting
     void refreshDashboardTiles(final String TAG) {
         final PreferenceScreen screen = getPreferenceScreen();
 
         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 List<String> remove = new ArrayList<>(mDashboardTilePrefKeys);
 
         // There are dashboard tiles, so we need to install SummaryLoader.
         if (mSummaryLoader != null) {
             mSummaryLoader.release();
         }
         final Context context = getContext();
         mSummaryLoader = new SummaryLoader(getActivity(), getCategoryKey());
         mSummaryLoader.setSummaryConsumer(this);
         // Install dashboard tiles.
         final boolean forceRoundedIcons = shouldForceRoundedIcon();
         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.contains(key)) {
                 // Have the key already, will rebind.
                 final Preference preference = screen.findPreference(key);
                 mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons,
                         getMetricsCategory(), preference, tile, key,
                         mPlaceholderPreferenceController.getOrder());
             } else {
                 // Don't have this key, add it.
                 final Preference pref = new Preference(getPrefContext());
                 mDashboardFeatureProvider.bindPreferenceToTile(getActivity(), forceRoundedIcons,
                         getMetricsCategory(), pref, tile, key,
                         mPlaceholderPreferenceController.getOrder());
                 screen.addPreference(pref);
                 mDashboardTilePrefKeys.add(key);
             }
             remove.remove(key);
         }
         // Finally remove tiles that are gone.
         for (String key : remove) {
             mDashboardTilePrefKeys.remove(key);
             final Preference preference = screen.findPreference(key);
             if (preference != null) {
                 screen.removePreference(preference);
             }
         }
         mSummaryLoader.setListening(true);
     }
 
     @Override
     public void onBlockerWorkFinished(BasePreferenceController controller) {
         mBlockerController.countDown(controller.getPreferenceKey());
    }
}

进入DashboardFragment.java后,在onAttach方法中完成preferenceControllers的加载。调用createPreferenceControllers方法,由源码可知此方法返回null,故只创建了一个空的controllersFromCode集合。调用getPreferenceControllersFromXml方法解析子类的资源文件(top_level_settings.xml),拿到xml中定义的controller,然后添加到controllers集合中,再返回controllers集合。通过PreferenceControllerListHelper.filterControllers过滤掉重复的controller。把过滤后的controller通过addPreferenceController添加到mPreferenceControllers

/packages/apps/Settings/src/com/android/settings/core/PreferenceControllerListHelper.java

package com.android.settings.core;

import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER;
import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;

import android.annotation.NonNull;
import android.annotation.XmlRes;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;

import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag;
import com.android.settingslib.core.AbstractPreferenceController;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * Helper to load {@link BasePreferenceController} lists from Xml.
 */
public class PreferenceControllerListHelper {

    private static final String TAG = "PrefCtrlListHelper";

    /**
     * Instantiates a list of controller based on xml definition.
     */
    @NonNull
    public static List<BasePreferenceController> getPreferenceControllersFromXml(Context context,
            @XmlRes int xmlResId) {
        final List<BasePreferenceController> controllers = new ArrayList<>();
        List<Bundle> preferenceMetadata;
        try {
            preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId,
                    MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER
                            | MetadataFlag.FLAG_INCLUDE_PREF_SCREEN);
        } catch (IOException | XmlPullParserException e) {
            Log.e(TAG, "Failed to parse preference xml for getting controllers", e);
            return controllers;
        }

        for (Bundle metadata : preferenceMetadata) {
            final String controllerName = metadata.getString(METADATA_CONTROLLER);
            if (TextUtils.isEmpty(controllerName)) {
                continue;
            }
            BasePreferenceController controller;
            try {
                controller = BasePreferenceController.createInstance(context, controllerName);
            } catch (IllegalStateException e) {
                Log.d(TAG, "Could not find Context-only controller for pref: " + controllerName);
                final String key = metadata.getString(METADATA_KEY);
                if (TextUtils.isEmpty(key)) {
                    Log.w(TAG, "Controller requires key but it's not defined in xml: "
                            + controllerName);
                    continue;
                }
                try {
                    controller = BasePreferenceController.createInstance(context, controllerName,
                            key);
                } catch (IllegalStateException e2) {
                    Log.w(TAG, "Cannot instantiate controller from reflection: " + controllerName);
                    continue;
                }
            }
            controllers.add(controller);
        }
        return controllers;
    }

    /**
     * Return a sub list of {@link AbstractPreferenceController} to only contain controller that
     * doesn't exist in filter.
     *
     * @param filter The filter. This list will be unchanged.
     * @param input  This list will be filtered into a sublist and element is kept
     *               IFF the controller key is not used by anything from {@param filter}.
      */
     @NonNull
     public static List<BasePreferenceController> filterControllers(
             @NonNull List<BasePreferenceController> input,
             List<AbstractPreferenceController> filter) {
         if (input == null || filter == null) {
             return input;
         }
         final Set<String> keys = new TreeSet<>();
         final List<BasePreferenceController> filteredList = new ArrayList<>();
         for (AbstractPreferenceController controller : filter) {
             final String key = controller.getPreferenceKey();
             if (key != null) {
                 keys.add(key);
             }
         }
         for (BasePreferenceController controller : input) {
             if (keys.contains(controller.getPreferenceKey())) {
                 Log.w(TAG, controller.getPreferenceKey() + " already has a controller");
                 continue;
             }
             filteredList.add(controller);
         }
         return filteredList;
    }
 
}

然后会调用onCreatePreferences方法,再调用refreshAllPreferences。

/**
      * Refresh all preference items, including both static prefs from xml, and dynamic items from
      * DashboardCategory.
      */
     private void refreshAllPreferences(final String TAG) {
         final PreferenceScreen screen = getPreferenceScreen();
         // First remove old preferences.
         if (screen != null) {
             // Intentionally do not cache PreferenceScreen because it will be recreated later.
             screen.removeAll();
         }
 
         // Add resource based tiles.
         displayResourceTiles();
 
         refreshDashboardTiles(TAG);
 
         final Activity activity = getActivity();
         if (activity != null) {
             Log.d(TAG, "All preferences added, reporting fully drawn");
             activity.reportFullyDrawn();
         }
 
         updatePreferenceVisibility(mPreferenceControllers);
     }

通过displayResourceTiles方法加载资源文件,但是getPreferenceScreenResId是一个抽象方法,具体实现在子类中,加载子类的资源文件。

@Override
protected abstract int getPreferenceScreenResId();

.................................................................................

private void displayResourceTiles() {
    final int resId = getPreferenceScreenResId();
    if (resId <= 0) {
        return;
    }
    addPreferencesFromResource(resId);
    final PreferenceScreen screen = getPreferenceScreen();
    screen.setOnExpandButtonClickListener(this);
    mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
            controller -> controller.displayPreference(screen));
}

遍历controllers并且调用controller的displayPreference方法。所有的controller都基础自BasePreferenceController.java。

/packages/apps/Settings/src/com/android/settings/core/BasePreferenceController.java

package com.android.settings.core;

import android.annotation.IntDef;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.search.SearchIndexableRaw;
import com.android.settings.slices.SliceData;
import com.android.settings.slices.Sliceable;
import com.android.settingslib.core.AbstractPreferenceController;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

/**
 * Abstract class to consolidate utility between preference controllers and act as an interface
 * for Slices. The abstract classes that inherit from this class will act as the direct interfaces
 * for each type when plugging into Slices.
 */
public abstract class BasePreferenceController extends AbstractPreferenceController implements
        Sliceable {

    private static final String TAG = "SettingsPrefController";

    /**
     * Denotes the availability of the Setting.
     * <p>
     * Used both explicitly and by the convenience methods {@link #isAvailable()} and
     * {@link #isSupported()}.
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({AVAILABLE, AVAILABLE_UNSEARCHABLE, UNSUPPORTED_ON_DEVICE, DISABLED_FOR_USER,
            DISABLED_DEPENDENT_SETTING, CONDITIONALLY_UNAVAILABLE})
    public @interface AvailabilityStatus {
    }

    /**
     * The setting is available, and searchable to all search clients.
     */
    public static final int AVAILABLE = 0;

    /**
     * The setting is available, but is not searchable to any search client.
     */
    public static final int AVAILABLE_UNSEARCHABLE = 1;

    /**
     * A generic catch for settings which are currently unavailable, but may become available in
     * the future. You should use {@link #DISABLED_FOR_USER} or {@link #DISABLED_DEPENDENT_SETTING}
     * if they describe the condition more accurately.
     */
    public static final int CONDITIONALLY_UNAVAILABLE = 2;

    /**
     * The setting is not, and will not supported by this device.
     * <p>
     * There is no guarantee that the setting page exists, and any links to the Setting should take
     * you to the home page of Settings.
     */
    public static final int UNSUPPORTED_ON_DEVICE = 3;


    /**
     * The setting cannot be changed by the current user.
     * <p>
     * Links to the Setting should take you to the page of the Setting, even if it cannot be
     * changed.
     */
    public static final int DISABLED_FOR_USER = 4;

    /**
     * The setting has a dependency in the Settings App which is currently blocking access.
     * <p>
     * It must be possible for the Setting to be enabled by changing the configuration of the device
     * settings. That is, a setting that cannot be changed because of the state of another setting.
     * This should not be used for a setting that would be hidden from the UI entirely.
     * <p>
     * Correct use: Intensity of night display should be {@link #DISABLED_DEPENDENT_SETTING} when
     * night display is off.
      * Incorrect use: Mobile Data is {@link #DISABLED_DEPENDENT_SETTING} when there is no
      * data-enabled sim.
      * <p>
      * Links to the Setting should take you to the page of the Setting, even if it cannot be
      * changed.
      */
    public static final int DISABLED_DEPENDENT_SETTING = 5;
 
 
    protected final String mPreferenceKey;
    protected UiBlockListener mUiBlockListener;
 
    /**
     * Instantiate a controller as specified controller type and user-defined key.
     * <p/>
     * This is done through reflection. Do not use this method unless you know what you are doing.
     */
    public static BasePreferenceController createInstance(Context context,
            String controllerName, String key) {
        try {
            final Class<?> clazz = Class.forName(controllerName);
            final Constructor<?> preferenceConstructor =
                    clazz.getConstructor(Context.class, String.class);
            final Object[] params = new Object[]{context, key};
            return (BasePreferenceController) preferenceConstructor.newInstance(params);
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
                IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
            throw new IllegalStateException(
                    "Invalid preference controller: " + controllerName, e);
        }
    }
 
    /**
     * Instantiate a controller as specified controller type.
     * <p/>
     * This is done through reflection. Do not use this method unless you know what you are doing.
     */
    public static BasePreferenceController createInstance(Context context, String controllerName) {
        try {
            final Class<?> clazz = Class.forName(controllerName);
            final Constructor<?> preferenceConstructor = clazz.getConstructor(Context.class);
            final Object[] params = new Object[]{context};
            return (BasePreferenceController) preferenceConstructor.newInstance(params);
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
                IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
            throw new IllegalStateException(
                    "Invalid preference controller: " + controllerName, e);
        }
    }
 
    public BasePreferenceController(Context context, String preferenceKey) {
        super(context);
        mPreferenceKey = preferenceKey;
        if (TextUtils.isEmpty(mPreferenceKey)) {
            throw new IllegalArgumentException("Preference key must be set");
        }
    }
 
    /**
     * @return {@AvailabilityStatus} for the Setting. This status is used to determine if the
     * Setting should be shown or disabled in Settings. Further, it can be used to produce
     * appropriate error / warning Slice in the case of unavailability.
     * </p>
     * The status is used for the convenience methods: {@link #isAvailable()},
     * {@link #isSupported()}
     */
    @AvailabilityStatus
    public abstract int getAvailabilityStatus();
 
    @Override
    public String getPreferenceKey() {
        return mPreferenceKey;
    }
 
    /**
     * @return {@code true} when the controller can be changed on the device.
     *
     * <p>
     * Will return true for {@link #AVAILABLE} and {@link #DISABLED_DEPENDENT_SETTING}.
     * <p>
     * When the availability status returned by {@link #getAvailabilityStatus()} is
     * {@link #DISABLED_DEPENDENT_SETTING}, then the setting will be disabled by default in the
     * DashboardFragment, and it is up to the {@link BasePreferenceController} to enable the
     * preference at the right time.
     *
     * TODO (mfritze) Build a dependency mechanism to allow a controller to easily define the
     * dependent setting.
     */
    @Override
    public final boolean isAvailable() {
        final int availabilityStatus = getAvailabilityStatus();
        return (availabilityStatus == AVAILABLE
                || availabilityStatus == AVAILABLE_UNSEARCHABLE
                || availabilityStatus == DISABLED_DEPENDENT_SETTING);
    }
 
    /**
     * @return {@code false} if the setting is not applicable to the device. This covers both
     * settings which were only introduced in future versions of android, or settings that have
     * hardware dependencies.
     * </p>
     * Note that a return value of {@code true} does not mean that the setting is available.
     */
    public final boolean isSupported() {
        return getAvailabilityStatus() != UNSUPPORTED_ON_DEVICE;
    }
 
    /**
     * Displays preference in this controller.
     */
    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
            // Disable preference if it depends on another setting.
            final Preference preference = screen.findPreference(getPreferenceKey());
            if (preference != null) {
                preference.setEnabled(false);
            }
        }
    }
 
    /**
     * @return the UI type supported by the controller.
     */
    @SliceData.SliceType
    public int getSliceType() {
        return SliceData.SliceType.INTENT;
    }
 
    /**
     * Updates non-indexable keys for search provider.
     *
     * Called by SearchIndexProvider#getNonIndexableKeys
     */
    public void updateNonIndexableKeys(List<String> keys) {
        final boolean shouldSuppressFromSearch = !isAvailable()
                || getAvailabilityStatus() == AVAILABLE_UNSEARCHABLE;
        if (shouldSuppressFromSearch) {
            final String key = getPreferenceKey();
            if (TextUtils.isEmpty(key)) {
                Log.w(TAG, "Skipping updateNonIndexableKeys due to empty key " + toString());
                return;
            }
            if (keys.contains(key)) {
                Log.w(TAG, "Skipping updateNonIndexableKeys, key already in list. " + toString());
                return;
            }
            keys.add(key);
        }
    }
 
    /**
     * Updates raw data for search provider.
     *
     * Called by SearchIndexProvider#getRawDataToIndex
     */
    public void updateRawDataToIndex(List<SearchIndexableRaw> rawData) {
    }
 
    /**
     * Set {@link UiBlockListener}
     *
     * @param uiBlockListener listener to set
     */
    public void setUiBlockListener(UiBlockListener uiBlockListener) {
        mUiBlockListener = uiBlockListener;
    }
 
    /**
     * Listener to invoke when background job is finished
     */
    public interface UiBlockListener {
        /**
         * To notify client that UI related background work is finished.
         * (i.e. Slice is fully loaded.)
         *
         * @param controller Controller that contains background work
         */
        void onBlockerWorkFinished(BasePreferenceController controller);
    }
 
    /**
     * Used for {@link BasePreferenceController} to decide whether it is ui blocker.
     * If it is, entire UI will be invisible for a certain period until controller
     * invokes {@link UiBlockListener}
     *
     * This won't block UI thread however has similar side effect. Please use it if you
     * want to avoid janky animation(i.e. new preference is added in the middle of page).
     *
     * This music be used in {@link BasePreferenceController}
     */
    public interface UiBlocker {
    }
}

而BasePreferenceController 继承自 AbstractPreferenceController

package com.android.settingslib.core;

import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
 
/**
 * A controller that manages event for preference.
 */
public abstract class AbstractPreferenceController {

    private static final String TAG = "AbstractPrefController";

    protected final Context mContext;

    public AbstractPreferenceController(Context context) {
        mContext = context;
    }

    /**
     * Displays preference in this controller.
     */
    public void displayPreference(PreferenceScreen screen) {
        final String prefKey = getPreferenceKey();
        if (TextUtils.isEmpty(prefKey)) {
            Log.w(TAG, "Skipping displayPreference because key is empty:" + getClass().getName());
            return;
        }
        if (isAvailable()) {
            setVisible(screen, prefKey, true /* visible */);
            if (this instanceof Preference.OnPreferenceChangeListener) {
                final Preference preference = screen.findPreference(prefKey);
                preference.setOnPreferenceChangeListener(
                        (Preference.OnPreferenceChangeListener) this);
            }
        } else {
            setVisible(screen, prefKey, false /* visible */);
        }
    }

    /**
     * Updates the current status of preference (summary, switch state, etc)
     */
    public void updateState(Preference preference) {
        refreshSummary(preference);
    }

    /**
     * Refresh preference summary with getSummary()
     */
    protected void refreshSummary(Preference preference) {
        if (preference == null) {
            return;
        }
        final CharSequence summary = getSummary();
        if (summary == null) {
            // Default getSummary returns null. If subclass didn't override this, there is nothing
            // we need to do.
            return;
        }
        preference.setSummary(summary);
    }

    /**
     * Returns true if preference is available (should be displayed)
     */
    public abstract boolean isAvailable();

    /**
     * Handles preference tree click
     *
     * @param preference the preference being clicked
     * @return true if click is handled
     */
    public boolean handlePreferenceTreeClick(Preference preference) {
        return false;
    }

    /**
     * Returns the key for this preference.
     */
    public abstract String getPreferenceKey();

    /**
     * Show/hide a preference.
     */
    protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
        final Preference pref = group.findPreference(key);
        if (pref != null) {
            pref.setVisible(isVisible);
        }
    }


    /**
      * @return a {@link CharSequence} for the summary of the preference.
      */
    public CharSequence getSummary() {
        return null;
    }
}
  

AbstractPreferenceController类中的displayPreference方法调用了getPreferenceKey方法,该方法是个抽象类,具体在子类BasePreferenceController中实现,返回mPreferenceKey。而这个mPreferenceKey就是资源文件xml中的android:key属性。根据这个熟悉去设置preference是否可见。

/** * Preference controller for "Open network select" */ public class OpenNetworkSelectPagePreferenceController extends TelephonyBasePreferenceController implements AutoSelectPreferenceController.OnNetworkSelectModeListener, LifecycleObserver { private TelephonyManager mTelephonyManager; private Preference mPreference; private PreferenceScreen mPreferenceScreen; private AllowedNetworkTypesListener mAllowedNetworkTypesListener; private int mCacheOfModeStatus; public OpenNetworkSelectPagePreferenceController(Context context, String key) { super(context, key); mTelephonyManager = context.getSystemService(TelephonyManager.class); mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; mCacheOfModeStatus = TelephonyManager.NETWORK_SELECTION_MODE_UNKNOWN; mAllowedNetworkTypesListener = new AllowedNetworkTypesListener( context.getMainExecutor()); mAllowedNetworkTypesListener.setAllowedNetworkTypesListener( () -> updatePreference()); } private void updatePreference() { if (mPreferenceScreen != null) { displayPreference(mPreferenceScreen); } if (mPreference != null) { updateState(mPreference); } } @Override public int getAvailabilityStatus(int subId) { return MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } @OnLifecycleEvent(ON_START) public void onStart() { mAllowedNetworkTypesListener.register(mContext, mSubId); } @OnLifecycleEvent(ON_STOP) public void onStop() { mAllowedNetworkTypesListener.unregister(mContext, mSubId); } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreferenceScreen = screen; mPreference = screen.findPreference(getPreferenceKey()); } @Override public void updateState(Preference preference) { super.updateState(preference); preference.setEnabled(mCacheOfModeStatus != TelephonyManager.NETWORK_SELECTION_MODE_AUTO); Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, SETTINGS_PACKAGE_NAME + ".Settings$NetworkSelectActivity"); intent.putExtra(Settings.EXTRA_SUB_ID, mSubId); preference.setIntent(intent); } @Override public CharSequence getSummary() { final ServiceState ss = mTelephonyManager.getServiceState(); if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) { return MobileNetworkUtils.getCurrentCarrierNameForDisplay(mContext, mSubId); } else { return mContext.getString(R.string.network_disconnected); } } /** * Initialization based on given subscription id. **/ public OpenNetworkSelectPagePreferenceController init(int subId) { mSubId = subId; mTelephonyManager = mContext.getSystemService(TelephonyManager.class) .createForSubscriptionId(mSubId); return this; } @Override public void onNetworkSelectModeUpdated(int mode) { mCacheOfModeStatus = mode; if (mPreference != null) { updateState(mPreference); } } }代码分析
03-08
<think>嗯,我现在需要分析这段Java代码,它是一个Android中的PreferenceController,用于处理“打开网络选择”的偏好设置。首先,我得理解这个类的作用和结构。 首先,这个类叫做OpenNetworkSelectPagePreferenceController,继承自TelephonyBasePreferenceController,并且实现了AutoSelectPreferenceController.OnNetworkSelectModeListener接口和LifecycleObserver。这说明它和电话功能相关,同时需要监听网络选择模式的变化,并且能够响应生命周期的变化。 接下来看构造函数,接受Context和key参数,初始化了TelephonyManager,mSubId被设为无效的订阅ID,mCacheOfModeStatus初始化为未知。然后创建了一个AllowedNetworkTypesListener,并设置了监听器,当允许的网络类型变化时,会触发updatePreference方法。这个updatePreference方法会重新显示PreferenceScreen并更新状态,可能是为了在允许的网络类型变化时刷新UI。 然后看getAvailabilityStatus方法,它调用MobileNetworkUtils.shouldDisplayNetworkSelectOptions来判断是否显示网络选择选项,返回可用或条件不可用。这说明这个控制器的可见性取决于是否应该显示网络选择选项,可能根据SIM卡状态或用户权限等条件决定。 生命周期方法onStart和onStop中注册和注销了AllowedNetworkTypesListener,说明在界面可见时开始监听网络类型变化,不可见时停止监听,避免资源浪费。 displayPreference方法中保存了PreferenceScreen和对应的Preference,可能是为了后续的更新操作。updateState方法中设置Preference是否可用,根据缓存的模式状态是否是自动选择。如果不是自动模式,则启用该Preference,允许用户手动选择网络。同时设置了点击Preference后跳转到NetworkSelectActivity的Intent,并传递了subId参数。 getSummary方法返回当前网络的运营商名称,如果服务状态正常,否则显示“网络未连接”。这会在Preference的摘要部分显示当前网络状态。 init方法用于初始化subId,并创建对应的TelephonyManager实例,确保操作针对特定的SIM卡订阅。 最后,onNetworkSelectModeUpdated方法在模式变化时更新缓存的状态,并刷新Preference的状态,确保UI正确反映当前的网络选择模式(自动或手动)。 可能需要注意的地方:AllowedNetworkTypesListener的具体实现没有给出,但它的作用是监听允许的网络类型变化,触发更新。另外,这个控制器与界面紧密相关,特别是Preference的启用状态和摘要信息,需要确保在正确的生命周期阶段进行注册和注销监听器,避免内存泄漏或无效回调。 总结一下,这个控制器主要负责管理“打开网络选择”Preference的显示、状态和交互,根据网络状态和用户设置动态更新UI,同时处理生命周期事件以优化资源使用。</think>以下是对`OpenNetworkSelectPagePreferenceController`类的详细分析: ### 一、类继承与实现 - **继承关系**:继承自`TelephonyBasePreferenceController`,表明与电话网络配置相关 - **实现接口**: - `OnNetworkSelectModeListener`:监听网络选择模式变化(自动/手动) - `LifecycleObserver`:响应生命周期事件 ### 二、关键成员变量 | 变量名 | 类型 | 作用描述 | |---------------------------|-------------------------|-----------------------------------------------------------------------| | mTelephonyManager | TelephonyManager | 管理电话相关服务,如网络状态查询 | | mPreference | Preference | 对应的设置项UI元素 | | mCacheOfModeStatus | int | 缓存当前网络选择模式(`NETWORK_SELECTION_MODE_AUTO`或手动模式) | ### 三、核心方法解析 #### 1. 初始化方法 ```java public OpenNetworkSelectPagePreferenceController init(int subId) { mSubId = subId; mTelephonyManager = mContext.getSystemService(TelephonyManager.class) .createForSubscriptionId(mSubId); return this; } ``` - **作用**:初始化SIM卡订阅ID - **关键点**:通过`createForSubscriptionId`创建指定SIM卡的TelephonyManager实例 #### 2. 可用性控制 ```java public int getAvailabilityStatus(int subId) { return MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } ``` - **判断逻辑**:根据`MobileNetworkUtils`工具类决定是否显示网络选择选项 - **典型场景**:当设备不支持多SIM卡或用户无权限时返回不可用 #### 3. 状态更新机制 ```java private void updatePreference() { if (mPreferenceScreen != null) { displayPreference(mPreferenceScreen); } if (mPreference != null) { updateState(mPreference); } } ``` - **触发条件**:当允许的网络类型变化时(通过`AllowedNetworkTypesListener`) - **UI更新**:重新渲染Preference界面和状态 #### 4. 生命周期管理 ```java @OnLifecycleEvent(ON_START) public void onStart() { mAllowedNetworkTypesListener.register(mContext, mSubId); } @OnLifecycleEvent(ON_STOP) public void onStop() { mAllowedNetworkTypesListener.unregister(mContext, mSubId); } ``` - **最佳实践**:在界面可见时注册监听器,不可见时注销,优化系统资源使用 ### 四、UI交互逻辑 #### 1. 跳转网络选择页面 ```java @Override public void updateState(Preference preference) { // ... Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, SETTINGS_PACKAGE_NAME + ".Settings$NetworkSelectActivity"); intent.putExtra(Settings.EXTRA_SUB_ID, mSubId); preference.setIntent(intent); } ``` - **功能实现**:设置点击事件跳转到网络选择Activity - **参数传递**:携带当前SIM卡的订阅ID #### 2. 状态显示控制 ```java @Override public CharSequence getSummary() { final ServiceState ss = mTelephonyManager.getServiceState(); return (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) ? MobileNetworkUtils.getCurrentCarrierNameForDisplay(mContext, mSubId) : mContext.getString(R.string.network_disconnected); } ``` - **显示规则**: - 网络连接正常 → 显示运营商名称(通过`getCurrentCarrierNameForDisplay`获取) - 无网络连接 → 显示"网络未连接" ### 五、设计模式分析 1. **观察者模式**: - 实现`OnNetworkSelectModeListener`监听网络选择模式变化 - 使用`AllowedNetworkTypesListener`监听网络类型变化 2. **生命周期感知**: - 通过`@OnLifecycleEvent`注解实现界面生命周期的精准管理 3. **条件控制渲染**: ```java preference.setEnabled(mCacheOfModeStatus != TelephonyManager.NETWORK_SELECTION_MODE_AUTO); ``` - 仅在手动选择模式时启用设置项 ### 六、潜在优化点 1. **状态缓存更新**:可考虑使用`LiveData`实现更高效的状态管理 2. **依赖注入**:使用Hilt等框架改进`TelephonyManager`的实例化方式 3. **国际化处理**:`R.string.network_disconnected`需要确保多语言支持 该控制器通过整合生命周期管理、网络状态监听和UI交互逻辑,实现了动态的网络选择配置功能,是典型的Android设置项实现方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值