Jetpack系列-ViewModel的使用及原理浅析

作者:碎星

简介

ViewModel在架构中用于承载业务逻辑和作为容器保存屏幕状态,它可以缓存界面的状态,并且能在配置变更后持久保留相应的界面状态。

在jetpack套件中,ViewModel随lifecycle一起提供。

优势

简介

ViewModel在架构中用于承载业务逻辑和作为容器保存屏幕状态,它可以缓存界面的状态,并且能在配置变更后持久保留相应的界面状态。

在jetpack套件中,ViewModel随lifecycle一起提供。

优势

  • 可以持久的保持界面状态:一是界面因配置变更导致的重建,不会销毁内存中的;二是可以借助SavedStateHandle在进程销毁-重建过程中恢复数据。
  • ViewModel具有作用域(如:Activity、Fragment等),ViewModel中的异步工作将被限定在这个Lifecycle上执行。
  • ViewModel可用用来承载之前处于界面层的部分业务逻辑:将数据层传递的数据处理成界面状态。
  • 可以作为桥梁在Activity与Fragment、Fragment与Fragment之间共享数据。

使用

定义

// 直接继承ViewModel
class DemoViewModel : ViewModel() {private val api = MyService()// 通常配合LiveData、StateFlow这些可感知对象为界面提供状态。private val _uiState = MutableLiveData("")val uiState: LiveData<String> = _uiState// 使用SharedFlow为界面提供事件回调private val _uiEvent = MutableSharedFlow<DemoEvent>()val uiEvent = _uiEvent.asSharedFlow()fun reqData(param: String) {// ViewModel能自动处理协程scope的生命周期viewModelScope.launch(Dispatchers.IO) {_uiEvent.emit(DemoEvent.Loading)try {val data = api.reqData(param)_uiState.postValue("rsp: $data")} finally {_uiEvent.emit(DemoEvent.Completed)}}}
}

基本用法

在androidx的Activity中使用:

class ViewModelDemoActivity : AppCompatActivity() {private lateinit var viewModel: DemoViewModel// viewmodel的ktx扩展库中提供了委托方式获取viewmodel实例
//  private val viewModel: DemoViewModel by viewModels()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_view_model_demo)// ViewModelProvider是获取viewmodel的基础工具,它需要一个ViewModelStoreOwner实例// 这个实例就是用来存储和管理viewmodel的,androidx的ComponentActivity// 实现了这个接口,因此可以直接使用AppCompatActivity来初始化ViewModelProvider。viewModel = ViewModelProvider(this).get(DemoViewModel::class.java)// 监听界面状态以及事件,并做出响应viewModel.uiState.observe(this) {Log.d(TAG, "received response: $it")}viewModel.uiEvent.onEach { showLoading(it == DemoEvent.Loading) }.launchIn(lifecycleScope)viewModel.reqData(param)}
}

在androidx的Fragment中使用:

class DemoFragment : Fragment() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// jetpack fragment也实现了ViewModelStoreOwner接口,因此也可以用于获取和管理viewmodelval selfViewModel = ViewModelProvider(this).get(FragmentViewModel::class.java)}override fun onAttach(context: Context) {super.onAttach(context)// 可以获取到Activity或者其它Fragment的ViewModel,只需要在构造ViewModelProvider// 时传递了对应的ViewModelStoreOwner。这个实例和DemoActivity// 中获取到的是同一个实例,因此你可以通过这个实例实现和Activity的通信。val parentViewModel = ViewModelProvider(requireActivity()).get(DemoViewModel::class.java)}
}

SavedStateHandle

SavedStateHandle主要用于在进程销毁-重建过程中恢复数据,它可以将数据持久化到存储中,并在重建后恢复数据。

class DemoViewModel(val savedState: SavedStateHandle) : ViewModel() {// 可以使用getLiveData将要获取的数据转为LiveDataprivate val _savedData = savedState.getLiveData<String>(DATA_KEY)val savedData: LiveData<String> = _savedData  fun saveData(data: String) {savedState[DATA_KEY] = data}fun readData(): String? {// 也可以直接获取return savedState[DATA_KEY]}companion object {private const val DATA_KEY = "data"}
}

AndroidViewModel

