Android 10.0 Settings 加载流程

一、系统设置首页

代码路径:packages/app/Settings/

1 主界面加载:
    <!-- 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>

Settings的主界面是Settings.java,但是从Settings.java来看,除了大量的静态类继承SettingsActivity,就无其他有效信息了。但看其xml定义可以发现targetActivity属性,实质应是SettingsHomepageActivity.java。
先看其xml配置:

        <activity android:name=".homepage.SettingsHomepageActivity"android:label="@string/settings_label_launcher"android:theme="@style/Theme.Settings.Home"android:launchMode="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>

SettingsHomepageActivity.java,主要从onCreate()方法开始:

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);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();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);if (!getSystemService(ActivityManager.class).isLowRamDevice()) {// Only allow contextual feature on high ram devices.showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);}showFragment(new TopLevelSettings(), R.id.main_content);((FrameLayout) findViewById(R.id.main_content)).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}

可以看到主界面的layout为settings_homepage_container.xml:

<androidx.coordinatorlayout.widget.CoordinatorLayoutxmlns: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.NestedScrollViewandroid:id="@+id/main_content_scrollable_container"android:layout_width="match_parent"android:layout_height="match_parent"app:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior"><LinearLayoutandroid:id="@+id/homepage_container"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:descendantFocusability="blocksDescendants"><FrameLayoutandroid: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"/><FrameLayoutandroid: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.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><include layout="@layout/search_bar"/></com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

主界面布局中主要包含三部分:两个FrameLayout,一个顶部快捷搜索栏。其中Id为main_content的FrameLayout就是用来显示主设置内容的,即Settings的一级菜单项界面。.homepage.SettingsHomepageActivity 中的逻辑并不复杂,直接加载了TopLevelSettings这个Fragment。

showFragment(new TopLevelSettings(), R.id.main_content);

TopLevelSettings通过AndroidX的Preference来展示设置项列表,设置项列表的内容通过静态配置+动态添加的方式获取。
后面分开分析:SettingsActivity.java、DashboardFragment.java。

2 SettingsActivity.java

Settings 继承了 SettingsActivity,有着大量的静态类,但其中并没有实现任何逻辑,那它是怎么加载到自己应有的布局的呢?
其实这些Activity的逻辑都是在SettingsActivity中实现。
在父类SettingsActivity的onCreate()中:

    @Override  protected void onCreate(Bundle savedState) {  super.onCreate(savedState);  long startTime = System.currentTimeMillis();  //工厂类实现方法com.android.settings.overlay.FeatureFactoryImpl.java  final FeatureFactory factory = FeatureFactory.getFactory(this);  //获取菜单信息的工厂类,实现类为DashboardFeatureProviderImpl.java  mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);  mMetricsFeatureProvider = factory.getMetricsFeatureProvider();  // 第一步    从intent信息中获取<meta-data/>标签名为"com.android.settings.FRAGMENT_CLASS"的值(下文用于加载Fragment的类名)  getMetaData();  // 第二步final Intent intent = getIntent();if (intent.hasExtra(EXTRA_UI_OPTIONS)) {getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));}//获取上面getMetaData()得到的类名  final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);  //是否为快捷进入方式(如从其它的应用进入Settings的某个设置项)  mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||  intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);  ... ...  if (savedState != null) {  ... ...  } else {  //  第三步   加载布局  launchSettingFragment(initialFragmentName, isSubSettings, intent);  }  ... ...  }

第一步:
首先通过getMetaData()获取该Activity在manifest中配置的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 recoveryLog.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());}}

第二步:
通过getIntent()方法、getStartingFragmentClass()方法筛选出要启动的Fragment。
第三步:
通过launchSettingFragment()启动对应Fragment,这里的initialFragmentName参数就是第二步Intent中包含的EXTRA_SHOW_FRAGMENT参数,mFragmentClass不为空的情况下传入的就是mFragmentClass。

3 DashboardFragment.java

