Android Fragment 要你何用?2.0版本

作者:小鱼人爱编程

1. 老生常谈:为什么需要Fragment?

先看Activity、Fragment、View三者的关系:

Activity 拥有生命周期,但是需要和AMS通信(跨进程),比较臃肿。
View 不需要和AMS通信,但没有生命周期,不好处理复杂的逻辑(如网络请求数据渲染到View上)。
而Fragment介于两者之间,它拥有生命周期(借助于Activity),无需与AMS通信,速度快。

Fragment更多时候充当着中介的作用:

将Activity里复杂的UI逻辑分离出来放到Fragment里,将对View复杂的操作分离到Fragment里。

一言蔽之,使用Fragment的理由:

  1. 无需跨进程,轻量级
  2. 拥有生命周期

2. Fragment 的生命周期与重建流程

与Activity生命周期对比

Fragment创建分为动态和静态,以动态创建Fragment为例分析:

这么看Fragment生命周期的回调方法比Activity更多,还好大部分是成对出现有迹可循。
通常情况下,我们只需要重写onCreateView(),提供自定义界面就好。

Fragment 重建流程

现象

先看简单Demo:在Activity的onCreate里添加Fragment:

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.layout_fragment )val fragment = FishPureFragment()fragment.arguments = Bundle().apply {putString("hello", "fragment:${count++}")}supportFragmentManager.beginTransaction().add(R.id.root, fragment).commitNow()}

Activity 显示的同时Fragment也显示了,一切看起来很正常,此时我们将手机横竖屏切换一下:

可以看出,随着横竖屏的切换,创建的Fragment越来越多。
此种重建现象在很多场景下并不符合需求,比如进入Activity后拉取接口并显示弹窗,若使用DialogFragment展示弹窗,则Activity重建后会出现多个弹窗。

原理

我们知道,Fragment的生命周期依赖于Activity,当横竖屏切换时候Activity进行了重建,同时会查看关联该Activity的所有Fragment是否需要重建,若是则进行重建。
第一次显示Fragment后,横竖屏切换导致Activity重建,此时也会重建Fragment,而在Activity.onCreate()里又新建了Fragment,因此此时Activity里关联了2个Fragment。

核心点在于红色部分:

  1. Activity 销毁时保存Fragment状态
  2. Activity 重建时根据状态恢复并展示Fragment

除了横竖屏会导致Activity重建,其它配置项变更、系统内存紧张kill掉Activity等也会引起Activity重建

Fragment重建引发的Crash

和Activity等四大组件不一样的是:我们可以直接创建Fragment实例。
若是我们重写了Fragment默认构造函数:

class FishPureFragment(val str:String):Fragment() 

在Fragment重建的时候会Crash:

    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.fish.kotlindemo/com.fish.kotlindemo.fragment.dialogFragment.FishFragmentActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.fish.kotlindemo.fragment.dialogFragment.FishPureFragment: could not find Fragment constructor

原因是Fragment重建时会去寻找默认构造函数构造新的实例,而我们重写了构造函数,此时已经不存在默认的无参构造函数,当然会Crash。
因此,通常来说无需重写Fragment构造函数,若是想要传递参数给Fragment,可使用Fragment.setArguments()。

如何禁止Fragment重建

有以下几种方式:
第一种:
既然是Activity重建引发的Fragment重建,那么釜底抽薪,禁止Activity重建。
比如禁止屏幕旋转时重建Activity,可以在AndroidManifest.xml里配置:

android:configChanges="orientation|screenSize"

第二种:
不想改配置,也可以从Activity保存的状态入手,Frament重建依赖于恢复状态:

    //FragmentActivity.javaprivate void init() {addOnContextAvailableListener(new OnContextAvailableListener() {@Overridepublic void onContextAvailable(@NonNull Context context) {//Activity.onCreate里会调用此方法mFragments.attachHost(null /*parent*/);//找到需要恢复的Fragment状态Bundle savedInstanceState = getSavedStateRegistry().consumeRestoredStateForKey(FRAGMENTS_TAG);if (savedInstanceState != null) {//不为空则进行Fragment恢复,重建Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);mFragments.restoreSaveState(p);}}});}

