Android Settings源码结构分析与自实现

最近的项目一直是按照PRD与高清,修改系统设置,调整布局、间距、颜色,涉及到一些流程的更改与自定义控件,以及对settings源码结构的研究。在项目相对空闲是,做个整理记录。由于项目依赖系统源码环境,而且在赶项目的时候,只能以最快的速度解决当前的问题,而下面的设计的代码与效果图,都是个人封装的DEMO测试,毕竟不能仅仅只是最求项目的解决过关,学过用过,就应该做点总结,毕竟我觉得很多东西,在赶项目的时候是无法去过多的仔细研究,所以有居多“废代码”,很多地方是值得仔细研究与优化改进的。

首先对原生Settings的布局,及切换跳转,按照我的研究理解,做个流程的分析简介,后面会给出我的改进与实现

1.先从布局简单的说起:

在PreferenceActivity中 可以看到:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2.   protected void onCreate(Bundle savedInstanceState) {  
  3.       super.onCreate(savedInstanceState);  
  4.   
  5.       setContentView(com.android.internal.R.layout.preference_list_content);  

系统默认会加载这个布局文件,它是一个左右分屏的,左边是一个ListView,右边是一个android.preference.PreferenceFrameLayout,左边ListView 负责切换,右边显示相应的Fragment。可以到sdk目录下查看该布局文件(sdk\platforms\android-17\data\res\layout\preference_list_content)

2.右边ListView的显示。

代码详见内部类:HeaderAdapter。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //类型一,分类title,无焦点,不可点击  
  2.         static final int  HEADER_TYPE_CATEGORY= 0;  
  3.         //类型二,正常的可点击的header项  
  4.         static final int HEADER_TYPE_NORMAL = 1;  
  5.         //带 switch 开关的header项  
  6.         static final int HEADER_TYPE_SWITCH = 2;  

三种类型,分别对应的布局:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. case HEADER_TYPE_CATEGORY://下划线样式的TextView  
  2.                        view = new TextView(getContext(), null,android.R.attr.listSeparatorTextViewStyle);  
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. case HEADER_TYPE_SWITCH://含有switch 控件的布局  
  2.                        view = mInflater.inflate(R.layout.preference_header_switch_item, parent,false);  

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. case HEADER_TYPE_NORMAL:  
  2.     view = mInflater.inflate(R.layout.preference_header_item, parent, false);  

说完布局,我们再来说说关于启动流程的问题:

getMetaData-->onBuildHeaders-->onGetInitialHeader-->super.switchToHeader-->highlightHeader

1.onCreat 方法:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //...省略部分  
  2.   
  3. getMetaData();  
  4.        mInLocalHeaderSwitch = true;  
  5.        super.onCreate(savedInstanceState);  
  6.        mInLocalHeaderSwitch = false;  
  7.   
  8.  highlightHeader(mTopLevelHeaderId);  
  9.   
  10. //... 省略部分  



2.getMetaData:

主要作用是获取当前Activity的 meta信息,参看manifest的定义,如这个是wifi设置界面的Activity信息描述

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <activity android:name="Settings$WifiSettingsActivity"  
  2.                android:label="@string/wifi_settings"  
  3.                android:configChanges="orientation|keyboardHidden|screenSize"  
  4.                android:clearTaskOnLaunch="true"  
  5.                android:parentActivityName="Settings">  
  6.            <intent-filter>  
  7.                <action android:name="android.intent.action.MAIN" />  
  8.                <action android:name="android.settings.WIFI_SETTINGS" />  
  9.                <category android:name="android.intent.category.DEFAULT" />  
  10.                <category android:name="android.intent.category.VOICE_LAUNCH" />  
  11.                <category android:name="com.android.settings.SHORTCUT" />  
  12.            </intent-filter>  
  13.            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"  
  14.                android:value="com.android.settings.wifi.WifiSettings" />  
  15.            <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"  
  16.                android:resource="@id/wifi_settings" />  
  17.        </activity>  

定义了,跳转进来的action,已经这个它要展示的信息,左边Header的 id(mTopLevelHeaderId),右边显示的fragment 类(mFragmentClass)。

下面是读取该信息。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. private void getMetaData() {  
  2.        try {  
  3.         //获取当前Activity的Meta 信息  
  4.            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),  
  5.                    PackageManager.GET_META_DATA);  
  6.            if (ai == null || ai.metaData == nullreturn;  
  7.            //ListView 中要选中的Header的Id 如R.id.wifi_settings  
  8.            mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);  
  9.            //对应的切换 右边显示的Fragment  
  10.            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);  
  11.   
  12.            // Check if it has a parent specified and create a Header object  
  13.            //这个 应该是针对 single panel 检查它是否有上一级。  
  14.            final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);  
  15.            String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);  
  16.            if (parentFragmentClass != null) {  
  17.                mParentHeader = new Header();  
  18.                mParentHeader.fragment = parentFragmentClass;  
  19.                if (parentHeaderTitleRes != 0) {  
  20.                    mParentHeader.title = getResources().getString(parentHeaderTitleRes);  
  21.                }  
  22.            }  
  23.        } catch (NameNotFoundException nnfe) {  
  24.            // No recovery  
  25.        }  
  26.    }  

