前言

今天要很任性的研究一下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 {@Overridepublic 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 Activityimplements PreferenceManager.OnPreferenceTreeClickListener,PreferenceFragment.OnPreferenceStartFragmentCallback,ButtonBarHandler, FragmentManager.OnBackStackChangedListener,SearchView.OnQueryTextListener, SearchView.OnCloseListener,MenuItem.OnActionExpandListener {// ......
}

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

    @Overrideprotected 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 recoveryLog.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源码如下:

    @Overridepublic Intent getIntent() {Intent superIntent = super.getIntent();// 处理个别特殊情况,本例就是将mFragmentClass赋值给了startingFragmentString startingFragment = getStartingFragmentClass(superIntent);// This is called from super.onCreate, isMultiPane() is not yet reliable// Do not use onIsHidingHeaders either, which relies itself on this methodif (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为WifiSettingsActivitymIsShowingDashboard = className.equals(Settings.class.getName()); // 该值为falsesetContentView(mIsShowingDashboard ?R.layout.settings_main_dashboard : R.layout.settings_main_prefs); // 由于mIsShowingDashboard为false,所以WifiSettingsActivity加载的布局为R.layout.settings_main_prefsmContent = (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);}// 实例化fragmentFragment 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的跳转实现。

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

  1. android 5.0 设置铃声,android5.0联系人铃声设置和来电读取分析

    android5.0联系人铃声设置和来电读取 一,单个联系人的铃声设置流程 1,联系人编辑界面下菜单设置来电铃声,会弹出一个ringtone列表供用户选择. 从ContactEditorFragmen ...

  2. Android5.0,6.0,7.0,8.0新特性整理

    背景 Android5.0(Android Lollipop)是谷歌公司2014年10月发布的全新安卓系统,至今已经两年多.然而由于国产手机对安卓ROM的深度定制或修改,以及手机厂商.芯片制造商.运营 ...

  3. android5.0及以上版本的新特性

    android5.0及以上版本的新特性 Android5.0 Android6.0 Android7.0 Android8.0 Android9.0 Android5.0 Android 5.0 除了 ...

  4. Android5.0,6.0,7.0新特性整理

    背景 Android5.0(Android Lollipop)是谷歌公司2014年10月发布的全新安卓系统,至今已经两年多.然而由于国产手机对安卓ROM的深度定制或修改,以及手机厂商.芯片制造商.运营 ...

  5. Android5.0 6.0 7.0新特性

    原文链接:http://blog.csdn.net/haovip123/article/details/54618642 背景 Android5.0(Android Lollipop)是谷歌公司201 ...

  6. android5.0联系人铃声设置和来电读取分析

    android5.0联系人铃声设置和来电读取 一,单个联系人的铃声设置流程 1,联系人编辑界面下菜单设置来电铃声,会弹出一个ringtone列表供用户选择. 从ContactEditorFragmen ...

  7. Android5.0源码分析—— Zygote进程分析

    1      Zygote简介 Android的应用程序一般都是由Java语言编写而成的,这样的应用程序需要运行在独自的Dalvik虚拟机之上(当然,5.0好像默认了ART了).但是,如果在每一个进程 ...

  8. Notification之 - Android5.0实现原理(二)

    概述 前文讲解了Notification的构造,现在来讲讲notification的发送,以及公布前文留下的疑问(自定义view不论高度是多高,最后只能显示为64dp,why?) Notificati ...

  9. android5.1 显示方向,Android5.1 Settings.apk定制显示选项

    在Android5.0后,系统应用的目录结构发生了一些变化,以往/system/app/下直接是APK文件,目前是/system/app/应用名目录/应用apk类似这种目录结构.同时在Android5 ...

最新文章

  1. python恶搞程序-愚人节恶搞程序源码【两种语言】
  2. android多行文本框hint居中,在安卓等移动浏览器中placeholder中的文字不垂直居中问题...
  3. bzoj 4880 [Lydsy1705月赛]排名的战争 贪心
  4. 北京 | 蚂蚁集团共享智能团队招聘研究实习生
  5. LWIP初体验-修改ST官方demo
  6. 传高盛与德劭前合伙人组5亿美元私募基金
  7. 写在通用权限管理系统销售200套,从刚开始求人家用到人家主动索取,写一下亲身感受...
  8. 遊戲是這樣寫成的 (第三篇: 簡單的遊戲框架)
  9. 【设计模式】组合模式 Composite Pattern
  10. udp端口转发 Linux,Linux iptables 端口转发
  11. TFT-ST7789 方向调整
  12. Win10系统电脑开机后显示无法登录到你的账户解决办法(亲测)
  13. 机器学习算法(8)之多元线性回归分析理论详解
  14. Factory IO的应用(一)
  15. ES6新特性:解构、对象扩展、函数扩展、数组扩展、数值扩展
  16. Pytho 常见模块 / 用法备忘录
  17. 鼠标悬停大小缩略图片切换_3D缩略图悬停效果
  18. 微信小程序上传silk格式录音并转码为mp3
  19. 数据采集:Flume和Logstash的工作原理和应用场景
  20. qRT-PCR 注意事项

热门文章

  1. echarts 雷达图
  2. gtx1060和gtx1650 的差距 哪个好
  3. python使用matplotlib可视化、自定义设置X轴刻度标签字体的大小( setting axis ticks size in matplotlib x axis)
  4. html5 单元格宽度,html table呈现个人简历以及单元格宽度失效的问题解决
  5. android 评论功能盖楼,微信公众号留言功能升级,评论区能“盖楼”了
  6. Kaggle数据竞赛记录 - IEEE-CIS Fraud Detection
  7. 线程编程——经典案例
  8. Mac系统应用已经删除,但是右键还有残留的解决办法
  9. PMP 项目管理知识框架 - 引子
  10. tirm php,PHP中trim 会导致乱码的原因