Settings源码分析

来源:互联网 发布:黑马程序员52期百度云 编辑:程序博客网 时间:2024/05/17 04:24

本文代码基于5.1.1。

1.概述介绍

Settings源码位置:packages/apps/Settings/

SettingsProvider源码位置:frameworks/base/packages/SettingsProvider/
frameworks/base/core/java/android/provider/Settings.java

db在数据库中存在的位置:/data/data/com.android.providers.settings/databases/settings.db

数据库的处理文件:frameworks/base/packages/SettingsProvider/src/com/Android/providers/settings/DatabaseHelper.Java

第一次开机的时候从frameworks/base/packages/SettingsProvider/res/values/defaults.xml获取数据,初始化数据库

Settings会对SettingsProvider中的数据库进行操作和监听。
Settings中大部分选项都会涉及到对SettingsProvider的操作。

Settings大部分操作的就是SettingsProvider中的数据,也有一些直接操作系统属性的等等。
当用户在修改系统设置时,大部分实际上是在修改SettingsProvider中的值。
当SettingsProvider数据库中的值被改变时,一些系统服务什么的就会监听到,这时候就会通过jni等当时操作底层,从而达到系统属性或配置改变的效果。

这里写图片描述


2.架构分析

2.1Settings特点

  1. Settings页面很多,但是Activity却很少,基本上都是使用PreferenceFragment
  2. Settings中包含大量对provider的操作与监听
  3. Settings UI基本上都是采用Preference来实现

2.2Settings架构

  1. Settings主界面Activity使用的是Settings
  2. Settings子界面Activity基本上都是使用SubSettings
  3. Settings与SubSettings中都是空Activity,这里的空Activity指的是没有重写7大生命周期方法
  4. Settings与SubSettings都是继承于SettingsActivity
  5. 主界面使用的layout是:settings_main_dashboard,子界面使用的layout是:settings_main_prefs,是在SettingsActivity中加载的
  6. 主界面settings_main_dashboard中是使用DashboardSummary(Fragment)进行填充,子界面都是使用各自的Fragment进行填充
  7. 子界面fragment基本上都是直接或间接继承SettingsPreferenceFragment
  8. .主界面选项列表是定义在dashboard_categories.xml中,此文件是在SettingsActivity的buildDashboardCategories方法中进行解析的。代码中的List对应dashboard-categorys,DashboardCategory对应dashboard-category,而dashboard-tile则对因代码中的DashboardTile。
  9. 在Settings类中定义了很多static class,这些类都是继承SettingsActivity,但都是空的,如BluetoothSettingsActivity,这些类主要用于对外提供跳转页面,比如从SystemUI跳转至Settings中的某个界面
  10. Settings类中定义了的static class被定义在AndroidManifest中,通过meta-data参数将对应的Fragment绑定在一起
  11. 在Activity中填充Fragment主要使用的是SettingsActivity中的switchToFragment方法

有些应用会在桌面上生成两个图标,这两个图标有些是同一个Activity的入口,有些是另外一个Activity的入口,这样的效果是怎么实现的呢?使用的是< activity-alias >标签


2.3Settings主界面结构

这里写图片描述

  1. 从图中可以看到,大框中的属于一个DashboardCategory,小框中的属于DashboardTileView
  2. 在DashboardSummary中有多个DashboardCategory,DashboardCategory中包含一个title和多个DashboardTileView。在DashboardSummary.rebuildUI()中完成界面的初始化
  3. DashboardTileView具有onClick方法,点击后启动子界面,使用的是Utils.startWithFragment进行跳转
  4. startWithFragment方法中将子界面的Fragment传递给activity,这里会绑定对应的activity,也就是SubSettings

3.源码分析

从AndroidManifest.xml可以看出启动类是Settings.java,继承于SettingsActivity。

