Android Settings解析

news2024/11/26 4:15:39

首语

Android设置应用是Android系统中一个非常重要的系统应用,它允许用户调整和设置系统的各种参数和功能(系统设置/自定义设置/控制应用权限/开发者选项/系统信息等),使用户获得更好的使用体验。同时它一般也是Android系统开发者了解深入的第一个系统级应用,也是用户使用最频繁的系统应用。

源码目录

AOSP源码路径为packages/apps/Settings。src/com/android/settings目录下包含Settings的主要源码。libs目录下的contextualcards.aar包含实现上下文卡片功能的代码和资源,它可以将相关的内容组织在一起,以卡片的形式展现给用户。res目录下包含各种静态资源。Android.bp文件中可以看到模块名为"Settings"。

设计指南

Display

上图是Settings里一个普通的页面,从这个页面可以看出它将许多设置放在一起,设置列表是多个控件的组合。

它有如下优点:

  • 提供一个很好的概述。用户应该能够浏览设置屏幕并了解所有单独的设置及其值。
  • 直观的设置项目。常用设置放在屏幕顶部。限制一个屏幕上的设置数量。将一些设置移动到单独的屏幕来创建直观的菜单。
  • 使用明确的标题和状态。标题简短而有意义。在标题下方,显示状态以突出设置的值,显示具体细节。

关于Settings设计的详细规则及细节,可以参考官网:设计指南

Preference

在Android 常用组件里,存在一个Preference组件,它提供了一个方便的用户界面,用于管理和显示应用程序的各种设置选项,让用户可以轻松浏览和更改应用程序的设置。Preference还通过SharedPreference实现保存读取数据,以其key作为SharedPreference的键,实现持久化数据。Settings中大多数菜单都是通过Preference去实现,且使用的是androidx包的Preference,因此首先了解下Preference的使用。

Preference组件和其它页面组件使用类似,区别在于XML 资源必须放置于 res/xml/ 目录,Preference的根标签必须为PreferenceScreen。举例如下:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:andoird="http://schemas.android.com/apk/res-auto">
	<PreferenceCategory
        app:key="prefer_category">
   		<Preference
       		andoird:key="feedback"
       		andoird:title="Send feedback"
        	andoird:summary="Report technical issues or suggest new features"/>
	</PreferenceCategory>
</PreferenceScreen>

PreferenceCategory是对Preferences进行分组的标签,显示类别标题,并在视觉上进行分隔Preference。如设计指南中的Settings Display页面的截图中Brightness/Lock display类别标签。

以下是Preference相关属性的介绍:

attrdescription
andoird:allowDividerAbove在菜单上显示一条分割线
andoird:allowDividerBelow在菜单下显示一条分割线
android:defaultValue默认值。
android:dependency设置此元素附属于另一个元素,依赖的可用则当前元素也可用(enable),反之。
andoird:enableCopying启用长按复制
android:enabled设置是否可用。
android:fragment指定跳转fragment。
android:icon指定左侧的图标。
andoird:iconSpaceReserved为图标预留位置,菜单向右偏移,默认false
andoird:isPreferenceVisible菜单是否显示
android:key选项的名称,也是用来存储时唯一的key。
android:layout给当前元素指定一个自定义布局。
android:order偏好的顺序。如果不指定,默认的顺序将字母。
android:persistent是否将其值持久化。
android:selectable设置是否可以选择操作。
android:shouldDisableView当enabled设置为false变暗,同时此属性设置为false时disable但不变暗。
andoird:singleLineTitle菜单title限制为一行,默认为true
android:summary摘要,配置的简要说明,显示在标题下面。
android:title选项的标题,当没有设置summary时自动垂直居中显示。
android:widgetLayout控件可调小部件的布局。是为一个优先选择的布局,比如一个复选框选择要指定一个自定义布局(注意:包括的只是复选框)在这里。

Setting中扩展的attr如下:

    <declare-styleable name="Preference">
        <!-- 搜索关键词 -->
        <attr name="keywords" format="string" />
        <!-- 是否可搜索,默认为true -->
        <attr name="searchable" format="boolean" />
        <!-- Preference controller类 -->
        <attr name="controller" format="string" />
        <!-- 自定义字幕 -->
        <attr name="unavailableSliceSubtitle" format="string" />
        <!-- Preference针对work profile,默认为false -->
        <attr name="forWork" format="boolean" />
        <!-- 用于在双窗格上突出显示菜单首选项的标识符 -->
        <attr name="highlightableMenuKey" format="string" />
    </declare-styleable>

查看Preference的源码可知,还有一些自定义Preference实现的组件,如CheckBoxPreference/DropDownPreference/EditTextPreference/ListPreference/SwitchPreference等,它是针对不同Android控件(checkbox/dropdown/edittext等)实现的自定义Preference,如需使用只需要在xml引用即可

然后创建一个fragment,继承于PreferenceFragmentCompat。onCreatePreferences方法在PreferenceFragmentCompat的onCreate方法调用,用于创建Prerefence。通过setPreferencesFromResource引用定义的Preference xml资源。这样通过Preference实现的一个简单的菜单就显示在屏幕上了。

public class SettingsFragment extends PreferenceFragmentCompat {

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.root_preferences, rootKey);
    }
    //preference点击事件,通过key区分
    @Override
    public boolean onPreferenceTreeClick(@NonNull Preference preference) {
        return super.onPreferenceTreeClick(preference);
    }    
}

看下Preference的点击事件实现,如果对应的fragment实现了OnPreferenceStartFragmentCallback,重写了onPreferenceStartFragment方法,那么Preference的跳转实现就在onPreferenceStartFragment方法里,并返回处理结果,如果没有实现OnPreferenceStartFragmentCallback,则去获取xml中设置的android:fragment或者setFragment设置的fragment跳转。

