android framework13-settings【01 简单学习】

2,140 阅读28分钟

1.概述

  • 简单学习下系统设置功能,这个app在packages/apps/Settings目录下,
  • app里边引用了一些库可能在frameworks/base/packages/SettingsLib或SettingsProvider目录下

1.1.补充

>1.如何给settings动态添加选项流程?

  • 外部app,需要是系统应用,具体参考2.7,也就是ResolveInfo的system变量是true,通过adb install的不行,放到region里可以。也就是得是预制应用
  • 主要是配置清单文件,action,有4种:
  • OPERATOR_SETTINGS和MANUFACTURER_SETTINGS可以不用配置meta-data里的category
  • EXTRA_SETTINGS_ACTION和IA_SETTINGS_ACTION必须配置meta-data里的category
               <intent-filter>
                    <action
                        android:name="com.android.settings.action.IA_SETTINGS" />
                    <category
                        android:name="android.intent.category.DEFAULT" />
                </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.summary"
                    android:resource="@string/def_summary1" />

                <meta-data
                    android:name="com.android.settings.title"
                    android:resource="@string/def_title1" />
            
                <meta-data
                    android:name="com.android.settings.icon"
                    android:resource="@mipmap/ic_launcher" />
                <meta-data
                    android:name="com.android.settings.summary_uri"
                    android:value="content://com.example.myc/mycSummary" />
 <meta-data android:name="com.android.settings.order" android:value="-60"/>

可用的4种Action的值

EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS";

IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS"

OPERATOR_SETTINGS =
            "com.android.settings.OPERATOR_APPLICATION_SETTING";

MANUFACTURER_SETTINGS =
            "com.android.settings.MANUFACTURER_APPLICATION_SETTING";

>2.category查找

参考2.4.5

  • DashboardFragmentRegistry.java这里注册了所有的fragment的categoryKey
  • CategoryKey.java里边就是对应的categoryKey,到时候需要添加到哪个页面,上边的meta-data里category就写哪个

2.Fragment的层级关系

设置里都是选项卡,所以基类用的就是PreferenceFragmentCompat

2.1 ObservablePreferenceFragment

添加生命周期监听的逻辑

public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat
        implements LifecycleOwner {

    private final Lifecycle mLifecycle = new Lifecycle(this);

    public Lifecycle getSettingsLifecycle() {
        return mLifecycle;
    }

2.2 InstrumentedPreferenceFragment

  • Instrumented fragment that logs visibility state.
    public abstract class InstrumentedPreferenceFragment extends ObservablePreferenceFragment
            implements Instrumentable {
        
        //注意一下,下边的方法后边又被DashboardFragment重写了
        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
            final int resId = getPreferenceScreenResId();//布局通过这个方法设置
            if (resId > 0) {
                addPreferencesFromResource(resId);
            }
        }

2.3 SettingsPreferenceFragment

  • settings fragment的基类,带有一些辅助函数和对话框管理
    public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
            implements DialogCreatable, HelpResourceProvider, Indexable {
            
        public void addPreferencesFromResource(@XmlRes int preferencesResId) {
            super.addPreferencesFromResource(preferencesResId);
            checkAvailablePrefs(getPreferenceScreen());
        }

        //选项的可见性设置,xml里的选项不见得都是可见的
        void checkAvailablePrefs(PreferenceGroup preferenceGroup) {
            if (preferenceGroup == null) return;
            for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
                Preference pref = preferenceGroup.getPreference(i);
                if (pref instanceof SelfAvailablePreference
                        && !((SelfAvailablePreference) pref).isAvailable(getContext())) {
                    pref.setVisible(false);
                } else if (pref instanceof PreferenceGroup) {
                    checkAvailablePrefs((PreferenceGroup) pref);
                }
            }
        }

>1.showDialog

  • 这个类里封装了如下显示一个dialog的逻辑

  • 使用的时候调用showDialog传递一个int值,完事覆写OnCreateDialog里根据dialogId返回不同的Dialog即可。

  • 另外,cancel,dismiss的监听方法也都有,还有removeDialog的方法也有

      protected void showDialog(int dialogId) {
          mDialogFragment = SettingsDialogFragment.newInstance(this, dialogId);
          mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
      }
    
      @Override
      public Dialog onCreateDialog(int dialogId) {
          return null;
      }
      
      protected void removeDialog(int dialogId) {
          if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
              mDialogFragment.dismissAllowingStateLoss();
          }
          mDialogFragment = null;
      }
    
      protected void setOnCancelListener(DialogInterface.OnCancelListener listener) {
          if (mDialogFragment != null) {
              mDialogFragment.mOnCancelListener = listener;
          }
      }
      protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
          if (mDialogFragment != null) {
              mDialogFragment.mOnDismissListener = listener;
          }
      }    
    

2.4 DashboardFragment

  • Base fragment for dashboard style UI containing a list of static and dynamic setting items.
public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
        
    public void onAttach(Context context) {
        super.onAttach(context);
        //...
        //代码里获取controllers
        final List<AbstractPreferenceController> controllersFromCode =
                createPreferenceControllers(context);
        //从xml里解析获取controllers
        final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
                .getPreferenceControllersFromXml(context, getPreferenceScreenResId());
        // Filter xml-based controllers in case a similar controller is created from code already.
        //根据key值过滤重复的,假设controllersFromXml为[A B C],controllersFromCode为[B D],结果为[A C]
        //第一个参数为原数据,第二个参数为过滤器,也就是从第一个参数里过滤掉第二个参数里已经存在的。
        final List<BasePreferenceController> uniqueControllerFromXml =
                PreferenceControllerListHelper.filterControllers(
                        controllersFromXml, controllersFromCode);
//从下边addAll的逻辑可以看出,controllersFromCode里的controller优先级比较高
//controllersFromCode和controllersFromXml都有的controller,保留controllersFromCode里的
        // Add unique controllers to list.
        if (controllersFromCode != null) {
            mControllers.addAll(controllersFromCode);
        }
        mControllers.addAll(uniqueControllerFromXml);

        // Set metrics category for BasePreferenceController.
//...
    }
 
    private void refreshAllPreferences(final String tag) {
        final PreferenceScreen screen = getPreferenceScreen();
        if (screen != null) {
            screen.removeAll();
        }
        // 这个就是从xml加载数据
        displayResourceTiles();
        //这个是动态获取的数据,在清单文件里配置的,可以是settings自己,也可以是其他app
        refreshDashboardTiles(tag);//详见补充6

        final Activity activity = getActivity();
        if (activity != null) {
            activity.reportFullyDrawn();
        }
    //更新选项的可见性。
        updatePreferenceVisibility(mPreferenceControllers);
    }
    
    private void displayResourceTiles() {
        final int resId = getPreferenceScreenResId();
        if (resId <= 0) {
            return;
        }
        addPreferencesFromResource(resId);
        final PreferenceScreen screen = getPreferenceScreen();
        screen.setOnExpandButtonClickListener(this);
        displayResourceTilesToScreen(screen);
    }      

>1.updatePreferenceStates

更新选项的状态,主要是调用controller的updateState方法,默认实现是更新summary

    public void onResume() {
        super.onResume();
        updatePreferenceStates();
    }

循环所有的controller,根据key值找到对应的preference,找到的话就调用controller的updateState方法更新选项卡

    protected void updatePreferenceStates() {
        final PreferenceScreen screen = getPreferenceScreen();
        Collection<List<AbstractPreferenceController>> controllerLists =
                mPreferenceControllers.values();
        for (List<AbstractPreferenceController> controllerList : controllerLists) {
            for (AbstractPreferenceController controller : controllerList) {
                if (!controller.isAvailable()) {
                    continue;
                }

                final String key = controller.getPreferenceKey();
                if (TextUtils.isEmpty(key)) {
                    continue;
                }

                final Preference preference = screen.findPreference(key);
                if (preference == null) {
                    continue;
                }
                //核心是这个
                controller.updateState(preference);
            }
        }
    }

>2.AbstractPreferenceController.java

    public void displayPreference(PreferenceScreen screen) {
        final String prefKey = getPreferenceKey();
        if (TextUtils.isEmpty(prefKey)) {//没有key的不做处理
            return;
        }
        if (isAvailable()) {//根据方法的返回值决定这个选项是否可见
            setVisible(screen, prefKey, true /* visible */);
            if (this instanceof Preference.OnPreferenceChangeListener) {
                final Preference preference = screen.findPreference(prefKey);
                if (preference != null) {
                    preference.setOnPreferenceChangeListener(
                            (Preference.OnPreferenceChangeListener) this);
                }
            }
        } else {
            setVisible(screen, prefKey, false /* visible */);
        }
    }

>3.updatePreferenceVisibility()

这个方法有2个地方调用:

一个是checkUiBlocker方法,如下 keys不为null,表示有uiBlock的controller,那么会延迟300ms执行

        if (!keys.isEmpty()) {
            mBlockerController = new UiBlockerController(keys);
            mBlockerController.start(() -> {
                updatePreferenceVisibility(mPreferenceControllers);
                baseControllers.forEach(controller -> controller.setUiBlockerFinished(true));
            });
        }

另一个是在正常的onCreatePreferences > refreshAllPreferences里调用,这时候如果没有uiBlock的controller,那么这个方法里就会直接更新选项的可见性了。

>4.onPreferenceTreeClick()

