Android 8.1 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 settings
            if (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 Dashboard
            mDisplayHomeAsUpEnabled = 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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.dashboard, container, false);
    }

dashboard.xml

<com.android.settings.dashboard.conditional.FocusRecyclerView
    xmlns: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,看到的那个页面。

接着

	@Override
    public 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 type
        private 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>
	//省略细节
    <ImageView
        android: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;
        }
        @Override
        protected 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

   @VisibleForTesting
    void 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的实例,

    @Override
    public 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

@Override
        protected 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 info
                mSuggestionFeatureProvider.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。

    @VisibleForTesting
    void 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 category
            if (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 category
                if (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 priority
                tile.priority = usePriority || ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,
                        activityInfo.packageName) ? resolved.priority : 0;
                tile.metaData = activityInfo.metaData;
                /// M: Support priority in whitelist app
                if (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 priority
    tile.priority = usePriority || 
    ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,activityInfo.packageName) ? resolved.priority : 0;
    tile.metaData = activityInfo.metaData;
    /// M: Support priority in whitelist app
    if (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() {
    @Override
    public 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来处理。实在是厉害。

Android 8.1Android操作系统的一个版本,它是在Android 8.0基础上进行了改进和优化的最新版本。Android 8.1引入了一些新的功能和改进,例如:自适应图标,通知渠道,自动填充框架等。自适应图标功能使得应用程序的图标可以适应不同的设备和启动器,提供更好的用户体验。通知渠道则使得用户能够更好地管理和控制应用程序发送的通知,减少了用户被打扰的可能性。自动填充框架则简化了用户在输入表单时填写信息的操作,提高了用户填写表单的效率。 Android Studio是开发Android应用程序的集成开发环境。通过Android Studio,开发者可以创建、编辑、调试和测试Android应用程序。在Android Studio中,可以通过“设置”功能对开发环境进行个性化和配置调整。在“设置”菜单中,开发者可以选择和配置不同的主题和UI风格,调整代码编辑器的显示和行为,还可以选择不同的编程语言和版本进行开发。此外,开发者还可以设置和管理各种插件和工具,以满足自己的需求。Android Studio的“设置”功能提供了丰富的选项和配置,使得开发者能够根据自己的喜好和需要对开发环境进行个性化定制,提高了开发效率和舒适度。 总而言之,Android 8.1Android操作系统的最新版本,它引入了一些新的功能和改进,提供了更好的用户体验。Android Studio是一款用于开发Android应用程序的集成开发环境,它提供了丰富的个性化和配置选项,使得开发者能够根据自己的需求对开发环境进行调整和优化。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值