android开发中Settings结构简单分析

Posted JasonGaoH

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android开发中Settings结构简单分析相关的知识,希望对你有一定的参考价值。

Settings界面结构简单分析

Setting是android系统很重要的模块,这个模块并不是很复杂,这部分也一直在看,很多时候都是在看某个具体的选项,比如WLAN,蓝牙这样具体的源码,但是对于主界面的布局以及结构并不清楚。
在使用Hierarchy Viewer工具可以看到Settings模块的主界面显示的是Settings,

com.android.settings/com.android.settings.Settings

在进入设置的子界面的时候,显示永远是SubSettings,

com.android.settings/com.android.settings.SubSettings

这样我就感觉有点奇怪,为什么所有的子界面显示的都是SubSettings。
这几天搜了相关资料和结合源码看了一下这部分的逻辑,简单分析如下。

  1. Settings主界面Activity使用的是Settings
  2. Settings子界面Activity基本上都是使用SubSettings
  3. Settings与SubSettings中都是空Activity,这里的空Activity指的是没有重写7大生命周期方法
  4. Settings与SubSettings都是继承于SettingsActivity
    主界面使用的layout是:settings_main_dashboard,子界面使用的layout是:settings_main_prefs

在SettingsActivity的onCreate方法中,通过判断当前是Settings还是SubSettings来确定用什么布局来显示

 @Override
    protected void onCreate(Bundle savedState) 
        super.onCreate(savedState);
        .....
        mIsShowingDashboard = className.equals(Settings.class.getName());

        setContentView(mIsShowingDashboard ?
                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

        ....
        ....
    

这里会有两个问题,Settings作为主界面,加载的是settings_main_dashboard.xml文件,下面是这个xml文件具体内容

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/main_content"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:background="@color/dashboard_background_color" /> 

如果都是这个布局的话,那么主界面的不同item是如何显示出来的呢?

主界面settings_main_dashboard中是使用DashboardSummary(Fragment)进行填充。由于设置的主界面Settings和设置的子界面SubSettings都是继承于SettingsActivity的,Setting是和SubSettings里面是空的,所以会执行父类SettingsActivity的onCreate方法。
在SettingsActivity的onCreate方法后面,在这里会switchToFragment进行替换,
将其替换为DashboardSummary。DashboardSummary同样是一个Fragment,通过mIsShowingDashboard判断是否将DashboardSummary替换过来,而这里mIsShowingDashboard的值是通过判断当前是Settings还是SubSettings来获得的。
下面是switchToFragment方法的部分源码:

 if (savedState != null) 
        .....
        .....
     else 
            if (!mIsShowingDashboard) 
                ......
                ......
                switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);
             else 
                switchToFragment(DashboardSummary.class.getName(), null, false, false,mInitialTitleResId, mInitialTitle, false);
            
    

然后去看DashboardSummary这个类,它是一个Fragment。DashboardSummary继承了PreferenceFragment,并且在它的onCreateView方法中,加载的XML布局是dashboard.xml,dashboard.xml的布局如下,使用ScrollView嵌套了一个竖直的线性布局,这样,设置的主界面就是可以滚动的垂直的线性结构。

<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:clipToPadding="false"
                android:orientation="vertical"
                />
</ScrollView>

这样,设置主页面Settings的整体结构就出来了,接下来看这界面的选项列表是如何加载出来的。
注意到在DashboardSummary的onResume方法中,有个方法为sendRebuildUI,这个方法通过Handler发送Message来通知界面更新UI,更新UI的方法为rebuildUI。
在rebuildUI这个方法中,通过调用SettingsActivity中的getDashboardCategories来获得主界面的选项列表,在SettingsActivity中的getDashboardCategories方法中,通过调用buildDashboardCategories来从布局文件中将Setting主界面的选项解析出来,这个布局为dashboard_categories.xml。下面截取的是dashboard_categories.xml部分布局文件,大神board-categories节点表示的大的选项的分类,dashboard-tile则表示的是具体的选项比如wifi,蓝牙等。

上面红框圈住的为DashboardTitle,下面红框圈住的为DashboardTitleView,DashboardTitle对应的是xml布局中的dashboard-category这个节点,而DashboardTitleView则对应的是dashboard-title这个节点。

<?xml version="1.0" encoding="utf-8"?>
<dashboard-categories
        xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- WIRELESS and NETWORKS -->
    <dashboard-category
            android:id="@+id/wireless_section"
            android:key="@string/category_key_wireless"
            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"
                />
       .....
       .....

    </dashboard-category>
</dashboard-category>

通过以上一系列的操作,Setting模块的主界面的布局就加载出来了。

在Settings类中,定义了大量静态的内部类,但是都是空的,并未实现。
定义的这些静态内部类主要用于跳转用的,比如从SystemUI跳转至Setting的某一个页面,另外这些静态内部类在AndroidManifest.xml文件中通过meta-data将相应的Fragment绑定起来。

<activity android:name="Settings$WirelessSettingsActivity"
                android:taskAffinity="com.android.settings"
                android:label="@string/wireless_networks_settings_title"
                android:parentActivityName="Settings">
            <intent-filter android:priority="1">
                <action android:name="android.settings.WIRELESS_SETTINGS" />
                <action android:name="android.settings.AIRPLANE_MODE_SETTINGS" />
                <action android:name="android.settings.NFC_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>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.WirelessSettings" />
            <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
                android:resource="@id/wireless_settings" />
            <!-- Note that this doesn't really show any Wireless settings. -->
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                android:value="true" />
        </activity>

