This paper mainly discusses the simple implementation of system Settings in Android 11, mainly including the following points:
- System Settings Home page
- Other pages for system Settings
- Data control
First, the system Settings home page
Android system set up the main interface is com. Android Settings. The Settings, but it is just an activity – alias, points to the homepage. SettingsHomepageActivity.
<activity-alias android:name="Settings"
android:label="@string/settings_label_launcher"
android:taskAffinity="com.android.settings.root"
android:launchMode="singleTask"
android:targetActivity=".homepage.SettingsHomepageActivity">
<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>
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity-alias>
Copy the code
Need to be aware of is the command (adb shell dumpsys “window | grep mCurrentFocus”) to see the top mobile phone activity, not print out the targetActivity, but this activity – alias.
. Homepage. Logic is not complicated in SettingsHomepageActivity directly loading the TopLevelSettings the fragments
showFragment(new TopLevelSettings(), R.id.main_content);
Copy the code
TopLevelSettings displays the list of Settings through AndroidX Preference. The contents of the list of Settings are obtained through static configuration + dynamic addition.
1. Static configuration
The so-called static configuration is through XML configuration. If you still don’t understand the Preference, to: www.jianshu.com/p/348eb0928… Just a little bit
TopLevelSettings inherits from the abstract DashboardFragment class, implements the abstract method getPreferenceScreenResId(), and returns the preference configuration file to complete static configuration.
@Override
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings;
}
Copy the code
Top_level_settings specifies the configuration items to be displayed on the page:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="top_level_settings"> <Preference android:key="top_level_network" android:title="@string/network_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_network" android:order="-120" android:fragment="com.android.settings.network.NetworkDashboardFragment" settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/> <Preference android:key="top_level_connected_devices" android:title="@string/connected_devices_dashboard_title" android:summary="@string/summary_placeholder" android:icon="@drawable/ic_homepage_connected_device" android:order="-110" android:fragment="com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment" settings:controller="com.android.settings.connecteddevice.TopLevelConnectedDevicesPreferenceController"/> ... </PreferenceScreen>Copy the code
Among them:
key
: Indicates the primary key of the configuration itemtitle
: Indicates the title of the configuration itemsummary
: The text under the summary headingicon
: front iconorder
: used for sorting. The smaller the value, the higher the rankingfragment
: Click the interface to jump tocontroller
: A controller for the item that controls its presentation, availability, and click events
2. Dynamic add
Dynamically obtain the applications that match the action tags in the system using the packageManger command and dynamically add them to the list.
For example, configuration items such as network traffic monitoring, storage space management, and default applications are added dynamically.
Specific implementation can see the article: MTK Fast Bully is dynamically loaded
Two, system Settings other interfaces
In addition to system Settings. Homepage. SettingsHomepageActivity, other most of the Activity are defined in the Settings, and inherited from SettingsActivity, but which did not implement any logic. Therefore, the logic for these activities is implemented in SettingsActivity.
/** * Top-level Settings activity */ public class Settings extends SettingsActivity { /* * Settings subclasses for launching independently. */ public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */} public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ } public static class CreateShortcutActivity extends SettingsActivity { /* empty */ } public static class FaceSettingsActivity extends SettingsActivity { /* empty */ } public static class FingerprintSettingsActivity extends SettingsActivity { /* empty */ }... }Copy the code
These activities do not implement any logic, so how does it load into its proper layout?
In the onCreate() of the parent SettingsActivity class:
@Override protected void onCreate(Bundle savedState) { ... // Should happen before any call to getIntent() // First step getMetaData(); // Final Intent Intent = getIntent(); if (intent.hasExtra(EXTRA_UI_OPTIONS)) { getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0)); } // Getting Intent properties can only be done after the super.onCreate(...) final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT); . // launchSettingFragment(initialFragmentName, Intent); . }Copy the code
Follow the three steps above:
The first step
First, getMetaData() obtains the fragment configured in the manifest of the Activity and assigns it to the mFragmentClass
public static final String META_DATA_KEY_FRAGMENT_CLASS = "com.android.settings.FRAGMENT_CLASS"; 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()); }}Copy the code
So how is it configured in the manifest? As follows:
<activity android:name=".Settings$WifiInfoActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.DEVELOPMENT_PREFERENCE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="com.android.settings.FRAGMENT_CLASS" android:value="com.android.settings.wifi.WifiInfo" /> </activity>Copy the code
Therefore WifiInfoActivity the Acitivity corresponding fragment is: com. Android. Settings. Wifi. WifiInfo
The second step
Construct an intent with the EXTRA_SHOW_FRAGMENT through getIntent()
public Intent getIntent() { Intent superIntent = super.getIntent(); 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); modIntent.putExtra(EXTRA_SHOW_FRAGMENT, startingFragment); Bundle args = superIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); 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; } /** * Checks if the component name in the intent is different from the Settings class and * returns the class name to Load as a fragment. */ private String getStartingFragmentClass(Intent Intent) {// Return if mFragmentClass exists (mFragmentClass ! = null) return mFragmentClass; String intentClass = intent.getComponent().getClassName(); if (intentClass.equals(getClass().getName())) return null; if ("com.android.settings.RunningServices".equals(intentClass) || "com.android.settings.applications.StorageUse".equals(intentClass)) { // Old names of manage apps. intentClass = ManageApplications.class.getName(); } return intentClass; }Copy the code
This includes the case where mFragmentClass is empty, but forget it for now.
The third step
Launch the Fragment with the launchSettingFragment(). The initialFragmentName parameter is the EXTRA_SHOW_FRAGMENT parameter included in the second Intent. If the mFragmentClass is not empty, it is passed the mFragmentClass
void launchSettingFragment(String initialFragmentName, Intent intent) {
if (initialFragmentName != null) {
setTitleFromIntent(intent);
Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
switchToFragment(initialFragmentName, initialArguments, true,
mInitialTitleResId, mInitialTitle);
} else {
// Show search icon as up affordance if we are displaying the main Dashboard
mInitialTitleResId = R.string.dashboard_title;
switchToFragment(TopLevelSettings.class.getName(), null /* args */, false,
mInitialTitleResId, mInitialTitle);
}
}
Copy the code
Add the fragment to the activity in switchToFragment().
/** * Switch to a specific Fragment with taking care of validation, Title and BackStack */ private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate, int titleResId, CharSequence title) { Log.d(LOG_TAG, "Switching to fragment " + fragmentName); if (validate && ! isValidFragment(fragmentName)) { throw new IllegalArgumentException("Invalid fragment for this activity: " + fragmentName); Fragment f = utils.gettargetFragment (this, fragmentName, args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.main_content, f); if (titleResId > 0) { transaction.setBreadCrumbTitle(titleResId); } else if (title ! = null) { transaction.setBreadCrumbTitle(title); } / / to commit the transaction transaction.com mitAllowingStateLoss (); getSupportFragmentManager().executePendingTransactions(); Log.d(LOG_TAG, "Executed frag manager pendingTransactions"); return f; }Copy the code
Third, data control
On the home page – Battery Settings can display real-time power. So how is it implemented?
First look at how it is configured:
<Preference
android:key="top_level_battery"
android:title="@string/power_usage_summary_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_battery"
android:fragment="com.android.settings.fuelgauge.PowerUsageSummary"
android:order="-90"
settings:controller="com.android.settings.fuelgauge.TopLevelBatteryPreferenceController"/>
Copy the code
TopLevelBatteryPreferenceController controller was configured in the configuration items, it inherits from AbstractPreferenceController, this abstract class is used for unified management of all menu items on (for example to show or hide, listening to the click event, etc.).
TopLevelBatteryPreferenceController code is as follows:
public class TopLevelBatteryPreferenceController extends BasePreferenceController implements LifecycleObserver, OnStart, OnStop {/ / power change radio private final BatteryBroadcastReceiver mBatteryBroadcastReceiver; // The current configuration item private Preference mPreference; // Private BatteryInfo mBatteryInfo; public TopLevelBatteryPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext); mBatteryBroadcastReceiver.setBatteryChangedListener(type -> { BatteryInfo.getBatteryInfo(mContext, info -> { mBatteryInfo = info; updateState(mPreference); }, true /* shortString */); }); } @override public int getAvailabilityStatus() {return mContext.getResources().getBoolean(R.bool.config_show_top_level_battery) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); MPreference = screen.findPreference(getPreferenceKey()); } @ Override public void onStart () {/ / registered radio mBatteryBroadcastReceiver. Register (); } @ Override public void onStop () {/ / cancellation registration radio mBatteryBroadcastReceiver unRegister (); } @override public CharSequence getSummary() {return getDashboardLabel(mContext, mBatteryInfo); } static CharSequence getDashboardLabel(Context Context, BatteryInfo info) { if (info == null || context == null) { return null; } CharSequence label; if (! info.discharging && info.chargeLabel ! = null) { label = info.chargeLabel; } else if (info.remainingLabel == null) { label = info.batteryPercentString; } else { label = context.getString(R.string.power_remaining_settings_home_page, info.batteryPercentString, info.remainingLabel); } return label; }}Copy the code
The code is simple:
- Initialize the power change broadcast in the construction method
- in
onStart()
andonStop()
To register and unregister a broadcast - Once the power change broadcast is received, the power information is saved in
mBatteryInfo
In the - Then perform
updateState()
, the method is calledgetSummary()
Sets the information to the current configuration item getSummary()
Lt.mBatteryInfo
The stored electricity information is resolved
Summary:
Menu items such as display, hide, listening to the click event is done through inheritance from AbstractPreferenceController controller, the controller is configured in the XML, sometimes sometimes is dynamically add in fragments.
Disclaimer: Recently made a period of time Android 11 system setup application related development, will be related to the code of a simple combing summary, explain the improper place also please big guy pointed out
Reference: blog.csdn.net/qq_34149526…