Android 10.0 Settings 加载流程(一)

手机软件开发 2024-9-30 06:26:26 48 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()方法开始:
@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即可。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 16:53, Processed in 0.198660 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表