也就是说只要恢复状态为空,那么Fragment就不会进行重建,而该状态是从Activity.onCreate(Bundle)里传递过来的,因此只需要在Activity里进行如下设置:

    override fun onCreate(savedInstanceState: Bundle?) {
//        super.onCreate(savedInstanceState)//状态置空super.onCreate(null)setContentView(R.layout.layout_fragment )val fragment = FishPureFragment()fragment.arguments = Bundle().apply {putString("hello", "fragment:${count++}")}supportFragmentManager.beginTransaction().add(R.id.root, fragment).commitNow()}

第三种:
将Activity下的所有恢复状态置空有点粗暴了,会影响到其它组件如View的恢复,而我们仅仅只需要禁止Fragment重建,使用如下方式:

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)savedInstanceState?.let {it.getBundle("androidx.lifecycle.BundleSavedStateRegistry.key")?.remove("android:support:fragments");}}

注:此种方式与系统实现版本有关,有些版本取不到value

3. Fragment add/remove/replace/hide/show的快速理解

add

添加一个Fragment实例到Fragment管理栈里,此间Fragment生命周期经历了从onAttach()逐渐到onActivityCreated()。
此时Fragment所绑定的布局(View)已经添加到指定的父布局里了。

当管理栈里已有一个Fragment实例:Fragment1,此时再往里面添加Fragment2,Fragment2入栈并将其绑定的布局添加到父布局里。
依据父布局属性不同,有不同的展示方式:

  1. 若父布局是FrameLayout,则Fragment2绑定的布局将会覆盖Fragment1绑定的布局。
  2. 若父布局是LinearLayout,则Fragment2绑定的布局将会添加到Fragment1绑定的布局左边/右边。

remove

将一个Fragment实例从Fragment管理栈里移除,此间Fragment生命周期经历了从onDestroyView()逐渐到onDetach()。
此时Fragment所绑定的布局(View)已经从指定的父布局里移除了。

replace

当Fragment管理栈里没有Fragment实例时,replace与add效果一致。
当Fragment管理栈里有Fragment1实例时,replace(Fragment2)将触发Fragment1的生命周期从onDestroyView()逐渐到onDetach(),而Fragment2生命周期从onAttach()逐渐到onActivityCreated(),最终展示Fragment2。
replace 可简单理解为remove+add。

hide

不会触发Fragment生命周期,仅仅只是把Fragment绑定的布局隐藏(GONE)。

show

hide的反向操作,不会触发Fragment生命周期,仅仅只是把Fragment绑定的布局显示(VISIBLE)。

4. Fragment与ViewModel的结合

Activity ViewModel与FragmentManagerViewModel

如上图,Activity ViewModelStore里存储着不同的ViewModel实例,其中FragmentManagerViewModel是与Fragment有关的。
当Activity销毁重建时,在Activity销毁阶段ViewModelStore并没有被释放,而是被保留下来,等到Activity重建时继续使用,而ViewModelStore实例不变,其内部的ViewModel也不变,这就是ViewModel的原理。

那么FragmentManagerViewModel 是什么时候添加到ViewModelStore里的呢?
入口在这:

    #FragmentActivity.javaprotected void onCreate(@Nullable Bundle savedInstanceState) {//绑定FragmentManagermFragments.attachHost(null /*parent*/);}

最终调用到:

    #FragmentManager.javavoid attachController(@NonNull FragmentHostCallback<?> host,@NonNull FragmentContainer container, @Nullable final Fragment parent) {//...if (parent != null) {mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);} else if (host instanceof ViewModelStoreOwner) {//host 即为承载的Activity//取出Activity ViewModelStoreViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);} else {mNonConfig = new FragmentManagerViewModel(false);}}#FragmentManagerViewModel.javastatic FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,FACTORY);//从Activity的ViewModelStore 取出FragmentManagerViewModel//若没有,则创建FragmentManagerViewModel实例return viewModelProvider.get(FragmentManagerViewModel.class);}

答案是:Activity.onCreate()将FragmentManagerViewModel添加到Activity ViewModelStore里。