public abstract class PreferenceFragmentCompat extends Fragment implements
        PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceManager.OnDisplayPreferenceDialogListener,
        PreferenceManager.OnNavigateToScreenListener,
        DialogPreference.TargetFragment {
	@Override
    public boolean onPreferenceTreeClick(@NonNull Preference preference) {
        if (preference.getFragment() != null) {
            boolean handled = false;
            if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) {
                handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment())
                        .onPreferenceStartFragment(this, preference);
            }
            Fragment callbackFragment = this;
            while (!handled && callbackFragment != null) {
                if (callbackFragment instanceof OnPreferenceStartFragmentCallback) {
                    handled = ((OnPreferenceStartFragmentCallback) callbackFragment)
                            .onPreferenceStartFragment(this, preference);
                }
                callbackFragment = callbackFragment.getParentFragment();
            }
            if (!handled && getContext() instanceof OnPreferenceStartFragmentCallback) {
                handled = ((OnPreferenceStartFragmentCallback) getContext())
                        .onPreferenceStartFragment(this, preference);
            }
            if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback) {
                handled = ((OnPreferenceStartFragmentCallback) getActivity())
                        .onPreferenceStartFragment(this, preference);
            }
            if (!handled) {   
                final FragmentManager fragmentManager = getParentFragmentManager();
                final Bundle args = preference.getExtras();
                final Fragment fragment = fragmentManager.getFragmentFactory().instantiate(
                        requireActivity().getClassLoader(), preference.getFragment());
                fragment.setArguments(args);
                fragment.setTargetFragment(this, 0);
                fragmentManager.beginTransaction()
                        .replace(((View) requireView().getParent()).getId(), fragment)
                        .addToBackStack(null)
                        .commit();
            }
            return true;
        }
        return false;
    }            
}

androidx包中Preference只有针对Fragment的实现,没有针对Activity的实现。还有针对Dialog实现的PreferenceDialogFragmentCompat。在dialog里引用preference。

页面加载分析

本文以Android 13 Settings源码为例进行分析。

首页加载流程

在AndroidManifest.xml中可以看到,启动activity为Settings,Settings中包含大量的静态类继承于SettingsActivity。

 <activity-alias android:name="Settings"
                android:label="@string/settings_label_launcher"
                android:taskAffinity="com.android.settings.root"
                android:launchMode="singleTask"
                android:exported="true"
                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>

查看SettingsActivity的onCreate方法,首先读取fragment class和HighlightMenuKey,设置布局为settings_main_prefs.xml,通过intent传递的数据显示不同的布局。切换fragment或根据之前保存状态显示页面。settings_main_prefs.xml中包含顶部的switch bar,底部的button(Back/Skip/Next),和中间的framelayout显示Fragment,switch bar和button默认隐藏。

public class SettingsActivity extends SettingsBaseActivity
        implements PreferenceManager.OnPreferenceTreeClickListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
        ButtonBarHandler, FragmentManager.OnBackStackChangedListener {
    @Override
    protected void onCreate(Bundle savedState) {
        // Should happen before any call to getIntent()
        getMetaData();
        final Intent intent = getIntent();

        if (shouldShowTwoPaneDeepLink(intent) && tryStartTwoPaneDeepLink(intent)) {
            finish();
            super.onCreate(savedState);
            return;
        }

        super.onCreate(savedState);
        Log.d(LOG_TAG, "Starting onCreate");
        createUiFromIntent(savedState, intent);
    }
    private void getMetaData() {
        try {
            ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
                    PackageManager.GET_META_DATA);
            if (ai == null || ai.metaData == null) return;
            //读取设置的fragment
            mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
            mHighlightMenuKey = ai.metaData.getString(META_DATA_KEY_HIGHLIGHT_MENU_KEY);
        } catch (NameNotFoundException nnfe) {
            // No recovery
            Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
        }
    }
    protected void createUiFromIntent(Bundle savedState, Intent intent) {
        ...
        setContentView(R.layout.settings_main_prefs);
        ...
        if (savedState != null) {
            // We are restarting from a previous saved state; used that to initialize, instead
            // of starting fresh.
            setTitleFromIntent(intent);

            ArrayList<DashboardCategory> categories =
                    savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
            if (categories != null) {
                mCategories.clear();
                mCategories.addAll(categories);
                setTitleFromBackStack();
            }
        } else {
            //加载fragment
            launchSettingFragment(initialFragmentName, intent);
        }
    }
}

AndroidManifest中并没有传递Settings对应的fragment数据,而是指定targetActivity为SettingsHomepageActivity,查看SettingsHomepageActivity的onCreate方法,布局为settings_homepage_container.xml,然后初始化搜索栏。设备不是低内存的情况下加载Suggestion菜单,设置fragment为TopLevelSettings。

@Override
protected void onCreate(Bundle savedInstanceState) {
        ...
        setContentView(R.layout.settings_homepage_container);
    	...
        initSearchBarView();
        ...
        if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
            initAvatarView();
            final boolean scrollNeeded = mIsEmbeddingActivityEnabled
                    && !TextUtils.equals(getString(DEFAULT_HIGHLIGHT_MENU_KEY), highlightMenuKey);
            showSuggestionFragment(scrollNeeded);
            if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) {
                showFragment(() -> new ContextualCardsFragment(), R.id.contextual_cards_content);
                ((FrameLayout) findViewById(R.id.main_content))
                        .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
            }
        }
        mMainFragment = showFragment(() -> {
            final TopLevelSettings fragment = new TopLevelSettings();
            fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
                    highlightMenuKey);
            return fragment;
        }, R.id.main_content);
        ...
}
private void initSearchBarView() {
        final Toolbar toolbar = findViewById(R.id.search_action_bar);
        FeatureFactory.getFactory(this).getSearchFeatureProvider()
                .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);

        if (mIsEmbeddingActivityEnabled) {
            final Toolbar toolbarTwoPaneVersion = findViewById(R.id.search_action_bar_two_pane);
            //初始化搜索实现
            FeatureFactory.getFactory(this).getSearchFeatureProvider()
                    .initSearchToolbar(this /* activity */, toolbarTwoPaneVersion,
                            SettingsEnums.SETTINGS_HOMEPAGE);
        }
    }

