Settings即安卓设置应用,用户使用频率较高,可根据其偏好对android系统的各项功能、属性进行个性化配置。其源码位于/packages/apps/Settings目录下。阅读一个应用首先应从其清单文件中入手,找到入口activity。我们进入Setttings的AndroidManifest.xml中:
<activity android:name="Settings"
android:taskAffinity="com.android.settings"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.settings.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
<!-- 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>
可看到内部的activity-alias标签中有android.intent.action.MAIN(即应用入口action)。此标签较为少见,通过字面意思可理解为'activity别名'。此别名一般是提供给外部环境使用的。
打个比方,在Launcher中一般都会有数据库持久化存储各个应用的启动intent,intent内部包含有我们的main activity。假设某项目之前的main activity一直是com.why.demo.MainActivity,Launcher也一直是按此名称进行本地存储intent和启动应用。但某个版本我们把入口actiivty名改为了com.why.demo.HomeActivity,当用户升级到此版本时,可能由于Launcher数据库信息未更新,导致我们启动intent还是去启动MainActiivty而造成启动失败。而我们actiivty-alias标签的使用则可避免此问题,我们在activity-alias中通过加入android.intent.category.LAUNCHER和android.intent.action.MAIN两种intent-filter标识此activity-alias标签中的name=value为应用启动类(此name不表示一个实际Actiivty),此name即为外部环境如Launcher中存储的应用启动intent内部所标识的启动activity。当系统根据此name去启动应用时如果发现name所表示的是一个activity-alias。系统会去获取activity-alias的tartgetActivity(此处才是实际启动的Activity)。这就隔离了targetActivity对外部造成影响。
再回来看我们的Settings项目的清单文件,显然启动类targetActivity即是Settings类。
进入Settings.java中,发现其为空实现类,其内部只有一些供外部环境独立调用的Activity(这些Activity也是空实现,其真正的逻辑在清单文件中的对应的meta-data的com.android.settings.FRAGMENT_CLASS所声明的fragment中)。Settings所有逻辑都在父类SettingsActivity中。
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 */ }
}
进入Settings的父类SettingsActivity中,阅读一个Activity,应从其onCreate方法中开始:
/**
* Settings入口
*
* @param savedState
*/
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
//获取当前Activity的清单文件中的meta-data,
// 在meta-data中有次activity所要打开的fragment信息,次方法必须在getIntent之前被调用
getMetaData();
//getIntent方法已被SettingsActivity重写,内部包装了fragment
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
}
//sp文件
mDevelopmentPreferences = getSharedPreferences(DevelopmentSettings.PREF_FILE,
Context.MODE_PRIVATE);
//取出intent包裹的fragment
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
//获取启动此activity的组件class名
final ComponentName cn = intent.getComponent();
final String className = cn.getClassName();
//标识是否是Settings入口打开的此activity
mIsShowingDashboard = className.equals(Settings.class.getName());
//标识是否是子Settings类。
final boolean isSubSettings = className.equals(SubSettings.class.getName()) ||
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);
}
}
//settings_main_dashboard是FrameLayout - id->main_content之后会被fragment替换
setContentView(mIsShowingDashboard ?
R.layout.settings_main_dashboard : R.layout.settings_main_prefs);
//mContent被fragment replace掉
mContent = (ViewGroup) findViewById(R.id.main_content);
//此处是当stack发生变化时
getFragmentManager().addOnBackStackChangedListener(this);
if (mIsShowingDashboard) {
Index.getInstance(getApplicationContext()).update();
}
//变量与集合初始化
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);
//此处和OnBackStackChanged异曲同工。
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 {
//saveState为空,即初次进来。
if (!mIsShowingDashboard) {//表示非Settings页面(入口页面)
// Search is shown we are launched thru a Settings "shortcut". UP will be shown
// only if it is a sub settings
if (mIsShortcut) {//快捷方式
mDisplayHomeAsUpEnabled = isSubSettings;
mDisplaySearch = false;
} else if (isSubSettings) {//如果是子页面
mDisplayHomeAsUpEnabled = true;
mDisplaySearch = true;
} else {
mDisplayHomeAsUpEnabled = false;
mDisplaySearch = false;
}
//设置title
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
//main_content替换为目标fragment
switchToFragment(initialFragmentName, initialArguments, true, false,
mInitialTitleResId, mInitialTitle, false);
} else {//表示当前实在Settings入口
// No UP affordance if we are displaying the main Dashboard
mDisplayHomeAsUpEnabled = false;//返回键
// Show Search affordance
mDisplaySearch = true;//query图标
mInitialTitleResId = R.string.dashboard_title;//Settings
//
switchToFragment(DashboardSummary.class.getName(), null, false, false,
mInitialTitleResId, mInitialTitle, false);
}
}
mActionBar = getActionBar();
if (mActionBar != null) {
mActionBar.setDisplayHomeAsUpEnabled(mDisplayHomeAsUpEnabled);
mActionBar.setHomeButtonEnabled(mDisplayHomeAsUpEnabled);
}
mSwitchBar = (SwitchBar) findViewById(R.id.switch_bar);
// see if we should show Back/Next buttons
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
View buttonBar = findViewById(R.id.button_bar);
if (buttonBar != null) {
buttonBar.setVisibility(View.VISIBLE);
Button backButton = (Button)findViewById(R.id.back_button);
backButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED, getResultIntentData());
finish();
}
});
Button skipButton = (Button)findViewById(R.id.skip_button);
skipButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_OK, getResultIntentData());
finish();
}
});
mNextButton = (Button)findViewById(R.id.next_button);
mNextButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_OK, getResultIntentData());
finish();
}
});
// set our various button parameters
if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
if (TextUtils.isEmpty(buttonText)) {
mNextButton.setVisibility(View.GONE);
}
else {
mNextButton.setText(buttonText);
}
}
if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
if (TextUtils.isEmpty(buttonText)) {
backButton.setVisibility(View.GONE);
}
else {
backButton.setText(buttonText);
}
}
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
skipButton.setVisibility(View.VISIBLE);
}
}
}
mHomeActivitiesCount = getHomeActivitiesCount();
}
上边流程分析:
1.getMetaData(),此方法是从清单文件中获取Activity的meta-data元信息。在各启动Activity中大都在meta-data中定义了此Activity所对应的fragment,getMetaData方法取出元信息中fragment并保存在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());
}
}
2.getIntent(),此方法已被SettingsActivity重写,用于处理第一步获取的fragment,设置回intent中。可通过EXTRA_SHOW_FRAGMENT获取intent中保存的fragment。
public Intent getIntent() {
Intent superIntent = super.getIntent();
//获取当前activity要打开的fragment,内部会先从清单文件的meta-data中获取
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) {//代表有需要打开的fragment
Intent modIntent = new Intent(superIntent);
//把需要打开的fragment作为extra放入intent中。
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment);
//fragment是否有提供预置参数
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);
//返回包裹后的intent
return modIntent;
}
return superIntent;
}
3.取出步骤2中保存在intent中的fragment。并根据启动Activity是否为Settings判断当前是否是dashboard界面(首页)。根据此判断设置不同的contentView,本篇文章我们仅分析首页Settings加载过程,由此可知,当前设置的layout为R.layout.settings_main_dashboard文件,此布局中仅有一个framelayout作为fragment占位容器。
<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"
android:background="@color/dashboard_background_color"
/>
4.设置fragment回退栈回退监听。在监听listener中如果发现当前有fragment被back掉,则会改变actionbar的title。各个fragment的title时存放在BackStackEntry内部的breadCrumbTitle中。
5.接下来主要是进行saveState判断是否需Activity恢复现场。当我们首次进入时进入的是else流程,在此流程中是否是Settings页面(首页)来给main_content布局填充不同的fragment。而本文讨论的首页加载则可从代码中看出,其是填充了DashboardSummary类(其实质是一个Fragment)。当然此处也对一些actionbar的控件显示作为不同区分。
上文首页Setttings页面会加载DashboardSummary到main_content上显示,我们进入DashboardSummary中,发现其继承自fragment,在onCreateView中填充了一个线性布局。当DashboardSummary执行onResume时会去rebuildUI方法,在此方法中完成对UI页面的构建。同时还监听包变化(PACKAGE_ADDED,ACKAGE_REMOVED,PACKAGE_CAHNGED,PACKAGE_REPLACED),在包变化时也会执行rebuildUI操作。
private void rebuildUI(Context context) {
//判断fragment是否已经被add
if (!isAdded()) {
Log.w(LOG_TAG, "Cannot build the DashboardSummary UI yet as the Fragment is not added");
return;
}
long start = System.currentTimeMillis();
final Resources res = getResources();
//先清空掉所有的行
mDashboard.removeAllViews();
//此处调用SettingsActivity中的方法去获取所有的行
List<DashboardCategory> categories =
((SettingsActivity) context).getDashboardCategories(true);
final int count = categories.size();
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
mDashboard.addView(categoryView);
}
long delta = System.currentTimeMillis() - start;
Log.d(LOG_TAG, "rebuildUI took: " + delta + " ms");
}
在rebuildUI中,线性布局mDashboard清空所有子控件后,会调用SettingsActivity(即此fragment依存类)的getDashboardCategories方法从xml中解析列表。把解析出来的DashboardCategory和DashboardTile(两者实质上为model对象,数据实体)对应到DashboardContainerView(容器控件,内部layout方式类似于线性布局)和DashboardTileView中去,在UI层级上来说,DashboardContainerView作为mDashboard线性布局子控件存在,DashboardTileView作为DashboardContainerView子控件存在。
进入到getDashboardCategories中根据参数和mCategories集合的size来判断是否需要从xml中解析并重构数据。
public List<DashboardCategory> getDashboardCategories(boolean forceRefresh) {
if (forceRefresh || mCategories.size() == 0) {
buildDashboardCategories(mCategories);
}
return mCategories;
}
/**
* Called when the activity needs its list of categories/tiles built.
*
* @param categories The list in which to place the tiles categories.
*/
private void buildDashboardCategories(List<DashboardCategory> categories) {
categories.clear();//首先清理集合,
loadCategoriesFromResource(R.xml.dashboard_categories, categories);//加载xml文件
updateTilesList(categories);//此处会去除一些不可用的设置项
}
可以看到loadCategoriesFromResource方法会去解析res/xml文件夹中的dashboard_categories文件,在此文件中即是首页显示的列表数据。此文件内部区分为四个大类(dashboard-category),各大类下面又分为多条数据(dashboard-tile)。
1.Wireless&Networks。无线和网络设置部分,其内部有WiFi、蓝牙、移动网络、其他等列。
2.Device。设备设置部分,其内部分为Home、显示、声音&通知、存储、电池管理、应用管理、用户管理、NFC等列。
3.Personal。个性设置部分,其内部分为位置、安全、账户、语言和输入法、备份和还原等列。
4.System。系统设置部分,其内部分为日期和时间、辅助功能、打印、开发者选项、关于手机等列。
当然,dashboard_categories中这些列也并非全部显示到了设置列表中,buidlDashboardCategories方法在加载完xml文件后,会去根据各个列表功能的可用性保留加载的数据,即updateTileList方法。
/**
* 更新category集合中的内容,主要根据SystemFeature来看当前title关联的设置是否可用
* @param target
*/
private void updateTilesList(List<DashboardCategory> target) {
final boolean showDev = mDevelopmentPreferences.getBoolean(
DevelopmentSettings.PREF_SHOW,
android.os.Build.TYPE.equals("eng"));//eng模式adb有root权限,适合开发模式,而user模式下adb只有shell权限
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) {
//WiFi可用性
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
removeTile = true;
}
} else if (id == R.id.bluetooth_settings) {
//蓝牙可用性
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
removeTile = true;
}
} else if (id == R.id.data_usage_settings) {
//移动数据管理?
final INetworkManagementService netManager = INetworkManagementService.Stub
.asInterface(ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
try {
if (!netManager.isBandwidthControlEnabled()) {
removeTile = true;
}
} catch (RemoteException e) {
// ignored
}
} 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()
&& !hasMultipleUsers)
|| Utils.isMonkeyRunning()) {
removeTile = true;
}
} else if (id == R.id.nfc_payment_settings) {//当前手机是否有nfc支付功能
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(//卡仿真技术,模拟nfc卡支付
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) {//开发者选项模式
//showDev 当前是否处于eng
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--;
}
}
}
从updateTileList中可以看见对不可用的功能,会执行remove操作。这些步骤都执行完了之后,就会回到DashboardSummary中,把这些数据一一对应到UI中显示。
至此,设置页面首页的加载流程就算结束了。