目录
源码:Android 6.0
应用:Settings
目录:android-6.0.0_r1\packages\apps\Settings
Android 中Settings的代码很多,我这里只是简单分析一下,仅供参考。如果要深入了解设置,自己要多看看源码。
一般分析应用的话会先看其AndroidManifest.xml文件,这里会展示四大组件,默认启动入口Activity等信息。
好了,原归正传,我们开始Settings源码分析流程。
1、AndroidManifest.xml
撇开其他的,我们先找到Settings的入口函数的配置(看红色字体)
<activity
android:name="Settings"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask"
android:taskAffinity="com.android.settings" >
......
</activity>
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias
android:name="Settings"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask"
android:targetActivity="Settings"
android:taskAffinity="com.android.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>
关于activity-alias的讲解可以看我摘抄的文章《[摘]activity-alias详解及应用》,这里就不再细说。
恩恩,我们知道入口的Activity是“Settings”
2、Settings
public class Settings extends SettingsActivity {
public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ }
public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
......
}
Settings继承于SettingsActivity ,而且其内部都是类的空实现。
因此我们看其父类SettingsActivity
3、SettingsActivity
SettingsActivity是实现于Activity,并且核心功能都在这个类中。
public class SettingsActivity extends Activity{
......
}
既然是继承于Activity,那么我们就看其的生命周期流程。
3.1、SettingsActivity.onCreate()
PS:可以看英文注释,有些解释很清楚的
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
// Should happen before any call to getIntent()
【获取AndroidManiFest.xml中mate数据,从Settings(或桌面快捷图标)进入时获取是null】
getMetaData();
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
}
mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
Context.MODE_PRIVATE);
// Getting Intent properties can only be done after the super.onCreate(...)
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
【获取intent中的包名和类名】
final ComponentName cn = intent.getComponent();
final String className = cn.getClassName();
【从Settings从快捷键点击进入返回true】
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 = this instanceof SubSettings ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false);
// If this is a sub settings, then apply the SubSettings Theme for the ActionBar content insets
if (isSubSettings) {
// Check also that we are not a Theme Dialog as we don't want to override them
final int themeResId = getThemeResId();
if (themeResId != R.style.Theme_DialogWhenLarge &&
themeResId != R.style.Theme_SubSettingsDialogWhenLarge) {
setTheme(R.style.Theme_SubSettings);
}
}
【mIsShowingDashboard 是 ture】
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
mContent = (ViewGroup) findViewById(R.id.main_content);
【设置Fragment添加监听(onBackStackChanged)】
getFragmentManager().addOnBackStackChangedListener(this);
if (mIsShowingDashboard) {
// Run the Index update only if we have some space
if (!Utils.isLowStorage(this)) {【判断是否是低内存】
Index.getInstance(getApplicationContext()).update();
} else {
Log.w(LOG_TAG, "Cannot update the Indexer as we are running low on storage space!");
}
}
【第一次进入savedState为null的】
if (savedState != null) {
// We are restarting from a previous saved state; used that to initialize, instead
// of starting fresh.
mSearchMenuItemExpanded = savedState.getBoolean(SAVE_KEY_SEARCH_MENU_EXPANDED);
mSearchQuery = savedState.getString(SAVE_KEY_SEARCH_QUERY);
setTitleFromIntent(intent);
ArrayList<DashboardCategory> categories =
savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
if (categories != null) {
mCategories.clear();
mCategories.addAll(categories);
setTitleFromBackStack();
}
mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);
mDisplaySearch = savedState.getBoolean(SAVE_KEY_SHOW_SEARCH);
mHomeActivitiesCount = savedState.getInt(SAVE_KEY_HOME_ACTIVITIES_COUNT,
1 /* one home activity by default */);
} else {【第一次进来走这里,mIsShowingDashboard为true】
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;
【3.2 switchToFragment就是切换到DashboardSummary】
switchToFragment(DashboardSummary.class.getName(), null, false, false,
mInitialTitleResId, mInitialTitle, false);
}
}
......
}
3.2 switchToFragment
这里是更具传入参数切换对应的Fragment,上面我们传入的是DashboardSummary
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;
}
4、DashboardSummary
DashboardSummary也就是一个Fragment,因此我们看看其的生命周期的几个方法。
public class DashboardSummary extends InstrumentedFragment {
......
}
public abstract class InstrumentedFragment extends PreferenceFragment {
......
}
public abstract class PreferenceFragment extends Fragment implements
PreferenceManager.OnPreferenceTreeClickListener {
}
4.1、DashboardSummary.onCreateView()
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mLayoutInflater = inflater;
【加载一个布局dashboard】
final View rootView = inflater.inflate(R.layout.dashboard, container, false);
mDashboard = (ViewGroup) rootView.findViewById(R.id.dashboard_container);
return rootView;
}
4.2、DashboardSummary.onCreateView()
@Override
public void onResume() {
super.onResume();
【4.3 重新更新UI布局】
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);
}
4.3、重新更新UI布局(DashboardSummar.sendRebuildUI())
通知更新UI,重点是调用rebuildUI()方法
private void sendRebuildUI() {
if (!mHandler.hasMessages(MSG_REBUILD_UI)) {
mHandler.sendEmptyMessage(MSG_REBUILD_UI);
}
}
private static final int MSG_REBUILD_UI = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REBUILD_UI: {
final Context context = getActivity();
rebuildUI(context);
} break;
}
}
};
4.4、DashboardSummar.rebuildUI()
private void rebuildUI(Context context) {
......
long start = System.currentTimeMillis();
final Resources res = getResources();
mDashboard.removeAllViews();
【解析xml中的item值,具体看SettingsActivity.getDashboardCategories(boolean)方法】
List<DashboardCategory> categories =
((SettingsActivity) context).getDashboardCategories(true);
//后面是对解析的数据进行更新UI界面,具体就不看了
......
}
5、SettingsActivity.getDashboardCategories()
public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
if (forceRefresh || mCategories.size() == 0) {
buildDashboardCategories(mCategories);
}
return mCategories;
}
private void buildDashboardCategories(List<DashboardCategory> categories) {
【先清空】
categories.clear();
【loadCategoriesFromResource加载dashboard_categories中的数据】
loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
【6、更新categories到界面上】
updateTilesList(categories);
}
至于loadCategoriesFromResource具体我这就不展示了,代码很长,有兴趣的自己去看。
dashboard_categories.xml这里有所有设置选项的配置
6、SettingsActivity.updateTilesList()
这里可以设置设置中某个categories(比如wifi、蓝牙设置)是否展示。
private void updateTilesList(List<DashboardCategory> target) {
final boolean showDev = mDevelopmentPreferences.getBoolean(
DevelopmentSettings.PREF_SHOW,
android.os.Build.TYPE.equals("eng"));
final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
final int size = target.size();
for (int i = 0; i < size; i++) {
DashboardCategory category = target.get(i);
// Ids are integers, so downcasting is ok
int id = (int) category.id;
int n = category.getTilesCount() - 1;
while (n >= 0) {
DashboardTile tile = category.getTile(n);
boolean removeTile = false;
id = (int) tile.id;
if (id == R.id.operator_settings || id == R.id.manufacturer_settings) {
if (!Utils.updateTileToSpecificActivityFromMetaDataOrRemove(this, tile)) {
removeTile = true;
}
} else if (id == R.id.wifi_settings) {
// Remove WiFi Settings if WiFi service is not available.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
removeTile = true;
}
} else if (id == R.id.bluetooth_settings) {
// Remove Bluetooth Settings if Bluetooth service is not available.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
removeTile = true;
}
} else if (id == R.id.data_usage_settings) {
// Remove data usage when kernel module not enabled
if (!Utils.isBandwidthControlEnabled()) {
removeTile = true;
}
} else if (id == R.id.battery_settings) {
// Remove battery settings when battery is not available. (e.g. TV)
if (!mBatteryPresent) {
removeTile = true;
}
} else if (id == R.id.home_settings) {
if (!updateHomeSettingTiles(tile)) {
removeTile = true;
}
} else if (id == R.id.user_settings) {
boolean hasMultipleUsers =
((UserManager) getSystemService(Context.USER_SERVICE))
.getUserCount() > 1;
if (!UserHandle.MU_ENABLED
|| !UserManager.supportsMultipleUsers()
|| Utils.isMonkeyRunning()) {
removeTile = true;
}
} else if (id == R.id.nfc_payment_settings) {
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) {
removeTile = true;
} else {
// Only show if NFC is on and we have the HCE feature
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
if (adapter == null || !adapter.isEnabled() ||
!getPackageManager().hasSystemFeature(
PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
removeTile = true;
}
}
} else if (id == R.id.print_settings) {
boolean hasPrintingSupport = getPackageManager().hasSystemFeature(
PackageManager.FEATURE_PRINTING);
if (!hasPrintingSupport) {
removeTile = true;
}
} else if (id == R.id.development_settings) {
if (!showDev || um.hasUserRestriction(
UserManager.DISALLOW_DEBUGGING_FEATURES)) {
removeTile = true;
}
}
if (UserHandle.MU_ENABLED && UserHandle.myUserId() != 0
&& !ArrayUtils.contains(SETTINGS_FOR_RESTRICTED, id)) {
removeTile = true;
}
if (removeTile && n < category.getTilesCount()) {
category.removeTile(n);
}
n--;
}
}
addExternalTiles(target);
}
好了,到这里就基本展示出设置的界面了。
本文参考过《Android6.0源码分析之Settings(一)》