在搜索的实现类SearchFeatureProviderImpl中可以看到构造的搜索跳转intent如下,可以发现Settings的搜索核心实现在另外一个app内,包名为com.android.settings.intelligence。在后面会对搜索及这个模块进行深入分析,继续分析Settings页面加载。

    @Override
    public Intent buildSearchIntent(Context context, int pageId) {
        return new Intent(Settings.ACTION_APP_SEARCH_SETTINGS)
                .setPackage(getSettingsIntelligencePkgName(context))
                .putExtra(Intent.EXTRA_REFERRER, buildReferrer(context, pageId));
    }

    default String getSettingsIntelligencePkgName(Context context) {
        return context.getString(R.string.config_settingsintelligence_package_name);
    }

    <!-- Settings intelligence package name -->
    <string name="config_settingsintelligence_package_name" translatable="false">
        com.android.settings.intelligence
    </string>

首先看下TopLevelSettings的继承关系,TopLevelSettings继承于DashboardFragment,DashboardFragment是静态和动态Settings 菜单的基类,Settings中大多数菜单对应的fragment继承于DashboardFragment,它继承于SettingsPreferenceFragment,SettingsPreferenceFragment是Settings fragment的基类,它继承于InstrumentedPreferenceFragment,它记录fragment显示状态,继承于ObservablePreferenceFragment,ObservablePreferenceFragment是在SettingsLib里定义的,模块路径:frameworks/base/packages/SettingsLib,在后面会对这个模块深入分析。ObservablePreferenceFragment继承于PreferenceFragmentCompat。

public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
            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);
    }
    //设置preference对应xml资源
    @Override
    protected int getPreferenceScreenResId() {
        return R.xml.top_level_settings;
    }
     @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        HighlightableMenu.fromXml(context, getPreferenceScreenResId());
        use(SupportPreferenceController.class).setActivity(getActivity());
    }
}

首先查看onAttach方法,调用DashboardFragment的use方法获取SupportPreferenceController实例。SupportPreferenceController存储在mPreferenceControllers中,通过addPreferenceController方法将PreferenceController添加到mPreferenceControllers中,在DashboardFragment的onAttach方法中会调用addPreferenceController,通过createPreferenceControllers方法将代码设置的controller添加到mControllers集合,然后读取xml中设置的controller添加到mControllers集合。

然后看下加载Preference的onCreatePreferences方法,首先通过getPreferenceScreenResId获取对应的xml资源,TopLevelSettings对应的是top_level_settings.xml,xml中定义了Settings首页菜单,最终通过addPreferencesFromResource方法显示Preference。在TopLevelSettings的onCreatePreferences方法还对图标颜色进行了处理。Preference点击事件调用Preferencecontroller的handlePreferenceTreeClick方法。

public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
        @Override
    	public void onAttach(Context context) {
        	super.onAttach(context);
            ...
             // Load preference controllers from code
        	final List<AbstractPreferenceController> controllersFromCode =
                createPreferenceControllers(context);
        	// Load preference controllers from xml definition
        	final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
                .getPreferenceControllersFromXml(context, 			             getPreferenceScreenResId());
        // Filter xml-based controllers in case a similar controller is created from code already.
        final List<BasePreferenceController> uniqueControllerFromXml =
                PreferenceControllerListHelper.filterControllers(
                        controllersFromXml, controllersFromCode);

        // Add unique controllers to list.
        if (controllersFromCode != null) {
            mControllers.addAll(controllersFromCode);
        }
        mControllers.addAll(uniqueControllerFromXml);
            for (AbstractPreferenceController controller : mControllers) {
            	addPreferenceController(controller);
        	}	
        }
        //获取对应Preference controller实例
        protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {
        List<AbstractPreferenceController> controllerList = mPreferenceControllers.get(clazz);
        if (controllerList != null) {
            if (controllerList.size() > 1) {
                Log.w(TAG, "Multiple controllers of Class " + clazz.getSimpleName()
                        + " found, returning first one.");
            }
            return (T) controllerList.get(0);
        }

        return null;
    }
    protected void addPreferenceController(AbstractPreferenceController controller) {
        if (mPreferenceControllers.get(controller.getClass()) == null) {
            mPreferenceControllers.put(controller.getClass(), new ArrayList<>());
        }
        mPreferenceControllers.get(controller.getClass()).add(controller);
    }
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        checkUiBlocker(mControllers);
        refreshAllPreferences(getLogTag());
     	...
    }
    private void refreshAllPreferences(final String tag) {
        ...
         // Add resource based tiles.
        displayResourceTiles();
        ...
    }
    private void displayResourceTiles() {
        final int resId = getPreferenceScreenResId();
        if (resId <= 0) {
            return;
        }
        addPreferencesFromResource(resId);
        final PreferenceScreen screen = getPreferenceScreen();
        screen.setOnExpandButtonClickListener(this);
        displayResourceTilesToScreen(screen);
    }
    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        final Collection<List<AbstractPreferenceController>> controllers =
                mPreferenceControllers.values();
        for (List<AbstractPreferenceController> controllerList : controllers) {
            for (AbstractPreferenceController controller : controllerList) {
                if (controller.handlePreferenceTreeClick(preference)) {
                    // log here since calling super.onPreferenceTreeClick will be skipped
                    writePreferenceClickMetric(preference);
                    return true;
                }
            }
        }
        return super.onPreferenceTreeClick(preference);
    }
}

接下来我们以SupportPreferenceController为例,分析下PreferenceController。它继承于BasePreferenceController,BasePreferenceController继承于AbstractPreferenceController。