有时候ViewModel中可能会需要使用到Android Context(获取文本、颜色等资源),此时可以使用AndroidViewModel,它提供了一个getApplication()方法,可以很方便的获取上下文实例。使用方式如下:

class DemoViewModel(application: Application) : AndroidViewModel(application) { fun getString() = application.getString(R.string.hint_txt)
}

带参数的ViewModel

前面几个小节我们都假定了使用androidx的组件作为ViewModelStoreOwner来构造ViewModelProvider。这些androidx的组件会帮助我们自动提供ViewModel所依赖的SavedStateHandleApplication

然而,当我们使用自定义ViewModelStoreOwner时,或者想向ViewModel传递其它类型的参数时,就需要自定义ViewModeProvider.Factory了。

假如我们有如下ViewModel,它需要接收一个Repository作为参数:

class MyViewModel(private val myRepository: MyRepository
) : ViewModel() { }

为了实例化MyViewModel,我们需要再定义一个Factory,然后在create方法中获取依赖对象,构造ViewModel实例:

val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {override fun <T : ViewModel> create(modelClass: Class<T>,extras: CreationExtras): T {val repo = MyRepository(extras[MY_URL])return MyViewModel(repo) as T}
}

上面的CreationExtras用于从外界向ViewModel构造过程传递参数,它在ViewModelProvider构造时与factory实例一起传递给ViewModelProvider:

val extras = MutableCreationExtras().apply {this[MY_URL] = "https://..."
}
val viewModel = ViewModelProvider(this, factory, extras).get(MyViewModel::class.java)

原理分析

ViewModel的获取过程

ViewModelProvider的构造

顾名思义,ViewModelProvider就是用于提供ViewModel实例的类,它在构造时需要接受三个参数:

public open class ViewModelProvider
constructor(private val store: ViewModelStore,private val factory: Factory,private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,
)
  • ViewModelStore:用于存储ViewModel实例的类,内部持有一个HashMap保存实例,ViewModelProvider会将创建好的ViewModel实例保存到ViewModelStore中,之后再需要此类ViewModel的实例时就直接从中读取。
  • ViewModelProvider.Factory:前文已经提到,这是用于创建ViewModel实例的工厂,ViewModelProvider当需要ViewModel的实例又在ViewModelStore中没有找到对应实例时就会调用工厂的create方法创建。
  • CreationExtras:前文也已提到,它用于在创建ViewModel实例时从外界向构造过程传递参数,内部持有一个MutableMap,以key-value的形式存储和查找参数。

虽然ViewModelProvider需要三个参数来构造,但在实际使用中我们往往只在构造时传递了一个ViewModelStoreOwnerViewModelStoreOwner很好理解,可以用来提供ViewModelStore,而剩下两个参数,框架则提供了一系列的默认规则。

public constructor(owner: ViewModelStoreOwner
) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

ViewModelStore的获取

通常情况下ViewModelStoreViewModelStoreOwner提供,ViewModelStoreOwner是一个接口,里面只声明了一个getViewModelStore函数。androidx里的ComponentActivityFragmentFragmentViewLifecycleOwner等都实现了这个接口,下面我们看一看ComponentActivity中是如何实现的:

