Settings中主界面加载流程(一级菜单 静态加载)

Settings中主界面加载流程(一级菜单 静态加载)

1 SettingsHomepageActivity

在这个之中的onCreate方法有

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

启动了TopLevelSettings这个类,此类又是继承于DashboardFragment。先去查看它本身的构造方法。

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

可以看到通过构造方法传递了一个参数,从注释中可以看出,该参数的用意是由于主界面使用完整的搜索视图所以在主界面的actionbar中隐藏了搜索图标。然后再根据framgments生命周期先来看onAttach()方法:。然后再去查看该类中的onAttach方法。

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

它因为是继承DashboardFragment,我去看该父类的onAttach方法。

3 DashboardFragment
@Overridepublic 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);    // 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) {        mControllers.addAll(controllersFromCode);    }    mControllers.addAll(uniqueControllerFromXml);    // And wire up with lifecycle.    final Lifecycle lifecycle = getSettingsLifecycle();    uniqueControllerFromXml.forEach(controller -> {        if (controller instanceof LifecycleObserver) {            lifecycle.addObserver((LifecycleObserver) controller);        }    });    // Set metrics category for BasePreferenceController.    final int metricCategory = getMetricsCategory();    mControllers.forEach(controller -> {        if (controller instanceof BasePreferenceController) {            ((BasePreferenceController) 
controller).setMetricsCategory(metricCategory);        }    });    mPlaceholderPreferenceController =            new DashboardTilePlaceholderPreferenceController(context);    mControllers.add(mPlaceholderPreferenceController);    for (AbstractPreferenceController controller : mControllers) {        addPreferenceController(controller);    }}

通过方法注释可以知道该方法主要是完成preference controllers的加载。再去看该类的oncreate方法。
在这里有一个mDashboardFeatureProvider,它提供的数据就是一级菜单这些

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

可以通过注释看见,是设置ComparisonCallback,以便在列表获得更好的动画。
第一次进入时,icicle为null,根据log定位发现,其后调用DashboardFragment.java的onCreatePrefern方法(这个是我打印了log)