public class SupportPreferenceController extends BasePreferenceController {
    //指定显示状态
    @Override
    public int getAvailabilityStatus() {
        return mSupportFeatureProvider == null ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
    }
	//点击事件
    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (preference == null || mActivity == null ||
                !TextUtils.equals(preference.getKey(), getPreferenceKey())) {
            return false;
        }
        mSupportFeatureProvider.startSupport(mActivity);
        return true;

    }
}

AbstractPreferenceController是一个抽象类,主要方法如下:

public abstract class AbstractPreferenceController {
    //preference是否有效
    public abstract boolean isAvailable();
    //preference点击事件
    public boolean handlePreferenceTreeClick(Preference preference) {
        return false;
    }
    //显示preference
    public void displayPreference(PreferenceScreen screen) {
        final String prefKey = getPreferenceKey();
        if (TextUtils.isEmpty(prefKey)) {
            Log.w(TAG, "Skipping displayPreference because key is empty:" + getClass().getName());
            return;
        }
        if (isAvailable()) {
            setVisible(screen, prefKey, true /* visible */);
            if (this instanceof Preference.OnPreferenceChangeListener) {
                final Preference preference = screen.findPreference(prefKey);
                preference.setOnPreferenceChangeListener(
                        (Preference.OnPreferenceChangeListener) this);
            }
        } else {
            setVisible(screen, prefKey, false /* visible */);
        }
    }
    //更新preference状态(summary)
    public void updateState(Preference preference) {
        refreshSummary(preference);
    }
    //preference key
    public abstract String getPreferenceKey();
}

BasePreferenceController对AbstractPreferenceController进行了简单封装,对Preference状态进行处理,共有6中状态,其次对Preference搜索支持也进行了处理。

public abstract class BasePreferenceController extends AbstractPreferenceController implements Sliceable {
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({AVAILABLE, AVAILABLE_UNSEARCHABLE, UNSUPPORTED_ON_DEVICE, DISABLED_FOR_USER,
            DISABLED_DEPENDENT_SETTING, CONDITIONALLY_UNAVAILABLE})
    public @interface AvailabilityStatus {
    }
    //preference 有效
    public static final int AVAILABLE = 0;
    //preference 有效不能搜索
    public static final int AVAILABLE_UNSEARCHABLE = 1;
    //当前不可用,将来可能可用
    public static final int CONDITIONALLY_UNAVAILABLE = 2;
    //设备不支持
    public static final int UNSUPPORTED_ON_DEVICE = 3;
    //当前用户不支持
    public static final int DISABLED_FOR_USER = 4;
    //preference置灰,无法更改,依赖其它设置
    public static final int DISABLED_DEPENDENT_SETTING = 5;
    //指定Preference状态
    @AvailabilityStatus
    public abstract int getAvailabilityStatus();
    //preference 有效的实现
    @Override
    public final boolean isAvailable() {
        if (mIsForWork && mWorkProfileUser == null) {
            return false;
        }

        final int availabilityStatus = getAvailabilityStatus();
        return (availabilityStatus == AVAILABLE
                || availabilityStatus == AVAILABLE_UNSEARCHABLE
                || availabilityStatus == DISABLED_DEPENDENT_SETTING);
    }
    //针对DISABLED_DEPENDENT_SETTING状态进行置灰
    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
            // Disable preference if it depends on another setting.
            final Preference preference = screen.findPreference(getPreferenceKey());
            if (preference != null) {
                preference.setEnabled(false);
            }
        }
    }
}

从上面可以看出来,Preferencecontroller它是Preference的控制器,控制Preference的显示,点击事件,搜索。Settings中大多数Preference的控制器都继承于BasePreferenceController。以上就是首页菜单加载的流程。

那SupportPreferenceContoller是那个菜单的控制器呢,它是首页Tips & support菜单的控制器。top_level_settings.xml有定义这个preference。Settings中大多数是以这样的实现构成的,xml定义Preference和引用Preferencecontroller,Preferencecontroller去实现对应菜单的逻辑。

二级页面加载流程

首先查看首页菜单的点击事件,它是获取了Preference controller的handlePreferenceTreeClick方法处理点击事件。

public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
            @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        final Collection<List<AbstractPreferenceController>> controllers =
                mPreferenceControllers.values();
        for (List<AbstractPreferenceController> controllerList : controllers) {
            for (AbstractPreferenceController controller : controllerList) {
                if (controller.handlePreferenceTreeClick(preference)) {
                    // log here since calling super.onPreferenceTreeClick will be skipped
                    writePreferenceClickMetric(preference);
                    return true;
                }
            }
        }
        return super.onPreferenceTreeClick(preference);
    }
}

Preference controller的点击基础实现如下:

public abstract class BasePreferenceController extends AbstractPreferenceController implements  Sliceable {
    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
            return super.handlePreferenceTreeClick(preference);
        }
        if (!mIsForWork || mWorkProfileUser == null) {
            return super.handlePreferenceTreeClick(preference);
        }
        final Bundle extra = preference.getExtras();
        extra.putInt(EXTRA_USER_ID, mWorkProfileUser.getIdentifier());
        new SubSettingLauncher(preference.getContext())
                .setDestination(preference.getFragment())
                .setSourceMetricsCategory(preference.getExtras().getInt(CATEGORY,
                        SettingsEnums.PAGE_UNKNOWN))
                .setArguments(preference.getExtras())
                .setUserHandle(mWorkProfileUser)
                .launch();
        return true;
    }
}

如果Preference controller不处理,则通过onPreferenceStartFragment方法,TopLevelSettings实现了OnPreferenceStartFragmentCallback,

public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
       @Override
    public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
        new SubSettingLauncher(getActivity())
                .setDestination(pref.getFragment())
                .setArguments(pref.getExtras())
                .setSourceMetricsCategory(caller instanceof Instrumentable
                        ? ((Instrumentable) caller).getMetricsCategory()
                        : Instrumentable.METRICS_CATEGORY_UNKNOWN)
                .setTitleRes(-1)
                .setIsSecondLayerPage(true)
                .launch();
        return true;
    }
}

