最近一直在做Settings方面的东西,简单整理一下:
一、Settings
1、初始 AndroidManifest.xml
/packages/apps/Settings/AndroidManifest.xml
<activity android:name=".homepage.SettingsHomepageActivity" //Settings 的主界面
android:label="@string/settings_label_launcher"
android:theme="@style/Theme.Settings.Home"
android:launchMode="singleTask"> //启动模式为SingleTask
<intent-filter android:priority="1">
<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标签 通过 <activity-alias> 标签为每个 Activity 注册一个“别名”, 通过这个别名就能启动对应的目标 Activity,它最重要的属性是:android:enabled 属性,布尔类型,是否开启别名设置,默认值为 true
<!-- 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=".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>
从上面可以看到 activity-alias 只是一个别名,真正的指向的是 targetActivity,所以实际上主Activity就是SettingsHomepageActivity。
2、接着看这个别名 Settings.java
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 */ }
public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiTetherSettingsActivity extends SettingsActivity { /* empty */ }
public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
public static class DataSaverSummaryActivity extends SettingsActivity{ /* empty */ }
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrivateVolumeForgetActivity extends SettingsActivity { /* empty */ }
public static class PrivateVolumeSettingsActivity extends SettingsActivity { /* empty */ }
public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
....
}
3、看一下主界面 SettingsHomepageActivity.java
(/packages/apps/Settings/src/com/android/settings/homepage/SettingsHomepageActivity.java)
public class SettingsHomepageActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加载 settings_homepage_container.xml文件,这个文件就是 settings 主界面的布局
(1)setContentView(R.layout.settings_homepage_container);
final View root = findViewById(R.id.settings_homepage_container);
root.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
setHomepageContainerPaddingTop();
//设置 ToorBar,显示搜索框的内容
(2)final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
final ImageView avatarView = findViewById(R.id.account_avatar);
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(this, avatarView);
getLifecycle().addObserver(avatarViewMixin);
//判断设备的内存,是否显示 ContextualCardsFragment()
(3)if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
// Only allow contextual feature on high ram devices.
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
}
//通过 showFragment() 显示一级菜单
(4)showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
//通过 showFragment() 动态添加相应的 Fragment 并显示出来
private void showFragment(Fragment fragment, int id) {
final FragmentManager fragmentManager = getSupportFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
final Fragment showFragment = fragmentManager.findFragmentById(id);
if (showFragment == null) {
fragmentTransaction.add(id, fragment);
} else {
fragmentTransaction.show(showFragment);
}
fragmentTransaction.commit();
}
}
(1)通过setContentView()将settings_homepage_container.xml加载进去
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/settings_homepage_container"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/main_content_scrollable_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior">
<LinearLayout
android:id="@+id/homepage_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:descendantFocusability="blocksDescendants">
<FrameLayout
android:id="@+id/contextual_cards_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/contextual_card_side_margin"
android:layout_marginEnd="@dimen/contextual_card_side_margin"/>
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="?android:attr/windowBackground"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/search_bar"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
这个布局就是settings主界面的布局,通过一个NestedScrollView和一个AppBarLayout。AppBarLayout是用来显示搜索框的,NestedScrollView是用来显示主菜单的列表容器的。
4、TopLevelSettings.java
(/packages/apps/Settings/src/com/android/settings/homepage/TopLevelSettings.java)
public class TopLevelSettings extends DashboardFragment implements
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
private static final String TAG = "TopLevelSettings";
public TopLevelSettings() {
final Bundle args = new Bundle();
// Disable the search icon because this page uses a full search view in actionbar.
args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false);
setArguments(args);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings;
}
}
TopLevelSettings继承DashboardFragment ,getPreferenceScreenResId()加载指定的xml,即加载top_level_settings.xml文件,top_level_settings.xml文件就是设置的一级界面,里面包含诸多的item(但不包括动态加载的item)。
接着看一下 top_level_settings.xml 一级菜单
/packages/apps/Settings/res/xml/top_level_settings.xml
<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"/>
<Preference
android:key="top_level_apps_and_notifs" //应用和通知
android:title="@string/app_and_notification_dashboard_title"
android:summary="@string/app_and_notification_dashboard_summary"
android:icon="@drawable/ic_homepage_apps"
android:order="-100"
android:fragment="com.android.settings.applications.AppAndNotificationDashboardFragment"/>
<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"/>
.......
<Preference
android:key="top_level_location" //位置信息
android:title="@string/location_settings_title"
android:summary="@string/location_settings_loading_app_permission_stats"
android:icon="@drawable/ic_homepage_location"
android:order="-50"
android:fragment="com.android.settings.location.LocationSettings"
settings:controller="com.android.settings.location.TopLevelLocationPreferenceController"/>
<Preference
android:key="top_level_security" //安全
android:title="@string/security_settings_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_homepage_security"
android:order="-40"
android:fragment="com.android.settings.security.SecuritySettings"
settings:controller="com.android.settings.security.TopLevelSecurityEntryPreferenceController"/>
<Preference
android:key="top_level_accessibility" //无障碍
android:title="@string/accessibility_settings" //标题
android:summary="@string/accessibility_settings_summary" //summary 内容
android:icon="@drawable/ic_homepage_accessibility" //图标
android:order="-20" //界面的排序
android:fragment="com.android.settings.accessibility.AccessibilitySettings"
settings:controller="com.android.settings.accessibility.TopLevelAccessibilityPreferenceController"/>
/**
通过对应的fragment在AndroidManifest.xml里面找到对应的Activity
controller:
主要是 getSummary() 来更新summary,但要在对应的 controller 实现 SummaryLoader.SummaryConsumer 接口;
getAvailabilityStatus() 来判断当前item是否显示。
*/
</PreferenceScreen>
对应到页面,就是如下:
接着看一下DashboardFragment.java 文件,(/packages/apps/Settings/src/com/android/settings/dashboard/DashboardFragment.java)
public abstract class DashboardFragment extends SettingsPreferenceFragment
implements SettingsBaseActivity.CategoryListener, Indexable,
SummaryLoader.SummaryConsumer, PreferenceGroup.OnExpandButtonClickListener,
BasePreferenceController.UiBlockListener {
private static final String TAG = "DashboardFragment";
private final Map<Class, List<AbstractPreferenceController>> mPreferenceControllers =
new ArrayMap<>();
private final Set<String> mDashboardTilePrefKeys = new ArraySet<>();
private DashboardFeatureProvider mDashboardFeatureProvider;
private DashboardTilePlaceholderPreferenceController mPlaceholderPreferenceController;
private boolean mListeningToCategoryChange;
private SummaryLoader mSummaryLoader;
private List<String> mSuppressInjectedTileKeys;
@VisibleForTesting
UiBlockerController mBlockerController;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mSuppressInjectedTileKeys = Arrays.asList(context.getResources().getStringArray(
R.array.config_suppress_injected_tile_keys));
mDashboardFeatureProvider = FeatureFactory.getFactory(context).
getDashboardFeatureProvider(context);
final List<AbstractPreferenceController> controllers = new ArrayList<>();
// Load preference controllers from code
//从代码中加载preference controllers
final List<AbstractPreferenceController> controllersFromCode =
createPreferenceControllers(context);
// Load preference controllers from xml definition
//从xml中加载preference controllers
final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code already.
//从代码中加载preference controllers和从xml中加载preference controllers,然后进行重排
final List<BasePreferenceController> uniqueControllerFromXml =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
DashboardFragment 是一个抽象类,同时也继承SettingsPreferenceFragment,DashboardFragment 上述代码这里具体做了三件事情:1、 Load preference controllers from code;从代码中加载preference controllers。2、Load preference controllers from xml definition;从xml中加载preference controllers。3、 Filter xml-based controllers in case a similar controller is created from code already.从代码中加载preference controllers和从xml中加载preference controllers,然后进行重排。
5、SettingsActivity.java(/packages/apps/Settings/src/com/android/settings/SettingsActivity.java)
public class SettingsActivity extends SettingsBaseActivity
implements PreferenceManager.OnPreferenceTreeClickListener,
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
....
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
Log.d(LOG_TAG, "Starting onCreate");
long startTime = System.currentTimeMillis();
........
setContentView(R.layout.settings_main_prefs);
}
}
SettingsActivity.java 通过 setContentView() 将 settings_main_prefs.xml 布局引入,这个布局就是二级菜单界面。
接着看一下settings_main_prefs.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<com.android.settings.widget.SwitchBar
android:id="@+id/switch_bar"
android:layout_height="?android:attr/actionBarSize"
android:layout_width="match_parent"
android:theme="?attr/switchBarTheme"/>
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<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>
到这里流程基本就差不多了,后面的后续完成
二、SettingsProvider
个人改动的比较少,改动的话基本都是一些默认配置