public ViewModelStore getViewModelStore() {ensureViewModelStore();return mViewModelStore;
}void ensureViewModelStore() {if (mViewModelStore == null) {NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();// 这里会尝获取配置变更前保存的实例,这是ViewModel在配置变更后仍能保持数据的关键if (nc != null) {mViewModelStore = nc.viewModelStore;}if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();}}
}

默认工厂和及构造参数

defaultFactory, defaultCreationExtras用于提供默认的ViewModelProvider.FactoryCreationExtras

internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =if (owner is HasDefaultViewModelProviderFactory)owner.defaultViewModelProviderFactory else instanceinternal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras =if (owner is HasDefaultViewModelProviderFactory) {owner.defaultViewModelCreationExtraselse CreationExtras.Empty

可以看到,两个方法首先都尝试将ViewModelStoreOwner实例转为HasDefaultViewModelProviderFactory,然后从中获取对应的默认值。如果没获取到,则返回ViewModelProvider自己提供的默认值。

先来看下ViewModelProvider提供的默认值:

public open class NewInstanceFactory : Factory {override fun <T : ViewModel> create(modelClass: Class<T>): T {return try {modelClass.newInstance()} catch (...) {...}}public companion object {private var sInstance: NewInstanceFactory? = nullpublic val instance: NewInstanceFactory get() {if (sInstance == null) sInstance = NewInstanceFactory()return sInstance!!}}
}

可以看到,这个工厂通过直接调用Class的newInstance方法直接创建实例,这种情况下ViewModel必需要提供无参构造函数。

接下来我们看HasDefaultViewModelProviderFactory,这也是一个接口,里面声明了getDefaultViewModelProviderFactorygetDefaultViewModelCreationExtras两个方法,分别用于获取默认的工厂实例与默认的构造参数。androidx中的ComponentActivityFragment也实现了这个接口,以ComponentActivity中的实现为例:

public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {if (mDefaultFactory == null) {mDefaultFactory = new SavedStateViewModelFactory(getApplication(),this,getIntent() != null ? getIntent().getExtras() : null);}return mDefaultFactory;
}public CreationExtras getDefaultViewModelCreationExtras() {MutableCreationExtras extras = new MutableCreationExtras();if (getApplication() != null) {extras.set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, getApplication());}extras.set(SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY, this);extras.set(SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY, this);if (getIntent() != null && getIntent().getExtras() != null) {extras.set(SavedStateHandleSupport.DEFAULT_ARGS_KEY, getIntent().getExtras());}return extras;
}

ComponentActivity会提供一个SavedStateViewModelFactory实例,并且会提供一个预置了一些内容的CreationExtras实例,里面有Application实例、SavedStateRegistryOwner 的实例、ViewModelStoreOwner的实例,以及Intent中extras参数bundle。

然后是SavedStateViewModelFactory,我们直接看create方法:

override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]?: throw IllegalStateException("VIEW_MODEL_KEY must always be provided by ViewModelProvider")return if (extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&extras[VIEW_MODEL_STORE_OWNER_KEY] != null) {val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)} else {findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)}// doesn't need SavedStateHandleif (constructor == null) {return factory.create(modelClass, extras)}val viewModel = if (isAndroidViewModel && application != null) {newInstance(modelClass, constructor, application, extras.createSavedStateHandle())} else {newInstance(modelClass, constructor, extras.createSavedStateHandle())}viewModel} else {// 这里是为了兼容旧版本...}
}

除开旧版本的兼容逻辑,上面的代码根据是否使用SavedStateHandle分为两类:当不使用SavedStateHandle时,将ViewModel的构造请求发送给内部的AndroidViewModelFactory实例来处理;当使用SavedStateHandle时,则自己调用createSavedStateHandle方法创建SavedStateHandle实例,然后创建对应的ViewModel实例。关于SavedStateHandle的分析见后文。

获取ViewModel

回到开始,我们通过调用ViewModelProvider实例的get方法来获取ViewModel实例:

public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {val canonicalName = modelClass.canonicalName?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")return get("${ViewModelProvider.AndroidViewModelFactory.DEFAULT_KEY}:$canonicalName", modelClass)
}public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {val viewModel = store[key]if (modelClass.isInstance(viewModel)) {(factory as? ViewModelProvider.OnRequeryFactory)?.onRequery(viewModel)return viewModel as T} else {if (viewModel != null) { }}val extras = MutableCreationExtras(defaultCreationExtras)extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY] = keyreturn try {factory.create(modelClass, extras)} catch (e: AbstractMethodError) {factory.create(modelClass)}.also { store.put(key, it) }
}

获取实例需要两个参数:key和要获取的ViewModel所属类的Class对象,ViewModelProvider会从ViewModelStore中根据key查找是否有现成的实例,有就直接使用,没有就调用Factory的create创建一个。

生命周期管理的实现

ViewModel的作用域会被限定为实例化时使用的ViewModelStoreOwnerViewModelStoreOwner结束生命周期时,ViewModel就会自动回调onCleared方法用于清理依赖生命周期的工作或者对象。

