Android系统源码剖析(一)---Settings

来源:互联网 发布:知金教育好吗 编辑:程序博客网 时间:2024/05/17 02:12

本文为博主辛苦总结,针对Android4.42源码分析,转载请注明出处,http://blog.csdn.net/zrf1335348191/article/details/50837027


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


1,初识Settings

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

<activity android:name="Settings" android:label="@string/settings_label_launcher" android:taskAffinity="com.android.settings" android:launchMode="singleTask"> <intent-filter>  <action android:name="android.intent.action.MAIN" />   <action android:name="android.settings.SETTINGS" />   <category android:name="android.intent.category.DEFAULT" />   <category android:name="android.intent.category.LAUNCHER" />   </intent-filter>  </activity>

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

public class Settings extends PreferenceActivity        implements ButtonBarHandler, OnAccountsUpdateListener {         .....<pre name="code" class="java"> loadHeadersFromResource(R.xml.settings_headers, headers);//加载布局                 .....}

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

<preference-headers        xmlns:android="http://schemas.android.com/apk/res/android">    <!-- WIRELESS and NETWORKS -->    <header android:id="@+id/wireless_section"        android:title="@string/header_category_wireless_networks" />    <!-- Wifi -->    <header        android:id="@+id/wifi_settings"        android:fragment="com.android.settings.wifi.WifiSettings"        android:title="@string/wifi_settings_title"        android:icon="@drawable/ic_settings_wireless" />    <!-- MobileData -->    <header        android:id="@+id/mobiledata_settings"        android:icon="@drawable/stat_notify_mobile_data"        android:title="@string/data_usage_enable_mobile">        <intent            android:action="android.intent.action.MAIN"            android:targetPackage="com.android.phone"            android:targetClass="com.android.phone.MobileNetworkSettings" />    </header>.........</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中已经写明:

/** *Stub class for showing sub-settings; we can't use the main Settings class * since for our app it is a special singleTask class. * 不能直接使用Settings.java加载fragment,因为,我们的程序启动模式是singleTask */public class SubSettings extends Settings {    @Override    public boolean onNavigateUp() {        finish();        return true;    }    @Override    protected boolean isValidFragment(String fragmentName) {        return true;    }} 


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

第一,headers列表的加载

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

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


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

(1),加载xml布局文件

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

方法一:

loadHeadersFromResource(R.xml.settings_headers, headers);

方法二:

addPreferencesFromResource(R.xml.fragmented_preferences_inner); 

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

 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方法中:

if (getIntent().hasExtra(EXTRA_UI_OPTIONS)) {            getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));        }

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

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

mAuthenticatorHelper = new AuthenticatorHelper();        mAuthenticatorHelper.updateAuthDescriptions(this);        mAuthenticatorHelper.onAccountsUpdated(this, null);

这段代码属于配置一些认证或者更新账户信息,一般不做修改


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


private void getMetaData() {        try {            //获取到配置文件Androidmanifest.xml文件中<meta-data.../>节点下的数据           ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),                    PackageManager.GET_META_DATA);            //如果没有信息,则返回           if (ai == null || ai.metaData == null) return;            //获取到header所对应的id            mTopLevelHeaderId = ai.metaData.getInt(META_DATA_KEY_HEADER_ID);           //获取到header所对应的fragment文件            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);            // Check if it has a parent specified and create a Header object           //检查一下是否有parent,若有,就创建出来            //parent的title           final int parentHeaderTitleRes = ai.metaData.getInt(META_DATA_KEY_PARENT_TITLE);           //parent的fragment           String parentFragmentClass = ai.metaData.getString(META_DATA_KEY_PARENT_FRAGMENT_CLASS);if (parentFragmentClass != null) {                mParentHeader = new Header();                mParentHeader.fragment = parentFragmentClass;                if (parentHeaderTitleRes != 0) {                    mParentHeader.title = getResources().getString(parentHeaderTitleRes);                }            }        } catch (NameNotFoundException nnfe) {            // No recovery        }    }


if (!onIsHidingHeaders() && onIsMultiPane()) {            highlightHeader(mTopLevelHeaderId);            // Force the title so that it doesn't get overridden by a direct launch of            // a specific settings screen.            setTitle(R.string.settings_label);        }

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

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

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


if (onIsMultiPane()) {           //导航栏左上角图标的左边是否显示返回图标,false表示不显示            getActionBar().setDisplayHomeAsUpEnabled(false);            //导航栏左上角图标是否可点击,false代表不可点击            getActionBar().setHomeButtonEnabled(false);             //导航栏左上角的图标是否显示              getActionBar().setDisplayShowHomeEnabled(true)  }

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

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

showBreadCrumbs(mCurrentHeader.title, null);

设置当前header的标题显示


if (mParentHeader != null) {            setParentTitle(mParentHeader.title, null, new OnClickListener() {                @Override                public void onClick(View v) {                    。。。。。。                }            });        }

设置parentheader的标题title以及设置title的点击事件。


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

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

private static class HeaderAdapter extends ArrayAdapter<Header> {static int getHeaderType(Header header) {     .........} public View getView(int position, View convertView, ViewGroup parent) {    ..........}}

 (3),onBuildStartFragmentIntent方法

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

@Override    public void onBuildHeaders(List<Header> headers) {            if (!onIsHidingHeaders()) {            loadHeadersFromResource(R.xml.settings_headers, headers);                     //该方法用于判定某些特定的header是否显示,                 //比如若本机无蓝牙模块则不显示蓝牙的header                     updateHeaderList(headers); } }

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

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

 @Override    public void onNewIntent(Intent intent) {          super.onNewIntent(intent);        // If it is not launched from history, then reset to top-level        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {            if (mFirstHeader != null && !onIsHidingHeaders() && onIsMultiPane()) {                switchToHeaderLocal(mFirstHeader);            }            getListView().setSelectionFromTop(0, 0);        }    }

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

。。。。。。    public static class SecuritySettingsActivity extends Settings { /* empty */ }    public static class LocationSettingsActivity extends Settings { /* empty */ }。。。。。。。。

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

<activity android:name="Settings$WirelessSettingsActivity"                android:taskAffinity="com.android.settings"                android:label="@string/wireless_networks_settings_title"                android:parentActivityName="Settings">。。。。。。</activity>


4,自定义操作

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

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

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

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

<header        android:id="@+id/authority_management        android:fragment="com.android.settings.AuthorityManagementSettings"        android:icon="@drawable/ic_settings_authority"        android:title="@string/authority_settings"/>

     ii>,新建一个fragment,AuthorityManagementSettings类

public class DeviceInfoSettings extends RestrictedSettingsFragment { ........ @Override    public void onCreate(Bundle icicle) {        super.onCreate(icicle);        addPreferencesFromResource(R.xml.authority_management_settings);    .........}}
















4 1