Android 10.0 Settings源码分析之主界面加载(一)

您所在的位置:网站首页 android10118 Android 10.0 Settings源码分析之主界面加载(一)

Android 10.0 Settings源码分析之主界面加载(一)

2024-04-06 17:30| 来源: 网络整理| 查看: 265

本篇主要记录AndroidQ Settings源码主界面加载流程,方便后续工作调试其流程。由于篇幅较长,本篇主要记录主界面xml静态加载。

代码路径:

packages/app/Settings/

主界面加载:

从清单文件AndroidManifest.xml中入手:

Settings的主界面是Settings.java: 在这里插入图片描述从Settings.java来看,除了大量的静态类继承SettingsActivity,就无其他有效信息了。但看其xml定义可以发现targetActivity属性,实质应是SettingsHomepageActivity.java。 先看其xml配置:

SettingsHomepageActivity.java: 主要从onCreate()方法开始:

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

可以看到主界面的layout为settings_homepage_container.xml:

主界面布局中主要包含三部分:两个FrameLayout,一个顶部快捷搜索栏。其中Id为main_content的FrameLayout就是用来显示主设置内容的,即Settings的一级菜单项界面。 由于本篇主要记录主界面加载流程,所以主要看main_content。 回到onCreate()方法:

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

启动TopLevelSettings的fragment,此fragments主要继承于DashboardFragment.java,先来看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); }

可以看到构造方法中仅设置了个标志位,再根据framgments生命周期先来看onAttach()方法:

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

调用父类DashboardFragment.java的onAttach()方法,此方法主要是完成mPreferenceControllers的加载。 onCreate()方法:

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

第一次进入时,icicle为null,具体应该看引用的父类的onCreate()方法,由于此篇主要说主界面加载,故暂不展开。 根据log定位发现,其后调用DashboardFragment.java的onCreatePreferences()方法:

@Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { refreshAllPreferences(getLogTag()); }

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

可以看到此方法主要是用来加载显示的preference items,主要分为两部分,一个是静态xml定义的prefs(调用displayResourceTiles()方法),另一部分是从DashboardCategory动态加载(调用refreshDashboardTiles(TAG)方法,其中TAG为 “TopLevelSettings”)。

displayResourceTiles()

此方法主要是从xml资源文件中加载显示prefs:

/** * Displays resource based tiles. */ private void displayResourceTiles() { final int resId = getPreferenceScreenResId(); if (resId return R.xml.top_level_settings; }

可以看到Settings主界面加载的xml文件是top_level_settings,其内主要配置的是一些Preference菜单项如网络和互联网、已连接的设备、应用和通知、电池等等。以网络和互联网菜单项为例,xml配置如下:

1、key定义此preference的唯一性ID; 2、title定义标题,此字串显示网络和互联网; 3、summary,此显示WLAN、移动网络、流量使用和热点; 4、icon,定义图标; 5、order,加载显示优先级,order为负时,绝对值越高,界面显示越靠前;order为正时,值越高,显示越靠后; 6、fragment,定义点击此preference所跳转的fragment界面; 7、controller,控制管理类。 相关属性配置后,在机器设备上显示的实际效果如下: 在这里插入图片描述 再回到displayResourceTiles()中,继续来看:

addPreferencesFromResource(resId);

此主要是调用androidX Preference的addPreferencesFromResource()方法,由于androidX无源码不详细展开。此方法主要是将preferenceScreen下所有Preference添加到ArrayList中,然后再根据此集合构建生成PreferenceGroupAdapter,最后将此adapter设置到listview中,完成数据绑定,从而完成界面加载。 继续看displayResourceTiles()余下逻辑:

final PreferenceScreen screen = getPreferenceScreen(); screen.setOnExpandButtonClickListener(this); mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach( controller -> controller.displayPreference(screen));

先来看mPreferenceControllers是什么:

private final Map mPreferenceControllers = new ArrayMap(); protected void addPreferenceController(AbstractPreferenceController controller) { if (mPreferenceControllers.get(controller.getClass()) == null) { mPreferenceControllers.put(controller.getClass(), new ArrayList()); } mPreferenceControllers.get(controller.getClass()).add(controller); }

可以看到主要是在addPreferenceController()方法里面去完成赋值的。而调用此方法主要是在onAttach()中:

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

1、定义集合controllers; 2、从代码中加载preference controllers,调用createPreferenceControllers()方法:

/** * Get a list of {@link AbstractPreferenceController} for this fragment. */ protected List createPreferenceControllers(Context context) { return null; }

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

final List controllersFromXml = PreferenceControllerListHelper .getPreferenceControllersFromXml(context, getPreferenceScreenResId());

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

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

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

从而执行异常内语句,首先会再去读取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的构造方法,初始化其他变量完成构造: public BasePreferenceController(Context context, String preferenceKey) { super(context); mPreferenceKey = preferenceKey; if (TextUtils.isEmpty(mPreferenceKey)) { throw new IllegalArgumentException("Preference key must be set"); } }

赋值mPreferenceKey; controller构造方法的相关堆栈调用如下: 在这里插入图片描述

综上所述,故getPreferenceControllersFromXml()方法主要是获取xml中每个preference定义的“settings:controller”属性配置的controller name,通过此name去构造相应的controller类,将其添加到集合中并返回。

4、过滤重复定义的controller等,赋值填充mPreferenceControllers。

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

再回到displayResourceTiles()方法:

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

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

public class TopLevelNetworkEntryPreferenceController extends BasePreferenceController

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

/** * 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()方法:

/** * 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 */); } } getPreferenceKey()获取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. * *

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

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

* 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即可。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3