选项的点击事件处理,可以看到交给controller来处理了,如果controller没做处理,那交给fragment自己处理

    public boolean onPreferenceTreeClick(Preference preference) {
        final Collection<List<AbstractPreferenceController>> controllers =
                mPreferenceControllers.values();
        for (List<AbstractPreferenceController> controllerList : controllers) {
            for (AbstractPreferenceController controller : controllerList) {
                if (controller.handlePreferenceTreeClick(preference)) {
                   //controller已经处理了,就返回
                    return true;
                }
            }
        }
        //controller未处理,使用默认的实现
        return super.onPreferenceTreeClick(preference);
    }

我们看下PreferenceFragmentCompat.java里默认的处理逻辑

  • 首先判断回调的Fragment有没有实现OnPreferenceStartFragmentCallback,有的话用它来处理,
  • 没有的话,继续判断activity有没有实现OnPreferenceStartFragmentCallback,有的话用它来处理,
  • 还是没有处理的话,那我们就自动给它跳转,用当前fragment的父容器来加载新的fragment

>5.getCategoryKey

  • 动态数据会用到这个key,清单文件里注册的,

  • settings里每个页面都有自己的key,

  • 你想把动态的tile加到哪个页面,到时候清单文件里配置这个key就行,后边详细讲

      public String getCategoryKey() {
          return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
      }
    

>6.refreshDashboardTiles

根据categorykey查找对应的tile,动态添加选项

    private void refreshDashboardTiles(final String tag) {
        final PreferenceScreen screen = getPreferenceScreen();
    //数据获取逻辑参考 2.5到2.8小节
        final DashboardCategory category =
                mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
        if (category == null) {
            return;
        }
        final List<Tile> tiles = category.getTiles();
        if (tiles == null) {
            return;
        }
        //先把所有数据都放入要删除的集合里
        final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys);

        // Install dashboard tiles and collect pending observers.
        final boolean forceRoundedIcons = shouldForceRoundedIcon();
        final List<DynamicDataObserver> pendingObservers = new ArrayList<>();
        for (Tile tile : tiles) {
            final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
            if (TextUtils.isEmpty(key)) {
                continue;
            }
            
            if (!displayTile(tile)) {
            //可以配置哪些key被限制,限制的就不显示。
                continue;
            }
            
            final List<DynamicDataObserver> observers;
            if (mDashboardTilePrefKeys.containsKey(key)) {
                // Have the key already, will rebind.
                final Preference preference = screen.findPreference(key);
                observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
                        getActivity(), this, forceRoundedIcons, preference, tile, key,
                        mPlaceholderPreferenceController.getOrder());
            } else {
                //根据tile创建对应的选项,见下边的.7
                final Preference pref = createPreference(tile);
                //给选项卡绑定数据或者返回observer,见2.5.4
                observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
                        getActivity(), this, forceRoundedIcons, pref, tile, key,
                        mPlaceholderPreferenceController.getOrder());
                screen.addPreference(pref);
                registerDynamicDataObservers(observers);
                mDashboardTilePrefKeys.put(key, observers);
            }
            if (observers != null) {
                pendingObservers.addAll(observers);
            }
            //数据正常添加了,从remove集合里删除
            remove.remove(key);
        }

        // 循环集合,移除不需要显示的
        for (Map.Entry<String, List<DynamicDataObserver>> entry : remove.entrySet()) {
            final String key = entry.getKey();
            mDashboardTilePrefKeys.remove(key);
            final Preference preference = screen.findPreference(key);
            if (preference != null) {
                screen.removePreference(preference);
            }
            unregisterDynamicDataObservers(entry.getValue());
        }

        // Wait for pending observers to update UI.
        if (!pendingObservers.isEmpty()) {
            final CountDownLatch mainLatch = new CountDownLatch(1);
            new Thread(() -> {
                pendingObservers.forEach(observer ->
                        awaitObserverLatch(observer.getCountDownLatch()));
                mainLatch.countDown();
            }).start();
            awaitObserverLatch(mainLatch);
            pendingObservers.forEach(DynamicDataObserver::updateUi);
        }
    }

>7.createPreference

如果Tile是provider,那么返回switch选项,如果是activity,根据meta data里是否有switch的key,返回switch选项或者普通的选项

    protected Preference createPreference(Tile tile) {
        return tile instanceof ProviderTile
                ? new SwitchPreference(getPrefContext())
                : tile.hasSwitch()
                        ? new PrimarySwitchPreference(getPrefContext())
                        : new Preference(getPrefContext());
    }

>8.registerDynamicDataObservers

这里的observers就是选项卡的title,summary,switch的改变监听器,具体observer见2.5.1

    void registerDynamicDataObservers(List<DynamicDataObserver> observers) {
        if (observers == null || observers.isEmpty()) {
            return;
        }
        final ContentResolver resolver = getContentResolver();
        observers.forEach(observer -> registerDynamicDataObserver(resolver, observer));
    }

    private void registerDynamicDataObserver(ContentResolver resolver,
            DynamicDataObserver observer) {
        resolver.registerContentObserver(observer.getUri(), false, observer);
        mRegisteredObservers.add(observer);
    }

>9.onCategoriesChanged

补充10里注册的回调。

    public void onCategoriesChanged(Set<String> categories) {
        final String categoryKey = getCategoryKey();
        final DashboardCategory dashboardCategory =
                mDashboardFeatureProvider.getTilesForCategory(categoryKey);
        if (dashboardCategory == null) {
            return;
        }

        if (categories == null) {
            refreshDashboardTiles(getLogTag());//刷新tile数据,见补充6
        } else if (categories.contains(categoryKey)) {
            refreshDashboardTiles(getLogTag());
        }
    }

>10.onStart

    public void onStart() {
        super.onStart();
        final DashboardCategory category =
                mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
        if (category == null) {
            return;
        }
        final Activity activity = getActivity();
        if (activity instanceof CategoryHandler) {
            mListeningToCategoryChange = true;
            //注册回调,3.3.4用到
            ((CategoryHandler) activity).getCategoryMixin().addCategoryListener(this);
        }

2.5.mDashboardFeatureProvider

下边代码在上边的DashboardFragment里

        mDashboardFeatureProvider = FeatureFactory.getFactory(context).
                getDashboardFeatureProvider(context);

>1.对象的获取

  • 先获取facotory对象

通过读取配置里的factory类名,反射实例化对象

//FeatureFactory.java
    public static FeatureFactory getFactory(Context context) {
        if (sFactory != null) {
            return sFactory;
        }
        final String clsName = context.getString(R.string.config_featureFactory);

        try {
            sFactory = (FeatureFactory) context.getClassLoader().loadClass(clsName).newInstance();
        } 
        return sFactory;
    }

//配置类

    <!-- Fully-qualified class name for the implementation of the FeatureFactory to be instantiated. -->
    <string name="config_featureFactory" translatable="false">
    com.android.settings.overlay.FeatureFactoryImpl</string>
  • 获取provider
//FeatureFactoryImpl.java

    public DashboardFeatureProvider getDashboardFeatureProvider(Context context) {
        if (mDashboardFeatureProvider == null) {
            mDashboardFeatureProvider = new DashboardFeatureProviderImpl(
                    context.getApplicationContext());
        }
        return mDashboardFeatureProvider;
    }

>2.DashboardFeatureProviderImpl

    public DashboardFeatureProviderImpl(Context context) {
        mContext = context.getApplicationContext();
        mCategoryManager = CategoryManager.get(context);
        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
        mPackageManager = context.getPackageManager();
    }

    //不同的Fragment有不同的key
    public DashboardCategory getTilesForCategory(String key) {
    //见2.6.1
        return mCategoryManager.getTilesByCategory(mContext, key);
    }

>3.getDashboardKeyForTile

   private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_";
   
    public String getDashboardKeyForTile(Tile tile) {
        if (tile == null) {
            return null;
        }
        if (tile.hasKey()) {
        //有自己设置key,返回
            return tile.getKey(mContext);
        }
        //tile没有设置key,生成一个:固定的前缀dashboard_tile_pref_拼接组件的类名
        final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
        final ComponentName component = tile.getIntent().getComponent();
        sb.append(component.getClassName());
        return sb.toString();
    }

>4.bindPreferenceToTileAndGetObservers

title,summary,switch,有数据的直接设置数据,否则根据uri创建observer放入集合返回

        public List<DynamicDataObserver> bindPreferenceToTileAndGetObservers(FragmentActivity activity,
                DashboardFragment fragment, boolean forceRoundedIcon, Preference pref, Tile tile,
                String key, int baseOrder) {
            if (pref == null) {
                return null;
            }
            //给选项设置key值
            if (!TextUtils.isEmpty(key)) {
                pref.setKey(key);
            } else {
                pref.setKey(getDashboardKeyForTile(tile));
            }
            final List<DynamicDataObserver> outObservers = new ArrayList<>();
            //生成一个跟title有关的observer或者null
            DynamicDataObserver observer = bindTitleAndGetObserver(pref, tile);
            if (observer != null) {
                outObservers.add(observer);
            }
            //同理,这里是summary
            observer = bindSummaryAndGetObserver(pref, tile);
            if (observer != null) {
                outObservers.add(observer);
            }
            //选项开关的observer
            observer = bindSwitchAndGetObserver(pref, tile);
            if (observer != null) {
                outObservers.add(observer);
            }
            //给选项绑定图标
            bindIcon(pref, tile, forceRoundedIcon);
            //我们的tile是个activity
            if (tile instanceof ActivityTile) {
                final int sourceMetricsCategory = fragment.getMetricsCategory();
                final Bundle metadata = tile.getMetaData();
                String clsName = null;
                String action = null;
                //从meta data里获取数据
                if (metadata != null) {
                //获取要加载的fragment以及对应的action
                    clsName = metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
                    action = metadata.getString(META_DATA_KEY_INTENT_ACTION);
                }
                if (!TextUtils.isEmpty(clsName)) {
                //有class name,设置对应的fragment,默认回调里会自动跳转的
                    pref.setFragment(clsName);
                } else {
                //不是fragment,设置选项对应的intent
                    final Intent intent = new Intent(tile.getIntent());
                    intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
                            sourceMetricsCategory);
                    if (action != null) {
                        intent.setAction(action);
                    }
                    // Register the rule for injected apps.
                    //当前fragment是首页那个fragment
                    if (fragment instanceof TopLevelSettings) {
                        ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome(
                                mContext,
                                new ComponentName(tile.getPackageName(), tile.getComponentName()),
                                action,
                                true /* clearTop */);
                    }
                    //设置点击事件,就是跳转intent
                    pref.setOnPreferenceClickListener(preference -> {
                    //..
                        launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory,
                                highlightMixin, isDuplicateClick);
                        return true;
                    });
                }
            }
            //tile有order值的话,设置给选项卡
            if (tile.hasOrder()) {
                final String skipOffsetPackageName = activity.getPackageName();
                final int order = tile.getOrder();
                boolean shouldSkipBaseOrderOffset = TextUtils.equals(
                        skipOffsetPackageName, tile.getIntent().getComponent().getPackageName());
                if (shouldSkipBaseOrderOffset || baseOrder == Preference.DEFAULT_ORDER) {
                    pref.setOrder(order);
                } else {
                    pref.setOrder(order + baseOrder);
                }
            }
            return outObservers.isEmpty() ? null : outObservers;
        }