可以发现,跳转二级页面的实现都是通过SubSettingLauncher来传递参数并且跳转目标fragment。toIntent方法构造调整intent,可以看到跳转的类是SubSettings,launcher方法进行跳转。

public class SubSettingLauncher {
    public void launch() {
       	...
        final Intent intent = toIntent();

        boolean launchAsUser = mLaunchRequest.mUserHandle != null
                && mLaunchRequest.mUserHandle.getIdentifier() != UserHandle.myUserId();
        boolean launchForResult = mLaunchRequest.mResultListener != null;
        if (launchAsUser && launchForResult) {
            launchForResultAsUser(intent, mLaunchRequest.mUserHandle,
                    mLaunchRequest.mResultListener, mLaunchRequest.mRequestCode);
        } else if (launchAsUser && !launchForResult) {
            launchAsUser(intent, mLaunchRequest.mUserHandle);
        } else if (!launchAsUser && launchForResult) {
            launchForResult(mLaunchRequest.mResultListener, intent, mLaunchRequest.mRequestCode);
        } else {
            launch(intent);
        }
    }
    public Intent toIntent() {
        final Intent intent = new Intent(Intent.ACTION_MAIN);
        copyExtras(intent);
        intent.setClass(mContext, SubSettings.class);
        if (TextUtils.isEmpty(mLaunchRequest.mDestinationName)) {
            throw new IllegalArgumentException("Destination fragment must be set");
        }
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, mLaunchRequest.mDestinationName);

        if (mLaunchRequest.mSourceMetricsCategory < 0) {
            throw new IllegalArgumentException("Source metrics category must be set");
        }
        intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
                mLaunchRequest.mSourceMetricsCategory);

        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, mLaunchRequest.mArguments);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RES_PACKAGE_NAME,
                mLaunchRequest.mTitleResPackageName);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
                mLaunchRequest.mTitleResId);
        intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, mLaunchRequest.mTitle);
        intent.addFlags(mLaunchRequest.mFlags);
        intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
                mLaunchRequest.mTransitionType);
        intent.putExtra(SettingsActivity.EXTRA_IS_SECOND_LAYER_PAGE,
                mLaunchRequest.mIsSecondLayerPage);

        return intent;
    }

    @VisibleForTesting
    void launch(Intent intent) {
        mContext.startActivity(intent);
    }
}

SubSettings继承于SettingsActivity,说明首页菜单除了自定义实现页面跳转逻辑的之外,其它都是跳转到SubSettings这个Activity,这里有一个小技巧,正常情况下我们抓取当前页面的Activity可以通过以下命令:

adb shell dumpsys window | grep mCurrentFocus

但是我们不清楚这个页面对应的fragment,通过上面命令都是SubSettings。在跳转时可以通过以下命令获取fragment:

adb logcat -s "SubSettings"

这样就打印出了具体的启动fragment。

public class SubSettings extends SettingsActivity {

    @Override
    public boolean onNavigateUp() {
        finish();
        return true;
    }

    @Override
    protected boolean isValidFragment(String fragmentName) {
        //打印页面
        Log.d("SubSettings", "Launching fragment " + fragmentName);
        return true;
    }
}

剩下的相关逻辑都和TopLevelSettings类似,这里不继续展开分析了。

动态插入菜单

在Settings里的一些菜单,我们会发现一些菜单在xml和代码中并未添加,但实际上显示在页面上,这是为什么呢?原来是Settings支持动态插入菜单。实现逻辑如下:

在创建Preference的时候,refreshAllPreferences方法刷新Preference,包括来自xml的静态Preference和动态Preference。动态Preference添加实现在refreshDashboardTiles方法中。

public abstract class DashboardFragment extends SettingsPreferenceFragment
        implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener,
        BasePreferenceController.UiBlockListener {
    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        checkUiBlocker(mControllers);
        refreshAllPreferences(getLogTag());
        ...
    }
    private void refreshAllPreferences(final String tag) {
        ...
        // Add resource based tiles.
        displayResourceTiles();
		//动态Preference
        refreshDashboardTiles(tag);
    }
    private void refreshDashboardTiles(final String tag) {
        final PreferenceScreen screen = getPreferenceScreen();

        final DashboardCategory category =
                mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
      	...
        final List<Tile> tiles = category.getTiles();
        // Create a list to track which tiles are to be removed.
        final Map<String, List<DynamicDataObserver>> remove = new ArrayMap(mDashboardTilePrefKeys);

        // Install dashboard tiles and collect pending observers.
        final boolean forceRoundedIcons = shouldForceRoundedIcon();
        final List<DynamicDataObserver> pendingObservers = new ArrayList<>();
        for (Tile tile : tiles) {
            final String key = mDashboardFeatureProvider.getDashboardKeyForTile(tile);
            ...
            final List<DynamicDataObserver> observers;
            if (mDashboardTilePrefKeys.containsKey(key)) {
                // Have the key already, will rebind.
                final Preference preference = screen.findPreference(key);
                observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
                        getActivity(), this, forceRoundedIcons, preference, tile, key,
                        mPlaceholderPreferenceController.getOrder());
            } else {
                // Don't have this key, add it.
                final Preference pref = createPreference(tile);
                observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(
                        getActivity(), this, forceRoundedIcons, pref, tile, key,
                        mPlaceholderPreferenceController.getOrder());
                //添加Preference
                screen.addPreference(pref);
                registerDynamicDataObservers(observers);
                mDashboardTilePrefKeys.put(key, observers);
            }
            if (observers != null) {
                pendingObservers.addAll(observers);
            }
            remove.remove(key);
            ...
   //类别key             
   public String getCategoryKey() {
        return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
    }
}