通过上面知道,SettingsHomepageActivity 直接加载了TopLevelSettings这个Fragment。而该Fragment继承了DashboardFragment,先来看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);}

可以看到构造方法中仅设置了个标志位,再根据framgments生命周期先来看onAttach()方法:

    @Overridepublic void onAttach(Context context) {super.onAttach(context);use(SupportPreferenceController.class).setActivity(getActivity());}

调用父类DashboardFragment.java的onAttach()方法,此方法主要是完成mPreferenceControllers的加载。
接着看onCreate()方法,因为TopLevelSettings未重写父类的方法,所以直接看父类DashboardFragment的onCreate()方法。

    @Overridepublic void onCreate(Bundle icicle) {super.onCreate(icicle);// Set ComparisonCallback so we get better animation when list changes.getPreferenceManager().setPreferenceComparisonCallback(new PreferenceManager.SimplePreferenceComparisonCallback());if (icicle != null) {// Upon rotation configuration change we need to update preference states before any// editing dialog is recreated (that would happen before onResume is called).updatePreferenceStates();}}

根据log定位发现,其后调用DashboardFragment.java的onCreatePreferences()方法:这里我也不知道怎么调用到这来的,哈哈。

    @Overridepublic void onCreatePreferences(Bundle savedInstanceState, String rootKey) {refreshAllPreferences(getLogTag());}/*** Refresh all preference items, including both static prefs from xml, and dynamic items from* DashboardCategory.*/private void refreshAllPreferences(final String TAG) {final PreferenceScreen screen = getPreferenceScreen();// First remove old preferences.if (screen != null) {// Intentionally do not cache PreferenceScreen because it will be recreated later.screen.removeAll();}// Add resource based tiles.displayResourceTiles();refreshDashboardTiles(TAG);final Activity activity = getActivity();if (activity != null) {Log.d(TAG, "All preferences added, reporting fully drawn");activity.reportFullyDrawn();}updatePreferenceVisibility(mPreferenceControllers);}

以看到此方法主要是用来加载显示的preference items,主要分为两部分,一个是静态xml定义的prefs(调用displayResourceTiles()方法),另一部分是从DashboardCategory动态加载(调用refreshDashboardTiles(TAG)方法,其中TAG为 “TopLevelSettings”)。
displayResourceTiles()
此方法主要是从xml资源文件中加载显示prefs:

    /*** Displays resource based tiles.*/private void displayResourceTiles() {final int resId = getPreferenceScreenResId();if (resId <= 0) {return;}addPreferencesFromResource(resId);final PreferenceScreen screen = getPreferenceScreen();screen.setOnExpandButtonClickListener(this);mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(controller -> controller.displayPreference(screen));}

首先调用getPreferenceScreenResId()方法获取所要加载的xml的ID:

    @Overrideprotected abstract int getPreferenceScreenResId();

最终回调用到子类TopLevelSettings.java的getPreferenceScreenResId()方法:

    @Overrideprotected int getPreferenceScreenResId() {return R.xml.top_level_settings;}

此主要是调用androidX Preference的addPreferencesFromResource()方法。此方法主要是将preferenceScreen下所有Preference添加到ArrayList中,然后再根据此集合构建生成PreferenceGroupAdapter,最后将此adapter设置到listview中,完成数据绑定,从而完成界面加载。在这里就要明白mPreferenceControllers是什么,在哪初始化的?
我们很快就可以找到:在onAttach()中添加的。

        final List<AbstractPreferenceController> controllers = new ArrayList<>();// Load preference controllers from codefinal List<AbstractPreferenceController> controllersFromCode =createPreferenceControllers(context);// Load preference controllers from xml definitionfinal 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) {controllers.addAll(controllersFromCode);}controllers.addAll(uniqueControllerFromXml);// And wire up with lifecycle.final Lifecycle lifecycle = getSettingsLifecycle();uniqueControllerFromXml.stream().filter(controller -> controller instanceof LifecycleObserver).forEach(controller -> lifecycle.addObserver((LifecycleObserver) controller));mPlaceholderPreferenceController =new DashboardTilePlaceholderPreferenceController(context);controllers.add(mPlaceholderPreferenceController);for (AbstractPreferenceController controller : controllers) {addPreferenceController(controller);}

可以发现:

  1. 从代码中加载preference controllers,调用createPreferenceControllers()方法;
  2. 从xml定义中加载preference controllers,调用getPreferenceControllersFromXml()方法。
  3. 过滤重复定义的controller等,赋值填充mPreferenceControllers。

再回到displayResourceTiles()方法中的:

mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(controller -> controller.displayPreference(screen));

此语句主要就是调用各个controller的displayPreference()方法。
以网络和互联网菜单项为例,xml中配置的controller为"com.android.settings.network.TopLevelNetworkEntryPreferenceController",查看TopLevelNetworkEntryPreferenceController.java发现,其内并未实现displayPreference()方法,查看继承关系:是继承BasePreferenceController的,接着查看BasePreferenceController中的displayPreference()方法。

    /*** Displays preference in this controller.*/@Overridepublic 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);}}}