class MyViewModel(private val coroutineScope: CoroutineScope =CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {override fun onCleared() {coroutineScope.cancel()}
}

在2.5及更高版本的lifecycle库中,ViewModel提供了更多的支持:

  • ViewModel可以接受多个Closeable对象,ViewModel会在清除时自动调用这些对象的close方法。
  • ViewModel提供了addCloseablesetTagIfAbsent等方法,这些方法允许在任意时刻添加Closeable对象到ViewModel中,这些对象同样会被自动清除。

下面还是以ComponentActivity为例看一下清理过程的实现:

public ComponentActivity() {Lifecycle lifecycle = getLifecycle ();...getLifecycle().addObserver(new LifecycleEventObserver () {@Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {if (event == Lifecycle.Event.ON_DESTROY) {// Clear out the available contextmContextAwareHelper.clearAvailableContext();// And clear the ViewModelStoreif (!isChangingConfigurations()) {getViewModelStore().clear();}}}});
}

ComponentActivity会在构造时设置一个lifecycle监听,当activity onDestroy且并非配置改变引起的调用时,执行ViewModelStore的clear方法清空所有的ViewModel,在清空前,会调用每个ViewModel的clear方法。

// ViewModelStore
public final void clear() {for (ViewModel vm : mMap.values()) {vm.clear();}mMap.clear();
}
// ViewModel
final void clear() {mCleared = true;if (mBagOfTags != null) {synchronized (mBagOfTags) {for (Object value : mBagOfTags.values()) {closeWithRuntimeException(value);}}}// We need the same null check hereif (mCloseables != null) {synchronized (mCloseables) {for (Closeable closeable : mCloseables) {closeWithRuntimeException(closeable);}}}onCleared();
}

在ViewModel的clear方法中,对所有保存的Closeable执行close,然后调用onCleared

再看看ktx中的viewModelScope

public val ViewModel.viewModelScope: CoroutineScopeget() {val scope: CoroutineScope? = this.getTag(JOB_KEY)if (scope != null) return scopereturn setTagIfAbsent(JOB_KEY,CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))}internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {override val coroutineContext: CoroutineContext = contextoverride fun close() {coroutineContext.cancel()}
}

其本质也是提供了一个实现Closeable接口的CoroutineScope,然后通过setTagIfAbsent设置给ViewModel。

配置变更后仍保持数据的原理

前面在分析ViewModelStore的获取时,我们知道ComponentActivity在初始化ViewModelStore时,会先调用getLastNonConfigurationInstance,尝试恢复配置未变更前保存的ViewModelStore。与之对应的也有配置变更时保存ViewModelStore的逻辑:

public final Object onRetainNonConfigurationInstance() {// Maintain backward compatibility.Object custom = onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = mViewModelStore;if (viewModelStore == null) {// No one called getViewModelStore(), so see if there was an existing// ViewModelStore from our last NonConfigurationInstanceNonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {viewModelStore = nc.viewModelStore;}}if (viewModelStore == null && custom == null) return null;NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;nci.viewModelStore = viewModelStore;return nci;
}

onRetainNonConfigurationInstance会在Activity配置发生变更(如横竖屏切换)需要重建时,它会将返回的Object直接保存到ActivityClientRecord中:

// ActivityThread
void performDestroyActivity(ActivityClientRecord r, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason
) {...if (getNonConfigInstance) {try {r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();} catch (Exception e) {...}}...
}

Activity重建时再设置回去:

// ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.activityConfigCallback,r.assistToken, r.shareableActivityToken);...
}
// Activity
final void attach(Context context, ActivityThread aThread,...NonConfigurationInstances lastNonConfigurationInstances,...
) {...mLastNonConfigurationInstances = lastNonConfigurationInstances;...
}

既然ViewModelStore实例在重建时被保存和恢复了,那么其中的ViewModel及其状态数据也自然不会变化。

SavedStateHandle实现原理

前面我们在分析ComponentActivity中提供的默认工厂SavedStateViewModelFactory时,提到了工厂会在需要使用SavedStateHandle调用createSavedStateHandle创建实例:

public fun CreationExtras.createSavedStateHandle(): SavedStateHandle {val savedStateRegistryOwner = this[SAVED_STATE_REGISTRY_OWNER_KEY]?: throw IllegalArgumentException(...)val viewModelStateRegistryOwner = this[VIEW_MODEL_STORE_OWNER_KEY]?: throw IllegalArgumentException(...)val defaultArgs = this[DEFAULT_ARGS_KEY]val key = this[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY] ?: throw IllegalArgumentException(...)return createSavedStateHandle(savedStateRegistryOwner, viewModelStateRegistryOwner, key, defaultArgs)
}

这里会从CreationExtras中获取一些必要参数:

  • savedStateRegistryOwnerSavedStateRegistryOwner是jetpack-savedstate库中的一个接口,实现此接口的类表明可以在应用意外销毁时支持保存/恢复状态。jetpack中的ComponentActivityFragment实现了它。
  • viewModelStateRegistryOwner:这里实际获取的是当前的ViewModelStoreOwner,工厂会将创建出来的SavedStateHandle实例保存在一个专门的ViewModel—SavedStateHandlesVM中以加快访问。
  • defaultArgs:传递给SavedStateHandle的默认参数。
  • key:与待创建ViewModel相关联的键,用于从SavedStateHandlesVM中存取SavedStateHandle实例。
private fun createSavedStateHandle(savedStateRegistryOwner: SavedStateRegistryOwner,viewModelStoreOwner: ViewModelStoreOwner,key: String, defaultArgs: Bundle?
): SavedStateHandle {val provider = savedStateRegistryOwner.savedStateHandlesProviderval viewModel = viewModelStoreOwner.savedStateHandlesVM// If we already have a reference to a previously created SavedStateHandle// for a given key stored in our ViewModel, use that. Otherwise, create// a new SavedStateHandle, providing it any restored state we might have savedreturn viewModel.handles[key] ?: SavedStateHandle.createHandle(provider.consumeRestoredStateForKey(key), defaultArgs).also { viewModel.handles[key] = it }
}

这里获取到的SavedStateProvider专用于保存/恢复SavedStateHandle中状态,这个Provider在ComponentActivity 的构造时通过enableSavedStateHandles创建。

当savedStateHandlesVM没有缓存的实例时,就创建一个新实例返回,这里会先根据key从SavedStateProvider中读取之前保存的状态作为SavedStateHandle构造过程的参数。这样就实现了数据的恢复。

接下来看看数据的保存,我们知道SDK中Activity的状态保存是靠onSaveInstanceState回调实现的,SavedStateHandle也不例外:

// ComponentActivity
protected void onSaveInstanceState(@NonNull Bundle outState) {...mSavedStateRegistryController.performSave(outState);
}
// SavedStateRegistryController
fun performSave(outBundle: Bundle) {savedStateRegistry.performSave(outBundle)
}
//SavedStateRegistry
fun performSave(outBundle: Bundle) {....// 这里获取到所有注册的SavedStateProvider,调用他们的saveState获取到// 需要保存的数据,统一的保存到onSaveInstanceState传入的Bundle中val it: Iterator<Map.Entry<String, SavedStateProvider>> =this.components.iteratorWithAdditions()while (it.hasNext()) {val (key, value) = it.next()components.putBundle(key, value.saveState())}if (!components.isEmpty) {outBundle.putBundle(SAVED_COMPONENTS_KEY, components)}
}

用于保存SavedStateHandle中状态的SavedStateHandlesProvider,则早在Activity初始化时注册到了SavedStateRegistry中。来看看它的saveState方法:

private val viewModel by lazy { viewModelStoreOwner.savedStateHandlesVM }override fun saveState(): Bundle {return Bundle().apply {...viewModel.handles.forEach { (key, handle) ->val savedState = handle.savedStateProvider().saveState()if (savedState != Bundle.EMPTY) {putBundle(key, savedState)}}}
}

这里的ViewModel就是之前提到的用来保存所有SavedStateHandle实例的SavedStateHandlesVM

SavedStateHandle内部持有一个SavedStateProvider,在保存数据时,会将调用它的onSave方法将SavedStateHandle内部的状态打包成一个Bundle:

private val savedStateProvider = SavedStateRegistry.SavedStateProvider {...// Convert the Map of current values into a Bundleval keySet: Set<String> = regular.keysval keys: ArrayList<String> = ArrayList(keySet.size)val value: ArrayList<Any?> = ArrayList(keys.size)for (key in keySet) {keys.add(key)value.add(regular[key])}bundleOf(SavedStateHandle.KEYS to keys, SavedStateHandle.VALUES to value)
}

总结

ViewModel是Android Jetpack架构组件之一,它可以帮助我们解决Activity/Fragment等组件在配置更改时数据丢失的问题。通过创建ViewModel对象,我们可以将数据存储在其中,从而实现数据的持久化。ViewModel的使用非常灵活,我们可以将其与LiveData、Kotlin协程等其他组件一起使用,以实现更加强大的功能。在本文中,我们介绍了ViewModel的优势、简单的使用方法,并对主要功能的实现原理进行了分析。如果你有任何疑问,欢迎评论我们一起讨论。


为了帮助大家更好的熟知Jetpack 这一套体系的知识点,这里记录比较全比较细致的《Jetpack 入门到精通》(内含Compose) 学习笔记!!! 对Jetpose Compose这块感兴趣的小伙伴可以参考学习下……

Jetpack 全家桶(Compose)

Jetpack 部分

  1. Jetpack之Lifecycle
  2. Jetpack之ViewModel
  3. Jetpack之DataBinding
  4. Jetpack之Navigation
  5. Jetpack之LiveData

Compose 部分
1.Jetpack Compose入门详解
2.Compose学习笔记
3.Compose 动画使用详解

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

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

相关文章

第5步---MySQL的DQL查询语句

第5步---MySQL的DQL查询语句 DQL 数据库查询语言 1.基本的查询语句 1.完整得查询得语句 简化版的查询语句 select * from 表名 where 条件; 2.创建用于测试的表 1.创建测试数据 -- DQL -- 创建测试表 DROP TABLE IF EXISTS product; CREATE TABLE IF NOT EXISTS product( pi…

一文详解4种聚类算法及可视化(Python)

在这篇文章中&#xff0c;基于20家公司的股票价格时间序列数据。根据股票价格之间的相关性&#xff0c;看一下对这些公司进行聚类的四种不同方式。 苹果&#xff08;AAPL&#xff09;&#xff0c;亚马逊&#xff08;AMZN&#xff09;&#xff0c;Facebook&#xff08;META&…

计算机毕设项目之基于django+mysql的疫情实时监控大屏系统(前后全分离)

系统阐述的是一款新冠肺炎疫情实时监控系统的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系统的整体…

多线程实现与管理

进程与线程 进程 &#xff1a; 进程是操作系统进行资源分配的最小单位&#xff0c;每执行一个程序、一条命令操作系统都会启动一个进程&#xff0c;进程是一个程序的执行过程&#xff0c;当程序启动时&#xff0c;操作系统会把进程的代码加载到内存中&#xff0c;并为新进程分配…

uni-app根据经纬度逆解析详细地址

uni-app中的getLocation()方法可以获取到用户当前的地理位置&#xff08;经纬度&#xff09;、速度。 但是返回参数中的address在app中才会显示&#xff0c;小程序中不会显示&#xff0c;所以我们需要进行逆解析其地址&#xff0c;解析出它的地址信息。 1.首先要在腾讯位置服务…

如何正确地设置Outlook SMTP发送电子邮件(wordpress配置)

如何正确地设置Outlook SMTP发送电子邮件&#xff08;wordpress配置&#xff09; 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://pay.xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 正在寻找正确的Outlook SMTP设置&#xff1f…

LRU 算法

LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently Used&#xff0c;也就是说我们认为最近使用过的数据应该是是「有用的」&#xff0c;很久都没用过的数据应该是无用的&#xff0c;内存满了就优先删那些很久没用过的数据。 力扣&#xff08;LeetCode&#xff09…

自动方向识别式 TXB型电平转换芯片

大家好,这里是大话硬件。 在上一篇文章分析了LSF型的电平转换芯片,LSF型电平转换芯片最常见是应用在I2C总线上。I2C为OD型总线,LSF使用时增加电阻。 对于不是OD型总线的电平转换,比如UART,SPI,普通GPIO口信号,这些信号在进行双向电平转换使用什么样的芯片呢? 从上面…

面试之快速学习STL-deuqe和list

1. deque deque 容器用数组&#xff08;数组名假设为 map&#xff09;存储着各个连续空间的首地址。也就是说&#xff0c;map 数组中存储的都是指针如果 map 数组满了怎么办&#xff1f;很简单&#xff0c;再申请一块更大的连续空间供 map 数组使用&#xff0c;将原有数据&…

每天一道leetcode:433. 最小基因变化(图论中等广度优先遍历)

今日份题目&#xff1a; 基因序列可以表示为一条由 8 个字符组成的字符串&#xff0c;其中每个字符都是 A、C、G 和 T 之一。 假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。 例如&#xff0c;&quo…

博弈论 | 斐波那契博弈

斐波那契博弈 博弈论是二人或多人在平等的对局中各自利用对方的策略变换自己的对抗策略,达到取胜目标的理论。博弈论是研究互动决策的理论。博弈可以分析自己与对手的利弊关系,从而确立自己在博弈中的优势,因此有不少博弈理论,可以帮助对弈者分析局势,从而采取相应策略,最终达…

计算机提示mfc120u.dll缺失(找不到)怎么解决

在计算机领域&#xff0c;mfc120u.dll是一个重要的动态链接库文件。它包含了Microsoft Foundation Class (MFC) 库的特定版本&#xff0c;用于支持Windows操作系统中的应用程序开发。修复mfc120u.dll可能涉及到解决与该库相关的问题或错误。这可能包括程序崩溃、运行时错误或其…

13.实现业务功能--板块信息

目录 获取在首页中显示的版块 1. 实现逻辑 2. 创建扩展 Mapper.xml 3. 修改 DAO 4. 创建 Service 接口 5. 实现 Service 接口 6. 生成测试方法 7. 实现 Controller 8. 实现前端页面 在数据库中执行以下 SQL 语句&#xff1a; INSERT INTO t_board (id, name, article…

浅析Linux SCSI子系统:调试方法

文章目录 SCSI日志调试功能scsi_logging_level调整SCSI日志等级 SCSI trace events使能SCSI trace events方式一&#xff1a;通过set_event接口方式二&#xff1a;通过enable 跟踪trace信息 相关参考 SCSI日志调试功能 SCSI子系统支持内核选项CONFIG_SCSI_LOGGING配置日志调试…

kafka晋升之路-理论+场景

kafka晋升之路 一&#xff1a;故事背景二&#xff1a;核心概念2.1 系统架构2.2 生产者&#xff08;Producer&#xff09;2.2.1 生产者分区2.2.2 生产者分区策略 2.3 经纪人&#xff08;Broker&#xff09;2.3.1 主题&#xff08;Topic&#xff09;2.3.2 分区&#xff08;Partit…

WPS-RCE

版本&#xff1a; WPS Office 2023 个人版 < 11.1.0.12313 WPS Office 2019 企业版 < 11.8.2.12085 原理&#xff1a; Office 中的 WebExtension&#xff08;通常称为 Office 插件或 Office 应用程序&#xff09;是一种用于扩展 Microsoft Office 功能的技术。Office …

回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一…

FPGA应用学习笔记-----布线布局优化

优化约束&#xff1a; 设置到最坏情况下会过多 布局和布线之间的关系&#xff1a; 最重要的是与处理器努力的&#xff0c;挂钩允许设计者调整处理器努力的程度 逻辑复制&#xff1a; 不能放置多个负载&#xff0c;只使用在关键路径钟 减少布线延时&#xff0c;但会增加面积&a…

阿里云故障洞察提效 50%,全栈可观测建设有哪些技术要点?

本文根据作者在「TakinTalks 稳定性社区 」公开分享整理而成 #一分钟精华速览# 全栈可观测是一种更全面、更综合和更深入的观测能力&#xff0c;能协助全面了解和监测系统的各个层面和组件&#xff0c;它不仅仅是一个技术上的概念&#xff0c;更多地是技术与业务的结合。在“…

SpringBoot + MyBatis-Plus构建树形结构的几种方式

1. 树形结构 树形结构&#xff0c;是指&#xff1a;数据元素之间的关系像一颗树的数据结构。由树根延伸出多个树杈 它具有以下特点&#xff1a; 每个节点都只有有限个子节点或无子节点&#xff1b;没有父节点的节点称为根节点&#xff1b;每一个非根节点有且只有一个父节点&a…