彻底掌握Android中的ViewModel

news2024/12/24 10:23:35

彻底掌握Android中的ViewModel

ViewModel 属于Android Jetpack库的一部分,是一种业务逻辑或屏幕状态容器。它提供了在配置更改(如屏幕旋转)后依旧保留相应状态的特性,帮助开发者以更加清晰和可维护的方式处理UI相关的数据,从而避免了在 Activity 或 Fragment 中直接处理数据持久化的问题。

ViewModel的使用

创建

日常开发中,ViewModel 经常充当 MVVM 架构的 VM 层,分担 Activity/Fragment 的部分逻辑,充当页面的数据存储容器。ViewModel 的创建方式有好几种,官方的 API 也改了几版,ViewModelProviders 已标为废弃,目前创建 ViewModel 统一使用 ViewModelProvider,代码实现有如下几种方式:

//无参构造函数ViewModel
class MyViewModel() : ViewModel() {
    //...
}

1.通过ViewModelProdiver

Activity 中:

private val viewModel by lazy {
	ViewModelProvider(this).get(MyViewModel::class.java)
}

Fragment 中:

private val viewModel by lazy {
	ViewModelProvider(this).get(MyViewModel::class.java)	//关联的是Fragment
}

private val viewModel by lazy {
	ViewModelProvider(requireActivity()).get(MyViewModel::class.java)	//关联的是Activity
}

2.通过Android KTX

KTX 扩展库提供了很多常用功能的简洁实现,KTX 分为若干模块,开发者需要按需引用,这里需要用到 Fragment KTX 模块,首先将该模块代码依赖到工程:

implementation "androidx.fragment:fragment-ktx:1.6.2"

然后就可以用以下方式进行 ViewModel 的创建了,代码非常简洁:

Activity 中:

private val viewModel by viewModels<MyViewModel>()

Fragment 中:

private val viewModel1 by viewModels<MyViewModel>()	 //关联的是Fragment
private val viewModel2 by activityViewModels<MyViewModel>()  //关联的是Activity

3.有参数的ViewModel创建方式

上面两种创建的 ViewModel 构造器都是无参数的,但 ViewModel 有时候也需要依赖注入外部对象,这时 ViewModel 就需要提供有参数的构造器,重点是创建自定义的 ViewModel 的创建工厂。

先看下 UserViewModel 的定义:

//数据仓库层
object UserRepo {
    //网络逻辑...
}

//构造器有参的ViewModel
class UserViewModel(val repo: UserRepo) : ViewModel() {
	//...
}

一般情况下,重写 ViewModelProvider.Factory 一个参数的 create 方法即可:

class Factory1(val repo: UserRepo): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return UserViewModel(repo) as T
    }
}

创建代码:

//通过ViewModelProvider方式:
private val viewModel by lazy {
    ViewModelProvider(this, UserViewModel.Factory1(UserRepo)).get(UserViewModel::class.java)
}

//通过KTX方式:
private val viewModel by viewModels<UserViewModel>(factoryProducer = { UserViewModel.Factory1(UserRepo) })

ViewModelProvider 中还提供了几个默认工厂:

  1. NewInstanceFactory:用来创建无参的 ViewModel,也是 ViewModelProvider 的默认工厂。
  2. AndroidViewModelFactory:继承自 NewInstanceFactory ,用来创建构造函数需要 Application 参数的 ViewModel 实例,特殊情况下会调用 NewInstanceFactory 创建无参的 ViewModel。

其实 KTX 最后也是通过 ViewModelProdiver 进行创建的,只不过通过 Kotlin 的属性委托机制将语法简化了,源码如下:

