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
效果图如下,显示左右两部分,右侧部分需要自定义布局,默认右侧部分是不显示的。
/**
* 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内容的
>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
下图的控制器
>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】
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基本都继承这个,作用是在下图这个开关关闭的时候统一关闭其他选项
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
<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
>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数据的