Android4.42源码分析---Settings

最近在研究Android的Settings源码,先看一下源码的目录结构。大概967左右个文件,是不是及其头疼而且无从下手?待我娓娓道来~~~~~

Android4.42源码分析---Settings


1,初识Settings

首先,这么多文件,到底哪个文件是主界面呢?在Settings目录下找到Androidmanifest.xml清单配置文件,找到首先启动的activity:

[java] view plain copy
  1. <activity android:name="Settings" android:label="@string/settings_label_launcher" android:taskAffinity="com.android.settings" android:launchMode="singleTask">  
  2.  <intent-filter>  
  3.   <action android:name="android.intent.action.MAIN" />   
  4.   <action android:name="android.settings.SETTINGS" />   
  5.   <category android:name="android.intent.category.DEFAULT" />   
  6.   <category android:name="android.intent.category.LAUNCHER" />   
  7.   </intent-filter>  
  8.   </activity>  

可以看到,设置的主界面是Settings.Java(package com.android.settings;),

[java] view plain copy
  1. public class Settings extends PreferenceActivity  
  2.         implements ButtonBarHandler, OnAccountsUpdateListener {  
  3.          .....  
  4. <pre name="code" class="java"> loadHeadersFromResource(R.xml.settings_headers, headers);//加载布局  
  5.           
  6.          .....  
  7. }  


所对应的xml文件为Settings_headers.xml(res\xml\)文件。在此摘列出xml文件的一部分。

[java] view plain copy
  1. <preference-headers  
  2.         xmlns:android="http://schemas.android.com/apk/res/android">  
  3.   
  4.   
  5.     <!-- WIRELESS and NETWORKS -->  
  6.     <header android:id="@+id/wireless_section"  
  7.         android:title="@string/header_category_wireless_networks" />  
  8.   
  9.     <!-- Wifi -->  
  10.     <header  
  11.         android:id="@+id/wifi_settings"  
  12.         android:fragment="com.android.settings.wifi.WifiSettings"  
  13.         android:title="@string/wifi_settings_title"  
  14.         android:icon="@drawable/ic_settings_wireless" />  
  15.   
  16.     <!-- MobileData -->  
  17.     <header  
  18.         android:id="@+id/mobiledata_settings"  
  19.         android:icon="@drawable/stat_notify_mobile_data"  
  20.         android:title="@string/data_usage_enable_mobile">  
  21.         <intent  
  22.             android:action="android.intent.action.MAIN"  
  23.             android:targetPackage="com.android.phone"  
  24.             android:targetClass="com.android.phone.MobileNetworkSettings" />  
  25.     </header>  
  26. .........  
  27. </preference-headers>  

每个可以选择和点击的item基本有四个属性,以WiFi_header为例

id:对应的id

fragment:点击之后的fragment:WifiSettings

title:header的主标题,即在Settings主界面显示的文本:WLAN

icon:header的图标,即显示在文本左侧的图标


分析这两个文件可以总结下Settings的布局,Settings主界面显示借助PreferenceActivity,Preference意为偏爱偏好,特点是利用键值对记录用户上次的选择,在下次进入到该界面时直接读取上次的选择无须再进行配置。Activity意为界面,preferenceactivity结合两者。每行属于一个header,相当于listview中的item,每一个header又有fragment与之对应,而fragment的加载依赖于Activity,所依赖的Activity为SubSettings.java(package com.android.settings;//继承与Settings),在Subsetting.java中已经写明:

[java] view plain copy
  1. /** 
  2.  *Stub class for showing sub-settings; we can't use the main Settings class 
  3.  * since for our app it is a special singleTask class. 
  4.  * 不能直接使用Settings.java加载fragment,因为,我们的程序启动模式是singleTask 
  5.  */  
  6. public class SubSettings extends Settings {  
  7.   
  8.     @Override  
  9.     public boolean onNavigateUp() {  
  10.         finish();  
  11.         return true;  
  12.     }  
  13.   
  14.     @Override  
  15.     protected boolean isValidFragment(String fragmentName) {  
  16.         return true;  
  17.     }  
  18. }   

对Setting源码的分析可以分两个步骤进行入手,

第一,headers列表的加载

第二,header的点击事件的处理

解决以上两个问题后,就可以开始对不同模块进行分析

2,设置界面布局,加载headers

(1),加载xml布局文件

可以使用两种方式加载xml文件布局

方法一:

[java] view plain copy
  1. loadHeadersFromResource(R.xml.settings_headers, headers);  

方法二:

[java] view plain copy
  1. addPreferencesFromResource(R.xml.fragmented_preferences_inner);   

(2),定义adapter加载并显示headers

[java] view plain copy
  1. private static class HeaderAdapter extends ArrayAdapter<Header> {  

设置界面布局的适配器adapter,有以下几种type

 i>,HEADER_TYPE_CATEGORY:无焦点,不可以点击

 ii>,HEADER_TYPE_BUTTON:带有button的header,button的visibility(可见性)有条件(可自行设置)

 iii>,HEADER_TYPE_NORMAL:正常的可获取焦点可点击的不带button的header

3,Settings.java源码分析(部分提取)


(1),onCreate方法中:

[java] view plain copy
  1. if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {  
  2.             getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));  
  3.         }  