3.super.onCreate(savedInstanceState);

PreferenceActivity 源码 onCreat 方法主要 调用如下,

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.        super.onCreate(savedInstanceState);  
  4. //布局  
  5.        setContentView(com.android.internal.R.layout.preference_list_content);  
  6.   
  7.     //...省略部分代码  
  8.   
  9.  if (initialFragment != null && mSinglePane) {  
  10.               //单屏  
  11.            } else {  
  12.   
  13.                onBuildHeaders(mHeaders);  
  14.   
  15.                if (mHeaders.size() > 0) {  
  16.                    if (!mSinglePane) {  
  17.                        if (initialFragment == null) {  
  18.                            Header h = onGetInitialHeader();  
  19.                            switchToHeader(h);  
  20.                        } else {  
  21.                            switchToHeader(initialFragment, initialArguments);  
  22.                        }  
  23.                    }  
  24.                }  
  25.            }  

在PreferenceActivity 的onCreat 中 依次调用 onBuildHeaders-->onGetInitialHeader
onBuildHeaders:加载header资源,显示在右边的ListView。
onGetInitialHeader:初始化的时候,首显项。


4.Settings 类对 onBuildHeaders的重写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void onBuildHeaders(List<Header> headers) {  
  3.     //Header 资源  
  4.     loadHeadersFromResource(R.xml.settings_headers, headers);  
  5.     //根据相应条件,移除掉部分header,筛选出mFirstHeader   
  6.     //第一个不为HEADER_TYPE_CATEGORY(不能获得触摸焦点) 分类项的Header,作为备用显示  
  7.     //并记录 header id 对应ListView中的 index  
  8.     updateHeaderList(headers);  
  9. }  

5.Settings 类对 onGetInitialHeader的重写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public Header onGetInitialHeader() {  
  3. //获取 要显示的fragment   
  4.     String fragmentClass = getStartingFragmentClass(super.getIntent());  
  5.     //构造 header 对象  
  6.     if (fragmentClass != null) {  
  7.         Header header = new Header();  
  8.         header.fragment = fragmentClass;  
  9.         header.title = getTitle();  
  10.         header.fragmentArguments = getIntent().getExtras();  
  11.         mCurrentHeader = header;  
  12.         return header;  
  13.     }  
  14.     //如果meta中没有,intent中也没有,返回updateHeaderList 中选出来的header  
  15.     return mFirstHeader;  
  16. }  
  17.   
  18.   
  19. protected String getStartingFragmentClass(Intent intent) {  
  20. //如果前面 getMeta 中的读取到的mFragmentClass 不为空,直接return  
  21.     if (mFragmentClass != nullreturn mFragmentClass;  
  22.     //获取intent 中指定要 跳转到的类名  
  23.     String intentClass = intent.getComponent().getClassName();  
  24.     //就是当前activity ,不做处理  
  25.     if (intentClass.equals(getClass().getName())) return null;  
  26.   
  27.   
  28.     if ("com.android.settings.ManageApplications".equals(intentClass)  
  29.             || "com.android.settings.RunningServices".equals(intentClass)  
  30.             || "com.android.settings.applications.StorageUse".equals(intentClass)) {  
  31.         // Old names of manage apps.  
  32.         intentClass = com.android.settings.applications.ManageApplications.class.getName();  
  33.     }  
  34.   
  35.   
  36.     return intentClass;  
  37. }  


6.PreferenceActivity 的onCreate中 switchToHeader ,就显示 了指定的fragment和 高亮指定的header项。


7.Settings 的onCreate中 ,更新左边的选中项,mTopLevelHeaderId 也是从getMetaData 中读出来的。因为在PreferenceActivity 的switchToHeader 中,如果ListView没有找到Header,就不会有高亮的选中项。

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. highlightHeader(mTopLevelHeaderId);  

至此,Settings 的加载显示,就已经完成了,同时也展示了manifest中Activity的 meta-data的用法。
在此处,也就可以解释为何Settings类中有着那么多空实现的 public static 的内部类了,这可以说是一种Template 模式,使得Settings中的功能模块对外使用更加灵活,Settings主类描述了具体的算法架构,而不同的内部类通过manifest 中声明的meta-data,可以有不同的界面内容显示。如果说activity 可以通过 activity-alias 来描述一个多入口,以个性化的显示不同的信息,那么那些空实现的静态内部类,也是帮Settings实现这样的效果。一个有效的Intent 请求过来,匹配对应的内部类Activity,然后读取meta-data数据显示,从而“一步到位”的跳到想要的Settings页面,而不用一步步的点击切入。因为Fragment无法在manifest中进行描述声明,为其指定拦截的action,所以只能将其宿主到Activity中。

以上就是我对Settings启动流程的分析研究,有什么疑问或者不同的见解都可以交流交流。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值