美文网首页Android进阶之路Android 源码浅析
Android 10.0 Settings 加载流程(一)

Android 10.0 Settings 加载流程(一)

作者: 孤街酒客0911 | 来源:发表于2022-07-12 12:05 被阅读0次

    学习笔记:参考资源: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()方法开始:

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

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

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

    相关文章

      网友评论

        本文标题:Android 10.0 Settings 加载流程(一)

        本文链接:https://www.haomeiwen.com/subject/bsnobrtx.html