学习条记:参考资源:https://blog.csdn.net/Otaku_627/article/details/108618647
相识更多:https://blog.csdn.net/Otaku_627/article/details/108843487
一、体系设置首页
代码路径:packages/app/Settings/
1 主界面加载:
<!-- 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设置:
<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()方法开始:
@Overrideprotected 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:
<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" androidrientation="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。
showFragment(new TopLevelSettings(), R.id.main_content);TopLevelSettings通过AndroidX的Preference来展示设置项列表,设置项列表的内容通过静态设置+动态添加的方式获取。
背面分开分析:SettingsActivity.java、DashboardFragment.java。
2 SettingsActivity.java
Settings 继承了 SettingsActivity,有着大量的静态类,但此中并没有实现任何逻辑,那它是怎么加载到本身应有的布局的呢?
着实这些Activity的逻辑都是在SettingsActivity中实现。
在父类SettingsActivity的onCreate()中:
@Override protected void onCreate(Bundle savedState) { super.onCreate(savedState); long startTime = System.currentTimeMillis(); //工厂类实现方法com.android.settings.overlay.FeatureFactoryImpl.java final FeatureFactory factory = FeatureFactory.getFactory(this); //获取菜单信息的工厂类,实现类为DashboardFeatureProviderImpl.java mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this); mMetricsFeatureProvider = factory.getMetricsFeatureProvider(); // 第一步 从intent信息中获取<meta-data/>标署名为"com.android.settings.FRAGMENT_CLASS"的值(下文用于加载Fragment的类名) getMetaData(); // 第二步 final Intent intent = getIntent(); if (intent.hasExtra(EXTRA_UI_OPTIONS)) { getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); } //获取上面getMetaData()得到的类名 final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); //是否为快捷进入方式(如从别的的应用进入Settings的某个设置项) mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) || intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false); ... ... if (savedState != null) { ... ... } else { // 第三步 加载布局 launchSettingFragment(initialFragmentName, isSubSettings, intent); } ... ... } 第一步:
起首通过getMetaData()获取该Activity在manifest中设置的fragment, 并赋值给mFragmentClass。
private void getMetaData() { try { ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); if (ai == null || ai.metaData == null) return; mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS); } catch (NameNotFoundException nnfe) { // No recovery Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString()); } }第二步:
通过getIntent()方法、getStartingFragmentClass()方法筛选出要启动的Fragment。
第三步:
通过launchSettingFragment()启动对应Fragment,这里的initialFragmentName参数就是第二步Intent中包罗的EXTRA_SHOW_FRAGMENT参数,mFragmentClass不为空的环境下传入的就是mFragmentClass。
3 DashboardFragment.java
通过上面知道,SettingsHomepageActivity 直接加载了TopLevelSettings这个Fragment。而该Fragment继承了DashboardFragment,先来看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()方法,由于TopLevelSettings未重写父类的方法,以是直接看父类DashboardFragment的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(); } }根据log定位发现,其后调用DashboardFragment.java的onCreatePreferences()方法:这里我也不知道怎么调用到这来的,哈哈。
@Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { refreshAllPreferences(getLogTag()); } /** * Refresh all preference items, including both static prefs from xml, and dynamic items from * DashboardCategory. */ private void refreshAllPreferences(final String TAG) { final PreferenceScreen screen = getPreferenceScreen(); // First remove old preferences. if (screen != null) { // Intentionally do not cache PreferenceScreen because it will be recreated later. screen.removeAll(); } // Add resource based tiles. displayResourceTiles(); refreshDashboardTiles(TAG); final Activity activity = getActivity(); if (activity != null) { Log.d(TAG, "All preferences added, reporting fully drawn"); activity.reportFullyDrawn(); } updatePreferenceVisibility(mPreferenceControllers); }以看到此方法重要是用来加载体现的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 <= 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:
@Override protected abstract int getPreferenceScreenResId();终极回调用到子类TopLevelSettings.java的getPreferenceScreenResId()方法:
@Override protected int getPreferenceScreenResId() { return R.xml.top_level_settings; }此重要是调用androidX Preference的addPreferencesFromResource()方法。此方法重要是将preferenceScreen下全部Preference添加到ArrayList中,然后再根据此聚集构建天生PreferenceGroupAdapter,末了将此adapter设置到listview中,完成数据绑定,从而完成界面加载。在这里就要明白mPreferenceControllers是什么,在哪初始化的?
我们很快就可以找到:在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); }可以发现:
1、从代码中加载preference controllers,调用createPreferenceControllers()方法;
2、从xml界说中加载preference controllers,调用getPreferenceControllersFromXml()方法。
3、过滤重复界说的controller等,赋值添补mPreferenceControllers。
再回到displayResourceTiles()方法中的:
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()方法。
/** * 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); } } }又是调用BasePreferenceController父类AbstractPreferenceController中的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 */); } }1、getPreferenceKey()获取preference的key,会调用到子类BasePreferenceController.java的getPreferenceKey()方法:
@Override public String getPreferenceKey() { return mPreferenceKey; }而据上面分析到mPreferenceKey实质上即为xml中每个preference设置的android:key属性的值,即此处应为"top_level_network"。(以网络和互联网菜单项为例)
2、isAvailable();判断此preference是否可用便是否应该被体现。假如返回true,则被体现出来,反之则不被体现,终极也会调用到BasePreferenceController.java的isAvailable()方法:
@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()方法:
@Override public int getAvailabilityStatus() { return Utils.isDemoUser(mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE_UNSEARCHABLE; }3、 调用setVisible()方法设置是否可被体现:setVisible(screen, prefKey, true /* visible */);
// frameworks/base/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) { final Preference pref = group.findPreference(key); if (pref != null) { pref.setVisible(isVisible); } }4、判断controller是否实现了Preference.OnPreferenceChangeListener接口,是,则设置监听。
综上,假如盼望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()。二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值为DISABLED_DEPENDENT_SETTING即可。 |