这里是如何通过meta-data将相应的Fragment进行绑定的,可以看看这个博客:Android5.0 Settings各个子模块跳转和布局实现

第二个问题,进入设置子界面,也就是二级界面的时候,Hierarchy Viewer工具上显示的SubSettings,接下来分析这一块。
由SubSettingsActivity的onCreate方法可知,SubSettings加载的布局文件是settings_main_prefs.xml,布局文件的内容如下。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_height="match_parent"
              android:layout_width="match_parent">

    <LinearLayout
            android:orientation="vertical"
            android:layout_height="0px"
            android:layout_width="match_parent"
            android:layout_weight="1">

        <com.android.settings.widget.SwitchBar android:id="@+id/switch_bar"
                  android:layout_height="?android:attr/actionBarSize"
                  android:layout_width="match_parent"
                  android:layout_marginLeft="13dp"
                  android:layout_marginRight="13dp"
                  android:background="?attr/preferenceBackgroundColor"
                />
        <FrameLayout
                android:id="@+id/main_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="?attr/preferenceBackgroundColor"
                />

    </LinearLayout>

    <RelativeLayout android:id="@+id/button_bar"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    android:layout_weight="0"
                    android:visibility="gone">

        <Button android:id="@+id/back_button"
                android:layout_width="150dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:layout_alignParentStart="true"
                android:text="@*android:string/back_button_label"
                />

        <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true">

            <Button android:id="@+id/skip_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/skip_button_label"
                    android:visibility="gone"
                    />

            <Button android:id="@+id/next_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/next_button_label"
                    />

        </LinearLayout>

    </RelativeLayout>

</LinearLayout>

同样的,SubSettings的Fragment的替换也是在SettingsActivity的onCreate方法中处理,注意到在onCreate方法的后面,如下,当mIsShowingDashboard为false时,会调用
switchToFragment(),这时第一个参数穿进去的是initalFragmentName,initalFragmentName这个参数就是对应的就是子界面的Fragment,在onCreate方法内先后执行了getMetaData和getIntent两个方法后,会通过PackageManager获取Activity的信息,得到对应的Activity的meta-data中key为com.android.settings.FRAGMENT_CLASS的值value,比如
wifi设置模块的值就是com.android.settings.WirelessSettings,然后再通过构造一个intent,并且给它增加了一个特殊的键值对,key为:settings:show_fragment,value为mFragmentClass指定的Fragment类名。

 if (savedState != null) 
        .....
        .....
     else 
            if (!mIsShowingDashboard) 
                ......
                ......
                switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);
             else 
                switchToFragment(DashboardSummary.class.getName(), null, false, false,mInitialTitleResId, mInitialTitle, false);
            
    

这样,SubSettings中settings_main_prefs.xml对应的Fragment就会被响应的Fragment替换过来了。

这个部分介绍的是Settings子界面是如何渲染出来的。
最后介绍下,由主界面的Item项是如何进行跳转的,即由主界面的选项是如何响应用户的点击事件跳转至子界面的?

由于主界面的布局都是采用DashboardCategory和DashboardTileView进行构造的,DashboardCategory相当于父控件,而DashboardTileView则为子控件,一般DashboardCategory一般可以包含多个DashboardTileView。

查看DashboardTileView这个类的源码,会发现这里面有个onClick方法,这样用户的点击事件就可以响应了。当用户点击的时候,会调用Utils.startWithFragmen。

@Override
    public void onClick(View v) 
        if (mTile.fragment != null) 
            Utils.startWithFragment(getContext(), mTile.fragment, mTile.fragmentArguments, null, 0,
                    mTile.titleRes, mTile.getTitle(getResources()));
         else if (mTile.intent != null) 
            int numUserHandles = mTile.userHandle.size();
            if (numUserHandles > 1) 
                ProfileSelectDialog.show(((Activity) getContext()).getFragmentManager(), mTile);
             else if (numUserHandles == 1) 
                getContext().startActivityAsUser(mTile.intent, mTile.userHandle.get(0));
             else 
                getContext().startActivity(mTile.intent);
            
        
    

在startFragment中,获得点击的item项对应的FragmentName,,然后构造一个Intent进行跳转。

public static void startWithFragment(Context context, String fragmentName, Bundle args,
            Fragment resultTo, int resultRequestCode, String titleResPackageName, int titleResId,
            CharSequence title, boolean isShortcut) 
        Intent intent = onBuildStartFragmentIntent(context, fragmentName, args, titleResPackageName,
                titleResId, title, isShortcut);
        if (resultTo == null) 
            context.startActivity(intent);
         else 
            resultTo.startActivityForResult(intent, resultRequestCode);
        
    

	public static Intent onBuildStartFragmentIntent(Context context, String fragmentName,
            Bundle args, String titleResPackageName, int titleResId, CharSequence title,
            boolean isShortcut) 
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClass(context, SubSettings.class);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, fragmentName);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME,
                titleResPackageName);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, titleResId);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, isShortcut);
        return intent;
    
新人创作打卡挑战赛 发博客就能抽奖!定制产品红包拿不停!

以上是关于android开发中Settings结构简单分析的主要内容,如果未能解决你的问题,请参考以下文章

Android开发学习------项目结构分析

Android4.42-Settings源码分析之蓝牙模块Bluetooth(上)

Android Studio简单设置

Android应用开发项目结构分析

Android Settings模块架构浅析<1>

Android Gradle 插件Android Studio 工程 Gradle 构建流程 ② ( settings.gradle 构建脚本分析 | 根目录下 build.gradle 分析 )