//ViewModel会通过by关键字委托到该类,每次使用该属性时,都会走到get方法中
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory,
    private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {
    private var cached: VM? = null

    override val value: VM
        get() {
            val viewModel = cached
            return if (viewModel == null) {
                val factory = factoryProducer()
                val store = storeProducer()
                //最终还是通过ViewModelProvider进行创建的
                ViewModelProvider(
                    store,
                    factory,
                    extrasProducer()
                ).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized(): Boolean = cached != null
}
使用

ViewModel 一般作为 MVVM 架构的 VM 层,可以将 Activity/Fragment 的业务逻辑都封装到 ViewModel 中,比较常见的就是网络请求了。Google 推荐如下方式实现:

class MyViewModel : ViewModel() {
    private val _userLiveData: MutableLiveData<User> = MutableLiveData<User>()
    val userData: LiveData<User>    //外部获取的类型是LiveData,不可变的,防止外部随意修改
        get() = _userLiveData
    fun doAction() {
        //...比如请求网络,并更新user
        _userLiveData.postValue(User("白泽..."))
    }
}
class ViewModelActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
        viewModel.userData.observe(this) {  //1.订阅数据变化
            //update UI
        }

        findViewById<Button>(R.id.button).setOnClickListener {
            viewModel.doAction()	//2.触发数据更新
        }
    }
}

ViewModel是如何存储的

首先来看看 ViewModel 的几个重要类,分别是 ViewMdoel、ViewModelProvider、ViewModelStore 和 ViewModelStoreOwner,关系类图如下:
在这里插入图片描述

