当前位置: 代码迷 >> Android >> Android Settings模块架构浅析<1>
  详细解决方案

Android Settings模块架构浅析<1>

热度:131   发布时间:2016-04-24 11:30:41.0
Android Settings模块架构浅析<1>

概述

Android Settings模块说简单也简单,说难也难,里面涉及到的知识点也挺多的。我们知道Settings主要是用于配置一些系统选项或属性值,通过修改设置项就能达到修改系统配置的作用。那么问题来了,Settings是如何实现修改后能改变系统配置的呢?Settings又是采用怎样的架构实现的呢?里面又涉及到哪些知识点呢?让我们一起来揭开她的神秘面纱吧!

原理分析

Settings的主要功能就是改变系统配置,那么他是如何做到的呢?
  • Settings的战友SettingsProvider
通过跟踪Settings源码发现,Settings并不是孤军作战,它还有一个战友 - SettingsProvider。SettingsProvider又是做什么的呢?其实看到这个名字我们不难猜到他扮演着什么样的角色。SettingsProvider继承ContentProvider,ContentProvider在android中主要扮演着数据共享的角色。SettingsProvider中有一个数据库,并且这个数据库是对外公开的。Settings与SettingsProvider之间又是什么关系呢?且看下面分析。
  • Settings和SettingsProvider之间的关系
Settings源码位置:  packages/apps/Settings/SettingsProvider源码位置:  frameworks/base/packages/SettingsProvider/  frameworks/base/core/java/android/provider/Settings.javadb在数据库中存在的位置:  /data/data/com.android.providers.settings/databases/settings.db
他们之间存在什么联系呢?其实,Settings会对SettingsProvider中的数据库进行操作和监听。Settings中大部分选项都会涉及到对SettingsProvider的操作。
  • 原理分析
通过跟踪代码发现,Settings大部分操作的就是SettingsProvider中的数据,也有一些直接操作系统属性的等等。当用户在修改系统设置时,大部分实际上是在修改SettingsProvider中的值。当SettingsProvider数据库中的值被改变时,一些系统服务什么的就会监听到,这时候就会通过jni等当时操作底层,从而达到系统属性或配置改变的效果。
Andriod Framework Module Note Url 4.jpg

架构分析

Settings处在安卓的应用层,不同于市场上的app,Settings属于系统app,也是一个比较特别的app。
  • Settings特点
1.Settings页面很多,但是Activity却很少,基本上都是使用PreferenceFragment2.Settings中包含大量对provider的操作与监听3.Settings UI基本上都是采用Preference来实现
  • Settings架构
1.Settings主界面Activity使用的是Settings2.Settings子界面Activity基本上都是使用SubSettings3.Settings与SubSettings中都是空Activity,这里的空Activity指的是没有重写7大生命周期方法4.Settings与SubSettings都是继承于SettingsActivity5.主界面使用的layout是:settings_main_dashboard,子界面使用的layout是:settings_main_prefs6.主界面settings_main_dashboard中是使用DashboardSummary(Fragment)进行填充,子界面都是使用各自的Fragment进行填充7.子界面fragment基本上都是直接或间接继承SettingsPreferenceFragment8.主界面选项列表是定义在dashboard_categories.xml中,此文件是在SettingsActivity的buildDashboardCategories方法中进行解析的9.在Settings类中定义了很多static class,这些类都是继承SettingsActivity,但都是空的,如BluetoothSettingsActivity  这些类主要用于对外提供跳转页面,比如从SystemUI跳转至Settings中的某个界面10.Settings类中定义了的static class被定义在AndroidManifest中,通过meta-data参数将对应的Fragment绑定在一起11.在Activity中填充Fragment主要使用的是SettingsActivity中的switchToFragment方法
  • settings_main_dashboard中只有一个FrameLayout,后面会将其替换为DashboardSummary
