20150623----Android-Settings源码分析

</pre><p>Settings的时序图:</p><p><img src="https://img-blog.csdn.net/20150623143540579?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hhZGFpb2Rld2FuZ3dlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" height="451" width="679" alt="" /></p><p></p><p align="center"><span style="font-weight:bold">1.本文说明</span></p>  <p>    本文主要针对L平台上Settings模块正常启动流程做一个简要分析,并试着分析一下Settings下面Storage选项的实现过程。</p>  <p> </p>  <p> </p>  <p align="center"><strong>2.Settings概览</strong></p>  <p>    在之前的KK平台上Settings模块的第一个Activity名字为Settings,其继承的是PreferenceActivity,设置的每一个选项都是对应的一个Header对象,并且Header对象允许显示switch控件,button控件,checkbox控件等。如下图2.1,WLAN和蓝牙上使用到了switch开关。但在L上面,WLAN和蓝牙的这两个开关已经去掉了,如图2.2,在Settings模块的首个页面似乎就只是一个普通的Listview,那它用的还是不是Header呢?或者说取而代之的是什么呢?下一节详细说明。<img src="https://img-blog.csdn.net/20150623144026713?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hhZGFpb2Rld2FuZ3dlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /> KK Settings首届面                                   <img src="https://img-blog.csdn.net/20150623144058131?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2hhZGFpb2Rld2FuZ3dlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" height="300" width="177" alt="" />L Settings首届面</p><p></p><p></p><p align="center"><strong> 3 .L Settings 模块首界面初始化流程</strong></p>  <p> </p>  <p>L Settings模块首界面为Settings,继承自SettingsActivity,SettingsActivity继承自Activity。</p>  <p> </p>  <p>首先看一下Settings.java代码可以发现它没有重写任何SettingsActiviy的方法,也没有增加任何自己的方法,唯独增加了许多静态内部类,如:</p><p><pre name="code" class="java"> /*

    * Settings subclasses for launching independently.

    */

    public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }

    public static class WirelessSettingsActivity 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 StorageSettingsActivity extends SettingsActivity { /* empty */ }

    public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }

看注释可以知道,这些子类是为了启动特定独立的Settings选项而创建的,例如在某个应用里需要设置无线那么只需要启动 WirelessSettingsActivity 就可以了。

 

所以Settings模块的启动流程直接看SettingsActiviy就行了。

 

    3.1 SettingsActivity.onCreate方法

    

onCreate方法是Activity的生命周期第一步,看看 SettingsActivity在这里都做了些什么?

     // Should happen before any call to getIntent()

     getMetaData();

这个方法用来获得Activity的额外数据mFragmentClass,如果可以获得这个数据,那么下面会去显示mFragmentClass对应的Activity。直接启动Settings模块不会获得这个数据。
     mIsShowingDashboard = className.equals(Settings.class.getName());

这一步很重要,因为我们是从Settings这个Activity过来的,所以这里的 mIsShowingDashboard 为 true 。
     // This is a "Sub Settings" when:

        // - this is a real SubSettings

        // - or :settings:show_fragment_as_subsetting is passed to the Intent

        final boolean isSubSettings = className.equals(SubSettings.class.getName()) ||

                intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);

这个判断很重要但很明显这时isSubSettings的值是fasle,暂时忽略。

 

        setContentView(mIsShowingDashboard ?

                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

 

由于mIsShowingDashboard为true,直接走到下面这段

       else {

                // No UP affordance if we are displaying the main Dashboard

                mDisplayHomeAsUpEnabled = false;

                // Show Search affordance

                mDisplaySearch = true;

                mInitialTitleResId = R.string.dashboard_title;

                switchToFragment(DashboardSummary.class.getName(), null, false, false,

                        mInitialTitleResId, mInitialTitle, false);

              } 

这里看到switchToFragment这个方法,可以知道这里是要切换DashboardSummary这个Fragment.

 

接下来就看看DashboardSummary是个什么玩意?

 

dashboard中文意思是仪表盘,这里是指DashboardSummary就是用来显示Settings所有选项的。

在DashboardSummary的onCreateView里加载了这个布局文件R.layout.dashboard:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"

    android:id="@+id/dashboard"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:scrollbarStyle="outsideOverlay"

    android:clipToPadding="false">

 

        <LinearLayout

                android:id="@+id/dashboard_container"

                android:layout_width="match_parent"

                android:layout_height="match_parent"

                android:layout_gravity="center_horizontal"

                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:orientation="vertical"

                />

 

</ScrollView>

看了上面的布局文件可以知道Settings的选项视图应该就是显示在dashboard_container中了。

DashboardSummary走完onCreateView方法后会走onResume,然后一路下来又会调到SettingsActivity的

loadCategoriesFromResource(R.xml.dashboard_categories, categories);

过程试这样的:
DashboardSummary.java中rebuildUI方法:
 private void rebuildUI(Context context) {
        if (!isAdded()) {
            Log.w(LOG_TAG, "Cannot build the DashboardSummary UI yet as the Fragment is not added");
            return;
        }

        long start = System.currentTimeMillis();
        final Resources res = getResources();

        mDashboard.removeAllViews();

        List<DashboardCategory> categories =
                ((SettingsActivity) context).getDashboardCategories(true);


......
}
SettingsActivity.java中的getDashboardCategories(boolean b)

public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
        if (forceRefresh || mCategories.size() == 0) {
            buildDashboardCategories(mCategories);
        }
        return mCategories;
    }