>5.bindTitleAndGetObserver

给选项绑定标题或者返回observer

    private DynamicDataObserver bindTitleAndGetObserver(Preference preference, Tile tile) {
        final CharSequence title = tile.getTitle(mContext.getApplicationContext());
        if (title != null) {
        //设置title
            preference.setTitle(title);
            return null;
        }
        if (tile.getMetaData() != null && tile.getMetaData().containsKey(
                META_DATA_PREFERENCE_TITLE_URI)) {
                //给的uri,动态修改title,这里先给个默认值
            preference.setTitle(R.string.summary_placeholder);

            final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_TITLE_URI,
                    METHOD_GET_DYNAMIC_TITLE);
            //返回一个observer动态修改title
            return createDynamicDataObserver(METHOD_GET_DYNAMIC_TITLE, uri, preference);
        }
        return null;
    }

>6.bindSummaryAndGetObserver

给选项绑定summary或者返回observer

    private DynamicDataObserver bindSummaryAndGetObserver(Preference preference, Tile tile) {
    //见2.8.6
        final CharSequence summary = tile.getSummary(mContext);
        if (summary != null) {
        //不为空,直接使用
            preference.setSummary(summary);
        } else if (tile.getMetaData() != null
                && tile.getMetaData().containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
                //为空,先设置个占位符
            preference.setSummary(R.string.summary_placeholder);

            final Uri uri = TileUtils.getCompleteUri(tile, META_DATA_PREFERENCE_SUMMARY_URI,
                    METHOD_GET_DYNAMIC_SUMMARY);
                    //创建监听器获取数据
            return createDynamicDataObserver(METHOD_GET_DYNAMIC_SUMMARY, uri, preference);
        }
        return null;
    }

>7.createDynamicDataObserver

    private DynamicDataObserver createDynamicDataObserver(String method, Uri uri, Preference pref) {
        return new DynamicDataObserver() {
//..
            public void onDataChanged() {
                switch (method) {
                    case METHOD_GET_DYNAMIC_TITLE:
                        refreshTitle(uri, pref, this);
                        break;
                    case METHOD_GET_DYNAMIC_SUMMARY:
                        refreshSummary(uri, pref, this);
                        break;
                    case METHOD_IS_CHECKED:
                        refreshSwitch(uri, pref, this);
                        break;
                }
            }
        };
    }

根据uri获取里边的值

    private void refreshTitle(Uri uri, Preference preference, DynamicDataObserver observer) {
        ThreadUtils.postOnBackgroundThread(() -> {
            final Map<String, IContentProvider> providerMap = new ArrayMap<>();
            final String titleFromUri = TileUtils.getTextFromUri(
                    mContext, uri, providerMap, META_DATA_PREFERENCE_TITLE);
            if (!TextUtils.equals(titleFromUri, preference.getTitle())) {
                observer.post(() -> preference.setTitle(titleFromUri));
            }
        });
    }

2.6.CategoryManager.java

>1.getTilesByCategory

    public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
        tryInitCategories(context);
        return mCategoryByKeyMap.get(categoryKey);
    }

>2.tryInitCategories

获取数据前都会调用这个方法,先初始化下数据

    private synchronized void tryInitCategories(Context context) {
        tryInitCategories(context, false /* forceClearCache */);
    }

    private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
        if (mCategories == null) {
            final boolean firstLoading = mCategoryByKeyMap.isEmpty();
            if (forceClearCache) {
                mTileByComponentCache.clear();
            }
            mCategoryByKeyMap.clear();
            //参考2.7.2
            mCategories = TileUtils.getCategories(context, mTileByComponentCache);
            for (DashboardCategory category : mCategories) {
                mCategoryByKeyMap.put(category.key, category);
            }
            backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
            sortCategories(context, mCategoryByKeyMap);
            filterDuplicateTiles(mCategoryByKeyMap);
            if (firstLoading) {
                final DashboardCategory homepageCategory = mCategoryByKeyMap.get(
                        CategoryKey.CATEGORY_HOMEPAGE);
                if (homepageCategory == null) {
                    return;
                }
                for (Tile tile : homepageCategory.getTiles()) {
                    final String key = tile.getKey(context);
                    if (TextUtils.isEmpty(key)) {
                        continue;
                    }
                    HighlightableMenu.addMenuKey(key);
                }
            }
        }
    }

2.7.TileUtils

>1.可能用到的变量

    public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";

    public static final String PROFILE_ALL = "all_profiles";
    
    public static final String PROFILE_PRIMARY = "primary_profile_only";

//The key used to get the category from metadata of activities of action EXTRA_SETTINGS_ACTION The value must be from CategoryKey.
    static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";

//应该在AndroidManifest.xml中设置的元数据项的名称,以指定应该用于首选项的键。
    public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";   

   //Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
    private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";

    private static final String OPERATOR_SETTINGS =
            "com.android.settings.OPERATOR_APPLICATION_SETTING";
    private static final String OPERATOR_DEFAULT_CATEGORY =
            "com.android.settings.category.wireless";

    private static final String MANUFACTURER_SETTINGS =
            "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
    private static final String MANUFACTURER_DEFAULT_CATEGORY =
            "com.android.settings.category.device";            

>2.getCategories

    public static List<DashboardCategory> getCategories(Context context,
            Map<Pair<String, String>, Tile> cache) {
        final long startTime = System.currentTimeMillis();
        final boolean setup =
                Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;
        final ArrayList<Tile> tiles = new ArrayList<>();
        final UserManager userManager = (UserManager) context.getSystemService(
                Context.USER_SERVICE);
        for (UserHandle user : userManager.getUserProfiles()) {
         // 只为当前用户添加对应的settings信息
            if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
               //这个只从settings app的清单文件里查找
                loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
                //这个查找action是OPERATOR_SETTINGS,meta data要查找的key是OPERATOR_DEFAULT_CATEGORY
                loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,
                        OPERATOR_DEFAULT_CATEGORY, tiles, false);
               //
                loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
                        MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
            }
            if (setup) {
                loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
                loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
            }
        }
    //根据categoryKey把tile分组,放在map里,
        final HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
        //经过上边的loadTilesForAction方法,我们的tiles里已经填满了数据了
        for (Tile tile : tiles) {
            final String categoryKey = tile.getCategory();
            DashboardCategory category = categoryMap.get(categoryKey);
            if (category == null) {
                category = new DashboardCategory(categoryKey);
                //添加数据
                categoryMap.put(categoryKey, category);
            }
            category.addTile(tile);
        }
        final ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
        for (DashboardCategory category : categories) {
            category.sortTiles();
        }
        return categories;
    }

>3.loadTilesForAction

传递一个intent需要的action,后边查找对应的activity以及provider

    static void loadTilesForAction(Context context,
            UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
        final Intent intent = new Intent(action);
        if (requireSettings) {
        //为true的话,限制intent的包名是settings的包名
            intent.setPackage(SETTING_PKG);
        }
        loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent);
        loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent);
    }

>4.loadActivityTiles

根据intent查找相关的activity

    private static void loadActivityTiles(Context context,
            UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent) {
        final PackageManager pm = context.getPackageManager();
        final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                PackageManager.GET_META_DATA, user.getIdentifier());
        for (ResolveInfo resolved : results) {
            if (!resolved.system) {
                //不允许非系统app
                continue;
            }
            final ActivityInfo activityInfo = resolved.activityInfo;
            final Bundle metaData = activityInfo.metaData;
            loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo);
        }
    }

>5.loadProviderTiles