首先获取了类别 key,PARENT_TO_CATEGORY_KEY_MAP中实现了页面和key的对应。通过页面class name来确定页面对应的key。

public class DashboardFragmentRegistry {
     static {
        PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
        PARENT_TO_CATEGORY_KEY_MAP.put(TopLevelSettings.class.getName(),
                CategoryKey.CATEGORY_HOMEPAGE);
        PARENT_TO_CATEGORY_KEY_MAP.put(
                NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
        PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),
                CategoryKey.CATEGORY_CONNECT);
        PARENT_TO_CATEGORY_KEY_MAP.put(AdvancedConnectedDeviceDashboardFragment.class.getName(),
        ...
     }
}

页面key的定义在CategoryKey类中。这样通过key就清楚当前页面是否动态加载那些菜单。

public final class CategoryKey {
     // Activities in this category shows up in Settings homepage.
    public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";

    // Top level category.
    public static final String CATEGORY_NETWORK = "com.android.settings.category.ia.wireless";
    public static final String CATEGORY_CONNECT = "com.android.settings.category.ia.connect";
    public static final String CATEGORY_DEVICE = "com.android.settings.category.ia.device";
    public static final String CATEGORY_APPS = "com.android.settings.category.ia.apps";
    ...
}

getTilesForCategory方法的实现在DashboardFeatureProviderImpl类中,它是通过CategoryManager类的getTilesByCategory方法实现。mCategories是获取所有动态菜单的集合。

public class CategoryManager {
    public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey) {
        tryInitCategories(context);

        return mCategoryByKeyMap.get(categoryKey);
    }
    private synchronized void tryInitCategories(Context context) {
        // Keep cached tiles by default. The cache is only invalidated when InterestingConfigChange
        // happens.
        tryInitCategories(context, false /* forceClearCache */);
    }

    private synchronized void tryInitCategories(Context context, boolean forceClearCache) {
        if (mCategories == null) {
            final boolean firstLoading = mCategoryByKeyMap.isEmpty();
            if (forceClearCache) {
                mTileByComponentCache.clear();
            }
            mCategoryByKeyMap.clear();
            //获取categories list
            mCategories = TileUtils.getCategories(context, mTileByComponentCache);
            for (DashboardCategory category : mCategories) {
                mCategoryByKeyMap.put(category.key, category);
            }
            backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
            sortCategories(context, mCategoryByKeyMap);
            filterDuplicateTiles(mCategoryByKeyMap);
            if (firstLoading) {
                logTiles(context);

                final DashboardCategory homepageCategory = mCategoryByKeyMap.get(
                        CategoryKey.CATEGORY_HOMEPAGE);
                if (homepageCategory == null) {
                    return;
                }
                for (Tile tile : homepageCategory.getTiles()) {
                    final String key = tile.getKey(context);
                    if (TextUtils.isEmpty(key)) {
                        Log.w(TAG, "Key hint missing for homepage tile: " + tile.getTitle(context));
                        continue;
                    }
                    HighlightableMenu.addMenuKey(key);
                }
            }
        }
    }
}

从loadActivityTiles方法里可以看出,在Settings里动态插入菜单只能是系统应用。

源码路径:frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java

public class TileUtils {
    public static final String EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS";
    public static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS";
     private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";

    public static List<DashboardCategory> getCategories(Context context,
            Map<Pair<String, String>, Tile> cache) {
        final long startTime = System.currentTimeMillis();
        final boolean setup =
                Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;
        final ArrayList<Tile> tiles = new ArrayList<>();
        final UserManager userManager = (UserManager) context.getSystemService(
                Context.USER_SERVICE);
        for (UserHandle user : userManager.getUserProfiles()) {
            // TODO: Needs much optimization, too many PM queries going on here.
            if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
                loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
                loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,
                        OPERATOR_DEFAULT_CATEGORY, tiles, false);
                loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
                        MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
            }
            if (setup) {
                loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
                loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
            }
        }
        ...
        return categories;
    }
    static void loadTilesForAction(Context context,
            UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
        final Intent intent = new Intent(action);
        if (requireSettings) {
	        // 只允许settings通过SETTINGS_ACTION添加
            intent.setPackage(SETTING_PKG);
        }
        loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent);
        loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent);
    }
    
    private static void loadActivityTiles(Context context,
            UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent) {
        final PackageManager pm = context.getPackageManager();
        final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                PackageManager.GET_META_DATA, user.getIdentifier());
        for (ResolveInfo resolved : results) {
            //系统应用
            if (!resolved.system) {
                // Do not allow any app to add to settings, only system ones.
                continue;
            }
            final ActivityInfo activityInfo = resolved.activityInfo;
            final Bundle metaData = activityInfo.metaData;
            loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo);
        }
    }
    private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
            ComponentInfo componentInfo) {
        // Skip loading tile if the component is tagged primary_profile_only but not running on
        // the current user.
        if (user.getIdentifier() != ActivityManager.getCurrentUser()
                && Tile.isPrimaryProfileOnly(componentInfo.metaData)) {
            Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
                    + intent + " is primary profile only, skip loading tile for uid "
                    + user.getIdentifier());
            return;
        }

        String categoryKey = defaultCategory;
        // Load category
        categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
        final boolean isProvider = componentInfo instanceof ProviderInfo;
        final Pair<String, String> key = isProvider
                ? new Pair<>(((ProviderInfo) componentInfo).authority,
                        metaData.getString(META_DATA_PREFERENCE_KEYHINT))
                : new Pair<>(componentInfo.packageName, componentInfo.name);
        Tile tile = addedCache.get(key);
        if (tile == null) {
            tile = isProvider
                    ? new ProviderTile((ProviderInfo) componentInfo, categoryKey, metaData)
                    : new ActivityTile((ActivityInfo) componentInfo, categoryKey);
            addedCache.put(key, tile);
        } else {
            tile.setMetaData(metaData);
        }
        if (!tile.userHandle.contains(user)) {
            tile.userHandle.add(user);
        }
        if (!outTiles.contains(tile)) {
            outTiles.add(tile);
        }
    }
}