buildDashboardCategories()方法:

/**
     * Called when the activity needs its list of categories/tiles built.
     *
     * @param categories The list in which to place the tiles categories.
     */
    private void buildDashboardCategories(List<DashboardCategory> categories) {
        categories.clear();
        loadCategoriesFromResource(R.xml.dashboard_categories, categories);
        updateTilesList(categories);
    }

loadCategoriesFromResource()方法:

/**
     * Parse the given XML file as a categories description, adding each
     * parsed categories and tiles into the target list.
     *
     * @param resid The XML resource to load and parse.
     * @param target The list in which the parsed categories and tiles should be placed.
     */
    private void loadCategoriesFromResource(int resid, List<DashboardCategory> target) {
        XmlResourceParser parser = null;
        try {
            parser = getResources().getXml(resid);
            AttributeSet attrs = Xml.asAttributeSet(parser);

            int type;
            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && type != XmlPullParser.START_TAG) {
                // Parse next until start tag is found
            }

            String nodeName = parser.getName();
            if (!"dashboard-categories".equals(nodeName)) {
                throw new RuntimeException(
                        "XML document must start with <preference-categories> tag; found"
                                + nodeName + " at " + parser.getPositionDescription());
            }

            Bundle curBundle = null;

            final int outerDepth = parser.getDepth();
            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                nodeName = parser.getName();
                if ("dashboard-category".equals(nodeName)) {
                    DashboardCategory category = new DashboardCategory();

                    TypedArray sa = obtainStyledAttributes(
                            attrs, com.android.internal.R.styleable.PreferenceHeader);
                    category.id = sa.getResourceId(
                            com.android.internal.R.styleable.PreferenceHeader_id,
                            (int)DashboardCategory.CAT_ID_UNDEFINED);

                    TypedValue tv = sa.peekValue(
                            com.android.internal.R.styleable.PreferenceHeader_title);
                    if (tv != null && tv.type == TypedValue.TYPE_STRING) {
                        if (tv.resourceId != 0) {
                            category.titleRes = tv.resourceId;
                        } else {
                            category.title = tv.string;
                        }
                    }
                    sa.recycle();

                    final int innerDepth = parser.getDepth();
                    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                            continue;
                        }

                        String innerNodeName = parser.getName();
                        if (innerNodeName.equals("dashboard-tile")) {
                            DashboardTile tile = new DashboardTile();

                            sa = obtainStyledAttributes(
                                    attrs, com.android.internal.R.styleable.PreferenceHeader);
                            tile.id = sa.getResourceId(
                                    com.android.internal.R.styleable.PreferenceHeader_id,
                                    (int)TILE_ID_UNDEFINED);
                            tv = sa.peekValue(
                                    com.android.internal.R.styleable.PreferenceHeader_title);
                            if (tv != null && tv.type == TypedValue.TYPE_STRING) {
                                if (tv.resourceId != 0) {
                                    tile.titleRes = tv.resourceId;
                                } else {
                                    tile.title = tv.string;
                                }
                            }
                            tv = sa.peekValue(
                                    com.android.internal.R.styleable.PreferenceHeader_summary);
                            if (tv != null && tv.type == TypedValue.TYPE_STRING) {
                                if (tv.resourceId != 0) {
                                    tile.summaryRes = tv.resourceId;
                                } else {
                                    tile.summary = tv.string;
                                }
                            }
                            tile.iconRes = sa.getResourceId(
                                    com.android.internal.R.styleable.PreferenceHeader_icon, 0);
                            tile.fragment = sa.getString(
                                    com.android.internal.R.styleable.PreferenceHeader_fragment);
                            sa.recycle();

                            if (curBundle == null) {
                                curBundle = new Bundle();
                            }

                            final int innerDepth2 = parser.getDepth();
                            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                                    && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth2)) {
                                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                                    continue;
                                }

                                String innerNodeName2 = parser.getName();
                                if (innerNodeName2.equals("extra")) {
                                    getResources().parseBundleExtra("extra", attrs, curBundle);
                                    XmlUtils.skipCurrentTag(parser);

                                } else if (innerNodeName2.equals("intent")) {
                                    tile.intent = Intent.parseIntent(getResources(), parser, attrs);

                                } else {
                                    XmlUtils.skipCurrentTag(parser);
                                }
                            }

                            if (curBundle.size() > 0) {
                                tile.fragmentArguments = curBundle;
                                curBundle = null;
                            }

                            // Show the SIM Cards setting if there are more than 2 SIMs installed.
                            if(tile.id != R.id.sim_settings || Utils.showSimCardTile(this)){
                                category.addTile(tile);
                            }

                        } else {
                            XmlUtils.skipCurrentTag(parser);
                        }
                    }

                    target.add(category);
                } else {
                    XmlUtils.skipCurrentTag(parser);
                }
            }

        } catch (XmlPullParserException e) {
            throw new RuntimeException("Error parsing categories", e);
        } catch (IOException e) {
            throw new RuntimeException("Error parsing categories", e);
        } finally {
            if (parser != null) parser.close();
        }
    }