通过intent查找相关的ContentProvider

    private static void loadProviderTiles(Context context,
            UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent) {
        final PackageManager pm = context.getPackageManager();
        final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent,
                0 /* flags */, user.getIdentifier());
        for (ResolveInfo resolved : results) {
            if (!resolved.system) {
                //只允许系统app
                continue;
            }
            final ProviderInfo providerInfo = resolved.providerInfo;
            //查找出switch data
            final List<Bundle> switchData = getSwitchDataFromProvider(context,
                    providerInfo.authority);
            if (switchData == null || switchData.isEmpty()) {
                continue;
            }
            for (Bundle metaData : switchData) {
                loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
                        providerInfo);
            }
        }
    }

>getTextFromUri

    public static String getTextFromUri(Context context, Uri uri,
            Map<String, IContentProvider> providerMap, String key) {
        final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
        return (bundle != null) ? bundle.getString(key) : null;
    }

//读取uri指向的数据,返回一个bundle

    private static Bundle getBundleFromUri(Context context, Uri uri,
            Map<String, IContentProvider> providerMap, Bundle bundle) {
        final Pair<String, String> args = getMethodAndKey(uri);
        if (args == null) {
            return null;
        }
        final String method = args.first;
        final String key = args.second;
        if (TextUtils.isEmpty(method)) {
            return null;
        }
        final IContentProvider provider = getProviderFromUri(context, uri, providerMap);
        if (provider == null) {
            return null;
        }
        if (!TextUtils.isEmpty(key)) {
            if (bundle == null) {
                bundle = new Bundle();
            }
            bundle.putString(META_DATA_PREFERENCE_KEYHINT, key);
        }
        try {
            return provider.call(context.getAttributionSource(),
                    uri.getAuthority(), method, uri.toString(), bundle);
        } catch (RemoteException e) {
            return null;
        }
    }

>6.loadTile

    private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
            ComponentInfo componentInfo) {
        if (user.getIdentifier() != ActivityManager.getCurrentUser()
                && Tile.isPrimaryProfileOnly(componentInfo.metaData)) {
         //非当前用户,或者有 primary_profile_only 标志的,默认标志是all_profiles       
            return;
        }

        String categoryKey = defaultCategory;
        // Load category
        if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
                && categoryKey == null) {
        //判断下meta data里没有EXTRA_CATEGORY_KEY,并且categoryKey为null
            return;
        } else {
            categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
        }

    //区分下activity和contentProvider,存储对应的pair数据
        final boolean isProvider = componentInfo instanceof ProviderInfo;
        final Pair<String, String> key = isProvider
                ? new Pair<>(((ProviderInfo) componentInfo).authority,
                        metaData.getString(META_DATA_PREFERENCE_KEYHINT))
                : new Pair<>(componentInfo.packageName, componentInfo.name);
        Tile tile = addedCache.get(key);
        if (tile == null) {
            tile = isProvider
                    ? new ProviderTile((ProviderInfo) componentInfo, categoryKey, metaData)
                    : new ActivityTile((ActivityInfo) componentInfo, categoryKey);
            //数据添加到缓存里
            addedCache.put(key, tile);
        } else {
            tile.setMetaData(metaData);
        }

        if (!tile.userHandle.contains(user)) {
            tile.userHandle.add(user);
        }
        if (!outTiles.contains(tile)) {
        //把最终的tile添加到输出集合里
            outTiles.add(tile);
        }
    }

2.8.Tile

>1.isPrimaryProfileOnly

根据key读取meta data里的值,如果meta data为空,或者key对应的value为null,这默认为profile_all,这里是判断是否为 prifile primary only

     static boolean isPrimaryProfileOnly(Bundle metaData) {
        String profile = metaData != null
                ? metaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
        profile = (profile != null ? profile : PROFILE_ALL);
        return TextUtils.equals(profile, PROFILE_PRIMARY);
    }

>2.hasKey

    public boolean hasKey() {
    //"com.android.settings.keyhint";
        return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT);
    }

>3.getKey

获取对应com.android.settings.keyhint的value值

    public String getKey(Context context) {
        if (!hasKey()) {
            return null;
        }
        ensureMetadataNotStale(context);
        if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
            return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
        } else {
            return mMetaData.getString(META_DATA_PREFERENCE_KEYHINT);
        }
    }

>4.hasSwitch

    public static final String META_DATA_PREFERENCE_SWITCH_URI =
            "com.android.settings.switch_uri";
            
    public boolean hasSwitch() {
        return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SWITCH_URI);
    }

>5.getTitle

    public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
    public static final String META_DATA_PREFERENCE_TITLE_URI =
            "com.android.settings.title_uri";
            
    public CharSequence getTitle(Context context) {
        CharSequence title = null;
        ensureMetadataNotStale(context);
        final PackageManager packageManager = context.getPackageManager();
        //先判断有没有对应的key
        if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
            if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
                // If has as uri to provide dynamic title, skip loading here. UI will later load
                // at tile binding time.
                //有动态标题uri,则返回空,后续动态加载
                return null;
            }
            //获取对应的value值
            if (mMetaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
                try {
                    final Resources res =
                            packageManager.getResourcesForApplication(mComponentPackage);
                    title = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_TITLE));
                } 
            } else {
                title = mMetaData.getString(META_DATA_PREFERENCE_TITLE);
            }
        }
        //没有设置title值
        if (title == null) {
        //返回组件的label值
            title = getComponentLabel(context);
        }
        return title;
    }

>6.getSummary

    public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
    public static final String META_DATA_PREFERENCE_TITLE_URI =
            "com.android.settings.title_uri";
            
    public CharSequence getSummary(Context context) {
        if (mSummaryOverride != null) {
            return mSummaryOverride;
        }
        ensureMetadataNotStale(context);
        CharSequence summary = null;
        final PackageManager packageManager = context.getPackageManager();
        if (mMetaData != null) {
            if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
            //uri 是通过observer动态处理的,这里先返回空
                return null;
            }
            //meta data里有对应的summary,读取
            if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
                if (mMetaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
                    try {
                        final Resources res =
                                packageManager.getResourcesForApplication(mComponentPackage);
                        summary = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_SUMMARY));
                    } 
                } else {
                    summary = mMetaData.getString(META_DATA_PREFERENCE_SUMMARY);
                }
            }
        }
        return summary;
    }

2.9.DashboardCategory

这个类里就两个变量,一个是key,一个是tile集合

    public final String key;

    /**
     * List of the category's children
     */
    private List<Tile> mTiles = new ArrayList<>();

    public DashboardCategory(String key) {
        this.key = key;
    }

2.10.TopLevelSettings.java

这个是setting首页用到的Fragment

@SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
        //...
    protected int getPreferenceScreenResId() {
        return R.xml.top_level_settings;
    }
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        super.onCreatePreferences(savedInstanceState, rootKey);
        int tintColor = Utils.getHomepageIconColor(getContext());
        iteratePreferences(preference -> {
            Drawable icon = preference.getIcon();
            if (icon != null) {
            //我试着换了个颜色,竟然不生效。
                icon.setTint(tintColor);
            }
        });
    }

>1.icon.tint

icon.setTint(Color.RED) 改成红色,发现没有效果。
icon.setAlpha(111) 修改透明度,有效果。
把上边修改icon颜色的代码,延迟1秒执行,可以变色,所以应该是其他地方也有修改icon的tint值,全局搜了下setTint,找到了,原来在adapter里又设置过

//mIsEmbeddingActivityEnabled是true,而我们首页也确实是SettingsHomepageActivity
    protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
        if (!mIsEmbeddingActivityEnabled || !(getActivity() instanceof SettingsHomepageActivity)) {
            return super.onCreateAdapter(preferenceScreen);
        }
        //上边的if条件不满足,所以走这里
        return mHighlightMixin.onCreateAdapter(this, preferenceScreen, mScrollNeeded);
    }

HighlightableTopLevelPreferenceAdapter.java

可以看到,高亮的时候是一种tint,非高亮的时候是一种tint,模拟器测试不支持高亮,所以走的都是removeHighlightBackground方法

    private void addHighlightBackground(PreferenceViewHolder holder) {
        final View v = holder.itemView;
//...
        final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
        if (drawable != null) {
            drawable.setTint(mIconColorHighlight);
        }
    }

    private void removeHighlightBackground(PreferenceViewHolder holder) {
        final View v = holder.itemView;
//...
        final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable();
        if (drawable != null) {
            drawable.setTint(mIconColorNormal);
        }
    }

>2.onPreferenceStartFragment()

这里是自定义的处理onPreferenceTreeClick事件

    public Fragment getCallbackFragment() {
        return this;
    }
    
    public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
        new SubSettingLauncher(getActivity())
                .setDestination(pref.getFragment())
                .setArguments(pref.getExtras())
                .setSourceMetricsCategory(caller instanceof Instrumentable
                        ? ((Instrumentable) caller).getMetricsCategory()
                        : Instrumentable.METRICS_CATEGORY_UNKNOWN)
                .setTitleRes(-1)
                .setIsSecondLayerPage(true)
                .launch();
        return true;
    }
>>launch()

简单看下launch的方法,可以看到,是固定跳转到SubSettings页面去了。

    public void launch() {
//...
        final Intent intent = toIntent();
//...
        if (launchAsUser && launchForResult) {
            launchForResultAsUser(intent, mLaunchRequest.mUserHandle,
                    mLaunchRequest.mResultListener, mLaunchRequest.mRequestCode);
        } else if (launchAsUser && !launchForResult) {
            launchAsUser(intent, mLaunchRequest.mUserHandle);
        } else if (!launchAsUser && launchForResult) {
            launchForResult(mLaunchRequest.mResultListener, intent, mLaunchRequest.mRequestCode);
        } else {
            launch(intent);
        }
    }

    public Intent toIntent() {
        final Intent intent = new Intent(Intent.ACTION_MAIN);
        copyExtras(intent);
        intent.setClass(mContext, SubSettings.class);
//...
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, mLaunchRequest.mDestinationName);
//...
        return intent;
    }
    void launch(Intent intent) {
        mContext.startActivity(intent);
    }
    

