Android 6.0 Settings--设置主页加载流程

Android 6.0 Settings–设置主页加载流程

声明

郑重声明:博文为原创内容,可以转载或引用,但必须在明显位置标明原文作者和出处,未经同意不得擅自修改本文内容!

博客地址:http://blog.csdn.net/luzhenrong45


代码环境

以下博文内容基于Android 6.0 Setting原生代码

Settings

Settings也即大家经常用到的设置应用,每一个Android设备都会预置Settings.apk(MTK平台重命名为MtkSettings.apk),一般预置在/system/priv-app/目录下。Settings 一般是作为系统全局类功能设置和信息展示的服务类应用,,并对外提供多种功能入口,比如网络设置、铃声设置、日期设置、手机信息显示等等,部分功能入口支持从第三方应用跳转进入。这里从代码角度记录下,从启动设置应用到显示一级UI页面的大致加载流程。

首先看下原生设置启后动的一级主页样貌:
settings -note

图示设置主页加载流程

先以一张图概述整个设置主页的加载流程,下面再通过文字形式对这个过程进行学习记录。

设置加载主页流程-qq

文解设置主页加载流程

设置入口

查看AndroidMenufest.xml找到入口,Settings对应主Activity入口为Settings

【Settings/AndroidMenufest.xml】

        <!-- Alias for launcher activity only, as this belongs to each profile. -->
        <activity-alias android:name="Settings"
                android:taskAffinity="com.android.settings"
                android:label="@string/settings_label_launcher"
                android:launchMode="singleTask"
                android:targetActivity="Settings">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>

Settings继承自SettingsActivity, 里面只是声明了一系列内部静态类,这些内部静态类对应着各个功能项的设置入口,除此之外Setting并没有做其他工作。因此,设置的启动加载实际是在SettingsActivity里面实现的。

【Settings/Settings.java】

public class Settings extends SettingsActivity {

    /*
    * Settings subclasses for launching independently.
    */
    public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
    public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ }
    public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
    public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
    public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
    public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
    
    .......
}

设置主页加载

SettingsActivity

SettingsActivity继承自Activity,并实现多种接口, 可以说是整个Settings最为核心的一个类,设置里面绝大多数的页面从SettingsActivity衍生或跳转,包括今天负责主页页面加载显示的DashboardSummary。

【Settings/SettingsActivity.java】

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);
      
        // Should happen before any call to getIntent()
        // 读取是否有配置好的meta数据,进入主页这里返回是null
        getMetaData();

        final Intent intent = getIntent();
       
        // Getting Intent properties can only be done after the super.onCreate(...)
        final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);

        final ComponentName cn = intent.getComponent();
        final String className = cn.getClassName();

        //mIsShowingDashboard这个变量是关键,决定是否显示Dashboard主页,
        //前面我们说到设置的入口Activity就是Settings.class,因此如果是正常启动设置,这里会是true
        mIsShowingDashboard = className.equals(Settings.class.getName());

        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) {
            
                Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
                switchToFragment(initialFragmentName, initialArguments, true, false, mInitialTitleResId, mInitialTitle, false);
            } else {
               //mIsShowingDashboard为true,因此会跳转到 DashboardSummary 中
                mInitialTitleResId = R.string.dashboard_title;
                switchToFragment(DashboardSummary.class.getName(), null, false, false, mInitialTitleResId, mInitialTitle, false);
            }
        }
        
       ......
    }

SettingsActivity的onCreate的函数里面会获取和初始化很多相关变量,其中我们比较关注的是mIsShowingDashboard这个布尔型变量,从变量名称不难判断,这个是代表是否显示Dashboard主页的。如果该变量是true,则下面会通过 Fragment 常见的replace方式,跳转到DashboardSummary中。

【Settings/SettingsActivity.java】

    switchToFragment(DashboardSummary.class.getName(), null, false, false,
                        mInitialTitleResId, mInitialTitle, false);

    /**
     * Switch to a specific Fragment with taking care of validation, Title and BackStack
     */
    private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,
            boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {
        if (validate && !isValidFragment(fragmentName)) {
            throw new IllegalArgumentException("Invalid fragment for this activity: "
                    + fragmentName);
        }
        Fragment f = Fragment.instantiate(this, fragmentName, args);
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        transaction.replace(R.id.main_content, f);
        if (withTransition) {
            TransitionManager.beginDelayedTransition(mContent);
        }
        if (addToBackStack) {
            transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);
        }
        if (titleResId > 0) {
            transaction.setBreadCrumbTitle(titleResId);
        } else if (title != null) {
            transaction.setBreadCrumbTitle(title);
        }
        transaction.commitAllowingStateLoss();
        getFragmentManager().executePendingTransactions();
        return f;
    }

