本文主要是介绍Android 8.1 MTK平台 Settings源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言:客制化开发的过程中接触Setting次数挺多的,但是在接触的过程中发现Setting和其他应用的逻辑很不一样,Setting到底是怎么在实现逻辑的,这个问题一直环绕着在我心里,趁现在有时间,决定写个博客记录一下,温故而知新。
从启动开始说起
进入setting的AndroidManifest.xml里看一看,找启动Activity
<activity-alias android:name="Settings"android:taskAffinity="com.android.settings"android:label="@string/settings_label_launcher"android:launchMode="singleTask"android:targetActivity="Settings"><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是Settings,但是前面的标签是activity-alias,所以这是另一个Activity的别名,然后它真实的启动Activity应该是targetActivity所标注的Settings。
走进Settings.java
public class Settings extends SettingsActivity {public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */}public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }public static class SimSettingsActivity extends SettingsActivity { /* empty */ }public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }public static class PrivateVolumeForgetActivity extends SettingsActivity { /* empty */ }public static class PrivateVolumeSettingsActivity extends SettingsActivity { /* empty */ }public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ }public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ }public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }...//代码省略
第一次打开Setting.java这个文件的时候我被吓到了,里面全是静态内部类,而且都是跟Settings一样,继承了SettingsActivity,但是所有的类内容都是empty,可想而知这里是使用了特殊的抽象才这样设计的,啥也没有,我们就需要去他们的父类去看了。
SettingsActivity
@Override
protected void onCreate(Bundle savedState) {...final ComponentName cn = intent.getComponent();final String className = cn.getClassName();mIsShowingDashboard = className.equals(Settings.class.getName());setContentView(mIsShowingDashboard ?R.layout.settings_main_dashboard :R.layout.settings_main_prefs);...mContent = findViewById(R.id.main_content);...if (savedState != null) {setTitleFromIntent(intent);ArrayList<DashboardCategory> categories =savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);if (categories != null) {mCategories.clear();mCategories.addAll(categories);setTitleFromBackStack();}mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);} else {launchSettingFragment(initialFragmentName, isSubSettings, intent);}...//代码省略
正常第一次启动自然是进入launchSettingFragment了,
void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {if (!mIsShowingDashboard && initialFragmentName != null) {// UP will be shown only if it is a sub settingsif (mIsShortcut) {mDisplayHomeAsUpEnabled = isSubSettings;} else if (isSubSettings) {mDisplayHomeAsUpEnabled = true;} else {mDisplayHomeAsUpEnabled = false;}setTitleFromIntent(intent);Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);} else {// Show search icon as up affordance if we are displaying the main DashboardmDisplayHomeAsUpEnabled = true;mInitialTitleResId = R.string.dashboard_title;switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,mInitialTitleResId, mInitialTitle, false);}}
接着就去开启Fragment
private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {if (validate && !isValidFragment(fragmentName)) {throw new IllegalArgumentException("Invalid fragment for this activity: "+ fragmentName);}Fragment f = Fragment.instantiate(this, fragmentName, args);FragmentTransaction transaction = getFragmentManager().beginTransaction();transaction.replace(R.id.main_content, f);if (withTransition) {TransitionManager.beginDelayedTransition(mContent);}if (addToBackStack) {transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);}if (titleResId > 0) {transaction.setBreadCrumbTitle(titleResId);} else if (title != null) {transaction.setBreadCrumbTitle(title);}transaction.commitAllowingStateLoss();getFragmentManager().executePendingTransactions();return f;}
可以看到,这里是直接初始化创建一个fragmentName指定的Fragment,
由于前面mIsShowingDashboard = className.equals(Settings.class.getName());
而第一次启动,当然是Settings.class,所以mIsShowingDashboard = true;
那么这里的初始化第一个Fragment就是DashboardSummary.class了
这样,第一次初始化就是打开Settings这个空Activity,然后加载DashboardSummary这个Fragment。
DashboardSummary
@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.dashboard, container, false);}
dashboard.xml
<com.android.settings.dashboard.conditional.FocusRecyclerViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/dashboard_container"android:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:clipToPadding="false"android:focusable="false"android:paddingStart="@dimen/dashboard_padding_start"android:paddingEnd="@dimen/dashboard_padding_end"android:paddingTop="@dimen/dashboard_padding_top"android:paddingBottom="@dimen/dashboard_padding_bottom"android:scrollbars="vertical"/>
DashboardSummary的布局文件里,只有一个FocusRecyclerView,所有的视图内容都是加载到这个RecyclerView里的,这也是我们第一次打开Setting,看到的那个页面。
接着
@Overridepublic void onViewCreated(View view, Bundle bundle) {long startTime = System.currentTimeMillis();mDashboard = view.findViewById(R.id.dashboard_container);mLayoutManager = new LinearLayoutManager(getContext());mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);if (bundle != null) {int scrollPosition = bundle.getInt(EXTRA_SCROLL_POSITION);mLayoutManager.scrollToPosition(scrollPosition);}mDashboard.setLayoutManager(mLayoutManager);mDashboard.setHasFixedSize(true);mDashboard.setListener(this);mDashboard.setDetachListener(this);mAdapter = new DashboardAdapter(getContext(), bundle,mConditionManager.getConditions(),mSuggestionParser, this /* SuggestionDismissController.Callback */);mDashboard.setAdapter(mAdapter);mDashboard.setItemAnimator(new DashboardItemAnimator());mSummaryLoader.setSummaryConsumer(mAdapter);ActionBarShadowController.attachToRecyclerView(getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);rebuildUI();}
在onViewCreated里,处理了Recyclerview的数据加载,其中mDashboard就是Recyclerview,然后我们需要去看
它的setAdapter,这个mAdapter就是DashboardAdapter。
DashboardAdapter
public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>
构造方法
public DashboardAdapter(Context context, Bundle savedInstanceState,List<Condition> conditions, SuggestionParser suggestionParser,SuggestionDismissController.Callback callback) {List<Tile> suggestions = null;DashboardCategory category = null;int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;mContext = context;final FeatureFactory factory = FeatureFactory.getFactory(context);mMetricsFeatureProvider = factory.getMetricsFeatureProvider();mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);mCache = new IconCache(context);mSuggestionParser = suggestionParser;mCallback = callback;setHasStableIds(true);if (savedInstanceState != null) {suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);suggestionConditionMode = savedInstanceState.getInt(STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);mSuggestionsShownLogged = savedInstanceState.getStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED);} else {mSuggestionsShownLogged = new ArrayList<>();}mDashboardData = new DashboardData.Builder().setConditions(conditions).setSuggestions(suggestions).setCategory(category).setSuggestionConditionMode(suggestionConditionMode).build();}
其中数据的初始化来自于mDashboardData,这里使用的是建造者模式,builder构造。
其中,设置了三个参数setConditions,setSuggestions,setCategory。
public DashboardData build() {return new DashboardData(this);}
private DashboardData(Builder builder) {mCategory = builder.mCategory;mConditions = builder.mConditions;mSuggestions = builder.mSuggestions;mSuggestionConditionMode = builder.mSuggestionConditionMode;mItems = new ArrayList<>();buildItemsData();}
private void buildItemsData() {final boolean hasSuggestions = sizeOf(mSuggestions) > 0;final List<Condition> conditions = getConditionsToShow(mConditions);final boolean hasConditions = sizeOf(conditions) > 0;final List<Tile> suggestions = getSuggestionsToShow(mSuggestions);final int hiddenSuggestion =hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;final boolean hasSuggestionAndCollapsed = hasSuggestions&& mSuggestionConditionMode == HEADER_MODE_COLLAPSED;final boolean onlyHasConditionAndCollapsed = !hasSuggestions&& hasConditions&& mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED;/* Top suggestion/condition header. This will be present when there is any suggestion* and the mode is collapsed */addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),R.layout.suggestion_condition_header,STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER, hasSuggestionAndCollapsed);/* Use mid header if there is only condition & it's in collapsed mode */addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),R.layout.suggestion_condition_header,STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER, onlyHasConditionAndCollapsed);/* Suggestion container. This is the card view that contains the list of suggestions.* This will be added whenever the suggestion list is not empty */addToItemList(suggestions, R.layout.suggestion_condition_container,STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);/* Second suggestion/condition header. This will be added when there is at least one* suggestion or condition that is not currently displayed, and the user can expand the* section to view more items. */addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),R.layout.suggestion_condition_header,STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER,mSuggestionConditionMode != HEADER_MODE_COLLAPSED&& mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED&& (hiddenSuggestion > 0 || hasConditions && hasSuggestions));/* Condition container. This is the card view that contains the list of conditions.* This will be added whenever the condition list is not empty */addToItemList(conditions, R.layout.suggestion_condition_container,STABLE_ID_CONDITION_CONTAINER,hasConditions && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED);/* Suggestion/condition footer. This will be present when the section is fully expanded* or when there is no conditions and no hidden suggestions */addToItemList(null /* item */, R.layout.suggestion_condition_footer,STABLE_ID_SUGGESTION_CONDITION_FOOTER,(hasConditions || hasSuggestions)&& mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED|| hasSuggestions&& !hasConditions&& hiddenSuggestion == 0);if(mCategory != null) {for (int j = 0; j < mCategory.tiles.size(); j++) {final Tile tile = mCategory.tiles.get(j);addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),true /* add */);}}}
这里加载了很多tile,然后全都调用了addToItemList,
private void addToItemList(Object item, int type, int stableId, boolean add) {if (add) {mItems.add(new Item(item, type, stableId));}}
这个Item就是存储设置选项的信息
static class Item {// valid types in field typeprivate static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;private static final int TYPE_SUGGESTION_CONDITION_CONTAINER =R.layout.suggestion_condition_container;private static final int TYPE_SUGGESTION_CONDITION_HEADER =R.layout.suggestion_condition_header;private static final int TYPE_SUGGESTION_CONDITION_FOOTER =R.layout.suggestion_condition_footer;private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer;@IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONDITION_CONTAINER,TYPE_SUGGESTION_CONDITION_HEADER, TYPE_SUGGESTION_CONDITION_FOOTER,TYPE_DASHBOARD_SPACER})
public Item(Object entity, @ItemTypes int type, int id) {this.entity = entity;this.type = type;this.id = id;}
这里的构造方法显示三个参数,其中根据不同的TYPE,存储不同的layout的ID,可以知道有五种类型的layout,分别是Tile,Container,Header,Footer,Space,我们在设置里能看到的就这几种条目类型。
我们最基础的类型就是TYPE_DASHBOARD_TILE了,
<LinearLayout>//省略细节<ImageViewandroid:id="@android:id/icon"<LinearLayout<TextView android:id="@android:id/title"<TextView android:id="@android:id/summary"</LinearLayout>
</LinearLayout>
可以看到在普通的设置选项条目里,我们能看到的就是一个图标,一个标题,和一个摘要。
就像下面这样
上面Item的构造方法这里传入的其中一个参数是Object类型,但是我们需要知道到底这个Object的实例是什么。
看看前面的addToItemList,分别传入的是
“SuggestionConditionHeaderData”,
“List suggestions”,
“List conditions”,
DashboardCategory mCategory的“Tile”。
suggestions,conditions是在DashboardAdapter创建的时候传递的,我们看看这里的数据来源。
conditions
当时DashboardAdapter的第二个构造参数是
mConditionManager.getConditions();
mConditionManager = ConditionManager.get(activity, false);
单例模式
public static ConditionManager get(Context context, boolean loadConditionsNow) {if (sInstance == null) {sInstance = new ConditionManager(context.getApplicationContext(), loadConditionsNow);}return sInstance;}
private ConditionManager(Context context, boolean loadConditionsNow) {mContext = context;mConditions = new ArrayList<>();if (loadConditionsNow) {Log.d(TAG, "conditions loading synchronously");ConditionLoader loader = new ConditionLoader();loader.onPostExecute(loader.doInBackground());} else {Log.d(TAG, "conditions loading asychronously");new ConditionLoader().execute();}}
new ConditionLoader().execute();
protected ArrayList<Condition> doInBackground(Void... params) {Log.d(TAG, "loading conditions from xml");ArrayList<Condition> conditions = new ArrayList<>();mXmlFile = new File(mContext.getFilesDir(), FILE_NAME);if (mXmlFile.exists()) {readFromXml(mXmlFile, conditions);}addMissingConditions(conditions);return conditions;}@Overrideprotected void onPostExecute(ArrayList<Condition> conditions) {Log.d(TAG, "conditions loaded from xml, refreshing conditions");mConditions.clear();mConditions.addAll(conditions);refreshAll();}
所以,mConditions来自这里,这里显示的却是readFromXml。从xml里去读吗?
private static final String FILE_NAME = "condition_state.xml";
然后很尴尬的发现系统里找不到这个文件,这样只能去看addMissingConditions了。
private void addMissingConditions(ArrayList<Condition> conditions) {addIfMissing(AirplaneModeCondition.class, conditions);addIfMissing(HotspotCondition.class, conditions);addIfMissing(DndCondition.class, conditions);addIfMissing(BatterySaverCondition.class, conditions);addIfMissing(CellularDataCondition.class, conditions);addIfMissing(BackgroundDataCondition.class, conditions);addIfMissing(WorkModeCondition.class, conditions);addIfMissing(NightDisplayCondition.class, conditions);Collections.sort(conditions, CONDITION_COMPARATOR);}
private void addIfMissing(Class<? extends Condition> clz, ArrayList<Condition> conditions) {if (getCondition(clz, conditions) == null) {if (DEBUG) Log.d(TAG, "Adding missing " + clz.getName());Condition condition = createCondition(clz);if (condition != null) {conditions.add(condition);}}}
好吧,原来是这些condition,那这些condition是什么呢?
举个例子,如果我们打开飞行模式,就会看到这个条目了。
condition已经搞明白了,剩下的还有suggestions和category了。
suggestions
suggestions是一个Tile的列表
在DashboardSummary的onViewCreated里,最后调用的rebuildUI
rebuildUI
@VisibleForTestingvoid rebuildUI() {if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {Log.d(TAG, "Suggestion feature is disabled, skipping suggestion entirely");updateCategoryAndSuggestion(null /* tiles */);} else {new SuggestionLoader().execute();// Set categories on their own if loading suggestions takes too long.mHandler.postDelayed(() -> {updateCategoryAndSuggestion(null /* tiles */);}, MAX_WAIT_MILLIS);}}
这里做了一个判断,isSuggestionEnabled就是建议是否开启,然后决定是否去加载Suggestion。
我们需要去看看isSuggestionEnabled是什么
mSuggestionFeatureProvider.isSuggestionEnabled(getContext())
----------------
private SuggestionFeatureProvider mSuggestionFeatureProvider;
----------------
public interface SuggestionFeatureProvider
----------------
public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider
SuggestionFeatureProviderImpl是接口SuggestionFeatureProvider的实例,
@Overridepublic boolean isSuggestionEnabled(Context context) {final ActivityManager am =(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);return !am.isLowRamDevice();}
我们可以看到,这里决定是否isSuggestionEnabled的结果是判断当前设备是否是低Ram设备。
所以,如果我们是Ram低于512M的设备就返回false,如果是大于512M设备就返回true。
当前我们开发的设备是1G Ram的,所以返回True
new SuggestionLoader().execute();
// Set categories on their own if loading suggestions takes too long.
mHandler.postDelayed(() -> {updateCategoryAndSuggestion(null /* tiles */);
}, MAX_WAIT_MILLIS);
SuggestionLoader加载suggestions
@Overrideprotected List<Tile> doInBackground(Void... params) {final Context context = getContext();boolean isSmartSuggestionEnabled =mSuggestionFeatureProvider.isSmartSuggestionEnabled(context);final SuggestionList sl = mSuggestionParser.getSuggestions(isSmartSuggestionEnabled);final List<Tile> suggestions = sl.getSuggestions();if (isSmartSuggestionEnabled) {List<String> suggestionIds = new ArrayList<>(suggestions.size());for (Tile suggestion : suggestions) {suggestionIds.add(mSuggestionFeatureProvider.getSuggestionIdentifier(context, suggestion));}// TODO: create a Suggestion class to maintain the id and other infomSuggestionFeatureProvider.rankSuggestions(suggestions, suggestionIds);}for (int i = 0; i < suggestions.size(); i++) {Tile suggestion = suggestions.get(i);if (mSuggestionsChecks.isSuggestionComplete(suggestion)) {suggestions.remove(i--);}}if (sl.isExclusiveSuggestionCategory()) {mSuggestionFeatureProvider.filterExclusiveSuggestions(suggestions);}return suggestions;}
其中
mSuggestionParser.getSuggestions(isSmartSuggestionEnabled);
public SuggestionList getSuggestions(boolean isSmartSuggestionEnabled) {final SuggestionList suggestionList = new SuggestionList();final int N = mSuggestionList.size();for (int i = 0; i < N; i++) {final SuggestionCategory category = mSuggestionList.get(i);if (category.exclusive && !isExclusiveCategoryExpired(category)) {final List<Tile> exclusiveSuggestions = new ArrayList<>();readSuggestions(category, exclusiveSuggestions, false );if (!exclusiveSuggestions.isEmpty()) {final SuggestionList exclusiveList = new SuggestionList();exclusiveList.addSuggestions(category, exclusiveSuggestions);return exclusiveList;}} else {final List<Tile> suggestions = new ArrayList<>();readSuggestions(category, suggestions, isSmartSuggestionEnabled);suggestionList.addSuggestions(category, suggestions);}}return suggestionList;}
void readSuggestions(SuggestionCategory category, List<Tile> suggestions, boolean isSmartSuggestionEnabled) {int countBefore = suggestions.size();Intent intent = new Intent(Intent.ACTION_MAIN);intent.addCategory(category.category);if (category.pkg != null) {intent.setPackage(category.pkg);}TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,mAddCache, null, suggestions, true, false, false, true );filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);if (!category.multiple && suggestions.size() > (countBefore + 1)) {Tile item = suggestions.remove(suggestions.size() - 1);while (suggestions.size() > countBefore) {Tile last = suggestions.remove(suggestions.size() - 1);if (last.priority > item.priority) {item = last;}}if (!isCategoryDone(category.category)) {suggestions.add(item);}}}
其中就是从SuggestionCategory来加载Tile到suggestions,但是我们需要知道category是从哪里来的,
final SuggestionCategory category = mSuggestionList.get(i);
mSuggestionList = suggestionList;
在SuggestionParser的构造时候,传入了suggestionList,
(List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml)
然后
public Object parse(int resource) {XmlPullParser parser = mContext.getResources().getXml(resource);final AttributeSet attrs = Xml.asAttributeSet(parser);try {int type;do {type = parser.next();} while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}Object xmlRoot = onCreateItem(parser.getName(), attrs);rParse(parser, xmlRoot, attrs);return xmlRoot;} catch (XmlPullParserException | IOException e) {Log.w(TAG, "Problem parser resource " + resource, e);return null;}}
也是从xml里读取
继续找,发现前面传递的是 R.xml.suggestion_ordering
mSuggestionParser = new SuggestionParser(activity,mSuggestionFeatureProvider.getSharedPrefs(activity), R.xml.suggestion_ordering);
<optional-steps><step category="com.android.settings.suggested.category.DEFERRED_SETUP"exclusive="true" /><step category="com.android.settings.suggested.category.LOCK_SCREEN" /><step category="com.android.settings.suggested.category.TRUST_AGENT" /><step category="com.android.settings.suggested.category.EMAIL" /><step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"multiple="true" /><step category="com.android.settings.suggested.category.GESTURE" /><step category="com.android.settings.suggested.category.HOTWORD" /><step category="com.android.settings.suggested.category.DEFAULT"multiple="true" /><step category="com.android.settings.suggested.category.SETTINGS_ONLY"multiple="true" />
</optional-steps>
parse解析的过程里
rParse(parser, xmlRoot, attrs);
private void rParse(XmlPullParser parser, Object parent, final AttributeSet attrs)throws XmlPullParserException, IOException {final int depth = parser.getDepth();int type;while (((type = parser.next()) != XmlPullParser.END_TAG ||parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {if (type != XmlPullParser.START_TAG) {continue;}final String name = parser.getName();Object item = onCreateItem(name, attrs);onAddChildItem(parent, item);rParse(parser, item, attrs);}}
protected Object onCreateItem(String name, AttributeSet attrs) {if (name.equals(TAG_LIST)) {return new ArrayList<SuggestionCategory>();} else if (name.equals(TAG_ITEM)) {SuggestionCategory category = new SuggestionCategory();category.category = attrs.getAttributeValue(null, ATTR_CATEGORY);category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);category.exclusive =!TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);String expireDaysAttr = attrs.getAttributeValue(null,ATTR_EXCLUSIVE_EXPIRE_DAYS);long expireDays = !TextUtils.isEmpty(expireDaysAttr)? Integer.parseInt(expireDaysAttr): -1;category.exclusiveExpireDaysInMillis = DateUtils.DAY_IN_MILLIS *expireDays;return category;} else {throw new IllegalArgumentException("Unknown item " + name);}}
当扫描到step的时候去创建SuggestionCategory,
所以这里的SuggestionCategory列表最后Item内容就是上面XML里的item内容。
我们仔细看这个category,发现很像我们在AndroidManifest.xml里定义的category。
那么前面mSuggestionList就是parse解析的结果new ArrayList();
在前面readSuggestions的时候传入的就是携带这xml里step的category。
我们继续回到readSuggestions
Intent intent = new Intent(Intent.ACTION_MAIN);intent.addCategory(category.category);if (category.pkg != null) {intent.setPackage(category.pkg);}TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);
这里把category的category给了Intent然后调用了addCategory
然后走到TileUtils.getTilesForIntent,去把对应的Tile加到maddCache里。
private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();
找到完了suggestion,最后找category
category
在DashboardAdapter构造的时候category = null;一直到setCategory都是null,那么category到底是什么情况呢?
最后我们需要看看**DashboardAdapter的setCategory(category)**这里的category可是null无疑,
但是前面 SuggestionLoader().execute() 获取 suggestion完了,后面还有个post更新
mHandler.postDelayed(() -> {updateCategoryAndSuggestion(null /* tiles */);
}, MAX_WAIT_MILLIS);
updateCategoryAndSuggestion
看到上面传入的tiles 是NULL。
@VisibleForTestingvoid updateCategoryAndSuggestion(List<Tile> suggestions) {final Activity activity = getActivity();if (activity == null) {return;}final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(CategoryKey.CATEGO RY_HOMEPAGE);mSummaryLoader.updateSummaryToCache(category);if (suggestions != null) {mAdapter.setCategoriesAndSuggestions(category, suggestions);} else {mAdapter.setCategory(category);}}
DashboardCategory
mDashboardFeatureProvider.getTilesForCategory(CategoryKey.CATEGO RY_HOMEPAGE);
找到实例
@Override
public DashboardCategory getTilesForCategory(String key) {return mCategoryManager.getTilesByCategory(mContext, key);
}
---------------------------------------------
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {return getTilesByCategory(context, categoryKey, TileUtils.SETTING_PKG);
}
---------------------------------------------
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey,String settingPkg) {tryInitCategories(context, settingPkg); //return mCategoryByKeyMap.get(categoryKey);
}
在获取category的时候,有一个初始化的过程
private synchronized void tryInitCategories(Context context, String settingPkg) {tryInitCategories(context, false /* forceClearCache */, settingPkg);
}
---------------------------------------------
private synchronized void tryInitCategories(Context context, booleanforceClearCache,String settingPkg) {if (mCategories == null) {if (forceClearCache) {mTileByComponentCache.clear();}mCategoryByKeyMap.clear();mCategories = TileUtils.getCategories(context, mTileByComponentCache,false /* categoryDefinedInManifest */, mExtraAction, settingPkg);for (DashboardCategory category : mCategories) {mCategoryByKeyMap.put(category.key, category);}backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);sortCategories(context, mCategoryByKeyMap);filterDuplicateTiles(mCategoryByKeyMap);}
}
其关键在于mCategories当我们第一次去获取category的时候mCategories == null。
于是
mCategories = TileUtils.getCategories(context, mTileByComponentCache,false, mExtraAction, settingPkg);
TileUtils.getCategories
public static List<DashboardCategory> getCategories(Context context,Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,String extraAction, String settingPkg) {final long startTime = System.currentTimeMillis();boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)!= 0;ArrayList<Tile> tiles = new ArrayList<>();UserManager userManager = (UserManager)context.getSystemService(Context.USER_SERVICE);for (UserHandle user : userManager.getUserProfiles()) {// TODO: Needs much optimization, too many PM queries going on here.if (user.getIdentifier() == ActivityManager.getCurrentUser()) {// Only add Settings for this user.getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles,true, settingPkg);getTilesForAction(context, user, OPERATOR_SETTINGS, cache,OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);}...//省略}HashMap<String, DashboardCategory> categoryMap = new HashMap<>();for (Tile tile : tiles) {DashboardCategory category = categoryMap.get(tile.category);if (category == null) {category = createCategory(context, tile.category, categoryDefinedInManifest);if (category == null) {Log.w(LOG_TAG, "Couldn't find category " + tile.category);continue;}categoryMap.put(category.key, category);}category.addTile(tile);}ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());for (DashboardCategory category : categories) {Collections.sort(category.tiles, TILE_COMPARATOR);}Collections.sort(categories, CATEGORY_COMPARATOR);return categories;}
我们发现,在初始化categories的时候,做了很多事情,
其中第一步是查询并填充ArrayList tiles,
通过
getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles,true, settingPkg);
getTilesForAction(context, user, OPERATOR_SETTINGS, cache,OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
这三步去填充tiles,
然后再用for循环,创建category
for (Tile tile : tiles)。。。省略category = createCategory(context, tile.category, categoryDefinedInManifest);。。。
这里的category是DashboardCategory,
category.addTile(tile);
public void addTile(Tile tile) {tiles.add(tile);
}
/*** List of the category's children*/
public List<Tile> tiles = new ArrayList<>();
所以,category.addTile的时候,是把Tile给加到了一个内部List里,这就说明,一个Category就是一些Tile的集合。
getTilesForAction
private static void getTilesForAction(Context context,UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,String settingPkg) {getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,requireSettings, requireSettings, settingPkg);
}
private static void getTilesForAction(Context context,UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,boolean usePriority, String settingPkg) {Intent intent = new Intent(action);if (requireSettings) {intent.setPackage(settingPkg);}getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,usePriority, true, true);}
然后走到了和前面获取suggestion一样的地方,都是getTilesForIntent,只不过这里的category和suggestion那里的xml的category不一样。
这里三个category分别是
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 MANUFACTURER_SETTINGS ="com.android.settings.MANUFACTURER_APPLICATION_SETTING";
最后一起来看看是怎么获取Tile的。
TileUtils
public static void getTilesForIntent(Context context, UserHandle user, Intent intent,Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,boolean shouldUpdateTiles) {PackageManager pm = context.getPackageManager();List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,PackageManager.GET_META_DATA, user.getIdentifier());Map<String, IContentProvider> providerMap = new HashMap<>();for (ResolveInfo resolved : results) {if (!resolved.system && !ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,resolved.activityInfo.packageName)) {continue;}ActivityInfo activityInfo = resolved.activityInfo;Bundle metaData = activityInfo.metaData;String categoryKey = defaultCategory;// Load categoryif (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))&& categoryKey == null) {Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "+ intent + " missing metadata "+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));continue;} else {categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);/// M: set default category when extra data not set categoryif (categoryKey == null) {categoryKey = defaultCategory;}}Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,activityInfo.name);Tile tile = addedCache.get(key);if (tile == null) {tile = new Tile();tile.intent = new Intent().setClassName(activityInfo.packageName, activityInfo.name);tile.category = categoryKey;/// M: white list could use prioritytile.priority = usePriority || ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,activityInfo.packageName) ? resolved.priority : 0;tile.metaData = activityInfo.metaData;/// M: Support priority in whitelist appif (tile.metaData.containsKey(META_DATA_KEY_PRIORITY) && ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST, activityInfo.packageName)) {tile.priority = tile.metaData.getInt(META_DATA_KEY_PRIORITY);}updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,pm, providerMap, forceTintExternalIcon);if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);addedCache.put(key, tile);} else if (shouldUpdateTiles) {updateSummaryAndTitle(context, providerMap, tile);}if (!tile.userHandle.contains(user)) {tile.userHandle.add(user);}if (!outTiles.contains(tile)) {outTiles.add(tile);}}}
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,PackageManager.GET_META_DATA, user.getIdentifier());
这个Results是用PackageManager来query的结果,最后查询的结果是当前包里有相同的category的activity信息
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;
然后我们看到了下面是创建Tile的过程
Tile tile = addedCache.get(key);
if (tile == null) {tile = new Tile();tile.intent = new Intent().setClassName(activityInfo.packageName, activityInfo.name);tile.category = categoryKey;/// M: white list could use prioritytile.priority = usePriority || ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,activityInfo.packageName) ? resolved.priority : 0;tile.metaData = activityInfo.metaData;/// M: Support priority in whitelist appif (tile.metaData.containsKey(META_DATA_KEY_PRIORITY) &&ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST, activityInfo.packageName)) {tile.priority = tile.metaData.getInt(META_DATA_KEY_PRIORITY);}updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,pm, providerMap, forceTintExternalIcon);if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);addedCache.put(key, tile);
} else if (shouldUpdateTiles) {updateSummaryAndTitle(context, providerMap, tile);
}
分别把activityInfo.packageName, activityInfo.name,categoryKey等这些信息赋值给Tile,然后把tile加入到addedCache,
private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();
就这样通过不同的category来得到不同activity在xml里定义的信息。
三个类型,分别对应着
setConditions(conditions)
setSuggestions(suggestions)
setCategory(category)
这就是DashboardAdapter在初始化的时候,获取的数据的过程。
这里有一点需要说明的是,我在使用suggestion_ordering.xml里的category去AndroidMannifest.xml找,没有找到一个对应的activity,而使用SETTINGS_ACTION却能找到许多acticity。
比如
<activity android:name=".Settings$NetworkDashboardActivity"android:taskAffinity="com.android.settings"android:label="@string/network_dashboard_title"android:icon="@drawable/ic_settings_wireless"android:parentActivityName="Settings"><intent-filter android:priority="1"><action android:name="android.settings.WIRELESS_SETTINGS" /><action android:name="android.settings.AIRPLANE_MODE_SETTINGS" /><category android:name="android.intent.category.DEFAULT" /></intent-filter><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><intent-filter android:priority="11"><action android:name="com.android.settings.action.SETTINGS"/></intent-filter><meta-data android:name="com.android.settings.category"android:value="com.android.settings.category.ia.homepage"/><meta-data android:name="com.android.settings.FRAGMENT_CLASS"android:value="com.android.settings.network.NetworkDashboardFragment"/><meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"android:value="true" /></activity>
这里的NetworkDashboardActivity是在Setting里面定义了的。
这里可以看到,它其实是action。
而我们就是根据这个action来分门别类的找到对应的目录。
加载view
数据加载告一段落,回到DashboardAdapter的加载view的流程。
DashboardAdapter
构造ViewHolder,根据viewType不同来加载不同的viewType并创建Viewholder
这里的viewtype就是layoutID
@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);if (viewType == R.layout.suggestion_condition_header) {return new SuggestionAndConditionHeaderHolder(view);}if (viewType == R.layout.suggestion_condition_container) {return new SuggestionAndConditionContainerHolder(view);}return new DashboardItemHolder(view);
}
默认ViewHolder就是DashboardItemHolder
public DashboardItemHolder(View itemView) {super(itemView);icon = itemView.findViewById(android.R.id.icon);title = itemView.findViewById(android.R.id.title);summary = itemView.findViewById(android.R.id.summary);
}
各个item的type就是前面说的那个layout的ID
@Override
public int getItemViewType(int position) {return mDashboardData.getItemTypeByPosition(position);
}
public int getItemTypeByPosition(int position) {return mItems.get(position).type;
}
然后我们看onBindViewHolder
@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {final int type = mDashboardData.getItemTypeByPosition(position);switch (type) {case R.layout.dashboard_tile:final Tile tile = (Tile)mDashboardData.getItemEntityByPosition(position);onBindTile(holder, tile);holder.itemView.setTag(tile);holder.itemView.setOnClickListener(mTileClickListener);break;case R.layout.suggestion_condition_container:onBindConditionAndSuggestion((SuggestionAndConditionContainerHolder) holder, position);break;case R.layout.suggestion_condition_header:onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder)holder,(SuggestionConditionHeaderData)mDashboardData.getItemEntityByPosition(position));break;case R.layout.suggestion_condition_footer:holder.itemView.setOnClickListener(v -> {mMetricsFeatureProvider.action(mContext,MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);DashboardData prevData = mDashboardData;mDashboardData = new DashboardData.Builder(prevData).setSuggestionConditionMode(DashboardData.HEADER_MODE_COLLAPSED).build();notifyDashboardDataChanged(prevData);mRecyclerView.scrollToPosition(SUGGESTION_CONDITION_HEADER_POSITION);});break;}
}
在Bindview的时候分别根据Tile,Container,header和footer来创建view。
看看onBindTile
private void onBindTile(DashboardItemHolder holder, Tile tile) {if (tile.remoteViews != null) {final ViewGroup itemView = (ViewGroup) holder.itemView;itemView.removeAllViews();itemView.addView(tile.remoteViews.apply(itemView.getContext(), itemView));} else {holder.icon.setImageDrawable(mCache.getIcon(tile.icon));holder.title.setText(tile.title);if (!TextUtils.isEmpty(tile.summary)) {holder.summary.setText(tile.summary);holder.summary.setVisibility(View.VISIBLE);} else {holder.summary.setVisibility(View.GONE);}}
}
就是这样,根据不同的layout,来加载不同的item,实现RecyclerView的填充。
点击事件
显示问题搞清楚了,接着我们看点击事件
holder.itemView.setTag(tile);
holder.itemView.setOnClickListener(mTileClickListener);
在bindview的时候设置了OnClickListener
private View.OnClickListener mTileClickListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile)v.getTag());}
};
DashboardFeatureProviderImpl实现了DashboardFeatureProvider接口,这里是面向接口编程的思想。
@Override
public void openTileIntent(Activity activity, Tile tile) {if (tile == null) {Intent intent = new Intent(Settings.ACTION_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);mContext.startActivity(intent);return;}if (tile.intent == null) {return;}final Intent intent = new Intent(tile.intent).putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,MetricsEvent.DASHBOARD_SUMMARY).putExtra(SettingsDrawerActivity.EXTRA_SHOW_MENU, true).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);launchIntentOrSelectProfile(activity, tile, intent,MetricsEvent.DASHBOARD_SUMMARY);
}
在前面创建每个Tile的时候,Intent是这样的,
tile.intent = new Intent().setClassName(
activityInfo.packageName, activityInfo.name);
里面包含了对应包名,activity的名字。
private void launchIntentOrSelectProfile(Activity activity, Tile tile, Intent intent,int sourceMetricCategory) {if (!isIntentResolvable(intent)) {Log.w(TAG, "Cannot resolve intent, skipping. " + intent);return;}ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile);if (tile.userHandle == null) {mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);activity.startActivityForResult(intent, 0);} else if (tile.userHandle.size() == 1) {mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0));} else {ProfileSelectDialog.show(activity.getFragmentManager(), tile);}
}activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0));
直接开启对应的activity,然后这里几乎所有activity都是继承自SettingsActivity,所有,就是启动它本身,换一个子类来显示。
然后在SettingsActivity的oncreate里,这时候的mIsShowingDashboard就是false,
就会显示R.layout.settings_main_prefs这个layout了,而且这时候的Intent是携带者具体要启动的activity信息的
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
然后就回到循环之中。
总结:Setting里所有的Activity都继承了SettingsActivity,通过action条目来统一管理加载,切换不同的activity实际也是调用它本身的不同子类,然后根据不同子类加载不同的fragment,所以说到底,Setting只有一个父Activity,所有的显示通过不同的Fragment来处理。实在是厉害。
文章转自
https://blog.csdn.net/csdnxialei/article/details/86555050
这篇关于Android 8.1 MTK平台 Settings源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!