SettingsActivity里会解析intent数据,实例化fragment并显示。

>3.top_level_settings.xml

如下,有的带fragment属性,有的不带,不带的一般都会在Controller里自己处理。

<PreferenceScreen
    android:key="top_level_settings">

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.network.NetworkDashboardFragment"
        android:icon="@drawable/ic_settings_wireless"
        android:key="top_level_network"
        android:order="-150"
        android:title="@string/network_dashboard_title"
        android:summary="@string/summary_placeholder"
        settings:highlightableMenuKey="@string/menu_key_network"
        settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.applications.AppDashboardFragment"
        android:icon="@drawable/ic_apps"
        android:key="top_level_apps"
        android:order="-130"
        android:title="@string/apps_dashboard_title"
        android:summary="@string/app_and_notification_dashboard_summary"
        settings:highlightableMenuKey="@string/menu_key_apps"/>

>4.用到的key

            PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
                    CategoryKey.CATEGORY_HOMEPAGE);
       public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";

3.SettingsHomepageActivity

查下LAUNCHER,如下,可以看到启动页是SettingsHomepageActivity

        <!-- Alias for launcher activity only, as this belongs to each profile. -->
        <activity-alias android:name="Settings"
                android:label="@string/settings_label_launcher"
                android:taskAffinity="com.android.settings.root"
                android:launchMode="singleTask"
                android:exported="true"
                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>

看下activity的onCreate方法.(我们这里只分析phone模式,平板模式的话是另外一套逻辑)

public class SettingsHomepageActivity extends FragmentActivity implements
        CategoryMixin.CategoryHandler {
        //...
        setContentView(R.layout.settings_homepage_container);
        //...
        mMainFragment = showFragment(() -> {
        //可以看到,加载的是TopLevelSettings,2.5小节有这个fragment的介绍
            final TopLevelSettings fragment = new TopLevelSettings();
            fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
                    highlightMenuKey);
            return fragment;
        }, R.id.main_content);        

3.1.SubSettings

settings首页选项点击以后都会跳转到这个类,具体逻辑见2.5小节的launch方法

public class SubSettings extends SettingsActivity {

}

3.2.SettingsActivity

public class SettingsActivity extends SettingsBaseActivity
        implements PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
        ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
        //..
    protected void onCreate(Bundle savedState) {
//..
        final Intent intent = getIntent();

        if (shouldShowTwoPaneDeepLink(intent) && tryStartTwoPaneDeepLink(intent)) {
            finish();
            super.onCreate(savedState);
            return;
        }

        super.onCreate(savedState);
       //..
        createUiFromIntent(savedState, intent);
    }        

>createUiFromIntent

    protected void createUiFromIntent(Bundle savedState, Intent intent) {
//..
        final String initialFragmentName = getInitialFragmentName(intent);
//..
        setContentView(R.layout.settings_main_prefs);
//..
        } else {
        //加载fragment
            launchSettingFragment(initialFragmentName, intent);
        }
//..
        // see if we should show Back/Next buttons
        //下边是底部的按钮,设备首次启动,设置默认配置的时候会显示,
        if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {

>launchSettingFragment

    void launchSettingFragment(String initialFragmentName, Intent intent) {
        if (initialFragmentName != null) {
            setTitleFromIntent(intent);

            Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
            //here
            switchToFragment(initialFragmentName, initialArguments, true,
                    mInitialTitleResId, mInitialTitle);
        } else {
            // Show search icon as up affordance if we are displaying the main Dashboard
            mInitialTitleResId = R.string.dashboard_title;
            switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
                    mInitialTitleResId, mInitialTitle);
        }
    }

>switchToFragment

根据类名获取实例对象,完事加载到对应的布局里

    private void switchToFragment(String fragmentName, Bundle args, boolean validate,
            int titleResId, CharSequence title) {
        Fragment f = Utils.getTargetFragment(this, fragmentName, args);
        if (f == null) {
            return;
        }
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.main_content, f);
        if (titleResId > 0) {
            transaction.setBreadCrumbTitle(titleResId);
        } else if (title != null) {
            transaction.setBreadCrumbTitle(title);
        }
        transaction.commitAllowingStateLoss();
        getSupportFragmentManager().executePendingTransactions();
    }

3.3.CategoryMixin

自身实现了生命周期

public class CategoryMixin implements LifecycleObserver {

>1.onResume

    private boolean mFirstOnResume = true;
    public void onResume() {
        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        filter.addDataScheme(DATA_SCHEME_PKG);
        mContext.registerReceiver(mPackageReceiver, filter);

        if (mFirstOnResume) {
            // Skip since all tiles have been refreshed in DashboardFragment.onCreatePreferences().
            Log.d(TAG, "Skip categories update");
            mFirstOnResume = false;
            return;
        }
        //除了第一次,后边再resume的时候会刷新数据,参考补充3
        updateCategories();
    }

>2.监听

在SettingsHomepageActivity.java的onCreate方法里调用

        mCategoryMixin = new CategoryMixin(this);
        getLifecycle().addObserver(mCategoryMixin);

>3.updateCategories

    public void updateCategories() {
        updateCategories(false /* fromBroadcast */);
    }
    private void updateCategories(boolean fromBroadcast) {
        if (mCategoriesUpdateTaskCount < 2) {
        //启动个task
            new CategoriesUpdateTask().execute(fromBroadcast);
        }
    }

>4.CategoriesUpdateTask

        protected void onPostExecute(Set<String> categories) {
            if (categories == null || !categories.isEmpty()) {
                onCategoriesChanged(categories);
            }
            mCategoriesUpdateTaskCount--;
        }

回调,注册见2.4.10,回调见2.4.9