然后遍历tiles集合,Tile类里包含Preference的数据(Key/order/intent等等),也可以设置这些字段的key。

最后一个动态菜单就被成功添加到当前页面了。我们以System->Developer options 菜单为例,它是被动态添加到Settings里的菜单。它定义在Settings的AndroidManifest.xml中。

<activity
            android:name="Settings$DevelopmentSettingsDashboardActivity"
            android:label="@string/development_settings_title"
            android:icon="@drawable/ic_settings_development"
            android:exported="true"
            android:enabled="false">
            <intent-filter android:priority="1">
                <action android:name="android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
                <action android:name="com.android.settings.APPLICATION_DEVELOPMENT_SETTINGS" />
                <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="com.android.settings.action.SETTINGS" />
            </intent-filter>
            <meta-data android:name="com.android.settings.order" android:value="-40"/>
            <meta-data android:name="com.android.settings.category"
                       android:value="com.android.settings.category.ia.system" />
            <meta-data android:name="com.android.settings.summary"
                       android:resource="@string/summary_empty"/>
            <meta-data android:name="com.android.settings.icon"
                       android:resource="@drawable/ic_settings_development" />
            <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
                       android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
            <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
                       android:value="@string/menu_key_system"/>
            <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
                       android:value="true" />
        </activity>

首先它设置action为com.android.settings.action.SETTING,从前面TileUtils类分析知道这个action只能Settings里添加时设置。然后设置菜单顺序,菜单category为com.android.settings.category.ia.system,查阅DashboardFragmentRegistry类中PARENT_TO_CATEGORY_KEY_MAP的对应关系,可知对应页面fragment为SystemDashboardFragment,即System页面,接着指定了Summary,icon,fragment等等。这样开发者选项菜单就被插入到了System页面下。

Settings中还存在其它动态插入的选项,例如Google GMS插入的首页菜单Google和Digital Wellbeing & parental controlls。

因为很多应用需要在Settings中增加菜单,作为应用的入口,这种不需要修改Settings代码,而直接修改应用的AndroidManifest.xml文件,实现解耦并自动适配。当然只有系统应用可以动态在Settings插入菜单。

SettingsLib

在分析Settings页面加载分析的时候,发现有部分类来自SettingsLib模块,这个模块是干嘛的呢?

SettingsLib是Android系统中一个专注于为Settings应用提供服务的库。它包含了许多Settings基础功能,并封装了一些操作。

源码路径:frameworks/base/packages/SettingsLib

从bp文件可知,编译后会生成一个SettingsLib的jar包。SettingsLib下根据不同功能,UI基础实现有许多模块,SettingsLib引用这些模块。

SettingsLib模块只有具有系统级别权限如系统应用,framework等才可以调用,第三方应用无法使用。

此时在想,为什么不直接在Settings中直接实现呢?因为将不同功能,UI等的基础实现放在一个公共模块中,可以方便其它与Settings交互的模块或framework使用,进行定制使用,因此,SettingsLib虽专注于为Settings,但它服务于系统,供系统进行Settings相关扩展使用,例如SystemUI模块就在使用SettingsLib相关实现。

相关资料

官方文档:Android“设置”菜单

总结

AndroidSettings具有以下优势:

  • 界面。引入Preference显示菜单设置项。统一的页面风格,页面简单,标题状态清晰。

  • 扩展性。Android Settings页面采用单个Activity(SubSettings),多Fragment,支持其它系统应用在Settings添加菜单,可扩展性强。

    Preference和PreferenceController的配合使用,方便定制新的设置项和页面,厂商定制性高。

  • 使用。添加了搜索,让用户可以轻松快速找到设置项。界面也决定了用户可以轻松修改各种设置项。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1122703.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构(递归,链表实现递归)

a.宏观描述&#xff1a;本质上说&#xff0c;递归将原问题转化为更小的同一问题。 b.递归本身也是一个函数&#xff0c;来完成某一功能。 1.递归终止的条件 2.递归操作 1.猴子吃桃问题 猴子第一天偷吃了该堆桃子的一半&#xff0c;觉得好吃多吃了一个&#xff1b;第二天吃了该堆…

【计算机网络】计算机网络中的一些基本概念

IP地址&#xff08;互联网协议地址&#xff09;&#xff1a; IP地址是分配给连接到互联网的设备的唯一标识符。它是由四个数字&#xff08;IPv4&#xff09;或者由八个数字&#xff08;IPv6&#xff09;组成。这些数字是网络层协议的一部分&#xff0c;它定义了数据包的路由。…

源码解析SpringMVC处理请求的完整流程

1.WebMvcAutoConfiguration EnableWebMvcConfiguration自动装配类负责加载SpringMVC涉及的HandlerAdapter、HandlerMapping、ExceptionHandlerExceptionResolver等。 SpringMVC利用 DispatchServlet 处理上游Tomcat的请求时,会被HandlerMapping、HandlerAdapter的相关子类分别…

Java JDK环境变量配置

JDK 安装和配置完成后&#xff0c;可以测试其是否能够正常运行。选择“开始”|“运行”命令&#xff0c;在打开的“运行”对话框中输入 cmd 命令&#xff0c;按 Enter 键进入到 DOS 环境下。 在命令提示符后输入并执行java -version命令&#xff0c;系统如果输出类似图 1 所示的…

Java类的继承

继承&#xff1a; 类的继承基本思想是基于某个父类进行扩展&#xff0c;得到一个新的子类&#xff0c;子类可以继承父类的原有属性和方法&#xff0c;也可以增加原来父类所不具备的属性和方法&#xff0c;或者重写父类中的方法; 重写&#xff1a; 【重写】也可以称为【覆盖】&…

