概述
android 6.0 版本的设置代码相比4.4就没有那么直白,但仍然可以用启动activity,界面布局,逻辑控制流程的顺序来阅读理解。
- 入口
导入Settings源码同样以搜索android.intent.category.LAUNCHER的方式,可以找到启动activity名为Settings,打开代码:
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 */ }
这里可以看到与4.4的不同,即通过常规启动activity形式找到的Settings并不是所熟悉的包含onCreate等生命周期,xml布局文件,一些控制逻辑这样形式的类,其主体定义的都是一些空实现的静态内部类,但这里有个比较明显的继承关系来自
SettingsActivity,其实从命名也可以猜测得到,这个类才是需要阅读理解的所在,打开查看:
public class SettingsActivity extends Activity
这就来到了熟悉的标准activity模式,这里同样结合运行界面找一下布局实现,自然先从onCreate开始:
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
当以点击Launcher图标的形式进来,这里的mIsShowingDashboard为true,即这个主activity引用的布局文件为 R.layout.settings_main_dashboard:
<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"
/>
这里可以看到这个主布局,并没有太多确切内容,继续看可以发现:
if (!mIsShowingDashboard) {
mDisplaySearch = false;
// UP will be shown only if it is a sub settings
if (mIsShortcut) {
mDisplayHomeAsUpEnabled = isSubSettings;
} else if (isSubSettings) {
mDisplayHomeAsUpEnabled = true;
} else {
mDisplayHomeAsUpEnabled = false;
}
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true, false,
mInitialTitleResId, mInitialTitle, false);
} 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);
}
之前提过mIsShowingDashboard为true,这里会走下面的分子,而switchToFragment运用的是FragmentTransaction替换Fragment的形式来填入实际内容,相信凡是接触过android应用开发对fragment的使用都不陌生,而这里正是标准的activity对应一个空的父布局,然后具体内容由具体的fragment填充;
这里不得不说android源码时常有让人无法理解的冗长调用关联,一个简单的功能能秀一二十个类或lib的调用,但又时常能发现这种最为基础,google大神程序员竟然也写了和自己这种初学者类似的代码而欣喜。
这里打开DashboardSummary,这里的流程又夹杂了一些调用,但仍然是从各个生命周期来分析,并且优先关注onCreateView界面到底如何实现,流程如下:
- onCreateView里主要是对整个大框架的定义,关键内容为mDashboard;
- 然后在onResume()找到了比较明显的sendRebuildUI()方法;
- 发现其实也就是经过handle机制执行到了rebuildUI(context);
而看到rebuildUI的代码便豁然开朗:
List<DashboardCategory> categories =
((SettingsActivity) context).getDashboardCategories(true);
关键就在这里获取了具体的DashboardCategory列表,也就是主界面一项项功能信息的内容,后续便是简单的循环解析,填充view,生成界面,而这里的来源竟然还是在SettingsActivity中:
getDashboardCategories > buildDashboardCategories:
private void buildDashboardCategories(List<DashboardCategory> categories) {
categories.clear();
loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
updateTilesList(categories);
}
到这里就可以知道具体的功能项信息由loadCategoriesFromResource 以XML解析的形式将R.xml.dashboard_categories内容封装到列表变量中,而下面的 updateTilesList(categories)就是4.4也有的针对内容在某些条件下移除一些选项;
对R.xml.dashboard_categories:
<dashboard-categories
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 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"
/>
<!-- 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"
/>
这就是和4.4的header类似的控件了,依然是每项对应一个功能项,并标有fragment属性,最终在DashboardSummary中封装具体内容,并添加到SettingsActivity的布局中去。
- 点击事件
可以发现在SettingsActivity以及DashboardSummary中都未发现明显的点击事件实现,那么点击每一项是如何进入子功能界面的呢;
其实在DashboardSummary的rebuildUI中可以发现,拿到List后,最终的一个个功能子项为DashboardTileView对象,这里打开其代码:
public class DashboardTileView extends FrameLayout implements View.OnClickListener {
可以发现其继承基本的FrameLayout,并清楚写明了点击事件调用到Utils.startWithFragment,最终即封装带有fragment信息的intent启动SubSettings,这也就是为什么每个功能项点击进入的都是SubSettings,而具体内容就根据fragment信息来实现。
3.定制化
常见的定制化即增删功能项,如果熟悉了以上流程,那么对于删减功能项就有太多的方式,从布局文件,到解析,到封装到列表,到updateTilesList,到addView,整个流程任何地方进行一下截断,添加个if之类的就可以去掉,这里不一一列出;
如果要增加一个功能项,这里同样类似于4.4如果要加一个自动开关机功能上去,那么就可以:
- 布局文件中dashboard_categories.xml中:
2. <!-- Schedule Power Alarm -->
3. <dashboard-tile
4. android:id="@+id/schedule_power_settings"
5. android:title="@string/schedule_power_settings_title"
6. android:fragment="com.android.settings.schedulePower.SchedulePowerSettings"
7. android:icon="@drawable/ic_settings_display"
8. />
<!-- About Device -->
<dashboard-tile
这里对应的资源文件另外添加,fragment可以先建一个空的类不写逻辑;
2.SettingsActivity加上新加的id以及fragment类名:
@@ -277,6 +278,7 @@ public class SettingsActivity extends Activity
R.id.accessibility_settings,
R.id.print_settings,
R.id.nfc_payment_settings,
1. R.id.schedule_power_settings,
R.id.home_settings,
R.id.dashboard
};
@@ -354,6 +356,7 @@ public class SettingsActivity extends Activity
ProcessStatsSummary.class.getName(),
DrawOverlayDetails.class.getName(),
WriteSettingsDetails.class.getName(),
2. SchedulePowerSettings.class.getName()
};
这样只要图片字符串资源都有定义,跳转的fragment不为空,编译apk push到机器后,即可看见新加的功能项,点击进入空的fragment,之后再具体完善逻辑功能即可。