    void onCategoriesChanged(Set<String> categories) {
        mCategoryListeners.forEach(listener -> listener.onCategoriesChanged(categories));
    }

4. 自定义的preference学习

4.1.HomepagePreference

交给helper类来处理了

public class HomepagePreference extends Preference implements
        HomepagePreferenceLayoutHelper.HomepagePreferenceLayout {

    private final HomepagePreferenceLayoutHelper mHelper;

    public void onBindViewHolder(PreferenceViewHolder holder) {
        super.onBindViewHolder(holder);
        mHelper.onBindViewHolder(holder);
    }

    @Override
    public HomepagePreferenceLayoutHelper getHelper() {
        return mHelper;
    }

>HomepagePreferenceLayoutHelper

主要是设置了要加载的布局,以及图标和文字的可见性处理

/** Helper for homepage preference to manage layout. */
public class HomepagePreferenceLayoutHelper {

//默认加载的布局
    public HomepagePreferenceLayoutHelper(Preference preference) {
        preference.setLayoutResource(R.layout.homepage_preference);
    }
    //设置图标文字的可见性
    void onBindViewHolder(PreferenceViewHolder holder) {
        mIcon = holder.findViewById(R.id.icon_frame);
        mText = holder.findViewById(R.id.text_frame);
        setIconVisible(mIconVisible);
        setIconPaddingStart(mIconPaddingStart);
        setTextPaddingStart(mTextPaddingStart);
    }    

>homepage_preference.xml

左边是图标,右侧是title和summary

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="@dimen/homepage_preference_min_height"
    android:gravity="center_vertical"
    android:background="?android:attr/selectableItemBackground"
    android:clipToPadding="false"
    android:baselineAligned="false">

    <LinearLayout
        android:id="@+id/icon_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minWidth="48dp"
        android:gravity="end|center_vertical"
        android:orientation="horizontal"
        android:paddingStart="@dimen/homepage_preference_icon_padding_start"
        android:paddingTop="4dp"
        android:paddingBottom="4dp">

        <androidx.preference.internal.PreferenceImageView
            android:id="@android:id/icon"
            android:layout_width="24dp"
            android:layout_height="24dp"
            app:maxWidth="48dp"
            app:maxHeight="48dp"/>

    </LinearLayout>

    <RelativeLayout
        android:id="@+id/text_frame"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingTop="16dp"
        android:paddingBottom="16dp"
        android:paddingStart="@dimen/homepage_preference_text_padding_start"
        android:paddingEnd="24dp">

        <TextView
            android:id="@android:id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:textAppearance="?android:attr/textAppearanceListItem"
            android:hyphenationFrequency="normalFast"
            android:lineBreakWordStyle="phrase"
            android:ellipsize="marquee"/>

        <TextView
            android:id="@android:id/summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@android:id/title"
            android:layout_alignStart="@android:id/title"
            android:layout_gravity="start"
            android:textAlignment="viewStart"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="?android:attr/textColorSecondary"
            android:maxLines="4"
            android:hyphenationFrequency="normalFast"
            android:lineBreakWordStyle="phrase"
            style="@style/PreferenceSummaryTextStyle"/>
    </RelativeLayout>
</LinearLayout>

4.2 TwoTargetPreference.java

效果图如下,显示左右两部分,右侧部分需要自定义布局,默认右侧部分是不显示的。 image.png

/**
 * The Base preference with two target areas divided by a vertical divider
 */
public class TwoTargetPreference extends Preference {

    private void init(Context context) {
        setLayoutResource(R.layout.preference_two_target);
//...
        final int secondTargetResId = getSecondTargetResId();
        if (secondTargetResId != 0) {
            setWidgetLayoutResource(secondTargetResId);//这个是Prefrence里的方法
        }
    }
    
    public void onBindViewHolder(PreferenceViewHolder holder) {
//...
        final View divider = holder.findViewById(R.id.two_target_divider);
        final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
        final boolean shouldHideSecondTarget = shouldHideSecondTarget();
        // 提供了右侧布局以后,右侧的容器和divider才会显示
        if (divider != null) {
            divider.setVisibility(shouldHideSecondTarget ? View.GONE : View.VISIBLE);
        }
        if (widgetFrame != null) {
            widgetFrame.setVisibility(shouldHideSecondTarget ? View.GONE : View.VISIBLE);
        }
    }

    protected boolean shouldHideSecondTarget() {
        return getSecondTargetResId() == 0;
    }
//最右侧要添加的布局id
    protected int getSecondTargetResId() {
        return 0;
    }    

>setWidgetLayoutResource()

下边方法是源码PreferenceGroupAdapter.java里的,可以看到我们提供的widget被添加到widgetFrame里去了。

final ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame);
if (widgetFrame != null) {
    if (descriptor.mWidgetLayoutResId != 0) {
        inflater.inflate(descriptor.mWidgetLayoutResId, widgetFrame);
    } else {
        widgetFrame.setVisibility(View.GONE);
    }
}

>preference_two_target.xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeightSmall"
    android:gravity="center_vertical"
    android:background="@android:color/transparent"
    android:clipToPadding="false">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="?android:attr/selectableItemBackground"
        android:gravity="start|center_vertical"
        android:clipToPadding="false"
        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">

        <LinearLayout
            android:id="@+id/icon_frame"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="start|center_vertical"
            android:minWidth="56dp"
            android:orientation="horizontal"
            android:clipToPadding="false"
            android:paddingTop="4dp"
            android:paddingBottom="4dp">
            <androidx.preference.internal.PreferenceImageView
                android:id="@android:id/icon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                settings:maxWidth="48dp"
                settings:maxHeight="48dp" />
        </LinearLayout>

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:paddingTop="16dp"
            android:paddingBottom="16dp">

            <TextView
                android:id="@android:id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:textAppearance="?android:attr/textAppearanceListItem"
                android:ellipsize="marquee" />

            <TextView
                android:id="@android:id/summary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@android:id/title"
                android:layout_alignStart="@android:id/title"
                android:textAppearance="?android:attr/textAppearanceListItemSecondary"
                android:textColor="?android:attr/textColorSecondary"
                android:maxLines="10" />

        </RelativeLayout>

    </LinearLayout>

    <include layout="@layout/preference_two_target_divider" />

    <!-- Preference should place its actual preference widget here. -->
    <LinearLayout
        android:id="@android:id/widget_frame"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:minWidth="64dp"
        android:gravity="center"
        android:orientation="vertical" />

</LinearLayout>

4.3. RestrictedPreference.java

这个类在那个settingsLib里,功能由helper类处理。主要作用就是根据功能是否受限,决定是否disable掉这个选项,比如手机没插sim卡,那么那个mobile的功能就是灰色的。

/**
 * Preference class that supports being disabled by a user restriction
 * set by a device admin.
 */
public class RestrictedPreference extends TwoTargetPreference {
    RestrictedPreferenceHelper mHelper;

TwoTargetPreference就是正常的选项卡,左侧是icon,右边是title和summary,最后侧还可能有额外的view

4.4 AddPreference

继承的RestrictedPreference,右侧可以显示一个加号按钮,需要设置加号的点击事件,按钮才能显示

/**
 * A preference with a plus button on the side representing an "add" action. The plus button will
 * only be visible when a non-null click listener is registered.
 */
public class AddPreference extends RestrictedPreference implements View.OnClickListener {

    int getAddWidgetResId() {
        return R.id.add_preference_widget;
    }
    public void setOnAddClickListener(OnAddClickListener listener) {
        mListener = listener;
       if (mWidgetFrame != null) {
           mWidgetFrame.setVisibility(shouldHideSecondTarget() ? View.GONE : View.VISIBLE);
       }
    }    


    protected int getSecondTargetResId() {
        return R.layout.preference_widget_add;
    }

    //可以看到,需要设置listener右侧部分才能显示
    protected boolean shouldHideSecondTarget() {
        return mListener == null;
    }

4.5 RestrictedSwitchPreference

看名字就知道是受限的,也就是有条件的显示。

/**
 * Version of SwitchPreference that can be disabled by a device admin
 * using a user restriction.
 */
public class RestrictedSwitchPreference extends SwitchPreference {


4.6 其他

暂时不看了,系统自定义的有点多。

上边看完了首页的功能,现在继续看详细的功能,挨个看

5. StorageDashboardFragment.java

public class StorageDashboardFragment extends DashboardFragment
        implements
        LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.StorageResult>> {

5.1.storage_dashboard_fragment.xml

<PreferenceScreen
    android:key="storage_dashboard_fragment"
    android:title="@string/storage_settings"
    settings:keywords="@string/keywords_storage">
    <!--下拉列表,有外置存储卡的时候才可能显示,反正是总的存储卡个数大于1才显示-->
    <com.android.settingslib.widget.SettingsSpinnerPreference
        android:key="storage_spinner"
        android:order="1"
        settings:searchable="false"
        settings:controller="com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController"/>
        <!--存储卡的状态,已使用大小以及总大小的显示-->
    <com.android.settingslib.widget.UsageProgressBarPreference
        android:key="storage_summary"
        android:order="2"
        android:selectable="false"
        settings:searchable="false"
        settings:controller="com.android.settings.deviceinfo.storage.StorageUsageProgressBarPreferenceController"/>
        <!--存储管理器的开关,就是自动清理一些东西的-->
    <com.android.settingslib.PrimarySwitchPreference
        android:fragment="com.android.settings.deletionhelper.AutomaticStorageManagerSettings"
        android:key="toggle_asm"
        android:title="@string/automatic_storage_manager_preference_title"
        android:icon="@drawable/ic_storage"
        android:order="3"
        settings:controller="com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController"/>
        <!--点击可以跳转到释放控件的页面-->
    <com.android.settings.widget.CardPreference
        android:key="free_up_space"
        android:order="4"
        android:title="@string/storage_free_up_space_title"
        android:summary="@string/storage_free_up_space_summary"
        settings:controller="com.android.settings.deviceinfo.storage.ManageStoragePreferenceController"/>
        <!--下边就是一些常用数据类型的数据使用情况展示-->
    <!-- Preference order 100~200 are 'ONLY' for storage category preferences below. -->
    <Preference
        android:key="pref_public_storage"
        android:title="@string/storage_files"
        android:icon="@drawable/ic_folder_vd_theme_24"
        android:order="100"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_images"
        android:title="@string/storage_images"
        android:icon="@drawable/ic_photo_library"
        android:order="101"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_videos"
        android:title="@string/storage_videos"
        android:icon="@drawable/ic_local_movies"
        android:order="102"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_audio"
        android:title="@string/storage_audio"
        android:icon="@drawable/ic_media_stream"
        android:order="103"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_apps"
        android:title="@string/storage_apps"
        android:icon="@drawable/ic_storage_apps"
        android:order="104"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_games"
        android:title="@string/storage_games"
        android:icon="@drawable/ic_videogame_vd_theme_24"
        android:order="105"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_documents_and_other"
        android:title="@string/storage_documents_and_other"
        android:icon="@drawable/ic_folder_vd_theme_24"
        android:order="106"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_system"
        android:title="@string/storage_system"
        android:icon="@drawable/ic_system_update"
        android:order="107"/>
    <com.android.settings.deviceinfo.StorageItemPreference
        android:key="pref_trash"
        android:title="@string/storage_trash"
        android:icon="@drawable/ic_trash_can"
        android:order="108"/>
    <!-- Preference order 100~200 are 'ONLY' for storage category preferences above. -->
    <PreferenceCategory
        android:key="pref_secondary_users"
        android:title="@string/storage_other_users"
        android:order="201" />
</PreferenceScreen>

5.2.VolumeOptionMenuController

控制右上角menu内容的

image.png

>initializeOptionsMenu

    void initializeOptionsMenu(Activity activity) {
        mOptionMenuController = new VolumeOptionMenuController(activity, this,
                mSelectedStorageEntry);
       //这个很重要,父类ObservablePreferenceFragment里会调用controller实现的接口的方法         
        getSettingsLifecycle().addObserver(mOptionMenuController);
        setHasOptionsMenu(true);
        activity.invalidateOptionsMenu();
    }

>onCreateOptionsMenu

这个类实现了OnCreateOptionsMenu接口并加入了observer里,所以在父类对应的方法里会自动调用这个方法

public class VolumeOptionMenuController implements LifecycleObserver, OnCreateOptionsMenu,
        OnPrepareOptionsMenu, OnOptionsItemSelected {
    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
        inflater.inflate(R.menu.storage_volume, menu);
    }

>updateOptionsMenu

更新menu的可见性,具体条件见代码

    private void updateOptionsMenu() {
        if (mRename == null || mMount == null || mUnmount == null || mFormat == null
                || mFormatAsPortable == null || mFormatAsInternal == null || mMigrate == null
                || mFree == null || mForget == null) {
            return;
        }

        mRename.setVisible(false);
        mMount.setVisible(false);
        mUnmount.setVisible(false);
        mFormat.setVisible(false);
        mFormatAsPortable.setVisible(false);
        mFormatAsInternal.setVisible(false);
        mMigrate.setVisible(false);
        mFree.setVisible(false);
        mForget.setVisible(false);

        if (mStorageEntry.isDiskInfoUnsupported()) {
            mFormat.setVisible(true);
            return;
        }
        if (mStorageEntry.isVolumeRecordMissed()) {
            mForget.setVisible(true);
            return;
        }
        if (mStorageEntry.isUnmounted()) {
            mMount.setVisible(true);
            return;
        }
        if (!mStorageEntry.isMounted()) {
            return;
        }

        if (mStorageEntry.isPrivate()) {
            if (!mStorageEntry.isDefaultInternalStorage()) {
                mRename.setVisible(true);
                mFormatAsPortable.setVisible(true);
            }

            // Only offer to migrate when not current storage.
            final VolumeInfo primaryVolumeInfo = mPackageManager.getPrimaryStorageCurrentVolume();
            final VolumeInfo selectedVolumeInfo = mStorageEntry.getVolumeInfo();
            mMigrate.setVisible(primaryVolumeInfo != null
                    && primaryVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE
                    && !Objects.equals(selectedVolumeInfo, primaryVolumeInfo)
                    && primaryVolumeInfo.isMountedWritable());
            return;
        }

        if (mStorageEntry.isPublic()) {
            mRename.setVisible(true);
            mUnmount.setVisible(true);
            mFormatAsInternal.setVisible(true);
            return;
        }
    }

当前存储卡选择修改以后会调用上边的方法刷新menu的状态,另外初始化的时候也会刷新状态

    public void setSelectedStorageEntry(StorageEntry storageEntry) {
        mStorageEntry = storageEntry;

        updateOptionsMenu();
    }

>onOptionsItemSelected

menu的点击事件,这里主要看下mount和unmount

    public boolean onOptionsItemSelected(MenuItem menuItem) {
        if (!mFragment.isAdded()) {
            return false;
        }

        final int menuId = menuItem.getItemId();
        if (menuId == R.id.storage_mount) {
            if (mStorageEntry.isUnmounted()) {
                new MountTask(mFragment.getActivity(), mStorageEntry.getVolumeInfo()).execute();
                return true;
            }
            return false;
        }
        if (menuId == R.id.storage_unmount) {
            if (mStorageEntry.isMounted()) {
                if (mStorageEntry.isPublic()) {
                //public的,直接unmount
                    new UnmountTask(mFragment.getActivity(),
                            mStorageEntry.getVolumeInfo()).execute();
                    return true;
                }
                if (mStorageEntry.isPrivate() && !mStorageEntry.isDefaultInternalStorage()) {
                    final Bundle args = new Bundle();
                    args.putString(VolumeInfo.EXTRA_VOLUME_ID, mStorageEntry.getId());
                    //private的,跳到unmount确认页面,按钮的点击事件和上边的public一样
                    new SubSettingLauncher(mContext)
                            .setDestination(PrivateVolumeUnmount.class.getCanonicalName())
                            .setTitleRes(R.string.storage_menu_unmount)
                            .setSourceMetricsCategory(SettingsEnums.DEVICEINFO_STORAGE)
                            .setArguments(args)
                            .launch();
                    return true;
                }
            }
            return false;
        }

mount/unmount

具体看下mount和unmount的操作

        public MountTask(Context context, VolumeInfo volume) {
            mContext = context.getApplicationContext();
            mStorageManager = mContext.getSystemService(StorageManager.class);
            mVolumeId = volume.getId();
        }

        @Override
        protected Exception doInBackground(Void... params) {
            try {
                mStorageManager.mount(mVolumeId);
                return null;
            } catch (Exception e) {
                return e;
            }
        }

//

        protected Exception doInBackground(Void... params) {
            try {
                mStorageManager.unmount(mVolumeId);
                return null;
            } catch (Exception e) {
                return e;
            }
        }

5.3.StorageSelectionPreferenceController

下图的控制器 image.png

>onattach

设置了item选中的操作

        mStorageSelectionController = use(StorageSelectionPreferenceController.class);
        mStorageSelectionController.setOnItemSelectedListener(storageEntry -> {
            mSelectedStorageEntry = storageEntry;
            //要查看的存储卡发生了变化,所以需要更新相关的信息
            refreshUi();
           //对存储卡的状态进行判断,有问题的话会弹框提示
            if (storageEntry.isDiskInfoUnsupported() || storageEntry.isUnmountable()) {
                DiskInitFragment.show(this, R.string.storage_dialog_unmountable,
                        storageEntry.getDiskId());
            } else if (storageEntry.isVolumeRecordMissed()) {
                StorageUtils.launchForgetMissingVolumeRecordFragment(getContext(), storageEntry);
            }
        });

>数据来源

            mStorageEntries.clear();
            mStorageEntries.addAll(
                    StorageUtils.getAllStorageEntries(getContext(), mStorageManager));

>refreshUi

设置列表数据,以及选中的数据

    private void refreshUi() {
        mStorageSelectionController.setStorageEntries(mStorageEntries);
        mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry);

6.System选项

6.1.SystemDashboardFragment

@SearchIndexable
public class SystemDashboardFragment extends DashboardFragment {
//..
    protected int getPreferenceScreenResId() {
        return R.xml.system_dashboard_fragment;
    }
//..

>1.问题

上边的xml里有6个preference,还有两个没显示,可最终显示的有7个,那么3个新的哪里来的?

6.2.动态选项

>1.getCategoryKey

    public String getCategoryKey() {
        return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
    }

map里存的是fragment对应的key

        PARENT_TO_CATEGORY_KEY_MAP.put(
                SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);

具体的key如下

    public static final String CATEGORY_SYSTEM = "com.android.settings.category.ia.system";

具体逻辑参考小节2.4.6,tile数据的获取逻辑看2.7小节,就是根据action以及其他属性查找相关的组件。

>2.获取到的tile

这里看下settings的清单文件,

  • 查找满足action(com.android.settings.action.SETTINGS)的,有如下2个
  • meta data里是否有key为com.android.settings.category
  • 最终生成2个tile,他们的cagegoryKey都是com.android.settings.category.ia.system
        <activity
            android:name="Settings$UserSettingsActivity">

            <intent-filter>
            //这里
                <action android:name="com.android.settings.action.SETTINGS" />
            </intent-filter>
            <meta-data android:name="com.android.settings.order" android:value="-45"/>
            //这里
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.system" />
            <meta-data android:name="com.android.settings.summary_uri"
                       android:value="content://com.android.settings.dashboard.SummaryProvider/user" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_multiuser" />
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.users.UserSettings" />
            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
                       android:value="@string/menu_key_system"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true" />
        </activity>

        <activity
            android:name="Settings$DevelopmentSettingsDashboardActivity">

            <intent-filter>
            //这里
                <action android:name="com.android.settings.action.SETTINGS" />
            </intent-filter>
            <meta-data android:name="com.android.settings.order" android:value="-40"/>
            //这里
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.system" />
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/summary_empty"/>
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_development" />
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
                       android:value="@string/menu_key_system"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true" />
        </activity>

同样是settings下的,action是 com.android.settings.action.IA_SETTINGS

        <activity android:name=".backup.UserBackupSettingsActivity">

            <!-- 这里,Mark the activity as a dynamic setting -->
            <intent-filter>
                <action android:name="com.android.settings.action.IA_SETTINGS" />
            </intent-filter>
            <!-- Tell Settings app which category it belongs to -->
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.system" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_backup" />
            <meta-data android:name="com.android.settings.order" android:value="-60"/>
        </activity>

>3.tile的使用

具体逻辑看小节2.4.6,这里就是具体分析下上边3种tile,如下图,可以看到,红框部分那3个就是动态添加的,其他的几个是xml里有的。

  • com.android.settings.Settings$DevelopmentSettingsDashboardActivity 【Developer options】
  • com.android.settings.Settings$UserSettingsActivity 【Multiple users】
  • com.android.settings.backup.UserBackupSettingsActivity 【Backup】 image.png
key值

meta data里没有配置这个name(com.android.settings.keyhint),所以用的是自动生成的。 dashboard_tile_pref_加上组件名 如下,就是3个动态选项的key值,具体逻辑上边2.5.3有贴

dashboard_tile_pref_com.android.settings.Settings$DevelopmentSettingsDashboardActivity
dashboard_tile_pref_com.android.settings.Settings$UserSettingsActivity
dashboard_tile_pref_com.android.settings.backup.UserBackupSettingsActivity
title值

meta data里也没有name(com.android.settings.title),所以用的是组件的lable名

android:label="@string/development_settings_title"
android:label="@string/user_settings_title"
android:label="@string/privacy_settings_title"

<string name="privacy_settings_title">Backup</string>
summary值
  • backup 没有summary
  • multiply users给的是uri
            <meta-data android:name="com.android.settings.summary_uri"
                       android:value="content://com.android.settings.dashboard.SummaryProvider/user" />
  • 开发者模式 给的是固定的summary,为空
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/summary_empty"/>
    <string name="summary_empty" translatable="false"></string> //在settingsLib包里         
    
switch

meta data里没有name为com.android.settings.switch_uri的,所以都是不带开关状态的

icon
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_development" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_multiuser" />
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_backup" />
跳转
  • 开发者模式,有设置跳转的fragment
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
  • 多用户
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.users.UserSettings" />
  • backup 这个没有设置fragment,所以就跳转对应的activity了,如下
        <activity android:name=".backup.UserBackupSettingsActivity"
                  android:label="@string/privacy_settings_title"
                  android:exported="true"
                  android:icon="@drawable/ic_settings_backup">

7.about phone

    <com.android.settings.widget.HomepagePreference
        android:fragment="com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment"
        android:icon="@drawable/ic_phone_info"
        android:key="top_level_about_device"
        android:order="20"
        android:title="@string/about_settings"
        android:summary="@string/summary_placeholder"
        settings:highlightableMenuKey="@string/menu_key_about_device"
        settings:controller="com.android.settings.deviceinfo.aboutphone.TopLevelAboutDevicePreferenceController"/>

7.1.MyDeviceInfoFragment

>1.question

  • xml里看到的选项位置,和实际显示的有差别,没找到哪里修改的order?

  • 比如下边xml里的header,位置明明在第一,order也是0,实际显示效果却在第二位。

  • 难道order 0不是排在第一?查了下Preference的默认order是int的最大值

    public static final int DEFAULT_ORDER = Integer.MAX_VALUE;

        <com.android.settingslib.widget.LayoutPreference
            android:key="my_device_info_header"
            android:order="0"/>

        <PreferenceCategory
            android:key="basic_info_category"
            android:selectable="false"
            android:title="@string/my_device_info_basic_info_category_title">

            <!-- Device name -->
            <com.android.settings.widget.ValidatedEditTextPreference
                android:key="device_name"
                android:order="1"/>

            <!-- Account name -->
            <Preference
                android:key="branded_account"
                android:order="2"/>

            <!-- Phone number -->
            <com.android.settings.deviceinfo.PhoneNumberSummaryPreference
                android:key="phone_number"
                android:order="3"/>
        </PreferenceCategory>

>2.解答

参考上篇PreferenceFragmentCompat小节5.1.1

  • 添加preference的时候,如果order为默认值,会重新设置order,从0开始设置
  • 所以上边的basic_info_category最终设置的order为0,然后它的title不为空,my_device_info_header的title为空,所以排在后边了。

8.开发者属性页面

这个页面是从小节6 里的动态添加的选项跳转过来的,跳转页面如下

            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />

8.1.DeveloperOptionsPreferenceController.java

开发者模式下的选项controller基本都继承这个,作用是在下图这个开关关闭的时候统一关闭其他选项

image.png

public abstract class DeveloperOptionsPreferenceController extends AbstractPreferenceController {

    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mPreference = screen.findPreference(getPreferenceKey());
    }

    public void onDeveloperOptionsEnabled() {
        if (isAvailable()) {
            onDeveloperOptionsSwitchEnabled();
        }
    }

    public void onDeveloperOptionsDisabled() {
        if (isAvailable()) {
            onDeveloperOptionsSwitchDisabled();
        }
    }

    protected void onDeveloperOptionsSwitchEnabled() {
        mPreference.setEnabled(true);
    }

    protected void onDeveloperOptionsSwitchDisabled() {
        mPreference.setEnabled(false);
    }

>1.开关事件

下边看下fragment里上述开关的切换事件

    private void enableDeveloperOptions() {
        DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(getContext(), true);
        //所有的controller统一enable
        for (AbstractPreferenceController controller : mPreferenceControllers) {
            if (controller instanceof DeveloperOptionsPreferenceController) {
                ((DeveloperOptionsPreferenceController) controller).onDeveloperOptionsEnabled();
            }
        }
    }

    private void disableDeveloperOptions() {
        DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(getContext(), false);
        final SystemPropPoker poker = SystemPropPoker.getInstance();
        poker.blockPokes();
        //所有的controller统一disable
        for (AbstractPreferenceController controller : mPreferenceControllers) {
            if (controller instanceof DeveloperOptionsPreferenceController) {
                ((DeveloperOptionsPreferenceController) controller)
                        .onDeveloperOptionsDisabled();
            }
        }
        poker.unblockPokes();
        poker.poke();
    }

8.2.DevelopmentSettingsDashboardFragment

development_settings.xml

>1.createPreferenceControllers

    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        //这里添加了上百个controller
        mPreferenceControllers = buildPreferenceControllers(context, getActivity(),
                getSettingsLifecycle(), this /* devOptionsDashboardFragment */,
                new BluetoothA2dpConfigStore());
        return mPreferenceControllers;
    }

>2.registerReceivers

    private void registerReceivers() {
        LocalBroadcastManager.getInstance(getContext())
                .registerReceiver(mEnableAdbReceiver, new IntentFilter(
                        AdbPreferenceController.ACTION_ENABLE_ADB_STATE_CHANGED));

        final IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
        getActivity().registerReceiver(mBluetoothA2dpReceiver, filter);
    }

    private void unregisterReceivers() {
        LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mEnableAdbReceiver);
        getActivity().unregisterReceiver(mBluetoothA2dpReceiver);
    }
    
    
    private final BroadcastReceiver mEnableAdbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            for (AbstractPreferenceController controller : mPreferenceControllers) {
                if (controller instanceof AdbOnChangeListener) {
                //这里找到一个,就是8.4那个controller
                    ((AdbOnChangeListener) controller).onAdbSettingChanged();
                }
            }
        }
    };    

8.3.AdbPreferenceController.java

image.png

        <com.android.settingslib.RestrictedSwitchPreference
            android:key="enable_adb"
            android:title="@string/enable_adb"
            android:summary="@string/enable_adb_summary" />

主要传递一个Fragment进来,方便显示对话框,以及处理对话框按钮事件

public class AdbPreferenceController extends AbstractEnableAdbPreferenceController implements
        PreferenceControllerMixin {

    private final DevelopmentSettingsDashboardFragment mFragment;

    public AdbPreferenceController(Context context, DevelopmentSettingsDashboardFragment fragment) {
        super(context);
        mFragment = fragment;
    }

    public void onAdbDialogConfirmed() {
        writeAdbSetting(true);
    }

    public void onAdbDialogDismissed() {
        updateState(mPreference);
    }

    //显示确认对话框,父类调用
    public void showConfirmationDialog(@Nullable Preference preference) {
        EnableAdbWarningDialog.show(mFragment);
    }

>1.AbstractEnableAdbPreferenceController

public abstract class AbstractEnableAdbPreferenceController extends
        DeveloperOptionsPreferenceController implements ConfirmationDialogController {
    private static final String KEY_ENABLE_ADB = "enable_adb";
    public static final String ACTION_ENABLE_ADB_STATE_CHANGED =

//...
    private boolean isAdbEnabled() {
        final ContentResolver cr = mContext.getContentResolver();
        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, ADB_SETTING_OFF)
                != ADB_SETTING_OFF;
    }
//...
    public boolean handlePreferenceTreeClick(Preference preference) {

        if (TextUtils.equals(KEY_ENABLE_ADB, preference.getKey())) {
            if (!isAdbEnabled()) {
            //不可用的时候点击会弹框确认,子类实现
                showConfirmationDialog(preference);
            } else {
            //关闭的时候不需要确认,直接修改
                writeAdbSetting(false);
            }
            return true;
        } else {
            return false;
        }
    }
//
    protected void writeAdbSetting(boolean enabled) {
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
        notifyStateChanged();
    }
//发送广播,fragment里会监听
    private void notifyStateChanged() {
    //目前搜了下,影响的是8.4那个选项
        LocalBroadcastManager.getInstance(mContext)
                .sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED));
    }

8.4.VerifyAppsOverUsbPreferenceController.java

image.png

>1.功能是否可用

public class VerifyAppsOverUsbPreferenceController extends DeveloperOptionsPreferenceController
        implements Preference.OnPreferenceChangeListener, AdbOnChangeListener,
        PreferenceControllerMixin {
        //..

    public void onAdbSettingChanged() {
        if (isAvailable()) {
            updateState(mPreference);
        }
    }
//..
    private boolean shouldBeEnabled() {
        final ContentResolver cr = mContext.getContentResolver();
        //先判断usb debugging是否打开,没打开的话这个也不可用
        if (Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED,
                AdbPreferenceController.ADB_SETTING_OFF)
                == AdbPreferenceController.ADB_SETTING_OFF) {
            return false;
        }
        //然后查找有没有可以处理包安装的广播
        final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
        verification.setType(PACKAGE_MIME_TYPE);
        verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        final List<ResolveInfo> receivers = mPackageManager.queryBroadcastReceivers(
                verification, 0 /* flags */);
        //有的话就可用,没有的话就不可用
        return !receivers.isEmpty();
    }

>2.搜到的recevier

action是对的,可惜没有mimeType,所以上边的结果是空。

        <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
                 android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
            </intent-filter>
        </receiver>

8.5.开机后自动打开开发者模式以及usb debug

在 FallbackHome.java里加入如下的代码

import com.android.settingslib.development.DevelopmentSettingsEnabler;

    DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(this,true);
    Settings.Global.putInt(getContentResolver(),Settings.Global.ADB_ENABLED, 1);

10.总结

  • 核心基类DashboardFragment,静态数据(xml里加载),动态数据(Tiles)
  • 条目的内容,基本都是靠controller来控制的,
  • tile有两种数据来源,一种是activity的,一种是contentProvider的,最终生成的preference是switchPreference或者普通的Preference
  • TileUtils 是用来获取所有的Tile数据的