先看SettingsActivity.onCreate方法中的关键代码,如下

    @Override      protected void onCreate(Bundle savedState) {          super.onCreate(savedState);          // Should happen before any call to getIntent()        //获得Activity的额外数据mFragmentClass,如果可以获得这个数据,那么下面会去显示mFragmentClass        //对应的Activity。直接启动Settings模块不会获得这个数据。         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);              }          }        ......      }  

先调用getMetaData()方法,用于加载一些元数据,主要作用就是通过META_DATA_KEY_FRAGMENT_CLASS这个属性获得额外的mFragmentClass,如果可以获得将启动对应的mFragmentClass的Activity,但是直接启动Setting不会获得该数据。

由于我们是从Setting启动的,所以mIsShowingDashboard的值为true,而isSubSettings的值是false。由于mIsShowingDashboard的值为true,所以使用的是R.layout.settings_main_dashboard,即主页面,后面会将其替换为DashboardSummary。布局如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"             android:id="@+id/main_content"             android:layout_height="match_parent"             android:layout_width="match_parent"             android:background="@color/dashboard_background_color"             />

这里由于是第一次启动,所以savedState 为null,同时mIsShowingDashboard的值为true,看到进入了switchToFragment这个方法,这里切换到DashboardSummary这个Fragment。

DashboardSummary.java的onCreateView()方法加载了R.layout.dashboard,这个布局使用ScrollView嵌套了一个竖直的线性布局,这样,设置的主界面就是可以滚动的垂直的线性结构。接下来,开始真正加载Setting的界面了,在OnResume()方法中最终会调用rebuildUI()方法,代码如下:

    private void rebuildUI(Context context) { //完成界面的初始化        if (!isAdded()) {            Log.w(LOG_TAG, "Cannot build the DashboardSummary UI yet as the Fragment is not added");            return;        }        long start = System.currentTimeMillis();        final Resources res = getResources();        mDashboard.removeAllViews();//mDashboard这个View就是整个界面的总View        //(1)这里调用SettingActivity的getDashboardCategories,也就是加载整个Setting的内容          List<DashboardCategory> categories =                ((SettingsActivity) context).getDashboardCategories(true);        final int count = categories.size();        //遍历categories这个列表来获取DashboardCategory对象,将所有DashboardCategory对象和        //DashboardCategory对象中的DashboardTile对象转化为视图对象并添加到主视图对象mDashboard中。        for (int n = 0; n < count; n++) {            DashboardCategory category = categories.get(n);            View categoryView = mLayoutInflater.inflate(R.layout.dashboard_category, mDashboard,                    false);            TextView categoryLabel = (TextView) categoryView.findViewById(R.id.category_title);            categoryLabel.setText(category.getTitle(res));            ViewGroup categoryContent =                    (ViewGroup) categoryView.findViewById(R.id.category_content);            final int tilesCount = category.getTilesCount();            for (int i = 0; i < tilesCount; i++) {                DashboardTile tile = category.getTile(i);                //(2)创建DashboardTileView,也就是每个Setting的内容                  DashboardTileView tileView = new DashboardTileView(context);                updateTileView(context, res, tile, tileView.getImageView(),                        tileView.getTitleTextView(), tileView.getStatusTextView());                tileView.setTile(tile);                categoryContent.addView(tileView);            }            // Add the category            mDashboard.addView(categoryView);        }        long delta = System.currentTimeMillis() - start;        Log.d(LOG_TAG, "rebuildUI took: " + delta + " ms");    }

上面代码(1)处最终会调用SettingActivity.java的buildDashboardCategories方法,如下:

    private void buildDashboardCategories(List<DashboardCategory> categories) {        categories.clear();        loadCategoriesFromResource(R.xml.dashboard_categories, categories);        updateTilesList(categories);    }

该方法将加载一个xml文档并使用Android默认的xml解析器XmlPullParser对文档进行解析,最终将解析结果存入到一个List中,然后在上面代码的rebuildUI方法中for循环遍历读取。

(2)处将通过for循环遍历而来的数据通过创建DashboardTileView最终全部存入到mDashboard这个布局中,至此整个Setting模块的界面布局已经完成了。

接着再看一下DashboardTileView.java。这个类是Setting中每个条目数据的类,通过onClick方法启动不同的功能,比如WiFi,Bluetooth等。代码如下:

    public class DashboardTileView extends FrameLayout implements View.OnClickListener {          @Override          public void onClick(View v) {              if (mTile.fragment != null) {                  Utils.startWithFragment(getContext(), mTile.fragment, mTile.fragmentArguments, null, 0,                          mTile.titleRes, mTile.getTitle(getResources()));              } else if (mTile.intent != null) {                  getContext().startActivity(mTile.intent);              }          }      }  

这里可以看到页面跳转是使用了Utils.startWithFragment()方法。看下这个方法:

 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);        }    }

可以看出intent链接到onBuildStartFragmentIntent,现在跳转到onBuildStartFragmentIntent:

  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);        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;    }

看到它的启动信息,直接链接到SubSettings这个类,并且附带这个fragment的fragmentName,args,titleResPackageName,等信息直接跳转。可以看到SubSettings这个类继承了SettingsActivity,所以我们又得回到SettingsActivity的onCreate()方法。

继续看,又可以看到mIsShowingDashboard这个标志位。

mIsShowingDashboard = className.equals(Settings.class.getName());

没错,前面因为我们是第一次创建所以这个值返回的是true,而现在呢?我们的className已经改变了,所以返回false。

setContentView(mIsShowingDashboard ? R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

所以现在加载的是settings_main_prefs这个界面。

final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);

获取传递过来的FRAGMENTname,接着:

switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);

这样就进行Fragment切换,切到子页面去了。具体可参考Android5.0 Settings各个子模块跳转和布局实现


4.类图

从下面类图可以看出

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

这里写图片描述

这里写图片描述


5.时序图

下面的时序图为点击Settings图标启动Settings,在点击item启动子界面的时序图。

从图中可以看出启动的一个流程,按照这个流程,几乎所有的界面都会执行SettingsActivity。

这里写图片描述


参考文章
Android5.1源码分析系列(一)Settings源码分析
Android 5.1 Settings模块源码分析
Android Settings模块架构浅析<1>
android开发中Settings结构简单分析