这一步是通过 R.xml.dashboard_categories来加载categories,这里的categorys为ArrayList<DashboardCategory>mCategories。接着来看看dashboard_categories.xml这个文件吧


<dashboard-categories  xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- ----------------------------------------WIRELESS and NETWORKS ---------------------------------------->
    <dashboard-category
            android:id="@+id/wireless_section"
            android:title="@string/header_category_wireless_networks" >

        <!-- Wifi -->
        <dashboard-tile
                android:id="@+id/wifi_settings"
                android:title="@string/wifi_settings_title"
                android:fragment="com.android.settings.wifi.WifiSettings"
                android:icon="@drawable/ic_settings_wireless"
                />

        <!-- Bluetooth -->            
        <!-- Hotknot -->
        <!-- SIM Cards -->
        <!-- Data Usage -->

	<!-- Call Settings -->
        <dashboard-tile
            android:id="@+id/call_settings"
            android:title="@string/call_settings"
            android:icon="@drawable/ic_menu_phone">
        <intent
            android:action="android.intent.action.MAIN"
            android:targetPackage="com.android.dialer"
            android:targetClass="com.android.dialer.settings.DialerSettingsActivity"
            />
        </dashboard-tile>

        <!-- Operator hook -->
        <dashboard-tile
                android:id="@+id/operator_settings"
                android:fragment="com.android.settings.WirelessSettings" >
            <intent android:action="com.android.settings.OPERATOR_APPLICATION_SETTING" />
        </dashboard-tile>

        <!-- Other wireless and network controls -->
        <dashboard-tile
                android:id="@+id/wireless_settings"
                android:title="@string/radio_controls_title"
                android:fragment="com.android.settings.WirelessSettings"
                android:icon="@drawable/ic_settings_more"
                />

    </dashboard-category>

    <!--------------------------------------------------------------------------- DEVICE -------------------------------------------------->
    <dashboard-category
            android:id="@+id/device_section"
            android:title="@string/header_category_device" >

        <!-- Home -->
        <dashboard-tile
                android:id="@+id/home_settings"
                android:title="@string/home_settings"
                android:fragment="com.android.settings.HomeSettings"
                android:icon="@drawable/ic_settings_home"
                /> 
。
。
。
<pre name="code" class="html"> </dashboard-category>
。。。。。。
</dashboard-categories>

 根据这个文件我们可以知道了,所谓的dashboard就是Settings模块首界面的一个抽象。而dashboard-categorys则是设置分类集合的抽象,而dashboard-category是分类的抽象,dashboard-tile就是分类下每个选项的抽象了。代码中的List<DashboardCategory>对应dashboard-categorys, DashboardCategory对应dashboard-category,而dashboard-tile则对因代码中的DashboardTile。 

当加载完这些对象后SettingsActivity会将得到的 mCategories 返回给DashboardSummary来初始化Settings的设置选项。

下面这段代码就是DashboardSummary.rebuildUI()中完成界面的初始化

    long start = System.currentTimeMillis();

        final Resources res = getResources();

 

        mDashboard.removeAllViews();

 

        List<DashboardCategory> categories =

                ((SettingsActivity) context).getDashboardCategories(true);

 

        final int count = categories.size();

        for (int n = 0; n < count; n++) {

            DashboardCategory category = categories.get(n);

 

            View categoryView = mLayoutInflater.inflate(R.layout.dashboard_category, mDashboard,

                    false);

 

            TextView categoryLabel = (TextView) categoryView.findViewById(R.id.category_title);

            categoryLabel.setText(category.getTitle(res));

 

            ViewGroup categoryContent =

                    (ViewGroup) categoryView.findViewById(R.id.category_content);

 

            final int tilesCount = category.getTilesCount();

            for (int i = 0; i < tilesCount; i++) {

                DashboardTile tile = category.getTile(i);

 

                DashboardTileView tileView = new DashboardTileView(context);

                updateTileView(context, res, tile, tileView.getImageView(),

                        tileView.getTitleTextView(), tileView.getStatusTextView());

 

                tileView.setTile(tile);

 

                categoryContent.addView(tileView);

            }

 

            // Add the category

            mDashboard.addView(categoryView);

        }

这段代码我就不具体分析了,逻辑很简单,遍历categories这个列表来获取DashboardCategory对象,将所有DashboardCategory对象和DashboardCategory对象中的DashboardTile对象转化为视图对象并添加到主视图对象mDashboard中。

 

到这里SettingsActivity的onCreate方法就算结束了。总结一下,

    1.onCreate完成的任务是切换DashboardSmmary这个Fragment,然后从dashboard_categories.xml中读取预先配置好的文件来初始化Settings的首界面视图。

    2.L中舍弃了Header类,取而代之的是DashboardCategory和DashboardTile类。




























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值