又是调用BasePreferenceController父类AbstractPreferenceController中的displayPreference:

    /*** Displays preference in this controller.*/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 */);}}
  1. getPreferenceKey()获取preference的key,会调用到子类BasePreferenceController.java的getPreferenceKey()方法:
    @Overridepublic String getPreferenceKey() {return mPreferenceKey;}

而据上面分析到mPreferenceKey实质上即为xml中每个preference配置的android:key属性的值,即此处应为"top_level_network"。(以网络和互联网菜单项为例)
2. isAvailable();判断此preference是否可用即是否应该被显示。如果返回true,则被显示出来,反之则不被显示,最终也会调用到BasePreferenceController.java的isAvailable()方法:

    @Overridepublic final boolean isAvailable() {final int availabilityStatus = getAvailabilityStatus();return (availabilityStatus == AVAILABLE|| availabilityStatus == AVAILABLE_UNSEARCHABLE|| availabilityStatus == DISABLED_DEPENDENT_SETTING);}

注意:看这里的BasePreferenceController.java中的isAvailable()方法中的getAvailabilityStatus(),一直跟进去,会发现调用的是:BasePreferenceController子类TopLevelNetworkEntryPreferenceController.java的getAvailabilityStatus()方法:

    @Overridepublic int getAvailabilityStatus() {return Utils.isDemoUser(mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE_UNSEARCHABLE;}
  1. 调用setVisible()方法设置是否可被显示:setVisible(screen, prefKey, true /* visible */);
    frameworks/base/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
    protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {final Preference pref = group.findPreference(key);if (pref != null) {pref.setVisible(isVisible);}}
  1. 判断controller是否实现了Preference.OnPreferenceChangeListener接口,是,则设置监听。
    综上,如果希望preference不被显示在界面上,可以通过实现相关preference的controller的getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可。
  2. 继续看查看BasePreferenceController.java的displayPreference()方法的剩余语句:
        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);}}

根据子类controller实现的getAvailabilityStatus()方法的返回值判断是否需要将此preference置为不可点击。
至此,DashboardFragment.java中displayResourceTiles()方法分析完成。

总结:

  1. Settings的主Activity实质实现是在SettingsHomepageActivity.java内;
  2. Settings的主界面设置item的显示是在fragment上,fragment为TopLevelSettings.java,加载显示的布局为top_level_settings.xml;
  3. Settings主界面设置项item的加载显示主要分为两部分,一部分是xml定义的静态加载,xml为top_level_settings.xml;一部分是DashboardCategory来获取动态加载。
  4. 每个设置项item均为一个preference,通过xml定义加载时,必须要有一个controller,可以是在xml中定义"settings:controller"属性声明,名称必须与类的包名路径相同;也可直接在相关fragment中实现createPreferenceControllers()方法去调用构造相关controller。此二者存其一即可。
  5. xml中配置preference时,必须定义”android:key“属性;
  6. 需要隐藏不显示某个设置项时,一是可以直接在xml中注释其定义;二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可;
  7. 如果需要某个设置项不可点击,一是可以直接调用setEnabled()。二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值为DISABLED_DEPENDENT_SETTING即可。

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

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