<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" />
  • settings_main_prefs中也存在一个叫main_content的FrameLayout,后面会将其替换为各自的Fragment,switch_bar与button_bar只有在某些页面才会显示
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical" >    <LinearLayout        android:layout_width="match_parent"        android:layout_height="0px"        android:layout_weight="1"        android:orientation="vertical" >        <com.android.settings.widget.SwitchBar            android:id="@+id/switch_bar"            android:layout_width="match_parent"            android:layout_height="?android:attr/actionBarSize"            android:background="@drawable/switchbar_background"            android:theme="?attr/switchBarTheme" />        <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_width="match_parent"        android:layout_height="wrap_content"        android:layout_weight="0"        android:visibility="gone" >        <Button            android:id="@+id/back_button"            android:layout_width="150dip"            android:layout_height="wrap_content"            android:layout_alignParentStart="true"            android:layout_margin="5dip"            android:text="@*android:string/back_button_label" />        <LinearLayout            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentEnd="true"            android:orientation="horizontal" >            <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>
  • Settings主界面结构
Andriod Framework Module Note Url 5 5.jpg
1.从图中可以看到,红色框中的属于一个DashboardCategory,蓝色框中的属于DashboardTileView2.在DashboardSummary中有多个DashboardCategory,DashboardCategory中包含一个title和多个DashboardTileView3.DashboardTileView具有onClick方法,点击后启动子界面,使用的是Utils.startWithFragment进行跳转4.startWithFragment方法中将子界面的Fragment传递给activity,这里会绑定对应的activity,也就是SubSettings

Utils.startWithFragment关键方法

   
 public static void startWithFragment(Context context, String fragmentName, Bundle args,            Fragment resultTo, int resultRequestCode, int titleResId,            CharSequence title) {        startWithFragment(context, fragmentName, args, resultTo, resultRequestCode,                null /* titleResPackageName */, titleResId, title, false /* not a shortcut */);    }    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);        if (BluetoothSettings.class.getName().equals(fragmentName)) {            intent.setClass(context, SubSettings.BluetoothSubSettings.class);            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true);        } else if (WifiSettings.class.getName().equals(fragmentName)) {            intent.setClass(context, SubSettings.WifiSubSettings.class);            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true);        } else {            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;    }

SettingsActivity.onCreate方法中的关键代码

    @Override    protected void onCreate(Bundle savedState) {        super.onCreate(savedState);        // Should happen before any call to getIntent()        getMetaData();        final Intent intent = getIntent();        // Getting Intent properties can only be done after the super.onCreate(...)        final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);        mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||                intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);        final ComponentName cn = intent.getComponent();        final String className = cn.getClassName();        mIsShowingDashboard = className.equals(Settings.class.getName());        // This is a "Sub Settings" when:        // - this is a real SubSettings        // - or :settings:show_fragment_as_subsetting is passed to the Intent        final boolean isSubSettings = className.equals(SubSettings.class.getName()) ||                intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);        setContentView(mIsShowingDashboard ?                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);        mContent = (ViewGroup) findViewById(R.id.main_content);        getFragmentManager().addOnBackStackChangedListener(this);        if (savedState != null) {            ......        } else {            if (!mIsShowingDashboard) {                ......                setTitleFromIntent(intent);                Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);                switchToFragment(initialFragmentName, initialArguments, true, false,                        mInitialTitleResId, mInitialTitle, false);            } else {                ......                mInitialTitleResId = R.string.dashboard_title;                switchToFragment(DashboardSummary.class.getName(), null, false, false,                        mInitialTitleResId, mInitialTitle, false);            }        }      ......    }
1.当点击主界面上的item时会调用Utils.startWithFragment方法2.在Utils.startWithFragment会跳转至SubSettings,对应的fragment也作为参数传递给了SubSettings3.SubSettings是一个空的activity,但SubSettings继承于SettingsActivity,因此会调用父类SettingsActivity的onCreate方法4.在onCreate方法中,className为SubSettings,isSubSettings为true,mIsShowingDashboard为false5.因此会执行switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);6.通过switchToFragment将settings_main_prefs的main_content替换为了子界面对应的fragment

简单类图

从下面类图中可以看出

1.Settings中主要的Activity为SettingsActivity,其他基本上都是继承该activity,并且其他基本上都是空的2.Settings中fragment基本上都是继承至SettingsPreferenceFragment
Andriod Framework Module Note Url 1.jpg
Andriod Framework Module Note Url 2.jpg

时序图

下面的时序图为点击Settings图标启动Settings,在点击item启动子界面的时序图
从图中可以看出启动的一个流程,按照这个流程,几乎所有的界面都会执行SettingsActivity
Andriod Framework Module Note Url 3.jpg
  相关解决方案