美文网首页
Android 8 setting源码分析二

Android 8 setting源码分析二

作者: 梧叶已秋声 | 来源:发表于2019-12-20 18:21 被阅读0次

    下面以点进SystemDashboardActivity为例,也就是setting下的system这一部分的设置去简单分析一下二级菜单。

    //vendor\mediatek\proprietary\packages\xx\xx\src\com\android\settings\Settings.java
    public class Settings extends SettingsActivity {
    ......
        public static class SystemDashboardActivity extends SettingsActivity {}
    ......
    

    SystemDashboardActivity 是定义在Settings这个类(settings是启动activity)中的继承SettingsActivity的静态内部类。所以SystemDashboardActivity显示的部分依旧是定义在SettingsActivity中。所以这个结构是启动activity中定义了多个子activity?为什么要这样写?

     setContentView(mIsShowingDashboard ?
                    R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
    

    通过打印isSubSettings和mIsShowingDashboard的值可以判断。
    Log.d(TAG,"isSubSettings = " + String.valueOf(isSubSettings) + " , mIsShowingDashboard = " + String.valueOf(mIsShowingDashboard));

    打开setting后界面加载的是settings_main_dashboard.xml,点进SystemDashboardActivity加载的是settings_main_prefs,然后再点进去的就是SubSettings。
    settings_main_dashboard与settings_main_prefs的比较明显的差异如下所示。


    settings_main_dashboard settings_main_prefs 1
    2
    //vendor\mediatek\proprietary\packages\xx\xx\src\com\android\settings\SettingsActivity.java
    public class SettingsActivity extends SettingsDrawerActivity xxx{
    ........
        @VisibleForTesting
        void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
            if (!mIsShowingDashboard && initialFragmentName != null) {
                // UP will be shown only if it is a sub settings
                if (mIsShortcut) {
                    mDisplayHomeAsUpEnabled = isSubSettings;
                } else if (isSubSettings) {
                    mDisplayHomeAsUpEnabled = true;
                } else {
                    mDisplayHomeAsUpEnabled = false;
                }
                setTitleFromIntent(intent);
    
                Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
                Log.d(TAG,"initialFragmentName = " + initialFragmentName + " , initialArguments = " + initialArguments + " , mInitialTitleResId = " +
                        mInitialTitleResId + " , mInitialTitle = " + mInitialTitle);
                switchToFragment(initialFragmentName, initialArguments, true, false,
                    mInitialTitleResId, mInitialTitle, false);
            } else {
                // Show search icon as up affordance if we are displaying the main Dashboard
                mDisplayHomeAsUpEnabled = true;
                mInitialTitleResId = R.string.dashboard_title;
    
                switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
                    mInitialTitleResId, mInitialTitle, false);
            }
        }
    ........
    }
    

    当点进setting后调用的是switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false, mInitialTitleResId, mInitialTitle, false);,然后再点进system调用的是 switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);

    01-01 04:38:49.493 1167-1167/? D/SettingsActivity: initialFragmentName = com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment , initialArguments = Bundle[{show_drawer_menu=true, :settings:source_metrics=35, intent=Intent { flg=0x8000 cmp=com.android.settings/.Settings$ConnectedDeviceDashboardActivity (has extras) }}] , mInitialTitleResId = -1 , mInitialTitle = Connected devices
    01-01 04:38:53.164 1167-1167/? D/SettingsActivity: initialFragmentName = com.android.settings.applications.AppAndNotificationDashboardFragment , initialArguments = Bundle[{show_drawer_menu=true, :settings:source_metrics=35, intent=Intent { flg=0x8000 cmp=com.android.settings/.Settings$AppAndNotificationDashboardActivity (has extras) }}] , mInitialTitleResId = -1 , mInitialTitle = Apps & notifications
    01-01 04:45:53.917 1167-1167/? D/SettingsActivity: initialFragmentName = com.android.settings.system.SystemDashboardFragment , initialArguments = Bundle[{show_drawer_menu=true, :settings:source_metrics=35, intent=Intent { flg=0x8000 cmp=com.android.settings/.Settings$SystemDashboardActivity (has extras) }}] , mInitialTitleResId = -1 , mInitialTitle = System
    
    

    AndroidManifest中的FRAGMENT_CLASS定义了SystemDashboardActivity所对应的fragment,即SystemDashboardFragment。

    ......
            <activity android:name=".Settings$AppAndNotificationDashboardActivity"
                      android:label="@string/app_and_notification_dashboard_title"
                      android:icon="@drawable/ic_apps">
                <intent-filter android:priority="9">
                    <action android:name="com.android.settings.action.SETTINGS"/>
                </intent-filter>
                <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                           android:value="com.android.settings.applications.AppAndNotificationDashboardFragment"/>
                <meta-data android:name="com.android.settings.category"
                           android:value="com.android.settings.category.ia.homepage"/>
                <meta-data android:name="com.android.settings.summary"
                           android:resource="@string/app_and_notification_dashboard_summary"/>
            </activity>
    ......
            <activity android:name=".Settings$SystemDashboardActivity"
                      android:label="@string/header_category_system"
                      android:icon="@drawable/ic_settings_about">
                <intent-filter android:priority="-1">
                    <action android:name="com.android.settings.action.SETTINGS"/>
                </intent-filter>
                <meta-data android:name="com.android.settings.category"
                           android:value="com.android.settings.category.ia.homepage"/>
                <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                           android:value="com.android.settings.system.SystemDashboardFragment"/>
                <meta-data android:name="com.android.settings.summary"
                           android:resource="@string/system_dashboard_summary"/>
            </activity>
    ......
    

    所以,一开始点进setting用的是DashboardSummary这个fragment,而再点进 System 或 Apps & notifications 就分别对应显示SystemDashboardFragment或AppAndNotificationDashboardFragment。这些都是activity所对应的需要显示的fragment都定义在AndroidManifest中。然后在SettingsActivity中调用switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);去显示fragment。
    下面来看DashboardFragment。DashboardFragment的使用涉及到PreferenceFragment这个知识点。这个fragment的的使用首先,要在xml文件夹下定义一个以PreferenceScreen为根节点的xx.xml文件,然后新建一个fragment继承自PreferenceFragment,然后在fragment的onCreatePreferences函数中加载xx.xml文件。

    //vendor\mediatek\proprietary\packages\xx\xx\src\com\android\settings\dashboard\DashboardFragment.java
    public class SystemDashboardFragment extends DashboardFragment {
    ......
        @Override
        protected int getPreferenceScreenResId() {
          return R.xml.system_dashboard_fragment;
        }
    ......
    }
    
    public class AppAndNotificationDashboardFragment extends DashboardFragment {
    ......
        @Override
        protected int getPreferenceScreenResId() {
            return R.xml.app_and_notification;
        }
    ......
    }
    
    /**
     * Base fragment for dashboard style UI containing a list of static and dynamic setting items.
     */
    public abstract class DashboardFragment extends SettingsPreferenceFragment{
    ......
        @Override
        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
            super.onCreatePreferences(savedInstanceState, rootKey);
            refreshAllPreferences(getLogTag());
        }
    ......
        /**
         * Get the res id for static preference xml for this fragment.
         */
        protected abstract int getPreferenceScreenResId();
    ......
        /**
         * Refresh all preference items, including both static prefs from xml, and dynamic items from
         * DashboardCategory.
         */
        private void refreshAllPreferences(final String TAG) {
            // First remove old preferences.
            if (getPreferenceScreen() != null) {
                // Intentionally do not cache PreferenceScreen because it will be recreated later.
                getPreferenceScreen().removeAll();
            }
    
            // Add resource based tiles.
            displayResourceTiles();
            mProgressiveDisclosureMixin.collapse(getPreferenceScreen());
    
            refreshDashboardTiles(TAG);
        }
    
    ......
           /**
         * Displays resource based tiles.
         */
        private void displayResourceTiles() {
            final int resId = getPreferenceScreenResId();
            if (resId <= 0) {
                return;
            }
            addPreferencesFromResource(resId);
            final PreferenceScreen screen = getPreferenceScreen();
            Collection<AbstractPreferenceController> controllers = mPreferenceControllers.values();
            for (AbstractPreferenceController controller : controllers) {
                controller.displayPreference(screen);
            }
        }
    }
    ......
    

    DashboardFragment是AppAndNotificationDashboardFragment,SystemDashboardFragment等fragment的基类,在DashboardFragment中getPreferenceScreenResId是一个抽象函数,因此AppAndNotificationDashboardFragment和SystemDashboardFragment等会重写此函数,从而加载不同的xml文件。不得不说,虽然麻烦,这个复用度很高。

    system_dashboard_fragment.xml如下所示。

    <PreferenceScreen
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:title="@string/header_category_system">
    
        <Preference
            android:key="gesture_settings"
            android:title="@string/gesture_preference_title"
            android:icon="@drawable/ic_settings_gestures"
            android:order="-250"
            android:fragment="com.android.settings.gestures.GestureSettings" />
    
        <!-- Backup -->
        <Preference
            android:key="backup_settings"
            android:title="@string/privacy_settings_title"
            android:summary="@string/summary_placeholder"
            android:icon="@drawable/ic_settings_backup"
            android:order="-60">
            <intent android:action="android.settings.BACKUP_AND_RESET_SETTINGS" />
        </Preference>
    
        <!-- System updates -->
        <Preference
            android:key="system_update_settings"
            android:title="@string/system_update_settings_list_item_title"
            android:summary="@string/summary_placeholder"
            android:icon="@drawable/ic_system_update"
            android:order="-30">
            <intent android:action="android.settings.SYSTEM_UPDATE_SETTINGS" />
        </Preference>
    
        <Preference
            android:key="additional_system_update_settings"
            android:title="@string/additional_system_update_settings_list_item_title"
            android:order="-31">
            <intent android:action="android.intent.action.MAIN"
                    android:targetPackage="@string/additional_system_update"
                    android:targetClass="@string/additional_system_update_menu" />
        </Preference>
    
        <Preference
            android:key="reset_dashboard"
            android:title="@string/reset_dashboard_title"
            android:summary="@string/reset_dashboard_summary"
            android:icon="@drawable/ic_restore"
            android:order="-20"
            android:fragment="com.android.settings.system.ResetDashboardFragment" />
    
    </PreferenceScreen>
    

    这里的Preference有2种方式可以跳转新的界面,一种是Preference中定义了android:fragment属性,当点击Preference的时候,会跳转android:fragment所指向的fragment,另一种是添加intent。

    https://developer.android.com/guide/topics/ui/settings/customize-your-settings?hl=zh-cn#%E8%AE%BE%E7%BD%AE_intent
    设置 Intent
    您可以在 Preference 上设置 Intent,以在每次点按 Preference 时,启用新的 Fragment、Activity、或单独的应用。 这等同于使用给定 Intent 的 Context.startActivity()。
    您可以使用嵌套 <intent> 标签在 XML 中设置 Intent。 下方示例定义了启动 Activity 的 Intent:

    <Preference
            app:key=”activity”
            app:title="Launch activity">
        <intent
                android:targetPackage="com.example"
                android:targetClass="com.example.ExampleActivity"/>
    </Preference>
    

    或者,您也可以在 Preference 上直接使用 setIntent(),如下所示:

    Intent intent = new Intent(getContext(), ExampleActivity.class);
    activityPreference.setIntent(intent);
    

    Sets an Intent to be used for Context#startActivity(Intent) when this Preference is clicked.

    OnPreferenceClickListener
    您可以在 Preference 上设置 OnPreferenceClickListener,当点按 Preference 时,此操作会为 onPreferenceClick() 添加回调。 例如,如果您有更复杂的逻辑可以用于处理跳转,则可以使用侦听器跳转至另一个 Fragment 或 Activity。

    要设置 OnPreferenceClickListener,请使用与下方所示内容类似的代码:

    onClickPreference.setOnPreferenceClickListener(preference -> {
        // do something
        return true;
    });
    

    当Preference被点击时,最终会调用performClick函数,因此,添加intent的情况下会调用 context.startActivity(mIntent)从而实现跳转。而fragment的跳转应该是通过PreferenceManager去实现的。

    //frameworks\base\core\java\android\preference\Preference.java
        /**
         * Called when a click should be performed.
         *
         * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
         *            listener should be called in the proper order (between other
         *            processing). May be {@code null}.
         * @hide
         */
        public void performClick(PreferenceScreen preferenceScreen) {
    
            if (!isEnabled()) {
                return;
            }
    
            onClick();
    
            if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
                return;
            }
    
            PreferenceManager preferenceManager = getPreferenceManager();
            if (preferenceManager != null) {
                PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
                        .getOnPreferenceTreeClickListener();
                if (preferenceScreen != null && listener != null
                        && listener.onPreferenceTreeClick(preferenceScreen, this)) {
                    return;
                }
            }
    
            if (mIntent != null) {
                Context context = getContext();
                context.startActivity(mIntent);
            }
        }
    

    Preference更多属性可以看这里
    https://developer.android.com/reference/android/preference/Preference

    除了在xml中定义的Preference之外,还有AndroidManifest.xml中定义的value为com.android.settings.category.ia.system的类也会显示在System下(我是根据显示结果来下结论的,具体初始化的地方在代码中未找到)。

            <activity android:name=".Settings$LanguageAndInputSettingsActivity"
                android:label="@string/language_settings"
                android:icon="@drawable/ic_settings_language"
                android:taskAffinity="com.android.settings"
                android:parentActivityName="Settings$SystemDashboardActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.VOICE_LAUNCH" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
                <intent-filter android:priority="260">
                    <action android:name="com.android.settings.action.SETTINGS"/>
                </intent-filter>
                <meta-data android:name="com.android.settings.category"
                           android:value="com.android.settings.category.ia.system"/>
                <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                           android:value="com.android.settings.language.LanguageAndInputSettings"/>
                <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                           android:value="true"/>
            </activity>
    

    关键词:SettingsActivity,DashboardSummary,DashboardFragment。
    首先,点进setting是SettingsActivity显示DashboardSummary(Settings),然后再点进去下一级是显示DashboardFragment(.Settings$xxxActivity)。

    Settings以及.Settings$xxxActivity部分的显示基本就到此为止了,下一篇分析SubSettings部分的显示。

    参考链接:
    Preferencescreen中利用intent跳转activity

    Android进阶——Preference详解之Preference系的基本应用和管理(二)

    Android O Settings源码流程分析(数据加载之二级菜单)

    相关文章

      网友评论

          本文标题:Android 8 setting源码分析二

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