相关文章

5. HTML常用标签

5.1 标签语义 学习标签是有技巧的&#xff0c;重点是记住每个标签的语义。简单理解就是指标签的含义。即这个标签是用来干嘛的。 根据标签的语义&#xff0c;在合适的地方给一个最为合理的标签。可以让页面结构给清晰。 5.2 标题标签 <h1>-<h6>(重要) HTML提供了…

【学习辅助】Axure手机时间管理APP原型,告别手机控番茄任务模板

作品概况 页面数量&#xff1a;共 30 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;时间管理、系统工具 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本品为「手机时间管理」APP原型&#xff0c;…

第二座山:万事万物的宇宙规律,一定是站在共赢才能实现

第二座山 真善 实现大众利益 破我执、破小钱关&#xff0c;破小情关&#xff0c;破小事关众生皆苦&#xff0c;十年的目的就是牺牲自己任何那些你的身外之物&#xff0c;它根本不可控 真善 实现大众利益 破我执、破小钱关&#xff0c;破小情关&#xff0c;破小事关 你今天…

Redis集群,你真的学会了吗?

目录 1、为什么引入集群 1.1、先来了解集群是什么 1.2、哨兵模式的缺陷 引入集群解决了什么问题 1.3、使用集群&#xff0c;如何存储数据 2、三种主流的分片方式【经典面试题】 2.1、哈希求余算法 2.1.1、哈希求余算法的介绍 2.1.2、哈希求余算法如何扩容 2.2、一致性…

C# 并发编程

C# 并发编程 前言 对于现在很多编程语言来说&#xff0c;多线程已经得到了很好的支持&#xff0c; 以至于我们写多线程程序简单&#xff0c;但是一旦遇到并发产生的问题就会各种尝试。 因为不是明白为什么会产生并发问题&#xff0c;并发问题的根本原因是什么。 接下来就让…

acwing算法基础之数学知识--筛法求<=n的所有质数

目录 1 基础知识2 模板3 工程化 1 基础知识 核心思想&#xff1a;把2~n中的非质数打上标记&#xff08;也即&#xff0c;筛掉&#xff09;&#xff0c;剩余的就是质数。 一般做法&#xff1a; int primes[N]; //存储所有的质数 int st[N]; //存储是否被排除 int cnt; int n;…

vcomp120.dll丢失怎么办?vcomp120.dll丢失的解决方法分享

vcomp120.dll丢失”。这个错误通常会导致某些应用程序无法正常运行&#xff0c;给用户带来困扰。那么&#xff0c;当我们遇到这个问题时&#xff0c;应该如何修复呢&#xff1f;下面我将为大家介绍四个修复vcomp120.dll丢失的方法。 一、使用dll修复程序修复 可以通过百度或许…

flink的副输出sideoutput单元测试

背景 处理函数中处理输出主输出的数据流数据外,也可以输出多个其他的副输出的数据流数据&#xff0c;当我们的处理函数有副输出时&#xff0c;我们需要测试他们功能的正确性&#xff0c;本文就提供一个测试flink副输出单元测试的例子 测试flink副输出单元测试 首先看一下处理…

基于SWAT-MODFLOW地表水与地下水耦合

耦合模型被应用到很多科学和工程领域来改善模型的性能、效率和结果&#xff0c;SWAT作为一个地表水模型可以较好的模拟主要的水文过程&#xff0c;包括地表径流、降水、蒸发、风速、温度、渗流、侧向径流等&#xff0c;但是对于地下水部分的模拟相对粗糙&#xff0c;考虑到SWAT…

矢量绘图软件Sketch 99 for mac