这不是严格的UML图,只需大概理解即可,下面介绍下每个类的职责:

  • ViewModelProvider:只负责 ViewModel 的创建。无参构造器的 ViewModel 可以直接用其内部提供的 NewInstanceFactory 工厂创建,如果ViewModel 需要构造器参数,则需要实现 ViewModelProvider.Factory 接口并完善创建逻辑。

  • ViewModelStore:负责 ViewModel 实例的存储,内部通过 HashMap 实现,map 的 value 就是 ViewMode 的实例,key 的生成规则如下:

    private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"
        //...
        String canonicalName = modelClass.getCanonicalName(); //返回此类的规范名称,如com.xx.x.MyViewModel
        get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    	//...
    

    所以同一 ViewModelStore 中,同一个类型的 ViewModel 并不会重复创建。

  • ViewModelStoreOwner:负责提供 ViewModelStore,常见的 ViewModelStoreOwner 有 ComponentActivity、Fragment 等,它们的内部会对 ViewModelStore 进行管理,在适当的时机进行创建和回收。以 ComponentActivity 为例,其内部会监听生命周期,并在生命周期变动时调用如下代码,确保 mViewModelStore 的存在:

    //1.Activity销毁时调用该方法临时保存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 NonConfigurationInstance
            NonConfigurationInstances 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;
    }
    
    //2.Activity创建时恢复上次保存的ViewModelStore
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;  //拿到上次保存的ViewModelStore
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();  //创建新的ViewModelStore
            }
        }
    }
    
    

    ViewModelStore 的销毁时机:Activity 走到 Destroy 并且使非配置更改(如正常finish)。

    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
    

    面试常问的问题:

    为什么 Activity 在旋转屏幕时,Activity 对象都发生重建了,但 ViewModel 却还是原来的对象?

    ViewModel 是怎么保存和恢复的?

    上面两个问题问的其实是 mViewModelStore 的保存和恢复,因为它是持有 ViewModel 实例的仓库。而 mViewModelStore 的存储和恢复是通过 onRetainNonConfigurationInstancegetLastNonConfigurationInstance来实现的。

    在配置更改时会调用 Activity#onRetainNonConfigurationInstance() 保存 mViewModelStore 对象,并在 Activity 重建后通过 getLastNonConfigurationInstance 方法获取上次保存的 ViewModelStore 对象,如果有则直接使用,否则创建新的实例对象。

    状态保存:onRetainNonCongigurationInstance

    该方法是 Android 提供的在配置更改时,临时保存 Activity 数据的 API。onRetainNonConfigurationInstance() 允许 Activity 在配置改变之前返回一个对象,这个对象随后可以在 Activity 重新创建后的getLastNonConfigurationInstance()方法中被检索到。

    下面是该机制的源码,在设备配置发生更改时(如旋转屏幕),会调用到 ActivityThread#handleRelaunchActivity 方法:

    //看方法名字可以知道是处理Activity重建逻辑的
    public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) {
        //...
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
        //...
    }
    
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        //1. 处理旧Activity的销毁,注意第三个参数是getNonConfigInstance,传入的是true
        handleDestroyActivity(r, false, configChanges, true, reason);
    
        //2. 处理Activity新建逻辑
        handleLaunchActivity(r, pendingActions, customIntent);
    }
    

    这个方法处理了两件事,一是旧 Activity 的回收,二是 Activity 的新建。先从 Activity 销毁开始看:

    @Override
    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
        //...
    }
    
    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        //...注意:getNonConfigInstance为true
        if (getNonConfigInstance) {
            try {
                //调用旧activity的方法,并保存到ActivityClientRecord对象中
                r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                //...
            }
        }
        //...
    }
    
    NonConfigurationInstances retainNonConfigurationInstances() {
        Object activity = onRetainNonConfigurationInstance();  //1.调用了onRetainNonConfigurationInstance方法
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); //2.可以缓存一些自定义数据
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();  //3.Fragment状态
    	//...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if (mVoiceInteractor != null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;
    }
    

    可以看到,Activity 销毁做了两件事:

    1. 调用了 Activity 对象的 onRetainNonConfigurationInstance 方法拿到临时对象,并赋值给 ActivityClientRecord#lastNonConfigurationInstances变量。
    2. 调用 Activity 的 pause、stop、destroy 等生命周期方法。

    接下来看 Activity 新建逻辑:

    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        //...
        final Activity a = performLaunchActivity(r, customIntent);
    	//...
    }
    
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //...
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //1.通过反射创建Activity对象
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        }
        //...
        
        try {
            if (activity != null) {
                //2.调用 attach 方法,将缓存信息传入
                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);
    	//...
        return activity;
    }
    

    创建 Activity 流程同样做了两件事:

    1. 通过反射创建 Activity 实例对象
    2. 调用 attach 方法,将销毁时缓存在 ActivityClientRecord#lastNonConfigurationInstances 变量中的临时变量关联到新的 Activity 对象
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
    	//赋值给了mLastNonConfigurationInstances
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
    }
    
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
    

    执行完 Activity 的 attach 方法后,就可以通过 getLastNonConfigurationInstance 方法获取之前 Activity 销毁时保存的状态数据了,到此 Activity 保存和恢复数据的链路就通了。

    onRetainNonConfigurationInstance 方法用 final 修饰,并已标为废弃了,其实改保存数据方案在 Android 3.0 就已经废弃了,Google 不希望我们用这个机制去进行 Activity 状态保存,而是推荐用基于该机制上衍生的 ViewModel 进行状态保存,使用起来更简单、更安全。

    onRetainNonCongigurationInstance 和 onSaveInstanceState区别:

    onSaveInstanceState 同样是用于处理 Activity 状态保存和恢复的方法,它与 onRetainNonCongigurationInstance 方式区别如下:

    1. 使用场景不同
      • onRetainNonCongigurationInstance 用于在设备配置更改时(如屏幕旋转)临时保存 Activity 的状态或数据。
      • onSaveInstanceState 用于Activity 即将被销毁时(无论是由于用户离开、配置更改还是系统回收资源),保存 Activity 的状态或数据,是一个更通用、更灵活的状态保存机制。。
    2. 支持数据类型不同
      • onRetainNonCongigurationInstance 返回 Object 类型对象,可以是任何类型,包括 Activity 实例本身或大型数据结构,如果使用不当容易造成内存泄漏。
      • onSaveInstanceState 通过 Bundle 对象来保存状态,只能存储基本数据类型、可序列化的对象或实现了 Parcelable 接口的对象,确保数据的安全性和可恢复性。
    3. 恢复数据方式不同
      • onRetainNonConfigurationInstance 在Activity重新创建后,可以通过调用getLastNonConfigurationInstance()方法来检索之前保存的数据。
      • onSaveInstanceState 在Activity重新创建时,系统会将之前保存的Bundle对象传递给onCreate(Bundle savedInstanceState)onRestoreInstanceState(Bundle savedInstanceState)方法。

ViewModel的协程作用域

协程是 Kotlin 的又一高效编程利器,使用协程可以非常简单的进行多线程协作。ViewModel 提供了和其生命周期一致协程作用域,可以引入 KTX 的 ViewModel 模块,让使用更简单:

dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6"
}

使用时:

viewModel.viewModelScope.launch {
    if (isActive) {  //判断协程是否被取消了
        //doAction...
    }
}

下面来探索一下 ViewModel 协程作用域是如何管理的,首先看 viewModelScope 源码:

public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)  //1.缓存的协程作用域对象
        if (scope != null) {
            return scope
        }
        //2.创建协程
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

可以看到,如果没从缓存获取到则直接通过 setTagIfAbsent 方法创建,协程上下文类型是 SupervisorJobDispatchers.Main,可以让发生异常时不影响其他协程,并且代码运行在主线程。

private final Map<String, Object> mBagOfTags = new HashMap<>();

//1.setIfAbsend 系列方法一般表示,如果存在旧值就不进行赋值了,防止多次创建
<T> T setTagIfAbsent(String key, T newValue) {
    T previous;
    //2.通过同步锁,防止多线程场景错误
    synchronized (mBagOfTags) {
        previous = (T) mBagOfTags.get(key);
        if (previous == null) {
            mBagOfTags.put(key, newValue);
        }
    }
    T result = previous == null ? newValue : previous;
    if (mCleared) {
        closeWithRuntimeException(result);
    }
    return result;
}

通过该方法可以看到,ViewModel 的协程作用域创建后会缓存在 ViewModel 中的 mBagOfTags 中,这是一个 map 结构,key 为 JOB_KEY ,value 为协程作用域对象。

知道了 ViewModel 的协程作用域是如何创建和保存的,下面看协程是如何取消的。

还记得上面 ViewModel 对象的保存逻辑吗,ComponentActivity 会监听 DESTROY 生命周期,Activity 正常销毁时,会执行 ViewModelStore 的 clear 方法,ViewModelStore 又会遍历所有保存的 ViewModel 对象,并调用其 clear 方法。

final void clear() {
    mCleared = true;
    //1.加锁,进行协程作用域的取消
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                //2.取消协程
                closeWithRuntimeException(value);
            }
        }
    }
    //2.ViewModel销毁时会调用该方法,可以重写它进行长时间任务的清理工作
    onCleared();
}

可见最终会调用到 closeWithRuntimeException 方法进行协程取消,内部其实是 coroutineContext.cancel()

ViewModel 的协程作用域会在获取时进行创建,并缓存在 ViewModel 的 mBagOfTags 映射表内部,在 ViewModel 销毁时取消。协程取消并不会强制终端代码逻辑,使用 ViewModel 协程作用域进行长时间任务时,注意使用 isActive 方法适时判断协程是否被取消了。

ViewModel 的协程作用域和 Lifecycle 的协程作用域有何区别?

Android 中除了 ViewModel 提供了协程作用域外,Lifecycle 也提供了 lifecycleScope 协程作用域,首先看下 Lifecycle 的协程作用域是如何创建、保存和销毁的:

//Lifecycle:
//1.AtomicReference让对象读,写都是原子操作,保证修改对象引用时的线程安全
public var internalScopeRef: AtomicReference<Any> = AtomicReference<Any>()

public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            //2.创建协程作用域
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            //3.通过CAS机制设置对象
            if (internalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }
internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    init {
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            coroutineContext.cancel() //1.取消协程
        }
    }

    fun register() {
        launch(Dispatchers.Main.immediate) {
            if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
                lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
            } else {
                coroutineContext.cancel()
            }
        }
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
            lifecycle.removeObserver(this)
            coroutineContext.cancel()  //2.取消协程
        }
    }
}

Lifecycle 的代码非常简单,同样在获取时创建协程作用域,并通过CAS机制保存到 internalScopeRef 对象引用,在 DESTROY 时进行协程的销毁操作。

通过上面分析 ViewModel 和 Lifecycle 的协程作用域相关代码,可以分析出以下几点区别:

  • ViewModel 协程作用域创建时通过 synchronized 同步锁保证线程安全;Lifecycle 协程作用域创建时通过 while 循环 + CAS 机制保证线程安全。相对来说 CAS 机制更能保证效率,ViewModel 使用 synchronized,主要还是因为其中的 mBagOfTags,它是一个Map,Android 官方因为一些旧系统的限制,导致无法使用ConcurrentHashMap,所以才出此下策。
  • ViewModel 和 Lifecycle 的协程作用域生命周期不同,因为销毁时机不一样,就像刚开始 ViewModel 的生命周期和 Activity 生命周期一样。