FragmentManagerViewModel与Fragment的ViewModel

现在FragmentManagerViewModel可以在Activity重建时恢复,那么它和Fragment里的ViewModel又是如何关联的呢?

FragmentManagerViewModel 里有个成员变量:

private final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();

在Fragment里声明ViewModel:

private val vm by viewModels<MyViewModel2>()

查看viewModels调用链,关键要找到对应的ViewModelStore,而Fragment的ViewModelStore获取方式如下:

#Fragment.javapublic ViewModelStore getViewModelStore() {if (mFragmentManager == null) {throw new IllegalStateException("Can't access ViewModels from detached fragment");}return mFragmentManager.getViewModelStore(this);}#FragmentManagerViewModel.javaViewModelStore getViewModelStore(@NonNull Fragment f) {//mViewModelStores 是Map,key 是Fragment唯一标识符 value 为Fragment对应的ViewModelStoreViewModelStore viewModelStore = mViewModelStores.get(f.mWho);if (viewModelStore == null) {//不存在则创建viewModelStore = new ViewModelStore();//放入mapmViewModelStores.put(f.mWho, viewModelStore);}return viewModelStore;}

到此,流程就比较清晰了:

FragmentManagerViewModel里的mViewModelStores里存储着所有Fragment的ViewModelStore。也即是Fragment的ViewModel实际上是存储在FragmentManagerViewModel里的。

接着来整体捋一下Fragment的ViewModel是如何在重建时保持的。

显而易见,Fragment ViewModel实际上是间接依赖Activity ViewModeStore。

Fragment 关联的lifecycleScope

lifecycleScope 监听着Fragment生命周期,若是Fragment被销毁,则lifecycleScope也会被取消。

Fragment ViewModel关联的viewModelScope

viewModelScope 与ViewModel 生命周期保持一致,若是ViewModel被销毁(Fragment被销毁而非重建),则viewModelScope也会被取消。

5. DialogFragment(Dialog和Fragment的结晶)

DialogFragment 使用

普通的Dialog并没有生命周期,而不关联生命周期的Dialog处理异步请求比较麻烦,此时DialogFragment出现了。

class MyDialogFragment : DialogFragment() { override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {val tv = TextView(context).apply {text = "hello world"setTextColor(Color.RED)textSize = 30f}return tv}
}

显示DialogFragment:

MyDialogFragment().show(supportFragmentManager, "dd")

我们只需要定义Fragment所绑定的布局,最终布局将会显示在Dialog里。

DialogFragment 原理

Dialog 显示Fragment绑定的布局