阿里云服务结构--长期更新

CNCF 全称Cloud Native Computing Foundation&#xff08;云原生计算基金会&#xff09;&#xff0c;成立于 2015 年7月21日&#xff08;于美国波特兰OSCON 2015上宣布&#xff09;&#xff0c;其最初的口号是坚持和整合开源技术来让编排容器作为微服务架构的一部分&#xff0…

基于​Segment-and-Track Anything与ProPainter实现视频一键目标移除与一键祛除水印

一、 ProPainter 1.算法简介 ProPainter是由新加坡南洋理工大学&#xff08;Nanyang Technological University&#xff09;的S-Lab团队开发的一款视频修复工具。它融合了图像和特征修复的优势&#xff0c;以及高效的Transformer技术&#xff0c;旨在提供高质量的视频修复效果…

SolidWorks模型导入到Gazebo中

首先建立好solidworks模型&#xff0c;然后另存为stl格式&#xff0c; 导出为STL文件时&#xff0c;文件名最好不要是中文&#xff0c;并且要将后缀STL改为stl&#xff0c;否则Gazebo无法识别 这是我创建好的机器人充电桩模型&#xff1a; 尺寸是单位是mm&#xff1a; 135mm …

C语言程序的翻译环境和执行环境

目录 一、概述&#xff1a;翻译环境、执行环境 1.翻译环境 2.执行环境&#xff08;运行环境&#xff09; 二、详述翻译环境——编译环境、链接环境 1.编译环境 2.链接环境 三、详述编译过程——预编译、编译、汇编 1.预编译&#xff08;预处理&#xff09; 2.编译&…

JVM 堆外内存查看方法

JVM 堆外内存查看方法 概述 是否曾经想过为什么Java应用程序通过众所周知的*-Xms和-Xmx调整标志消耗的内存比指定的数量大得多 &#xff1f;由于各种原因和可能的优化&#xff0c;JVM可能会分配额外的本机内存。这些额外的分配最终可能使消耗的内存超出-Xmx* 限制。在本教程中…

第6周 .NET

好嘛&#xff01;本来以为上周SQL Server环境配置等已经够恶心了&#xff0c;没想到这周又得去搞所谓的Microsoft Visual Studio 2005了。 首先非常离谱的是&#xff0c;这个Microsoft Visual Studio 2005如果就是指Visual Studio 2005&#xff0c;那么已经是8年前的老的不行的…

【技能树笔记】网络篇——练习题解析(八)

目录 前言 一、LAN技术 1.1 堆叠与集群 1.2 MSTP的特点 二、WAN技术 2.1 PPP链路建立 2.2 PPPoE 2.3 组播 2.3.1 组播的IP 2.3.2 组播分发树 2.3.3 组播协议 三、IPv6基础 3.1 IPv6地址 3.2 IPv6协议 3.3 IPv6过渡技术 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1…

快速拿下 AI Prompt 工程师证书攻略!

Datawhale干货 贡献者&#xff1a;许文豪、司玉鑫、甘元琦 Prompt 是 AI 2.0 时代打开大模型能力的金钥匙&#xff0c;它能够大大的提高工作效率。 如果把大语言模型 (LLM&#xff0c;Large Language Model) 具象成一个的员工&#xff0c;那 Prompt 提示词则好比是你给员工下的…

留意差距:弥合网络安全基础设施的挑战

您最近一直在关注日益增加的网络威胁吗&#xff1f;如果您发现自己沉浸在 IT 或技术中&#xff0c;那么您可能会永远追求与时俱进。每天都会出现新的漏洞&#xff0c;这对保持消息灵通提出了巨大的挑战。 构建和维护能够应对复杂攻击者的网络安全基础设施所面临的挑战是真实存…

idea的debug调试

目录 断点条件设置(condition) 断点表达式(evaluate expression) 断点回退(reset frame) 断点条件设置(condition) 条件断点&#xff0c;一般是满足我们设置的某个条件时&#xff0c;debug断点才会生效。这种条件断点设置&#xff0c;我们一般用在多重循环中。 这儿我们以li…

codeforces (C++ In Love )

题目&#xff1a; 翻译&#xff1a; 思路&#xff1a; 1、在一个集合中有多组线段&#xff0c;如果有不相交的两组线段&#xff0c;则输出YES&#xff0c;否则输出NO。 2、每次操纵可以选择增加一组线段或者删除一组线段后&#xff0c;输出YES或者NO。 3、用flag标记该线段是否…

数据结构: map与set的简单实现

目录 map与set的模拟实现 1.基本框架 2.模拟实现map与set所需要做的事 1.使用模板 , 达到泛性编程 2.比较问题 3.迭代器 RBTree中: operator operator-- 4.map [ ] 的实现 5.使用普通迭代器构造const迭代器 效果 map与set的模拟实现 1.基本框架 map set 2.模拟实…

nodejs+vue旅行社网站系统-计算机毕业设计

激发了管理人员的创造性与主动性&#xff0c;对旅行社网站系统而言非常有利。 模块包括主界面&#xff0c;首页、个人中心、用户管理、景点分类管理、旅游景点管理、景点购票管理、酒店信息管理、酒店预定管理、旅游路线管理、系统管理等进行相应的操作。 目 录 摘 要 I ABST…

力扣刷题 day52:10-22

1.数组拆分 给定长度为 2n 的整数数组 nums &#xff0c;你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) &#xff0c;使得从 1 到 n 的 min(ai, bi) 总和最大。 返回该 最大总和 。 方法一&#xff1a;排序 #方法一&#xff1a;排序 def arrayPai…

【计算机网络】UDP的报文结构和注意事项

UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是一种无连接的协议&#xff0c;它在传输层中提供了简单、不可靠的数据传输服务。与TCP&#xff08;Transmission Control Protocol,传输控制协议&#xff09;不同&#xff0c;UDP不需要建立连接&…