总结

Android 的 ViewModel 是一个强大的架构组件,它通过提供数据、管理状态以及生命周期感知等能力,帮助开发者构建更加健壮、易于维护和测试的应用。在 MVVM 代码架构中,ViewModel 是视图(View)与数据(Model)之间的桥梁,它负责为 UI组件提供数据,并管理 UI组件的状态,UI状态与业务逻辑分离,使得代码耦合性更低,更易于测试。

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

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

相关文章

MySQL安装文档-Windows

文章目录 MySQL安装1. 安装2. 配置 MySQL安装 1. 安装 1). 双击官方下来的安装包文件 2). 根据安装提示进行安装 安装MySQL的相关组件&#xff0c;这个过程可能需要耗时几分钟&#xff0c;耐心等待。 输入MySQL中root用户的密码,一定记得记住该密码 2. 配置 安装好MySQL之后…

从零开始构建GPT风格的LLM分类器 微调GPT模型进行垃圾邮件分类

在这篇文章中&#xff0c;我想向您展示如何将预训练的大型语言模型&#xff08;LLM&#xff09;转变为强大的文本分类器。 为什么专注于分类&#xff1f; 首先&#xff0c;将预训练模型微调为分类器提供了一种温和而有效的微调入门方式。其次&#xff0c;许多现实世界和商业挑…

SpringBoot技术栈的网上超市开发实践

2 系统开发技术 这部分内容主要介绍本系统使用的技术&#xff0c;包括使用的工具&#xff0c;编程的语言等内容。 2.1 Java语言 Java语言自公元1995年至今&#xff0c;已经超过25年了&#xff0c;依然在软件开发上面有很大的市场占有率。当年Sun公司发明Java就是为了发展一门跨…

python类的call方法与init方法

