Android5.0 Settings各个子模块跳转和布局实现

前言

今天要很任性的研究一下Android5.0中Settings子模块的跳转实现。


Settings应用的Launcher类

我们首先看一下Settings应用的Launcher类。查看package/app/Settings/AndroidManifest.xml文件:

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

可以看到,指定的Launcher类为Settings.java,其源码实现如下:


import com.android.settings.applications.AppOpsSummary;

/**
 * Top-level Settings activity
 */
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 */ }
    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 */ }
    public static class AppOpsSummaryActivity extends SettingsActivity {
        @Override
        public boolean isValidFragment(String className) {
            if (AppOpsSummary.class.getName().equals(className)) {
                return true;
            }
            return super.isValidFragment(className);
            }
    }
    public static class StorageUseActivity extends SettingsActivity { /* empty */ }
    public static class DevelopmentSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AccessibilitySettingsActivity extends SettingsActivity { /* empty */ }
    public static class CaptioningSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AccessibilityInversionSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AccessibilityContrastSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AccessibilityDaltonizerSettingsActivity extends SettingsActivity { /* empty */ }
    public static class SecuritySettingsActivity extends SettingsActivity { /* empty */ }
    public static class UsageAccessSettingsActivity extends SettingsActivity { /* empty */ }
    public static class LocationSettingsActivity extends SettingsActivity { /* empty */ }
    public static class PrivacySettingsActivity extends SettingsActivity { /* empty */ }
    public static class RunningServicesActivity extends SettingsActivity { /* empty */ }
    public static class ManageAccountsSettingsActivity extends SettingsActivity { /* empty */ }
    public static class PowerUsageSummaryActivity extends SettingsActivity { /* empty */ }
    public static class BatterySaverSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AccountSyncSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AccountSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AccountSyncSettingsInAddAccountActivity extends SettingsActivity { /* empty */ }
    public static class CryptKeeperSettingsActivity extends SettingsActivity { /* empty */ }
    public static class DeviceAdminSettingsActivity extends SettingsActivity { /* empty */ }
    public static class DataUsageSummaryActivity extends SettingsActivity { /* empty */ }
    public static class AdvancedWifiSettingsActivity extends SettingsActivity { /* empty */ }
    public static class SavedAccessPointsSettingsActivity extends SettingsActivity { /* empty */ }
    public static class TextToSpeechSettingsActivity extends SettingsActivity { /* empty */ }
    public static class AndroidBeamSettingsActivity extends SettingsActivity { /* empty */ }
    public static class WifiDisplaySettingsActivity extends SettingsActivity { /* empty */ }
    public static class DreamSettingsActivity extends SettingsActivity { /* empty */ }
    public static class NotificationStationActivity extends SettingsActivity { /* empty */ }
    public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
    public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
    public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
    public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
    public static class TrustedCredentialsSettingsActivity extends SettingsActivity { /* empty */ }
    public static class PaymentSettingsActivity extends SettingsActivity { /* empty */ }
    public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
    public static class PrintJobSettingsActivity extends SettingsActivity { /* empty */ }
    public static class ZenModeSettingsActivity extends SettingsActivity { /* empty */ }
    public static class NotificationSettingsActivity extends SettingsActivity { /* empty */ }
    public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
    public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
    public static class OtherSoundSettingsActivity extends SettingsActivity { /* empty */ }
    public static class QuickLaunchSettingsActivity extends SettingsActivity { /* empty */ }

    public static class TopLevelSettings extends SettingsActivity { /* empty */ }
    public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
    /**M: Add new for settings activity @{*/
    public static class HDMISettingsActivity extends SettingsActivity { /* empty */ }
    /**@}*/
}

从源码我们发现,代码里定义了大量的静态内部类,却没有任何跟界面显示相关的内容。

这时候我们不禁会有疑问了,如果我要跳转到Wifi的界面,从源码看应该是WifiDisplaySettingsActivity类,应该如何跳转呢?


Settings子界面跳转实现

我们以Wifi界面为例,首先看一下其在AndroidManifest.xml中是如何定义的?

        <activity android:name="Settings$WifiSettingsActivity"
                android:taskAffinity=""
                android:label="@string/wifi_settings"
                android:configChanges="orientation|keyboardHidden|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.settings.WIFI_SETTINGS" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.VOICE_LAUNCH" />
                <category android:name="com.android.settings.SHORTCUT" />
            intent-filter>
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.wifi.WifiSettings" />
            <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
                android:resource="@id/wifi_settings" />
        activity>

