Android 10.0 Settings loading process

1. System settings home page

Code path: packages/app/Settings/

1 Main interface loading:
    <!-- 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>

The main interface of Settings is Settings.java, but from Settings.java, except for a large number of static classes inheriting SettingsActivity, there is no other effective information. But looking at its xml definition, you can find the targetActivity attribute, which in essence should be SettingsHomepageActivity.java.
Let’s look at its xml configuration first:

        <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>

SettingsHomepageActivity.java, mainly starting from the onCreate() method:

@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);
}

You can see that the layout of the main interface is settings_homepage_container.xml:

<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/settings_homepage_container"
    android:fitsSystemWindows="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.core.widget.NestedScrollView
        android:id="@+id/main_content_scrollable_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior">
        <LinearLayout
            android:id="@+id/homepage_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:descendantFocusability="blocksDescendants">
            <FrameLayout
                android:id="@+id/contextual_cards_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="@dimen/contextual_card_side_margin"
                android:layout_marginEnd="@dimen/contextual_card_side_margin"/>
            <FrameLayout
                android:id="@+id/main_content"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:animateLayoutChanges="true"
                android:background="?android:attr/windowBackground"/>
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <include layout="@layout/search_bar"/>
    </com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

The main interface layout mainly contains three parts: two FrameLayouts and a top quick search bar. The FrameLayout whose Id is main_content is used to display the main settings content, that is, the first-level menu item interface of Settings. The logic in .homepage.SettingsHomepageActivity is not complicated, and the TopLevelSettings fragment is loaded directly.

showFragment(new TopLevelSettings(), R.id.main_content);

TopLevelSettings uses AndroidX's Preference to display the setting list. The content of the setting list is obtained through static configuration + dynamic addition.
will be analyzed separately later: SettingsActivity.java, DashboardFragment.java.

2 SettingsActivity.java

Settings inherits SettingsActivity and has a large number of static classes, but it does not implement any logic. So how does it load into its own layout?
In fact, these Activities The logic is all implemented in SettingsActivity.
In onCreate() of the parent class SettingsActivity:

    @Override  
    protected void onCreate(Bundle savedState) {
    
      
        super.onCreate(savedState);  
        long startTime = System.currentTimeMillis();  
        //工厂类实现方法com.android.settings.overlay.FeatureFactoryImpl.java  
        final FeatureFactory factory = FeatureFactory.getFactory(this);  
        //获取菜单信息的工厂类,实现类为DashboardFeatureProviderImpl.java  
        mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);  
        mMetricsFeatureProvider = factory.getMetricsFeatureProvider();  
 // 第一步    从intent信息中获取<meta-data/>标签名为"com.android.settings.FRAGMENT_CLASS"的值(下文用于加载Fragment的类名)  
        getMetaData();  
 // 第二步
      final Intent intent = getIntent();
      if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
    
    
          getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
      }
      
        //获取上面getMetaData()得到的类名  
        final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);  
        //是否为快捷进入方式(如从其它的应用进入Settings的某个设置项)  
        mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||  
                intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);  
        ... ...  
     
        if (savedState != null) {
    
      
          ... ...  
        } else {
    
      
 //  第三步   加载布局  
            launchSettingFragment(initialFragmentName, isSubSettings, intent);  
        }  
  
        ... ...  
    }

Step one:
First, obtain the fragment configured in the manifest of the Activity through getMetaData(), and assign it to mFragmentClass.

    private void getMetaData() {
    
    
        try {
    
    
            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
                    PackageManager.GET_META_DATA);
            if (ai == null || ai.metaData == null) return;
            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
        } catch (NameNotFoundException nnfe) {
    
    
            // No recovery
            Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
        }
    }

Second step:
Use the getIntent() method and getStartingFragmentClass() method to filter out the Fragment to be started.
Step 3:
Launch the corresponding Fragment through launchSettingFragment(). The initialFragmentName parameter here is the EXTRA_SHOW_FRAGMENT parameter included in the Intent in the second step. mFragmentClass is not empty. In this case, mFragmentClass is passed in.

3 DashboardFragment.java

As we know from the above, SettingsHomepageActivity directly loads the TopLevelSettings Fragment. This Fragment inherits DashboardFragment. Let’s first look at the construction method of 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);
    }

It can be seen that only a flag bit is set in the construction method. Let's first look at the onAttach() method according to the fragments life cycle:

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

Call the onAttach() method of the parent class DashboardFragment.java. This method mainly completes the loading of mPreferenceControllers.
Next, look at the onCreate() method. Because TopLevelSettings does not override the method of the parent class, we directly look at the onCreate() method of the parent class DashboardFragment.

    @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();
        }
    }

