android10 Settings源码解析

/packages/apps/Settings/AndroidManifest.xml


    
        
        
    
    


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



    
        
        
        
    
    

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



    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

        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> mPreferenceControllers =
            new ArrayMap<>();
    private final Set mDashboardTilePrefKeys = new ArraySet<>();

    private DashboardFeatureProvider mDashboardFeatureProvider;
    private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
    private boolean mListeningToCategoryChange;
    private SummaryLoader mSummaryLoader;
    private List 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 controllers = new ArrayList<>();
        // Load preference controllers from code
        final List controllersFromCode =
                createPreferenceControllers(context);
        // Load preference controllers from xml definition
        final List controllersFromXml = PreferenceControllerListHelper
                .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
        // Filter xml-based controllers in case a similar controller is created from code already.
        final List 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 controllers) {
         final List 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> 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 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 use(Class clazz) {
         List 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 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> controllerLists =
                 mPreferenceControllers.values();
         for (List 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> preferenceControllers) {
         final PreferenceScreen screen = getPreferenceScreen();
         if (screen == null || preferenceControllers == null || mBlockerController == null) {
             return;
         }
 
         final boolean visible = mBlockerController.isBlockerFinished();
         for (List 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 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 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 getPreferenceControllersFromXml(Context context,
            @XmlRes int xmlResId) {
        final List controllers = new ArrayList<>();
        List 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 filterControllers(
             @NonNull List input,
             List filter) {
         if (input == null || filter == null) {
             return input;
         }
         final Set keys = new TreeSet<>();
         final List 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.
     * 

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

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

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

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

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

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

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

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

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

* Will return true for {@link #AVAILABLE} and {@link #DISABLED_DEPENDENT_SETTING}. *

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

* 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 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 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是否可见。

你可能感兴趣的:(android)