另外,与主页加载相关的,SettingsActivity还为后续 的DashboardSummary做了dashboard_categories.xml的解析工作:

先看一下dashboard_categories.xml的内容和格式:

【Settings/res/xml/dashboard_categories.xml】

<dashboard-categories
        xmlns:android="http://schemas.android.com/apk/res/android">

    //dashboard-category代表一个大的功能分区
    <!-- WIRELESS and NETWORKS -->
    <dashboard-category
            android:id="@+id/wireless_section"
            android:key="@string/category_key_wireless"
            android:title="@string/header_category_wireless_networks" >

        <!-- Wifi -->
        <dashboard-tile
                android:id="@+id/wifi_settings"
                android:title="@string/wifi_settings_title"
                android:fragment="com.android.settings.wifi.WifiSettings"
                android:icon="@drawable/ic_settings_wireless"
                />
                
        ...... //dashboard-tile代表某个具体功能项,如蓝牙,wifi,数据等
        
        <!-- Bluetooth -->
        <dashboard-tile
                android:id="@+id/bluetooth_settings"
                android:title="@string/bluetooth_settings_title"
                android:fragment="com.android.settings.bluetooth.BluetoothSettings"
                android:icon="@drawable/ic_settings_bluetooth"
                />
    </dashboard-category>
    
    ...... //其他分区和子项类同
    
</dashboard-categories>

说明:

  • 一个dashboard-categories里面包含多个dashboard-category
  • 一个dashboard-category里面包含多个dashboard-tile
  • 每一个dashboard-category代表一个大的分区,原生设置有四大区,分别为“无线和网络”、“设备”、“个人”、“系统”。无线与网络区里面会包含蓝牙,wifi,数据等多个子项
  • 每一个dashboard-tile代表一个具体的功能项入口,如蓝牙、wifi、数据等

dashboard_categories.xml可以理解为设置主页的“layout”,只不过它并非我们常见的布局文件的格式,需要对其进行解析。而提此重任的即是SettingsActivity,SA通过pull的方式对xml文件内容进行解析,将解析的结果保存在类型为DashboardCategory的List列表中,返回给后面的DashboardSummary调用。

【Settings/SettingsActivity.java】

    public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
        if (forceRefresh || mCategories.size() == 0) {
            buildDashboardCategories(mCategories);
        }
        return mCategories;
    }
    
    private void buildDashboardCategories(List<DashboardCategory> categories) {
        categories.clear();
        //解析dashboard_categories.xml文件
        loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
        updateTilesList(categories);
    }
    
    

xml解析方式是常用的pull方式:

小Q截图-20200310162029
小Q截图-20200310161905

DashboardSummary

DashboardSummary主要有以下几个作用:

  1. 获取SettingsActivity对dashboard_categories.xml的解析结果
  2. 根据获取的DashboardCategory列表,依次加载显示各个列表项,形成设置一级页面
  3. 注册Package广播接收器,监听Package移除、更新、改变等状态变化,更新UI页面

【Settings/DashboardSummary.java】

    //负责更新加载主页UI
    private void rebuildUI(Context context) {
        ......

        mDashboard.removeAllViews();

        //获取SettingsActivity对dashboard_categories.xml的解析结果
        List<DashboardCategory> categories =
                ((SettingsActivity) context).getDashboardCategories(true);

        //获取DashboardCategory个数,原生代码有四大区
        final int count = categories.size();

        //依次对解析的DashboardCategory进行处理,添加子view
        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);

                DashboardTileView tileView = new DashboardTileView(context);
                updateTileView(context, res, tile, tileView.getImageView(),
                        tileView.getTitleTextView(), tileView.getStatusTextView());

                tileView.setTile(tile);

                categoryContent.addView(tileView);
            }

            // Add the category
            //将处理好的DashboardCategory添加到Dashboard主页中来
            mDashboard.addView(categoryView);
        }
        long delta = System.currentTimeMillis() - start;
        Log.d(LOG_TAG, "rebuildUI took: " + delta + " ms");
    }

下面是注册监听package的状态变化,及时更新UI页面。

【Settings/DashboardSummary.java】

    private class HomePackageReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            //监听状态变化,更新UI
            rebuildUI(context);
        }
    }
    private HomePackageReceiver mHomePackageReceiver = new HomePackageReceiver();
    
    @Override
    public void onResume() {
        super.onResume();

        sendRebuildUI();

        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        filter.addDataScheme("package");
        getActivity().registerReceiver(mHomePackageReceiver, filter);
    }
    
    

至此,设置的一级页面就加载显示出来了。


修改说明

作者 版本 修改时间 修改说明
WalkAloner V1.0 2020/03/11 第一版
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章