一、系统设置首页 代码路径:packages/app/Settings/
1 主界面加载: 1 2 3 4 5 6 7 8 9 10 11 12 <!-- 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>
Settings的主界面是Settings.java,但是从Settings.java来看,除了大量的静态类继承SettingsActivity,就无其他有效信息了。但看其xml定义可以发现targetActivity属性,实质应是SettingsHomepageActivity.java。 先看其xml配置:
1 2 3 4 5 6 7 8 9 10 11 <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,主要从onCreate()方法开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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 , 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()) { 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <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 >
主界面布局中主要包含三部分:两个FrameLayout,一个顶部快捷搜索栏。其中Id为main_content的FrameLayout就是用来显示主设置内容的,即Settings的一级菜单项界面。.homepage.SettingsHomepageActivity 中的逻辑并不复杂,直接加载了TopLevelSettings这个Fragment。
1 showFragment(new TopLevelSettings (), R.id.main_content);
TopLevelSettings通过AndroidX的Preference来展示设置项列表,设置项列表的内容通过静态配置+动态添加的方式获取。 后面分开分析:SettingsActivity.java、DashboardFragment.java。
2 SettingsActivity.java Settings 继承了 SettingsActivity,有着大量的静态类,但其中并没有实现任何逻辑,那它是怎么加载到自己应有的布局的呢? 其实这些Activity的逻辑都是在SettingsActivity中实现。 在父类SettingsActivity的onCreate()中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override protected void onCreate (Bundle savedState) { super .onCreate(savedState); long startTime = System.currentTimeMillis(); final FeatureFactory factory = FeatureFactory.getFactory(this ); mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this ); mMetricsFeatureProvider = factory.getMetricsFeatureProvider(); getMetaData(); final Intent intent = getIntent(); if (intent.hasExtra(EXTRA_UI_OPTIONS)) { getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0 )); } final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) || intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false ); ... ... if (savedState != null ) { ... ... } else { launchSettingFragment(initialFragmentName, isSubSettings, intent); } ... ... }
第一步: 首先通过getMetaData()获取该Activity在manifest中配置的fragment, 并赋值给mFragmentClass。
1 2 3 4 5 6 7 8 9 10 11 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) { Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); } }
第二步: 通过getIntent()方法、getStartingFragmentClass()方法筛选出要启动的Fragment。 第三步: 通过launchSettingFragment()启动对应Fragment,这里的initialFragmentName参数就是第二步Intent中包含的EXTRA_SHOW_FRAGMENT参数,mFragmentClass不为空的情况下传入的就是mFragmentClass。
3 DashboardFragment.java 通过上面知道,SettingsHomepageActivity 直接加载了TopLevelSettings这个Fragment。而该Fragment继承了DashboardFragment,先来看TopLevelSettings的构造方法:
1 2 3 4 5 6 public TopLevelSettings () { final Bundle args = new Bundle (); args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false ); setArguments(args); }
可以看到构造方法中仅设置了个标志位,再根据framgments生命周期先来看onAttach()方法:
1 2 3 4 5 @Override public void onAttach (Context context) { super .onAttach(context); use(SupportPreferenceController.class).setActivity(getActivity()); }
调用父类DashboardFragment.java的onAttach()方法,此方法主要是完成mPreferenceControllers的加载。 接着看onCreate()方法,因为TopLevelSettings未重写父类的方法,所以直接看父类DashboardFragment的onCreate()方法。
1 2 3 4 5 6 7 8 9 10 11 12 @Override public void onCreate (Bundle icicle) { super .onCreate(icicle); getPreferenceManager().setPreferenceComparisonCallback( new PreferenceManager .SimplePreferenceComparisonCallback()); if (icicle != null ) { updatePreferenceStates(); } }
根据log定位发现,其后调用DashboardFragment.java的onCreatePreferences()方法:这里我也不知道怎么调用到这来的,哈哈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override public void onCreatePreferences (Bundle savedInstanceState, String rootKey) { refreshAllPreferences(getLogTag()); } private void refreshAllPreferences (final String TAG) { final PreferenceScreen screen = getPreferenceScreen(); if (screen != null ) { screen.removeAll(); } 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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)); }
首先调用getPreferenceScreenResId()方法获取所要加载的xml的ID:
1 2 @Override protected abstract int getPreferenceScreenResId () ;
最终回调用到子类TopLevelSettings.java的getPreferenceScreenResId()方法:
1 2 3 4 @Override protected int getPreferenceScreenResId () { return R.xml.top_level_settings; }
此主要是调用androidX Preference的addPreferencesFromResource()方法。此方法主要是将preferenceScreen下所有Preference添加到ArrayList中,然后再根据此集合构建生成PreferenceGroupAdapter,最后将此adapter设置到listview中,完成数据绑定,从而完成界面加载。在这里就要明白mPreferenceControllers是什么,在哪初始化的? 我们很快就可以找到:在onAttach()中添加的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 final List<AbstractPreferenceController> controllers = new ArrayList <>();final List<AbstractPreferenceController> controllersFromCode = createPreferenceControllers(context); final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper .getPreferenceControllersFromXml(context, getPreferenceScreenResId()); final List<BasePreferenceController> uniqueControllerFromXml = PreferenceControllerListHelper.filterControllers( controllersFromXml, controllersFromCode); if (controllersFromCode != null ) { controllers.addAll(controllersFromCode); } controllers.addAll(uniqueControllerFromXml); 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); }
可以发现:
从代码中加载preference controllers,调用createPreferenceControllers()方法;
从xml定义中加载preference controllers,调用getPreferenceControllersFromXml()方法。
过滤重复定义的controller等,赋值填充mPreferenceControllers。
再回到displayResourceTiles()方法中的:
1 2 mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach( controller -> controller.displayPreference(screen));
此语句主要就是调用各个controller的displayPreference()方法。 以网络和互联网菜单项为例,xml中配置的controller为”com.android.settings.network.TopLevelNetworkEntryPreferenceController”,查看TopLevelNetworkEntryPreferenceController.java发现,其内并未实现displayPreference()方法,查看继承关系:是继承BasePreferenceController的,接着查看BasePreferenceController中的displayPreference()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public void displayPreference (PreferenceScreen screen) { super .displayPreference(screen); if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) { final Preference preference = screen.findPreference(getPreferenceKey()); if (preference != null ) { preference.setEnabled(false ); } } }
又是调用BasePreferenceController父类AbstractPreferenceController中的displayPreference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 ); if (this instanceof Preference.OnPreferenceChangeListener) { final Preference preference = screen.findPreference(prefKey); preference.setOnPreferenceChangeListener( (Preference.OnPreferenceChangeListener) this ); } } else { setVisible(screen, prefKey, false ); } }
getPreferenceKey()获取preference的key,会调用到子类BasePreferenceController.java的getPreferenceKey()方法:1 2 3 4 @Override public String getPreferenceKey () { return mPreferenceKey; }
而据上面分析到mPreferenceKey实质上即为xml中每个preference配置的android:key属性的值,即此处应为”top_level_network”。(以网络和互联网菜单项为例)
isAvailable();判断此preference是否可用即是否应该被显示。如果返回true,则被显示出来,反之则不被显示,最终也会调用到BasePreferenceController.java的isAvailable()方法:1 2 3 4 5 6 7 @Override public final boolean isAvailable () { final int availabilityStatus = getAvailabilityStatus(); return (availabilityStatus == AVAILABLE || availabilityStatus == AVAILABLE_UNSEARCHABLE || availabilityStatus == DISABLED_DEPENDENT_SETTING); }
注意:看这里的BasePreferenceController.java中的isAvailable()方法中的getAvailabilityStatus(),一直跟进去,会发现调用的是:BasePreferenceController子类TopLevelNetworkEntryPreferenceController.java的getAvailabilityStatus()方法:
1 2 3 4 @Override public int getAvailabilityStatus () { return Utils.isDemoUser(mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE_UNSEARCHABLE; }
调用setVisible()方法设置是否可被显示:setVisible(screen, prefKey, true /* visible */);
frameworks/base/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
1 2 3 4 5 6 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接口,是,则设置监听。 综上,如果希望preference不被显示在界面上,可以通过实现相关preference的controller的getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可。
继续看查看BasePreferenceController.java的displayPreference()方法的剩余语句:1 2 3 4 5 6 7 if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) { final Preference preference = screen.findPreference(getPreferenceKey()); if (preference != null ) { preference.setEnabled(false ); } }
根据子类controller实现的getAvailabilityStatus()方法的返回值判断是否需要将此preference置为不可点击。 至此,DashboardFragment.java中displayResourceTiles()方法分析完成。
总结:
Settings的主Activity实质实现是在SettingsHomepageActivity.java内;
Settings的主界面设置item的显示是在fragment上,fragment为TopLevelSettings.java,加载显示的布局为top_level_settings.xml;
Settings主界面设置项item的加载显示主要分为两部分,一部分是xml定义的静态加载,xml为top_level_settings.xml;一部分是DashboardCategory来获取动态加载。
每个设置项item均为一个preference,通过xml定义加载时,必须要有一个controller,可以是在xml中定义”settings:controller”属性声明,名称必须与类的包名路径相同;也可直接在相关fragment中实现createPreferenceControllers()方法去调用构造相关controller。此二者存其一即可。
xml中配置preference时,必须定义”android:key“属性;
需要隐藏不显示某个设置项时,一是可以直接在xml中注释其定义;二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可;
如果需要某个设置项不可点击,一是可以直接调用setEnabled()。二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值为DISABLED_DEPENDENT_SETTING即可。
本文链接: http://longzhiye.top/2023/11/19/2023-11-19/