Settings5.1源码分析

Posted 他叫小黑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Settings5.1源码分析相关的知识,希望对你有一定的参考价值。

本文代码基于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结构简单分析

以上是关于Settings5.1源码分析的主要内容,如果未能解决你的问题,请参考以下文章

(26)Blender源码分析之顶层菜单的关于菜单

(26)Blender源码分析之顶层菜单的关于菜单

(26)Blender源码分析之顶层菜单的关于菜单

01.搭建 springboot 源码分析环境

01.搭建 springboot 源码分析环境

Tensorflow 源码分析-GPU调用是如何实现的