定义的还是蛮复杂的,那我们先写一个简单的demo,通过隐式的Intent看看能不能调起wifi的界面来。

Tips:
为什么不采用显示Intent的方法?是因为Settings的子类我们调用的类一般都不位于同一个包中,所以一般不采用显示调用的方法。

    private void startWifiActivity() {
        try {
            Intent intent = new Intent("android.settings.WIFI_SETTINGS");
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

可以看到,我们通过发送一个”android.settings.WIFI_SETTINGS”就已经将Wifi设置界面调度起来了,但是我们之前看SettingsActivity的源码中:

    public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }

WifiSettingsActivity的具体实现是空的,那布局到底是如何被渲染出来的呢?我们不禁想到应该是SettingsActivity做了一些手脚。

因为所有的Settings子界面类都是继承自SettingsActivity类,那就让我们以wifi为例,看一下这个SettingsActivity是如何能够正确呈现每个Settings子类的。


SettingsActivity

在研究SettingsActivity.java的源码之前,让我们先回顾一下WifiSettingsActivity在AndroidManifest.xml中的声明:

            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.wifi.WifiSettings" />

其中有meta-data的标签使用,从这个标签的key-value来看,很明显可以认为WifiSettings的具体实现应该是由WifiSettings这个Fragment来布局渲染的。让我们通过源码来分析一下是否是这样的。

SettingActivity的源码如下:

public class SettingsActivity extends Activity
        implements PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragment.OnPreferenceStartFragmentCallback,
        ButtonBarHandler, FragmentManager.OnBackStackChangedListener,
        SearchView.OnQueryTextListener, SearchView.OnCloseListener,
        MenuItem.OnActionExpandListener {
        // ......
}

既然SettingsActivity继承自Activity,我们直接从onCreate函数入手就好了。onCreate函数源码如下:

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        // Should happen before any call to getIntent()
        getMetaData();
    }

这里有个获取MetaData的方法,而且通过注释我们可以得到信息:这个方法应该再调用任何getIntent()方法之前进行调用。那我们来看一下这个方法的具体实现:

private static final String META_DATA_KEY_FRAGMENT_CLASS =
    "com.android.settings.FRAGMENT_CLASS";

private String mFragmentClass;

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

可以看到,这个函数的主要作用就是从Activity标签中获取meta-data标签中key为com.android.settings.FRAGMENT_CLASS的值,并将其赋值给mFragmentClass这个私有变量。
以WifiSettingsActivity为例,从这个Activity中meta-data标签中获取的信息为com.android.settings.wifi.WifiSettings,即mFragmentClass=”com.android.settings.wifi.WifiSettings”。

既然mFragmentClass已经指定为具体Fragment的路径,那我们回到onCreate函数,看一下activity是如何加载fragment的。

protected void onCreate(Bundle savedState) {
    final Intent intent = getIntent();
}

上面注释说getMetaData()要在getIntent()函数之前执行,那我们就看一下getIntent的实现,来了解为什么会有这样的规定。getIntent源码如下:

    @Override
    public Intent getIntent() {
        Intent superIntent = super.getIntent();
        // 处理个别特殊情况,本例就是将mFragmentClass赋值给了startingFragment
        String startingFragment = getStartingFragmentClass(superIntent);
        // This is called from super.onCreate, isMultiPane() is not yet reliable
        // Do not use onIsHidingHeaders either, which relies itself on this method
        if (startingFragment != null) {
            Intent modIntent = new Intent(superIntent);
            // 将startingFragment放入intent的以EXTRA_SHOW_FRAGMENT(":settings:show_fragment")为key的键值对中。
            modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
            Bundle args = superIntent.getExtras();
            if (args != null) {
                args = new Bundle(args);
            } else {
                args = new Bundle();
            }
            args.putParcelable("intent", superIntent);
            modIntent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
            return modIntent;
        }
        return superIntent;
    }

    private String getStartingFragmentClass(Intent intent) {
        if (mFragmentClass != null) return mFragmentClass;

        String intentClass = intent.getComponent().getClassName();
        if (intentClass.equals(getClass().getName())) return null;

        if ("com.android.settings.ManageApplications".equals(intentClass)
                || "com.android.settings.RunningServices".equals(intentClass)
                || "com.android.settings.applications.StorageUse".equals(intentClass)) {
            // Old names of manage apps.
            intentClass = com.android.settings.applications.ManageApplications.class.getName();
        }

        return intentClass;
    }

