android framework13-settings【0
1.概述
简单学习下系统设置功能,这个app在packages/apps/Settings目录下,app里边引用了一些库可能在frameworks/base/packages/SettingsLib或SettingsProvider目录下
2.Fragment的层级关系
因为设置里都是选项卡,所以基类用的就是PreferenceFragmentCompat,这里看下代码里自定义的Fragment
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);
}
}
@Override
public void addPreferencesFromResource(@XmlRes int preferencesResId) {
super.addPreferencesFromResource(preferencesResId);
updateActivityTitleWithScreenTitle(getPreferenceScreen());//更新activity的title
}
private void updateActivityTitleWithScreenTitle(PreferenceScreen screen) {
if (screen != null) {
final CharSequence title = screen.getTitle();
if (!TextUtils.isEmpty(title)) {
getActivity().setTitle(title);
} else {
Log.w(TAG, "Screen title missing for fragment " + this.getClass().getName());
}
}
}
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);
}
}
}
>showDialog
这个类里封装了如下显示一个dialog的逻辑
使用的时候调用showDialog传递一个int值,完事覆写OnCreateDialog里根据dialogId返回不同的Dialog即可。另外,cancel,dismiss的监听方法也都有,还有removeDialog的方法也有
protected void showDialog(int dialogId) {
if (mDialogFragment != null) {
Log.e(TAG, "Old dialog fragment not null!");
}
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);
// And wire up with lifecycle.
final Lifecycle lifecycle = getSettingsLifecycle();
uniqueControllerFromXml.forEach(controller -> {
if (controller instanceof LifecycleObserver) {
lifecycle.addObserver((LifecycleObserver) controller);
}
});
// Set metrics category for BasePreferenceController.
//...
mPlaceholderPreferenceController =
new DashboardTilePlaceholderPreferenceController(context);
//添加一个占位符,方便动态添加选项
mControllers.add(mPlaceholderPreferenceController);
for (AbstractPreferenceController controller : mControllers) {
//根据controller的class,把值保存在一个叫mPreferenceControllers的map里
addPreferenceController(controller);
}
}
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
checkUiBlocker(mControllers);
refreshAllPreferences(getLogTag());
mControllers.stream()
.map(controller -> (Preference) findPreference(controller.getPreferenceKey()))
.filter(Objects::nonNull)
.forEach(preference -> {
// Give all controllers a chance to handle click.
preference.getExtras().putInt(CATEGORY, getMetricsCategory());
});
}
//这个暂时不看了,搜了下继承UiBlock的目前就一个类BlockingSlicePrefController,蓝牙部分会用到
void checkUiBlocker(List<AbstractPreferenceController> controllers) {
final List<String> keys = new ArrayList<>();
final List<BasePreferenceController> baseControllers = new ArrayList<>();
controllers.forEach(controller -> {
if (controller instanceof BasePreferenceController.UiBlocker
&& controller.isAvailable()) {
((BasePreferenceController) controller).setUiBlockListener(this);
keys.add(controller.getPreferenceKey());
baseControllers.add((BasePreferenceController) controller);
}
});
if (!keys.isEmpty()) {
mBlockerController = new UiBlockerController(keys);
mBlockerController.start(() -> {//延迟300ms后,在主线程执行下边的代码
updatePreferenceVisibility(mPreferenceControllers);
baseControllers.forEach(controller -> controller.setUiBlockerFinished(true));
});
}
}
private void refreshAllPreferences(final String tag) {
final PreferenceScreen screen = getPreferenceScreen();
// First remove old preferences.
if (screen != null) {
// Intentionally do not cache PreferenceScreen because it will be recreated later.
screen.removeAll();
}
// 这个就是从xml加载数据,具体见下边的方法,用到了addPreferencesFromResource()
displayResourceTiles();
refreshDashboardTiles(tag);//详见后续分析
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);//这个就是处理选项的可见性
}
>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);
}
}
}
>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 */);
}
}
>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,那么这个方法里就会直接更新选项的可见性了。
>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
public Fragment getCallbackFragment() {
return null;//注意,这里默认是null,自己实现的话一般返回this
}
public boolean onPreferenceTreeClick(Preference preference) {
//先判断有没有设置要跳转的fragment
if (preference.getFragment() != null) {
boolean handled = false;
if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) {
handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment())
.onPreferenceStartFragment(this, preference);
}
if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback) {
handled = ((OnPreferenceStartFragmentCallback) getActivity())
.onPreferenceStartFragment(this, preference);
}
if (!handled) {
final FragmentManager fragmentManager = requireActivity()
.getSupportFragmentManager();
final Bundle args = preference.getExtras();
final Fragment fragment = fragmentManager.getFragmentFactory().instantiate(
requireActivity().getClassLoader(), preference.getFragment());
fragment.setArguments(args);
fragment.setTargetFragment(this, 0);
fragmentManager.beginTransaction()
.replace((((View) getView().getParent()).getId()), fragment)
.addToBackStack(null)
.commit();
}
return true;
}
return false;
}
>getCategoryKey
public String getCategoryKey() {
return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
}
settings的首页是这个
PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
CategoryKey.CATEGORY_HOMEPAGE);
public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";
>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;
}
// Create a list to track which tiles are to be removed.
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)) {
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创建对应的选项
final Preference pref = createPreference(tile);
//给选项卡绑定数据
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(key);
}
// Remove tiles that are gone.
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();
Log.d(tag, "Start waiting observers");
awaitObserverLatch(mainLatch);
Log.d(tag, "Stop waiting observers");
pendingObservers.forEach(DynamicDataObserver::updateUi);
}
}
>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());
}
>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);
}
2.5.mDashboardFeatureProvider
>实例化
下边代码在上边的DashboardFragment里
mDashboardFeatureProvider = FeatureFactory.getFactory(context).
getDashboardFeatureProvider(context);
>FeatureFactory.java
<!-- 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>
//
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();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
return sFactory;
}
>FeatureFactoryImpl.java
这里就是用到的provider
public DashboardFeatureProvider getDashboardFeatureProvider(Context context) {
if (mDashboardFeatureProvider == null) {
mDashboardFeatureProvider = new DashboardFeatureProviderImpl(
context.getApplicationContext());
}
return mDashboardFeatureProvider;
}
2.5.1.DashboardFeatureProviderImpl
public DashboardFeatureProviderImpl(Context context) {
mContext = context.getApplicationContext();
mCategoryManager = CategoryManager.get(context);
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
mPackageManager = context.getPackageManager();
}
@Override
public DashboardCategory getTilesForCategory(String key) {
return mCategoryManager.getTilesByCategory(mContext, key);
}
>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()) {
return tile.getKey(mContext);
}
final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
final ComponentName component = tile.getIntent().getComponent();
sb.append(component.getClassName());
return sb.toString();
}
>bindPreferenceToTileAndGetObservers
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 -> {
TopLevelHighlightMixin highlightMixin = null;
boolean isDuplicateClick = false;
if (fragment instanceof TopLevelSettings
&& ActivityEmbeddingUtils.isEmbeddingActivityEnabled(mContext)) {
// Highlight the preference whenever it's clicked
final TopLevelSettings topLevelSettings = (TopLevelSettings) fragment;
highlightMixin = topLevelSettings.getHighlightMixin();
isDuplicateClick = topLevelSettings.isDuplicateClick(preference);
topLevelSettings.setHighlightPreferenceKey(key);
}
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;
}
修改选项的标题
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;
}
2.6.CategoryManager.java
>getTilesByCategory
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
tryInitCategories(context);
return mCategoryByKeyMap.get(categoryKey);
}
>tryInitCategories
获取数据前都会调用这个方法,先初始化下数据
private synchronized void tryInitCategories(Context context) {
// Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
// happens.
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();
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
>可能用到的变量
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";
>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;
}
>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);
}
>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);
}
}
>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);
}
}
}
>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
>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);
}
>hasKey
public boolean hasKey() {
//"com.android.settings.keyhint";
return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT);
}
>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);
}
}
>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);
}
>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.
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;
}
>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 onAttach(Context context) {
super.onAttach(context);
HighlightableMenu.fromXml(context, getPreferenceScreenResId());
//这个controller需要activity,给它传一个
use(SupportPreferenceController.class).setActivity(getActivity());
}
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);
}
});
}
>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);
}
}
>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并显示。
>top_level_settings.xml
如下,可以看到,有的带fragment属性,有的不带,不带的一般都会在Controller里自己处理。
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
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.connecteddevice.ConnectedDeviceDashboardFragment"
android:icon="@drawable/ic_devices_other"
android:key="top_level_connected_devices"
android:order="-140"
android:title="@string/connected_devices_dashboard_title"
android:summary="@string/connected_devices_dashboard_default_summary"
settings:highlightableMenuKey="@string/menu_key_connected_devices"
settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/>
<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"/>
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 {
@Override
public boolean onNavigateUp() {
finish();
return true;
}
@Override
protected boolean isValidFragment(String fragmentName) {
return true;
}
}
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();
}
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
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
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. NetworkDashboardFragment
public class NetworkDashboardFragment extends DashboardFragment implements
MobilePlanPreferenceHost, OnActivityResultListener {
protected int getPreferenceScreenResId() {
return R.xml.network_provider_internet;
}
6.System选项
<com.android.settings.widget.HomepagePreference
android:fragment="com.android.settings.system.SystemDashboardFragment"
android:icon="@drawable/ic_settings_system_dashboard_white"
android:key="top_level_system"
android:order="10"
android:title="@string/header_category_system"
android:summary="@string/system_dashboard_summary"
settings:highlightableMenuKey="@string/menu_key_system"/>
6.1.SystemDashboardFragment
@SearchIndexable
public class SystemDashboardFragment extends DashboardFragment {
//..
protected int getPreferenceScreenResId() {
return R.xml.system_dashboard_fragment;
}
//..
问题描述:上边的xml里有6个preference,还有两个没显示,可最终显示的有7个,那么3个新的哪里来的?
6.2.动态选项
>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小节的refreshDashboardTiles方法,tile数据的获取逻辑看2.7小节,就是根据action以及其他属性查找相关的组件。
>获取到的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"
android:label="@string/user_settings_title"
android:exported="true"
android:icon="@drawable/ic_settings_multiuser">
<intent-filter android:priority="1">
<action android:name="android.settings.USER_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<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"
android:label="@string/development_settings_title"
android:icon="@drawable/ic_settings_development"
android:exported="true"
android:enabled="false">
<intent-filter android:priority="1">
<action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
<action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<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"
android:label="@string/privacy_settings_title"
android:exported="true"
android:icon="@drawable/ic_settings_backup">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE_LAUNCH" />
</intent-filter>
<!-- 这里,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>
>tile的使用
具体逻辑看2.4里的refreshDashboardTiles,这里就是具体分析下上边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值
dashboard_tile_pref_com.android.settings.Settings$DevelopmentSettingsDashboardActivity
dashboard_tile_pref_com.android.settings.Settings$UserSettingsActivity
dashboard_tile_pref_com.android.settings.backup.UserBackupSettingsActivity
具体逻辑上边有贴,再看下DashboardFeatureProviderImpl.java
public String getDashboardKeyForTile(Tile tile) {
if (tile == null) {
return null;
}
if (tile.hasKey()) {
return tile.getKey(mContext);
}
//固定前缀
final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX);
final ComponentName component = tile.getIntent().getComponent();
//加上组件类名
sb.append(component.getClassName());
return sb.toString();
}
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
>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>
<PreferenceCategory
android:title="@string/my_device_info_legal_category_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);
}
>开关事件
下边看下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
>getPreferenceScreenResId
protected int getPreferenceScreenResId() {
return R.xml.development_settings;
}
>createPreferenceControllers
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
if (Utils.isMonkeyRunning()) {
mPreferenceControllers = new ArrayList<>();
return null;
}
//这里添加了上百个controller
mPreferenceControllers = buildPreferenceControllers(context, getActivity(),
getSettingsLifecycle(), this /* devOptionsDashboardFragment */,
new BluetoothA2dpConfigStore());
return mPreferenceControllers;
}
>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);
}
>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
>VerifyAppsOverUsbPreferenceController
public class VerifyAppsOverUsbPreferenceController extends DeveloperOptionsPreferenceController
implements Preference.OnPreferenceChangeListener, AdbOnChangeListener,
PreferenceControllerMixin {
//..
public void onAdbSettingChanged() {
if (isAvailable()) {
updateState(mPreference);
}
}
//..
/**
* Checks whether the toggle should be enabled depending on whether verify apps over USB is
* possible currently. If ADB is disabled or if package verifier does not exist, the toggle
* should be disabled.
*/
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();
}
> 搜到的recevier
action是对的,可惜没有mimeType,所以上边的结果是空。
<!-- Broadcast Receiver listen to sufficient verifier requests from Package Manager
when install new SDK, to verifier SDK code during installation time
and terminate install if SDK not compatible with privacy sandbox restrictions. -->
<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>
x.杂记
x.1 开发者模式
需要用vysor控制设备,所以设备启动以后需要打开usb debug模式。 usb debug模式就算是打开的,重启机器以后也看不到设备,必须关闭再打开才行,所以打开在设备启动以后后台自动模拟这个操作。
>打开关闭开发者模式
DevelopmentSettingsEnabler.java
public static void setDevelopmentSettingsEnabled(Context context, boolean enable) {
Settings.Global.putInt(context.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, enable ? 1 : 0);
LocalBroadcastManager.getInstance(context)
.sendBroadcast(new Intent(DEVELOPMENT_SETTINGS_CHANGED_ACTION));
}
相关代码可以添加在FallbackHome.java里,这个类也是settings里的
import com.android.settingslib.development.DevelopmentSettingsEnabler;
DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(this,true);
>usb debug开关
来源可以看8.3
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
>开机后自动打开开发者模式以及usb debug
在 FallbackHome.java里加入如下的代码
import com.android.settingslib.development.DevelopmentSettingsEnabler;
DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(this,true);
Settings.Global.putInt(getContentResolver(),Settings.Global.ADB_ENABLED, 1);
.总结
- 核心基类DashboardFragment,静态数据(xml里加载),动态数据(Tiles)
- 条目的内容,基本都是靠controller来控制的,
- tile有两种数据来源,一种是activity的,一种是contentProvider的,最终生成的preference是switchPreference或者普通的Preference
- TileUtils 是用来获取所有的Tile数据的
查看14道真题和解析