@Overridepublic void onCreatePreferences(Bundle savedInstanceState, String rootKey) {    Log.d("wuzhangxiao", "wuzhangxiao    onCreatePreferences");    checkUiBlocker(mControllers);    refreshAllPreferences(getLogTag());    mControllers.stream()            .map(controller -> (Preference) 
findPreference(controller.getPreferenceKey()))            .filter(Objects::nonNull)            .forEach(preference -> {                // Give all controllers a chance to handle click.                /**                 * 给所有控制器一个处理点击的机会。                 */                preference.getExtras().putInt(CATEGORY, getMetricsCategory());            });}

这个里面给所有的菜单点击事件,再去查看refreshAllPreferences方法。

/** * Refresh all preference items, including both static prefs from xml, and dynamic 
items from * DashboardCategory. * 刷新所有首选项,包括来自 xml 的静态首选项和来自 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.        /**         * 有意不要缓存 PreferenceScreen,因为稍后会重新创建它。         */        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);}

刷新所有preference items,包括来自xml的静态preference和来自DashboardCategory的动态preference,静态xml定义的prefs(调用displayResourceTiles()方法),动态DashboardCategory动态加载(调用refreshDashboardTiles(TAG)方法,其中TAG为 “TopLevelSettings”)。displayResourceTiles():此方法主要是从xml资源文件中加载显示prefs

再去查看该方法调用了一个方法。displayResourceTiles,此方法主要从xml资源文件中加载prefs。

/** * Displays resource based tiles. * 显示基于资源的图块。 */
private void displayResourceTiles() {    final int resId = getPreferenceScreenResId();    if (resId <= 0) {        return;    }    addPreferencesFromResource(resId);    final PreferenceScreen screen = getPreferenceScreen();    screen.setOnExpandButtonClickListener(this);    displayResourceTilesToScreen(screen);}

我去查看getPreferenceScreenResId,我又发现它这个方法是
@Overrideprotected abstract int getPreferenceScreenResId(); 它又由TopLevelSettings继承,该类肯定会去实现

TopLevelSettings

`@Overrideprotected int getPreferenceScreenResId() {    return R.xml.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"/>

1、key定义此preference的唯一性ID;
2、title定义标题,此字串显示网络和互联网;
3、summary,此显示WLAN、移动网络、流量使用和热点;
4、icon,定义图标;
5、order,加载显示优先级,order为负时,绝对值越高,界面显示越靠前;order为正时,值越高,显示越靠后;
6、fragment,定义点击此preference所跳转的fragment界面;
7、controller,控制管理类。
相关属性配置后,在机器设备上显示的实际效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lcnDvHEP-1657868814715)(en-resource://database/1452:1)]

但是在这里我有个疑问,因为在这个布局中有着许多的菜单,但是没有显示完全,肯定还有别的方式进行显示。再回去看displayResourceTiles

displayResourceTiles

这个方法中有一个
addPreferencesFromResource(resId); 看这个类的在执行什么

SettingsPreferenceFragment addPreferencesFromResource
@Override
public void addPreferencesFromResource
(@XmlRes int preferencesResId) {    super.addPreferencesFromResource(preferencesResId);    checkAvailablePrefs(getPreferenceScreen());}
@VisibleForTesting
void checkAvailablePrefs(PreferenceGroup preferenceGroup) {   
if (preferenceGroup == null) return;    for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {        Preference pref = preferenceGroup.getPreference(i);        if (pref instanceof SelfAvailablePreference                && !((SelfAvailablePreference) pref).isAvailable(getContext())) {            pref.setVisible(false);        } else if (pref instanceof PreferenceGroup) {            checkAvailablePrefs((PreferenceGroup) pref);        }    }}

此方法主要是将preferenceScreen下所有Preference添加到ArrayList中,然后再根据此集合构建生成PreferenceGroupAdapter,最后将此adapter设置到listview中,完成数据绑定,从而完成界面加载。
还是去查看displayResourceTiles类中的方法。

displayResourceTiles
private void displayResourceTiles()
{    final int resId = getPreferenceScreenResId();  
if (resId <= 0) {        return;    }  
addPreferencesFromResource(resId);  
final PreferenceScreen screen = getPreferenceScreen();    screen.setOnExpandButtonClickListener(this);  
displayResourceTilesToScreen(screen);}

查看displayResourceTilesToScreen方法

displayResourceTilesToScreen
protected void displayResourceTilesToScreen(PreferenceScreen screen) {    mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(            controller -> controller.displayPreference(screen));}


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

在这里时再去查看onAttach方法

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

在这里又调用了addPreferenceController方法,而刚好与上面的静态加载相关。

addPreferenceController
protected void addPreferenceController(AbstractPreferenceController controller) {
    if (mPreferenceControllers.get(controller.getClass()) == null) {
        mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
    }
    mPreferenceControllers.get(controller.getClass()).add(controller);
}

而onAttach方法的作用是
1、定义集合controllers
2、从代码中加载preference controllers,调用createPreferenceControllers方法

/**
 * Get a list of {@link AbstractPreferenceController} for this fragment.
 获取此片段的 {@link AbstractPreferenceController} 列表。
 */
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
    return null;
}

这个是抽象方法,具体的实现是在子类中,上面分析可知应是子类TopLevelSettings.java实现,由于TopLevelSettings未实现此方法,故此返回null。
3、从xml定义中加载preference controllers,调用

// Load preference controllers from xml definition  从 xml 定义加载首选项控制器final List<BasePreferenceController> controllersFromXml = 
PreferenceControllerListHelper        .getPreferenceControllersFromXml(context, getPreferenceScreenResId());

这个时候的getPreferenceScreenResId()根据上面分析,加载的xml应是top_level_settings.xml,调用getPreferenceControllersFromXml()方法:

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

主要读取xml中配置的每个preference的METADATA_CONTROLLER即(“settings:controller”)属性,以上述网络和互联网菜单项为例,读取的即为"com.android.settings.network.TopLevelNetworkEntryPreferenceController";
首先根据此去调用BasePreferenceController.java的createInstance方法,即调用TopLevelNetworkEntryPreferenceController.java的带一个参数的构造方法:

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

而TopLevelNetworkEntryPreferenceController.java中只包含了一个带两个参数的构造函数,故执行此方法应会抛出异常。

TopLevelNetworkEntryPreferenceController

从而执行异常内语句,首先会再去读取xml中配置的每个preference的METADATA_KEY即(android:key)属性,同样的再据此去调用TopLevelNetworkEntryPreferenceController.java的构造函数:

public TopLevelNetworkEntryPreferenceController(Context context, String preferenceKey) {
    super(context, preferenceKey);
    mMobileNetworkPreferenceController = new MobileNetworkPreferenceController(mContext);
    mTetherPreferenceController = new TetherPreferenceController(
            mContext, null /* lifecycle */);
    mWifiPreferenceController = new WifiMasterSwitchPreferenceController(
            mContext, null /* metrics */);
}

这个时候的preferenceKey即为xml中配置的android:key属性的值,为"top_level_network"。调用父类BasePreferenceController.java的构造方法,初始化其他变量完成构造:

BasePreferenceController
public BasePreferenceController(Context context, String preferenceKey) {
    super(context);
    mPreferenceKey = preferenceKey;
    if (TextUtils.isEmpty(mPreferenceKey)) {
        throw new IllegalArgumentException("Preference key must be set");
    }
}

赋值mPreferenceKey,在这个时候看一下controller构造方法的相关堆栈调用如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-myRKde08-1657868814716)(en-resource://database/1454:1)]

在这里我就知道了,故getPreferenceControllersFromXml()方法主要是获取xml中每个preference定义的“settings:controller”属性配置的controller name,通过此name去构造相应的controller类,将其添加到集合中并返回。
4、过滤重复定义的controller等,赋值填充mPreferenceControllers。

故mPreferenceControllers主要是各种控制管理类的集合,包含xml中配置的每个preference的“settings:controller”属性和代码中通过createPreferenceControllers()方法构建的。
这个就是走完了
再回到displayResourceTiles()方法:

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

这个语句就是主要调用各个controller的displayPreference
此语句主要就是调用各个controller的displayPreference()方法。
依旧以网络和互联网菜单项为例,xml中配置的controller为"com.android.settings.network.TopLevelNetworkEntryPreferenceController",查看TopLevelNetworkEntryPreferenceController.java发现,其内并未实现displayPreference()方法,查看继承关系:

TopLevelNetworkEntryPreferenceController
public class TopLevelNetworkEntryPreferenceController extends BasePreferenceController

查看BasePreferenceController.java的displayPreference()方法:

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

1.继续调用父类的displayPreference方法,而它的继承关系如下

public abstract class BasePreferenceController extends 
AbstractPreferenceController implements        Sliceable {

AbstractPreferenceController.java的displayPreference()方法:

AbstractPreferenceController
/**
 * 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.getPrefersenceKey获取preference的key

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

BasePreferenceController.java的getPreferenceKey()方法:

@Override
public String getPreferenceKey() {
    return mPreferenceKey;
}

而据上面分析到mPreferenceKey实质上即为xml中每个preference配置的android:key属性的值,即此处应为"top_level_network"。

isAvailable();判断此preference是否可用即是否应该被显示。如果返回true,则被显示出来,反之则不被显示:
/**
* Returns true if preference is available (should be displayed)
*/
public abstract boolean isAvailable();

抽象方法,继续看子类BasePreferenceController.java的实现:

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

调用getAvailabilityStatus()方法:

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

抽象方法,按照上述举例,继续查看子类TopLevelNetworkEntryPreferenceController.java的getAvailabilityStatus()方法:

@Override
public int getAvailabilityStatus() {
    return Utils.isDemoUser(mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE_UNSEARCHABLE;
}

调用setVisible()方法设置是否可被显示:
setVisible(screen, prefKey, true /* visible */);

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

判断controller是否实现了Preference.OnPreferenceChangeListener接口,是,则设置监听:
if (this instanceof Preference.OnPreferenceChangeListener) {
final Preference preference = screen.findPreference(prefKey);
preference.setOnPreferenceChangeListener(
(Preference.OnPreferenceChangeListener) this);
}

综上,如果希望preference不被显示在界面上,可以通过实现相关preference的controller的getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可。

2、继续看查看BasePreferenceController.java的displayPreference()方法的剩余语句:

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

根据子类controller实现的getAvailabilityStatus()方法的返回值判断是否需要将此preference置为不可点击。

至此,DashboardFragment.java中displayResourceTiles()方法分析完成。

总结:
1、Settings的主Activity实质实现是在SettingsHomepageActivity.java内;
2、Settings的主界面设置item的显示是在fragment上,fragment为TopLevelSettings.java,加载显示的布局为top_level_settings.xml;
3、Settings主界面设置项item的加载显示主要分为两部分,一部分是xml定义的静态加载,xml为top_level_settings.xml;一部分是DashboardCategory来获取动态加载,
4、每个设置项item均为一个preference,通过xml定义加载时,必须要有一个controller,可以是在xml中定义"settings:controller"属性声明,名称必须与类的包名路径相同;也可直接在相关fragment中实现createPreferenceControllers()方法去调用构造相关controller。此二者存其一即可。
5、xml中配置preference时,必须定义”android:key“属性;
6、需要隐藏不显示某个设置项时,一是可以直接在xml中注释其定义;二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可;
7、如果需要某个设置项不可点击,一是可以直接调用setEnabled():

        final Preference preference = screen.findPreference(getPreferenceKey());
        if (preference != null) {
            preference.setEnabled(false);
        }

二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值为DISABLED_DEPENDENT_SETTING即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值