从源码看以看出,getIntent的作用就是构造了一个Intent,并且给它增加了一个特殊的键值对,key为”:settings:show_fragment”,value为mFragmentClass指定的Fragment类名。
之所以要先执行getMetaData,是因为mFragmentClass赋值是在getMeatData中进行的。

分析了getIntent,我们继续来看onCreate函数,看看这个intent到底是如何被使用的。

protected void onCreate(Bundle savedState) {
    final ComponentName cn = intent.getComponent();
    final String className = cn.getClassName(); // 本例中,className为WifiSettingsActivity

    mIsShowingDashboard = className.equals(Settings.class.getName()); // 该值为false
    setContentView(mIsShowingDashboard ?
        R.layout.settings_main_dashboard : R.layout.settings_main_prefs); // 由于mIsShowingDashboard为false,所以WifiSettingsActivity加载的布局为R.layout.settings_main_prefs
    mContent = (ViewGroup) findViewById(R.id.main_content); // 获取承载Fragment的ViewGroup
}

我们来看一下R.layout.settings_main_prefs布局实现:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_height="match_parent"
              android:layout_width="match_parent">

    <LinearLayout
            android:orientation="vertical"
            android:layout_height="0px"
            android:layout_width="match_parent"
            android:layout_weight="1">

        <com.android.settings.widget.SwitchBar android:id="@+id/switch_bar"
                  android:layout_height="?android:attr/actionBarSize"
                  android:layout_width="match_parent"
                  android:background="@drawable/switchbar_background"
                  android:theme="?attr/switchBarTheme"
                />

        <FrameLayout
                android:id="@+id/main_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#ff000000"
                />

    LinearLayout>

    <RelativeLayout android:id="@+id/button_bar"
                    android:layout_height="wrap_content"
                    android:layout_width="match_parent"
                    android:layout_weight="0"
                    android:visibility="gone">

        <Button android:id="@+id/back_button"
                android:layout_width="150dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:layout_alignParentStart="true"
                android:text="@*android:string/back_button_label"
                />

        <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true">

            <Button android:id="@+id/skip_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/skip_button_label"
                    android:visibility="gone"
                    />

            <Button android:id="@+id/next_button"
                    android:layout_width="150dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@*android:string/next_button_label"
                    />

        LinearLayout>

    RelativeLayout>

LinearLayout>

布局结构还是比较清晰的,我们继续研究onCreate方法。

protected void onCreate(Bundle savedState) {
    if (savedState != null) {
        // 这里为null,只需要关注else实现即可
    } else {
        if (!mIsShowingDashborad) {
            // 之前分析过本例中mIsShowingDashborad为true,所以看这个分支流程
            // 获取标题
            setTitleFromIntent(intent);
            // 这里给initialArguments赋值为之前设置的mFragmentClass的值
            Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
            switchToFragment(initialFragmentName, initialArguments, true, false,
                mInitialTitleResId, mInitialTitle, false);
        }
    }
}

从上面的源码,我们终于找到了关键部分,initialArguments通过赋值保存了meta-data中指定的com.android.settings.wifi.WifiSettings,我们就看一下switchToFragment的实现。

   /**
     * 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) {
        // 判断fragment是否是合法的类
        if (validate && !isValidFragment(fragmentName)) {
            throw new IllegalArgumentException("Invalid fragment for this activity: "
                    + fragmentName);
        }
        // 实例化fragment
        Fragment f = Fragment.instantiate(this, fragmentName, args);
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        // 通过FragmentTransaction的replace方法,讲Fragment的布局在R.id.main_content指定的位置进行渲染
        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;
    }

好了,通过上述分析,相信大家已经清楚如何通过隐式的intent跳转到其对应的Activity布局了。


总结

通过上面的分析,我们应该了解的事情是,AndroidManifest中每个Activity其meta-data中的数据都是很有用的,特别是com.android.settings.FRAGMENT_CLASS对应的键值对,指定了其所在Activity的真正布局实现。

其实,也是完成了从其他包的Activity向Settings中fragment的跳转实现。

你可能感兴趣的:(Android操作系统)