Android系统源码剖析(一)---Settings - Go语言中文社区

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


本文为博主辛苦总结,针对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(resxml)文件。在此摘列出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);
    .........
}
}
















版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/zrf1335348191/article/details/50837027
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-27 21:51:21
  • 阅读 ( 559 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