1. call方法 在对象被调用了的时候就会调用call方法a(666) class A:def __call__(self, args):print(call 方法被调用了,args) aA() a(666) 2.init方法 创建对象的时候 init 方法被调用 class A:def __init__(self,args):print(创建对象的时候 init 方法被调用了,args) aA(…

shardingjdbc-读写分离配置

文章目录 1、application.yml2、shardingsphere.yaml3、创建实体类 User4、创建 UserMapper5、添加依赖6、读写分离测试7、事务测试 我们的主从复制已经提前搭建好&#xff1a; mysql-搭建主从复制&#xff1a;https://blog.csdn.net/m0_65152767/article/details/142214434 1…

使用 Python 模拟光的折射,反射,和全反射

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

【一句话点破】【C++重要题目】基类与派生类的成员变量值被对象调用的结果(二)

【一句话点破】基类/派生类的成员变量由哪个对象初始化的&#xff0c;哪个对象调用该成员变量时就用由它初始化的值 [尤其找准是基类对象or派生类对象的值] 【重要例题】15浙工大卷二读程序5题 可运行代码如下 #include "bits/stdc.h" #include<iostream> u…

畅阅读微信小程序

畅阅读微信小程序 weixin051畅阅读微信小程序ssm 摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用j…

CVE-2024-1112 Resource Hacker 缓冲区溢出分析

漏洞简述 CVE-2024-1112 是 Resource Hacker 软件的一个缓冲区溢出漏洞。该漏洞存在于版本 3.6.0.92 中。由于软件在处理命令行中的文件路径时未对文件字符串长度进行限制&#xff0c;过长的字符串参数导致内存被过度写入&#xff0c;从而引发缓冲区溢出。 漏洞复现 构造长度…

简单的评论系统【JavaScript】

这段代码实现了一个简单的评论系统&#xff0c;用户可以输入评论并提交&#xff0c;评论会显示在页面上&#xff0c;同时可以通过点击“删除”按钮来删除相应的评论。 实现效果&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"zh"><head…

Excel名字查重筛选,查找重复内容原来这么简单

大家好&#xff0c;这里是效率办公指南&#xff01; &#x1f50d; 在处理大量数据时&#xff0c;尤其是人员名单或客户信息时&#xff0c;确保没有重复的名字是非常重要的。在Excel中&#xff0c;有几种方法可以帮助我们快速查找和处理重复的名字。今天&#xff0c;我们将介绍…

2024年9月24日历史上的今天大事件早读

1550年9月24日 明代戏剧家汤显祖出生 1852年9月24日 法国人吉法尔制造的用蒸汽机推进的飞船试飞成功 1884年9月24日 中国近代化学的先驱徐寿逝世 1905年9月24日 吴樾壮炸五大臣&#xff0c;身殉革命 1909年9月24日 京张铁路通车 1910年9月24日 剧作家曹禺诞生 1930年9月2…

Python基础练习题‌100道电子版及源码文件

Python基础练习题‌&#xff0c;旨在帮助学习者巩固和提升Python编程技能。以下是一些精选的练习题目&#xff0c;包括但不限于&#xff1a; 基础语法练习‌&#xff1a;涉及变量定义、数据类型、运算符、条件语句、循环等基础语法结构的应用。例如&#xff0c;编写程序来处理数…

使用Adobe XD进行制作SVG字体

制作SVG字体的办法有很多&#xff0c;我这里选择了Adobe XD进行制作。 1.选择画布尺寸 2 输入文本 设置字体样式 3 设置画布背景 4 转换字体&#xff08;物件&#xff09;路径 5 设置组 复制SVG代码 6 放入到Html中 <!DOCTYPE html> <html lang"zh">&l…

稀疏向量 milvus存储检索RAG使用案例

参考&#xff1a; https://milvus.io/docs/hybrid_search_with_milvus.md milvus使用不方便&#xff1a; 1&#xff09;离线计算向量很慢BGEM3EmbeddingFunction 2&#xff09;milvus安装环境支持很多问题&#xff0c;不支持windows、centos等 在线demo&#xff1a; https://co…

基于单片机的智能窗帘控制系统-设计说明书

设计摘要&#xff1a; 智能窗帘控制系统是一种利用单片机技术实现的智能化控制系统&#xff0c;可以实现窗帘的自动开合和定时控制功能。本系统的设计基于单片机技术&#xff0c;结合传感器、电机和执行器等硬件设备&#xff0c;实现对窗帘的智能化控制。通过传感器采集环境信…

同一网络下两台电脑IP一样吗?探究局域网内的IP分配机制

在日常生活和工作中&#xff0c;我们经常会在同一网络环境下使用多台电脑。这时&#xff0c;一个常见的问题就会浮现&#xff1a;同一网络下两台电脑IP一样吗&#xff1f;这个问题看似简单&#xff0c;但实际上涉及到局域网内的IP分配机制。本文将深入探讨这一问题&#xff0c;…

JDBC PreparedStatement解决SQL注入方案

文章目录 获取PreparedStatement对象PreparedStatement是如何解决SQL注入问题的PreparedStatement的 应用上述如何解决sql注入的问题呢&#xff1f; 获取PreparedStatement对象 PreparedStatement是Statement的子接口&#xff0c;可以防止sql注入问题。可以通过Connection接口…

AI最大的应用是什么,如何成为初代AGI产品经理?

❝ 在当今这个由数据驱动的时代&#xff0c;AI技术正以前所未有的速度发展&#xff0c;它不仅改变了我们与数字世界的互动方式&#xff0c;更在物理世界中掀起了一场革命。阿里巴巴集团CEO吴泳铭在2024云栖大会上的演讲&#xff0c;为我们描绘了AI技术未来的巨大潜力。他指出&a…

【浙江工业大学主办 | EI检索稳定】HCIVR 二轮截稿 时间

二轮截稿日期&#xff1a;2024年10月15日 收录检索&#xff1a;EI Compendex&#xff0c;Scopus 征稿主题&#xff1a; 光学手势识别系统、生物识别、眼动追踪和表情识别、光势行为分析、传感器技术、光学传感与虚拟现实交互、光学跟踪与定位系统、声光传感融合系统、基于VR的…