以上这段代码用于布局actionbar,即顶部的导航栏布局,如果获取到的intent中的数值为

ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW,即表示,当屏幕较窄时导航栏有一部分会显示在底部。

[java] view plain copy
  1. mAuthenticatorHelper = new AuthenticatorHelper();  
  2.         mAuthenticatorHelper.updateAuthDescriptions(this);  
  3.         mAuthenticatorHelper.onAccountsUpdated(thisnull);  
这段代码属于配置一些认证或者更新账户信息,一般不做修改

[java] view plain copy
  1. getMetaData();  
查看方法源码可以看到:方法是获取到配置文件Androidmanifest.xml中<meta-data.../>节点下的数据

[java] view plain copy
  1. private void getMetaData() {  
  2.         try {  
  3.             //获取到配置文件Androidmanifest.xml文件中<meta-data.../>节点下的数据  
  4.            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),  
  5.                     PackageManager.GET_META_DATA);  
  6.             //如果没有信息,则返回  
  7.            if (ai == null || ai.metaData == nullreturn;  
  8.             //获取到header所对应的id  
  9.             mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);  
  10.            //获取到header所对应的fragment文件  
  11.             mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);  
  12.   
  13.             // Check if it has a parent specified and create a Header object  
  14.            //检查一下是否有parent,若有,就创建出来  
  15.             //parent的title  
  16.            final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);  
  17.            //parent的fragment  
  18.            String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);  
  19.   
  20.             if (parentFragmentClass != null) {  
  21.                 mParentHeader = new Header();  
  22.                 mParentHeader.fragment = parentFragmentClass;  
  23.                 if (parentHeaderTitleRes != 0) {  
  24.                     mParentHeader.title = getResources().getString(parentHeaderTitleRes);  
  25.                 }  
  26.             }  
  27.         } catch (NameNotFoundException nnfe) {  
  28.             // No recovery  
  29.         }  
  30.     }  

[java] view plain copy
  1. if (!onIsHidingHeaders() && onIsMultiPane()) {  
  2.             highlightHeader(mTopLevelHeaderId);  
  3.             // Force the title so that it doesn't get overridden by a direct launch of  
  4.             // a specific settings screen.  
  5.             setTitle(R.string.settings_label);  
  6.         }  

onIsMultiPane()判断是否双屏幕MultiPane,平板双屏显示,手机一般单屏SinglePane显示,所以onIsMultiPane()方法可以设置为返回false。

onIsHidingHeaders判断是否是双屏的headers均有显示。

如果满足条件就利用highlightHeader()方法标亮所选择的header进行区别于其他headers,并且将导航栏title定为设置,保证不被覆盖。

[java] view plain copy
  1. if (onIsMultiPane()) {  
  2.            //导航栏左上角图标的左边是否显示返回图标,false表示不显示  
  3.             getActionBar().setDisplayHomeAsUpEnabled(false);  
  4.             //导航栏左上角图标是否可点击,false代表不可点击  
  5.             getActionBar().setHomeButtonEnabled(false);  
  6.              //导航栏左上角的图标是否显示   
  7.              getActionBar().setDisplayShowHomeEnabled(true)    
  8. }  



以上代码是说如果是多屏显示,则对导航栏左上角程序图标以及返回图标的设置

接下来是利用savedInstanceState恢复数据的操作,不再贴出

[java] view plain copy
  1. showBreadCrumbs(mCurrentHeader.title, null);  

设置当前header的标题显示

[java] view plain copy
  1. if (mParentHeader != null) {  
  2.             setParentTitle(mParentHeader.title, nullnew OnClickListener() {  
  3.                 @Override  
  4.                 public void onClick(View v) {  
  5.                     。。。。。。  
  6.                 }  
  7.             });  
  8.         }  
设置parentheader的标题title以及设置title的点击事件。


 (2),onresume方法,显示出来所有的header,借助于headerAdapter.resume()方法显示

header即item需要显示什么类型的布局可以在该adapter中进行修改,针对不同的item配置不同的布局文件

[java] view plain copy
  1. private static class HeaderAdapter extends ArrayAdapter<Header> {  
  2. static int getHeaderType(Header header) {  
  3.      .........  
  4. }  
  5.  public View getView(int position, View convertView, ViewGroup parent) {  
  6.     ..........  
  7. }  
  8. }  

 (3),onBuildStartFragmentIntent方法