According to the log positioning, it was found that the onCreatePreferences() method of DashboardFragment.java was then called: I don’t know how to call it here, haha.

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
    
    
        refreshAllPreferences(getLogTag());
    }
    /**
     * 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);
    }

You can see that this method is mainly used to load the displayed preference items. It is mainly divided into two parts. One is the prefs defined in static xml (calling the displayResourceTiles() method), and the other part is dynamically loaded from DashboardCategory (calling refreshDashboardTiles(TAG) method, where TAG is "TopLevelSettings").
displayResourceTiles()
This method mainly loads display prefs from the xml resource file:

    /**
     * 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));
    }

First call the getPreferenceScreenResId() method to get the ID of the xml to be loaded:

    @Override
    protected abstract int getPreferenceScreenResId();

The final callback is to the getPreferenceScreenResId() method of the subclass TopLevelSettings.java:

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

This is mainly to call the addPreferencesFromResource() method of androidX Preference. This method mainly adds all the Preferences under the preferenceScreen to the ArrayList, then builds and generates the PreferenceGroupAdapter based on this collection, and finally sets this adapter to the listview to complete data binding and thus complete the interface loading. Here we need to understand what mPreferenceControllers are and where are they initialized?
We can quickly find: added in onAttach().

        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);
        }

It can be found:

  1. Load preference controllers from code and call the createPreferenceControllers() method;
  2. Load preference controllers from the xml definition and call the getPreferenceControllersFromXml() method.
  3. Filter duplicately defined controllers, etc., and fill mPreferenceControllers with values.

Back to the displayResourceTiles() method:

mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
                controller -> controller.displayPreference(screen));

This statement mainly calls the displayPreference() method of each controller.
Taking the network and Internet menu items as an example, the controller configured in xml is "com.android.settings.network.TopLevelNetworkEntryPreferenceController". Looking at TopLevelNetworkEntryPreferenceController.java, it is found that displayPreference(( ) method, check the inheritance relationship: it inherits BasePreferenceController, then check the displayPreference() method in BasePreferenceController.

    /**
     * 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);
            }
        }
    }

Again, the displayPreference in the BasePreferenceController parent class AbstractPreferenceController is called:

    /**
     * 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 */);
        }
    }
  1. getPreferenceKey() gets the preference key and calls the getPreferenceKey() method of the subclass BasePreferenceController.java:
    @Override
    public String getPreferenceKey() {
    
    
        return mPreferenceKey;
    }

According to the above analysis, mPreferenceKey is essentially the value of the android:key attribute of each preference configuration in xml, that is, it should be "top_level_network". (Take the network and Internet menu items as an example)
2. isAvailable(); determine whether this preference is available, that is, whether it should be displayed. If true is returned, it will be displayed, otherwise it will not be displayed, and the isAvailable() method of BasePreferenceController.java will eventually be called:

    @Override
    public final boolean isAvailable() {
    
    
        final int availabilityStatus = getAvailabilityStatus();
        return (availabilityStatus == AVAILABLE
                || availabilityStatus == AVAILABLE_UNSEARCHABLE
                || availabilityStatus == DISABLED_DEPENDENT_SETTING);
    }

Note: Look at getAvailabilityStatus() in the isAvailable() method in BasePreferenceController.java here. If you follow it, you will find that the call is: getAvailabilityStatus() method of BasePreferenceController subclass TopLevelNetworkEntryPreferenceController.java:

    @Override
    public int getAvailabilityStatus() {
    
    
        return Utils.isDemoUser(mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE_UNSEARCHABLE;
    }
  1. Call the setVisible() method to set whether it can be displayed: setVisible(screen, prefKey, true /* visible */);
    frameworks/base/packages/SettingsLib/src/com /android/settingslib/core/AbstractPreferenceController.java
    protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
    
    
        final Preference pref = group.findPreference(key);
        if (pref != null) {
    
    
            pref.setVisible(isVisible);
        }
    }
  1. Determine whether the controller implements the Preference.OnPreferenceChangeListener interface. If so, set the listener.
    In summary, if you want the preference not to be displayed on the interface, you can implement the getAvailabilityStatus() method of the relevant preference controller so that the return value of this method is not AVAILABLE, AVAILABLE_UNSEARCHABLE, or DISABLED_DEPENDENT_SETTING. .
  2. Continue to look at the remaining statements of the displayPreference() method of BasePreferenceController.java:
        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);
            }
        }

Determine whether this preference needs to be set to non-clickable based on the return value of the getAvailabilityStatus() method implemented by the subclass controller.
At this point, the analysis of the displayResourceTiles() method in DashboardFragment.java is completed.

Summarize:

  1. The actual implementation of the main Activity of Settings is in SettingsHomepageActivity.java;
  2. The main interface setting items of Settings are displayed on the fragment. The fragment is TopLevelSettings.java, and the loaded and displayed layout is top_level_settings.xml;
  3. The loading display of setting items in the Settings main interface is mainly divided into two parts. One part is static loading defined by xml, xml is top_level_settings.xml; the other part is DashboardCategory to obtain dynamic loading.
  4. Each setting item is a preference. When loading through XML definition, there must be a controller. You can define the "settings:controller" attribute declaration in XML. The name must be the same as the package name path of the class; you can also directly Implement the createPreferenceControllers() method in the relevant fragment to call and construct the relevant controller. Just save one of these two.
  5. When configuring preference in xml, the "android:key" attribute must be defined;
  6. When you need to hide or not display a setting item, firstly, you can comment its definition directly in xml; secondly, you can implement the getAvailabilityStatus() method in the controller class of the relevant setting item preference so that the return value of this method is not AVAILABLE or AVAILABLE_UNSEARCHABLE. , DISABLED_DEPENDENT_SETTING is enough;
  7. If you need a setting item to be unclickable, first, you can call setEnabled() directly. Second, you can implement the getAvailabilityStatus() method in the controller class of the relevant setting item preference, so that the return value of this method is DISABLED_DEPENDENT_SETTING.

Guess you like

Origin blog.csdn.net/u010345983/article/details/134181772