你也许比较好奇:之前添加的Fragment都是指定父布局,将Fragment所绑定的布局添加到父布局里,那此时的DialogFragment所指定的父布局在哪呢?
从show方法入手:

    public void show(@NonNull FragmentManager manager, @Nullable String tag) {//就是添加普通的Fragment流程FragmentTransaction ft = manager.beginTransaction();ft.add(this, tag);ft.commit();}#FragmentTransaction.javapublic FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag)  {//没有指定父布局doAddOp(0, fragment, tag, OP_ADD);return this;}

与添加普通Fragment不同的是此时并没有指定Fragment的父布局。

Dialog会监听Fragment生命周期:

    #DialogFragment.javaprivate Observer<LifecycleOwner> mObserver = new Observer<LifecycleOwner>() {public void onChanged(LifecycleOwner lifecycleOwner) {if (lifecycleOwner != null && mShowsDialog) {//拿到Fragment绑定的ViewView view = requireView();if (mDialog != null) {//将View添加到Dialog里mDialog.setContentView(view);}}}};

当生命周期回调后拿到Fragment绑定的View添加到Dialog里。
如此一来,Fragment就完成了和Dialog的配合显示界面。

Dialog的创建时机

    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {return super.onCreateDialog(savedInstanceState);}

重写该方法可以自定义Dialog或是监听默认Dialog的创建。
值得注意的是:在onAttach()/onCreate()里是拿不到Dialog对象的,因为那时候还没有创建Dialog,它在onCreateView()之前创建的。

6. ViewPager2(Fragment与RecyclerView的结晶)

ViewPager2使用

除了DialogFragment,Fragment也与RecyclerView配合,形成了ViewPager2。
创建Adapter:

public class VPAdapter extends FragmentStateAdapter {private List<FishPureFragment> list = new ArrayList<>();public VPAdapter(@NonNull FragmentActivity fragmentActivity, List<FishPureFragment> list) {super(fragmentActivity);this.list = list;}@NonNull@Overridepublic Fragment createFragment(int position) {return list.get(position);}@Overridepublic int getItemCount() {return list.size();}
}

ViewPager2绑定Adapter:

        VPAdapter vpAdapter = new VPAdapter(this, list);viewPager2.setAdapter(vpAdapter);

可以看出,使用起来很简洁,ViewPager2的显示依靠着Fragment。

ViewPager2原理

RecyclerView缓存

在此之前先简单介绍一下RecyclerView(简称RV)的缓存设计。
RV缓存的是什么呢?
缓存的是ViewHolder(简称VH),VH里持有待显示的子布局(View)。

RecyclerView里有个内部类:Recycler,里面有5个缓存变量,归为3种缓存,分别为:
一级缓存(默认2个+1个预拉取)、二级缓存、三级缓存(默认5个,区分itemType)。
当RecyclerView渲染布局显示item时,先分别从一、二、三级缓存寻找可用的VH,若没有找到则重新创建子布局及其所属的VH,最终渲染。

VH有两种状态:

  1. VH保持着当前的数据状态,此种状态下当VH复用时可直接使用,对应一级缓存。
  2. VH仅仅保持着View,没有绑定数据,此种状态下当VH复用时需要重新绑定数据,也就是走onBindViewHolder()方法,对应三级缓存。

二级缓存是暴露给调用者设置自定义缓存的,此处先忽略。
先看看一、三级缓存是如何填充数据的。

再看RV是如何从缓存取数据的:

RecyclerView与Fragment联动

核心代码在FragmentStateAdapter.java里。
RecyclerView关键动作与Fragment的联动:

本质上还是将Fragment的View添加到RV提前构造的ItemView内。

我们来梳理一下ViewPager2(简称VP2)的滑动场景。

  1. VP2展示第一个元素,实际展示的是RV的第一个元素,此时RV回调了onCreateViewHolder、onBindViewHolder等回调,Fragment也走了生命周期,最终Fragment绑定的View添加到了RV的子Item里
  2. 当滑动VP2到第二个元素(下标为1)时,RV的第一个元素被放到一级缓存,此时RV触发移除子Item,注意这个时候并没有销毁Fragment
  3. 当滑动VP2回到第一个元素时,RV仅仅只需要addView即可,不会触发Fragment生命周期
  4. 当滑动VP2到后面几个元素时,此时一级缓存已满,将放到三级缓存里,然后触发子Item的回收,这个时候会移除对应的Fragment,Frament走生命周期里的销毁流程
  5. RV的预取元素时,会走onCreateViewHolder、onBindViewHolder回调,但不会触发addView,也就是不会触发Fragment的生命周期

VP2的缓存

public void setOffscreenPageLimit(@OffscreenPageLimit int limit)

设置VP2左右缓存的数量,默认是没有缓存的,也不能将缓存设置为0,只能是>=1。

VP2的缓存和RV的缓存是什么关系呢?
假设VP2缓存limit=1,再来梳理一下ViewPager2(简称VP2)的滑动场景。

  1. VP2展示第一个元素,实际展示的是RV的第一个元素,此时RV回调了onCreateViewHolder、onBindViewHolder等回调,Fragment也走了生命周期,最终Fragment绑定的View添加到了RV的子Item里。与此同时,VP2继续渲染第二个元素,与第一个元素步骤一致
  2. 当VP2滑动到第二个元素时,由于之前已经渲染过,此时是直接展示(RV无需addView)。于此同时会继续提前缓存第三个元素。
  3. 当VP2滑动到第一个元素时,,由于之前已经渲染过,此时是直接展示。

可以看出VP2的缓存实际上是提前将RV的元素渲染了,若设置了limit=1,那么此时RV活跃的Item有三个:当前1个+左右各一个。
当设置了VP2的缓存后,意味着多缓存了Fragment实例。

以上是Fragment实际应用与原理的相关内容。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

JavaEE——简单认识HTML

文章目录 一、简单解释 HTML二、认识 HTML 的结构三、了解HTML中的相关标签1.注释标签2.标题标签3.段落标签 p4. 换行标签 br5.格式化标签6.图片标签解释 src解释 alt解释其他有关 img 标签的属性 7.超链接标签 a8.表格标签9.列表标签10.input 标签11. select 下拉菜单以及 div…

Swagger示例

对于项目完成后不用写文档,好处还是蛮大的 不需要关注项目其他 只关注接口与实体类即可 SpringBoot项目 依赖 <!--Swagger依赖--> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version…

vulnhub靶场—matrix-breakout-2-morpheus靶机

一&#xff0c;实验环境 靶机ip&#xff1a;192.168.150.131攻击机ip&#xff1a;192.168.150.130 二&#xff0c;信息收集 arp-scan -l 扫描网段&#xff0c;寻找靶机ip 使用工具nmap进行端口扫描 nmap -A -T4 -p- 192.168.150.131 通过信息收集发现了靶机有80和81这两个…

今天遇到Windows 10里安装的Ubuntu(WSL)的缺点

随着技术的发展&#xff0c;越来越多开发者转向使用 Windows Subsystem for Linux&#xff08;WSL&#xff09;在 Windows 10 上进行开发&#xff0c;也就是说不用虚拟机&#xff0c;不用准备多一台电脑&#xff0c;只需要在Windows 10/11 里安装 WSL 就能体验 Linux 系统。因此…

阿里云CentOS主机开启ipv6

目录 一、云主机开启和使用 ipv6 1、网络和交换机开启 ipv6 2、创建 / 编辑云主机&#xff0c;开启ipv6 3、安全组放行ipv6端口 二、使用 ipv6 地址进行 ssh 连接 三、ipv6 地址绑定域名 一、云主机开启和使用 ipv6 1、网络和交换机开启 ipv6 进入网络、交换机详情页面…

初学Redis(Redis的启动以及字符串String)

首先使用在Windows PowerShell中输入指令来启动Redis&#xff1a; redis-server.exe 然后通过指令连接Redis&#xff1a; redis-cli 上图的127.0.0.1是计算机的回送地址 &#xff0c;6379是默认端口 上述代码中创建了两个键&#xff0c;注意Redis中严格区分大小写&#xff0…

解决docker运行elastic服务端启动不成功

现象&#xff1a; 然后查看docker日志&#xff0c;发现有vm.max_map_count报错 ERROR: [1] bootstrap checks failed [1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 解决办法&#xff1a; 1. 宿主机&#xff08;运行doc…

SecureCRT的“New line mode“

New line mode选中与不选中啥区别 在SecureCRT中&#xff0c;"New line mode"是一个关键配置项&#xff0c;主要用于解决不同操作系统之间的换行问题。当不选中"New line mode"时&#xff0c;SecureCRT会将接收到的数据按照原样发送&#xff0c;不会对数据…

Hadoop学习总结(MapRdeuce的词频统计)

MapRdeuce编程示例——词频统计 一、MapRdeuce的词频统计的过程 二、编程过程 1、Mapper 组件 WordcountMapper.java package com.itcast.mrdemo;import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; …

HP惠普光影精灵7笔记本Victus by HP 16.1英寸游戏本16-d0000原装出厂Windows11.21H2预装OEM系统

下载链接&#xff1a;https://pan.baidu.com/s/1LGNeQR1AF1XBJb5kfZca5w?pwdhwk6 提取码&#xff1a;hwk6 可适用的型号&#xff1a; 16-d0111tx&#xff0c;16-d0112tx&#xff0c;16-d0125tx&#xff0c;16-d0127tx&#xff0c;16-d0128tx&#xff0c;16-d0129tx&#…

MySql的C语言API

创建数据库&#xff08;开辟堆空间资源&#xff09; 连接数据库 查询数据库 获取查询结果&#xff0c;获取一行信息 mysql_use_result这个函数并不会真正获取数据&#xff0c;只有当使用mysql_fetch_row才真正获取 数据 mysql_store_result会直接把所有查询结果存储下来 释…

Python武器库开发-flask篇之Get与Post(二十五)

flask篇之Get与Post(二十五) 在Flask中通过request对象请求相关的数据,在正常的网页请求的过程中&#xff0c;有两种请求的方式&#xff0c;Get和Post Get请求 我们现在来看看在Flask中是如何以Get方式得到我们想要的值的&#xff0c;通过request.args可以获取Get请求中的所…

开源与闭源软件的辩论:对大模型技术发展的影响

目录 前言1 开源软件的优缺点1.1 开源软件的优点1.2 开源软件的缺点和挑战 2 闭源软件的优缺点2.1 闭源软件的优点2.2 闭源软件的缺点和挑战 3 大模型发展会走向哪一边结语 前言 近期&#xff0c;特斯拉CEO马斯克公开表示&#xff1a;OpenAI不该闭源&#xff0c;自家首款聊天机…

uniapp大概是怎么个开发法(前端)

写在前面&#xff0c;博主是个在北京打拼的码农&#xff0c;从事前端工作5年了&#xff0c;做过十多个大大小小不同类型的项目&#xff0c;最近心血来潮在这儿写点东西&#xff0c;欢迎大家多多指教。 对于文章中出现的任何错误请大家批评指出&#xff0c;一定及时修改。有任何…

mmdet 3.x 打印各类指标

和mmdet2.x中的修改地方不一样&#xff0c;在mmdet/evaluation/metrics/coco_metric.py中第72行将classwise设为True就可以打印各类指标了 但是在test的时候一直都是什么指标都不打印&#xff0c;不管是上面总的指标还是下面的各类指标&#xff0c;暂时不知道怎么处理 找到原因…

使用Pandas进行时间重采样,充分挖掘数据价值

大家好&#xff0c;时间序列数据蕴含着很大价值&#xff0c;通过重采样技术可以提升原始数据的表现形式。本文将介绍数据重采样方法和工具&#xff0c;提升数据可视化技巧。 在进行时间数据可视化时&#xff0c;数据重采样是至关重要且非常有用的&#xff0c;它支持控制数据的…

STM32 HAL库函数HAL_SPI_Receive_IT和HAL_SPI_Receive的区别

背景 前段时间开发一个按键板驱动&#xff0c;该板用的STM32F103系列单片机&#xff0c;前任工程师用STM32CubeMX生成的工程&#xff0c;里面全是HAL库调用&#xff0c;我接手后&#xff0c;学习了下HAL库的用法&#xff0c;踩坑不少&#xff0c;特别是带IT后缀的函数&#xf…

时序预测 | Python实现ConvLSTM卷积长短期记忆神经网络股票价格预测(Conv1D-LSTM)

时序预测 | Python实现ConvLSTM卷积长短期记忆神经网络股票价格预测(Conv1D-LSTM) 目录 时序预测 | Python实现ConvLSTM卷积长短期记忆神经网络股票价格预测(Conv1D-LSTM)预测效果基本介绍程序设计参考资料预测效果 基本介绍 时序预测 | Python实现ConvLSTM卷积长短期记忆神…

多视图聚类的论文阅读

当聚类的方式使用的是某一类预定义好的相似性度量时&#xff0c; 会出现如下情况&#xff1a; 数据聚类方面取得了成功&#xff0c;但它们通常依赖于预定义的相似性度量&#xff0c;而这些度量受原始方法的影响:当输入维数相对较高时&#xff0c;往往是无效的。 1. Deep Mult…

Flink1.17 DataStream API

目录 一.执行环境&#xff08;Execution Environment&#xff09; 1.1 创建执行环境 1.2 执行模式 1.3 触发程序执行 二.源算子&#xff08;Source&#xff09; 2.1 从集合中读取数据 2.2 从文件读取数据 2.3 从 RabbitMQ 中读取数据 2.4 从数据生成器读取数据 2.5 …