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特点
- Settings页面很多,但是Activity却很少,基本上都是使用PreferenceFragment
- Settings中包含大量对provider的操作与监听
- Settings UI基本上都是采用Preference来实现
2.2Settings架构
- Settings主界面Activity使用的是Settings
- Settings子界面Activity基本上都是使用SubSettings
- Settings与SubSettings中都是空Activity,这里的空Activity指的是没有重写7大生命周期方法
- Settings与SubSettings都是继承于SettingsActivity
- 主界面使用的layout是:settings_main_dashboard,子界面使用的layout是:settings_main_prefs,是在SettingsActivity中加载的
- 主界面settings_main_dashboard中是使用DashboardSummary(Fragment)进行填充,子界面都是使用各自的Fragment进行填充
- 子界面fragment基本上都是直接或间接继承SettingsPreferenceFragment
- .主界面选项列表是定义在dashboard_categories.xml中,此文件是在SettingsActivity的buildDashboardCategories方法中进行解析的。代码中的List对应dashboard-categorys,DashboardCategory对应dashboard-category,而dashboard-tile则对因代码中的DashboardTile。
- 在Settings类中定义了很多static class,这些类都是继承SettingsActivity,但都是空的,如BluetoothSettingsActivity,这些类主要用于对外提供跳转页面,比如从SystemUI跳转至Settings中的某个界面
- Settings类中定义了的static class被定义在AndroidManifest中,通过meta-data参数将对应的Fragment绑定在一起
- 在Activity中填充Fragment主要使用的是SettingsActivity中的switchToFragment方法
有些应用会在桌面上生成两个图标,这两个图标有些是同一个Activity的入口,有些是另外一个Activity的入口,这样的效果是怎么实现的呢?使用的是< activity-alias >标签
2.3Settings主界面结构
- 从图中可以看到,大框中的属于一个DashboardCategory,小框中的属于DashboardTileView
- 在DashboardSummary中有多个DashboardCategory,DashboardCategory中包含一个title和多个DashboardTileView。在DashboardSummary.rebuildUI()中完成界面的初始化
- DashboardTileView具有onClick方法,点击后启动子界面,使用的是Utils.startWithFragment进行跳转
- 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.类图
从下面类图可以看出
- Settings中主要的Activity为SettingsActivity,其他基本上都是继承该activity,并且其他基本上都是空的
- Settings中fragment基本上都是继承至SettingsPreferenceFragment
5.时序图
下面的时序图为点击Settings图标启动Settings,在点击item启动子界面的时序图。
从图中可以看出启动的一个流程,按照这个流程,几乎所有的界面都会执行SettingsActivity。
参考文章
Android5.1源码分析系列(一)Settings源码分析
Android 5.1 Settings模块源码分析
Android Settings模块架构浅析<1>
android开发中Settings结构简单分析
- Settings源码分析
- Scrapy-settings源码分析
- Settings源码分析
- 20150623----Android-Settings源码分析
- Android Settings源码流程分析
- Android原生Settings源码分析
- Android4.42源码分析---Settings
- Android Settings(系统设置)源码分析(一)
- Android 5.1 Settings源码简要分析
- Android 5.1 Settings源码简要分析
- Android 5.1settings源码简要分析
- Android Settings(系统设置)源码分析(一)
- Android 5.1 Settings模块源码分析
- Android 5.1 Settings源码简要分析
- Android5.1源码分析系列(一)Settings源码分析
- Settings分析
- Android 4.2 Wifi Display 之 Settings 源码分析(一)
- Android 4.2 Wifi Display 之 Settings 源码分析(二)
- leetcode--First Missing Positive
- 通过Eclipse的Git插件维护GitHub上的代码
- UI06-UIView视图层次关系
- centos 安装nginx与ftp
- Android高德地图如何让所有的轨迹在屏幕范围内都显示出来.
- Settings源码分析
- angularJS的$watch失效问题的解决方案
- 小程找工作之->c语言的运算符
- winfrom 程序打包成一个exe(伪绿色版)
- 用文件操作实现的通讯录
- x:Key的用法
- LeetCode 206. Reverse Linked List
- Oracle的Sql语句一些知识点(1)
- day7数组以及冒泡排序