Sketch是一款为用户提供设计和创建数字界面的矢量编辑工具。它主要用于UI/UX设计师、产品经理和开发人员&#xff0c;帮助他们快速设计和原型各种应用程序和网站。 Sketch具有简洁直观的界面&#xff0c;以及丰富的功能集&#xff0c;使得用户可以轻松地创建、编辑和共享精美的…

NSF服务器

目录 1.简介 1.1 NFS背景介绍 1.2 生产应用场景 2.NFS工作原理 2.1 实例图 2.2 流程 3.NFS的使用 3.1.安装 3.2.配置文件 3.3.主配置文件分析 3.4 实验 服务端&#xff1a; 客户端&#xff1a; 3.5.NFS账户映射 3.5.1.实验2 3.5.2.实验3 4.autofs自动挂载服务…

springboot整合Redis后间歇性io.lettuce.core.RedisCommandTimeoutException

在springboot中引入spring-boot-starter-data-redis依赖时&#xff0c;默认使用的时Lettuce 产生这种问题的原因有如下两点&#xff1a; 1、Lettuce 自适应拓扑刷新&#xff08;Adaptive updates&#xff09;与定时拓扑刷新&#xff08;Periodic updates&#xff09; 是默认关闭…

深入理解 Django 信号机制

Django 信号&#xff08;signals&#xff09;是一种实现解耦的有力工具&#xff0c;它允许某些发生的事件通知其他部分的代码。信号主要用于在 Django 应用中的不同部分之间传递信息&#xff0c;尤其是在模型操作发生时。本文将深入探讨 Django 信号的工作原理、如何定义和接收…

Mysql学习笔记--基础

一&#xff0c;SQL最重要的增删改命令格式 1&#xff0c;insert into 表名&#xff08;不写这个括号里面的内容就默认所有字段都要添加&#xff09; values&#xff08;&#xff09; 插入单条数据 2&#xff0c;insert into 表名 (里面是列名) values&#xff08;根据列名依次…

Java Web——前端HTML入门

目录 HTML&CSS3&JavaScript简述 1. HTML概念 2. 超文本 3. 标记语言 4. HTML基础结构 5. HTML基础词汇 6. HTML语法规则 7. VS Code 推荐使用的插件 8. 在线帮助文档 HTML&CSS3&JavaScript简述 HTML 主要用于网页主体结构的搭建&#xff0c;像一个毛坯…

ImportError: cannot import name ‘HTTPClientFactory‘ from ‘twisted.web.client‘

在scrapy框架下添加爬虫文件&#xff0c;运行时报错&#xff1a; ImportError: cannot import name HTTPClientFactory from twisted.web.client 解决方法&#xff1a; 降低了twisted的版本&#xff0c;开始在Python3.9上降低twisted的版本&#xff0c;全都失败&#xff0c;…

【LeetCode:715. Range 模块 | 线段树】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Django(四、路由层)

文章目录 一、路由层1.路由匹配url方法第一个是参数 的正则表达式 二、正则无名分组与有名分组无名分组有名分组 三、反向解析1.概念无名分组动态路由解析有名分组动态路由解析 四、路由分发为什么要用路由分发&#xff1f; 1.总路由分发配置名称空间 五、伪静态的概念六、虚拟…

超级干货:光纤知识总结最全的文章

你们好&#xff0c;我的网工朋友。 光纤已经是远距离有线信号传输的主要手段&#xff0c;而安装、维护光纤也是很多人网络布线的基本功。 在网络布线中&#xff0c;通常室外楼宇间幢与幢之间使用的是光缆&#xff0c;室内楼宇内部大都使用的是以太网双绞线&#xff0c;也有使用…

企业微信开发教程一:添加企微应用流程图解以及常见问题图文说明

最近在前辈的基础上新添加了一个企微应用&#xff0c;过程中遇到了一些卡点&#xff0c;这里一一通过图片标注与注释的方式记录一下&#xff0c;希望能给后来人提供一些清晰明了的帮助&#xff0c;话不多说&#xff0c;大家直接看图吧。 &#xff08;文中包括一些本项目独有的配…