[java] view plain copy
  1. @Override  
  2.     public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,  
  3.             int titleRes, int shortTitleRes) {  
  4.         Intent intent = super.onBuildStartFragmentIntent(fragmentName, args,  
  5.                 titleRes, shortTitleRes);  
  6.           
  7.   
  8.         // Some fragments want split ActionBar; these should stay in sync with  
  9.         // uiOptions for fragments also defined as activities in manifest.  
  10.         //有些header所对应的fragment会将信息同步更新到window即状态栏  
  11.          if (WifiSettings.class.getName().equals(fragmentName) ||  
  12.                 WifiP2pSettings.class.getName().equals(fragmentName) ||  
  13.                 BluetoothSettings.class.getName().equals(fragmentName) ||....) {  
  14.                //将想要更新的信息传递给fragment对应的activity,在这里是SubSettings  
  15.               intent.putExtra(EXTRA_UI_OPTIONS, ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW);  
  16.               
  17.         }  
  18.    
  19.         intent.setClass(this, SubSettings.class);  
  20.         return intent;  
  21.     }  
 (4),onBuildHeaders方法,用来布局,以及更新headers,在PreferenceActivity的oncreate()方法中被调用,以及onGetInitialHeader()方法,也是在PreferenceActivity的oncreate方法中被调用

[java] view plain copy
  1. @Override  
  2.     public void onBuildHeaders(List<Header> headers) {  
  3.       
  4.         if (!onIsHidingHeaders()) {  
  5.             loadHeadersFromResource(R.xml.settings_headers, headers);  
  6.                 
  7.                    //该方法用于判定某些特定的header是否显示,  
  8.                  //比如若本机无蓝牙模块则不显示蓝牙的header  
  9.                      updateHeaderList(headers); } }  

(5)doValidCheck(),以及isValidFragment 用来检查fragment是否有效,为适配Android4.4以下版本,保证不出异常

(6)onNewIntent:activity启动模式为singletask单任务模式,如果在战中存在activity的实例,当再次通过intent调起时不会再去oncreate创建实例,而是onNewIntent去重用该实例

[java] view plain copy
  1. @Override  
  2.    public void onNewIntent(Intent intent) {  
  3.    
  4.        super.onNewIntent(intent);  
  5.   
  6.        // If it is not launched from history, then reset to top-level  
  7.        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {  
  8.            if (mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {  
  9.                switchToHeaderLocal(mFirstHeader);  
  10.            }  
  11.            getListView().setSelectionFromTop(00);  
  12.        }  
  13.    }  

(7)Settings.java中的内部类,Settings.java中有好多实现的内部类

[java] view plain copy
  1. 。。。。。。  
  2.     public static class SecuritySettingsActivity extends Settings { /* empty */ }  
  3.     public static class LocationSettingsActivity extends Settings { /* empty */ }  
  4. 。。。。。。。。  

这些内部类是为了加载那些fragment,作为fragment的宿主,可以从Androidmanifest.xml中看到,从其他快捷方式进入某个单独的设置模块时借助这些内部类来加载。比如可以创建蓝牙快捷方式,以及状态栏进入蓝牙时需要借助这些内部类来加载那些fragment。

[java] view plain copy
  1. <activity android:name="Settings$WirelessSettingsActivity"  
  2.                 android:taskAffinity="com.android.settings"  
  3.                 android:label="@string/wireless_networks_settings_title"  
  4.                 android:parentActivityName="Settings">  
  5. 。。。。。。  
  6. </activity>  


4,自定义操作

明白Settings界面的布局原理后我们就可以随意的对Settings主界面的布局进行增删改了,对应的是header的修改

(1),修改header:在xml文件下找到想要修改的header对应的节点,文本,文本左侧图标,以及点击进入的fragment进行相应修改即可

(2),增加header:例如我要增加一项"权限管理",做法如下:

    i>,在Settings.headers.xml文件中增加一个header节点:

[java] view plain copy
  1. <header  
  2.         android:id="@+id/authority_management  
  3.         android:fragment="com.android.settings.AuthorityManagementSettings"  
  4.         android:icon="@drawable/ic_settings_authority"  
  5.         android:title="@string/authority_settings"/>  


ii>,新建一个fragment,AuthorityManagementSettings类

[java] view plain copy
  1. public class DeviceInfoSettings extends RestrictedSettingsFragment {  
  2.  ........  
  3.   
  4.  @Override  
  5.     public void onCreate(Bundle icicle) {  
  6.         super.onCreate(icicle);  
  7.   
  8.         addPreferencesFromResource(R.xml.authority_management_settings);  
  9.     .........  
  10. }  
  11. }  

出处:http://blog.csdn.NET/zrf1335348191/article/details/50837027