Android 5.1 Settings模块源码分析

前述:

本人已工作两年多,但是依然感觉还是Android的门外汉,之前一直从事Android的应用开发,每天就是各种调用SDK方法,各种拷贝网上的源码以及jar包,从来也不管为啥这样用,由于换了一份工作才开始接触到Android的源码,感觉Android的水好深啊。

今天这篇博客也是我的处女作啊,以后也希望通过多多研究源码来写出更多的博客,我觉得写博客主要还是作为一个记录吧,不然感觉有的东西真的很容易丢,尤其是平时不怎么接触的模块。

好啦,接下来开始今天的Setting旅行啦。


Settings简述:

Setting模块大家还是比较熟悉的吧?其实Setting也不是什么高级的东西,它就是一个APP,属于Android的应用层,源码在packages\apps\Settings中,今天分析的源码是基于Android5.1,如下图是5.1Setting模块的界面:

Android 5.1 Settings模块源码分析_第1张图片


查看一个应用首先是查看这个应用的AndroidManifest.xml文件,以便查看程序的入口,Setting模块的入口是Setting.java这个类,这个类继承SettingActivity,但是没有继承任何的方法,但却定义了一大堆内部类。

/**
 * Top-level Settings activity
 */
public class Settings extends SettingsActivity {
    public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
    public static class StorageSettingsActivity extends SettingsActivity { /* empty */ }
    public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
    public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }
    public static class InputMethodAndLanguageSettingsActivity extends SettingsActivity { /* empty */ }
    public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }
    public static class InputMethodAndSubtypeEnablerActivity extends SettingsActivity { /* empty */ }
    public static class VoiceInputSettingsActivity extends SettingsActivity { /* empty */ }
    public static class SpellCheckersSettingsActivity extends SettingsActivity { /* empty */ }
    public static class LocalePickerActivity extends SettingsActivity { /* empty */ }
    public static class UserDictionarySettingsActivity extends SettingsActivity { /* empty */ }
    public static class HomeSettingsActivity extends SettingsActivity { /* empty */ }
    public static class DisplaySettingsActivity extends SettingsActivity { /* empty */ }
    public static class DeviceInfoSettingsActivity extends SettingsActivity { /* empty */ }
    public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ }
    public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ }
}
 
  
 
  
 
  

这些类都是Setting模块的子界面类,是特定功能的类,比如WifiSettingsActivity是WiFi模块相关的类。

所以接下来我们直接分析SettingActivity这个类就可以了。

SettingActivity.java

先看该类的OnCreate方法

 @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);

        // Should happen before any call to getIntent()
        getMetaData();

        final Intent intent = getIntent();

先调用getMetaData()方法,用于加载一些元数据,进入getMetaData()方法

private void getMetaData() {
        try {
            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
                    PackageManager.GET_META_DATA);
            if (ai == null || ai.metaData == null) return;
            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
        } catch (NameNotFoundException nnfe) {
            // No recovery
            Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
        }
}
 
  

主要作用就是通过META_DATA_KEY_FRAGMENT_CLASS这个属性获得额外的mFragmentClass,如果可以获得将启动对应的mFragmentClass的Activity,但是直接启动Setting不会获得该数据。

继续往下看代码

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


由于我们是从Setting启动的,所以mIsShowingDashboard的值为true,而isSubSettings的值是false。

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

由于mIsShowingDashboard的值为true,所以使用的是R.layout.settings_main_dashboard

同时继续往下走,会看到这段代码块:

if (savedState != null) {
   ...         
} else {
    if (!mIsShowingDashboard) {
      ...
    } else {
	// No UP affordance if we are displaying the main Dashboard
	mDisplayHomeAsUpEnabled = false;
        // Show Search affordance
        mDisplaySearch = true;
        mInitialTitleResId = R.string.dashboard_title;
        switchToFragment(DashboardSummary.class.getName(), null, false, false,
        mInitialTitleResId, mInitialTitle, false);
    }
}
 
  

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

DashboardSummary.java

DashboardSummary的onCreateView方法加载了R.layout.dashboard,代码如下:



        


接下来将是重点,开始真正加载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这个View就是整个界面的总View
        mDashboard.removeAllViews();
(1)这里调用SettingActivity的getDashboardCategories,也就是加载整个Setting的内容
        List categories =
                ((SettingsActivity) context).getDashboardCategories(true);

        final int count = categories.size();

        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的buildDashboardCategories方法,


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

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


以下为Setting页面的xml文档:







    
    

        
        

        
        

        
        

        
        

        
        
            
        

        
        

    

    
    

        
        
            
         

        
        

        
        

                
        
               
        
        

        
        
        

        
        

        
        

        
        

        
        


        
        

        
        

        
        
            
        

    

    
    

        
        

        
        

        
        

        
        

        
        
        
        
            
        

    


    

    
    

        
        

        
        

        
        

        
        

        
        

    


根据这个文件可看出来,dashboard-categories这个标签对应着Java代码中的List集合,dashboard-category这个标签对应着DashboardCategory类,dashboard-tile这个标签对应着DashboardTile这个类。


(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);
        }
    }
}
 
  
最终启动不同的Setting子模块, 至此整个Setting模块的整体框架就已分析结束了。


总结一下:

1、整个Setting模块是在SettingActivity中加载DashboardSummary这个Fragment,然后从dashboard_categories.xml中读取预先配置好的文件来初始化Settings的首界面视图。

2、Setting的子界面基本上都是一个Fragment,并且基本上都是通过SettingActivity的OnCreate方法去加载的,同时大部分SubSetting在加载界面时,用的都是PreferenceFragment技术(在以后的博客中会讲述)。

3、Android5.1的Setting使用的是DashboardCategory和DashboardTile类来存储整个xml数据结构。

你可能感兴趣的:(Android底层分析)