ViewModel 完全指南:实践与背后原理全解

一、引言

在现代Android应用开发中,处理UI数据的有效管理和状态保持是开发者面临的重要挑战之一。Google推出的Jetpack组件库中的ViewModel已成为解决这些问题的关键工具。ViewModel旨在以生命周期意识的方式存储和管理界面相关的数据,从而使数据在配置更改如屏幕旋转时依然保持不变,极大地提高了应用的稳定性和响应速度。通过本文的走读和代码示例,不仅可以帮助新手开发者快速理解ViewModel的基本用法,还可以使有经验的开发者深入掌握其高级功能和最佳实践。

二、ViewModel基本使用

在Android应用中使用ViewModel可以有效地解决界面数据管理问题。ViewModel是一个负责准备和管理Activity或Fragment中UI所需数据的类,它存在的目的是使数据能够在配置变化时生存下来,例如屏幕旋转。这种机制简化了数据处理流程,并提高了应用的健壮性。

创建ViewModel

创建一个ViewModel通常涉及扩展ViewModel类,并在其中添加存储UI所需数据的变量和方法。下面是一个简单的ViewModel实例,展示了如何存储用户的输入字符串:

import androidx.lifecycle.ViewModel

class UserInputViewModel : ViewModel() {
    var userInput: String = ""
}

这个示例中,UserInputViewModel维护一个名为userInput的字符串。这个字符串在设备配置改变时将不会丢失,保证了用户数据的一致性。

使用ViewModel

在Activity或Fragment中使用ViewModel也非常直观。您需要通过ViewModelProvider来获取ViewModel的实例。这样做确保了配置更改时,您获得的ViewModel实例与之前的是相同的,从而数据得以保留。例如,在一个Activity中使用ViewModel来存储用户输入可以如下操作:

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: UserInputViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProvider(this).get(UserInputViewModel::class.java)

        // Use viewModel to manage UI data
    }
}

通过这种方式,ViewModel作为一个独立于Activity视图控制器的数据管理者,负责管理界面状态,降低了Activity或Fragment的负担,使其能够更专注于处理用户交互。


三、ViewModel的工作原理

ViewModel的核心功能是确保数据在设备配置更改时能够持久保存。这是通过将ViewModel的生命周期绑定到具体的Activity或Fragment的生命周期来实现的,确保ViewModel在屏幕旋转或其他配置变化时不会被销毁。

生命周期管理

当Activity重新创建时,例如屏幕旋转,Activity会被销毁并重新创建。如果没有ViewModel, 所有的界面数据将会丢失。但如果使用ViewModel,这些数据会被保留。这是因为ViewModel对象被设计为比它们服务的Activity或Fragment拥有更长的生命周期。

下面的代码片段展示了ViewModel如何在配置更改期间保持活动状态:

class ConfigurationViewModel : ViewModel() {
    var configChangeData: String = "Initial Data"

    // Data remains intact across configuration changes
}

```java

在Activity中使用此ViewModel

class ConfigActivity : AppCompatActivity() {
private lateinit var viewModel: ConfigurationViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_config)

    viewModel = ViewModelProvider(this).get(ConfigurationViewModel::class.java)

    // Displaying or updating data in the UI
    displayData(viewModel.configChangeData)
}

private fun displayData(data: String) {
    findViewById<TextView>(R.id.data_text_view).text = data
}

}


通过这种设计,ViewModel使得数据管理独立于ActivityFragment的生命周期,从而提高应用的稳定性和用户体验。

#### 数据的存储与管理

ViewModel不仅可以存储简单的数据,还可以与LiveData一起使用,实现数据的观察。这使得您可以构建响应式的UI组件,这些组件能够观察数据变化并做出响应。这种模式非常适合开发现代的、交互密集的应用。

```java
class LiveDataViewModel : ViewModel() {
    val liveData = MutableLiveData<String>()

    fun updateData(newData: String) {
        liveData.value = newData
    }
}

在Activity中订阅LiveData:

class LiveDataActivity : AppCompatActivity() {
    private lateinit var liveDataViewModel: LiveDataViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_live_data)

        liveDataViewModel = ViewModelProvider(this).get(LiveDataViewModel::class.java)

        // Observing data changes
        liveDataViewModel.liveData.observe(this, Observer { data ->
            findViewById<TextView>(R.id.data_text_view).text = data
        })
    }
}

通过LiveData,ViewModel提供了一种强大的数据管理和通信机制,极大地增强了应用的交互性和灵活性。

四、源码分析

4.分析源码前的准备工作

4.1ViewModel 的生命周期

在这里插入图片描述

4.2.几个类的感性认识
  • ViewModelStoreOwner:是一个接口,用来获取一个ViewModelStore对象
  • ViewModelStore:存储多个ViewModel,一个ViewModelStore的拥有者( Activity )在配置改变, 重建的时候,依然会有这个实例
  • ViewModel:一个对 Activity、Fragment 的数据管理类,通常配合 LiveData 使用
  • ViewModelProvider:创建一个 ViewModel 的实例,并且在给定的ViewModelStoreOwner中存储 ViewModel

再例子中的代码

class ViewModelActivity : AppCompatActivity() {

    //初始化 UserViewModel 通过 ViewModelProvider
    private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val button = Button(this)
        setContentView(button)

        //观察 User 数据,并打印
        userViewModel.userLiveData.observe(this, Observer { user ->
            "User = $user".log()
        })

        //点击按钮更新 User 信息
        button.setOnClickListener {
            userViewModel.updateUser()
        }
    }
}

首先看下UserViewModel的初始化过程。

private val userViewModel by lazy { ViewModelProvider(this)[UserViewModel::class.java] }

注:上面代码类似数组的写法是 Kotlin 的写法,其实是 ViewModelProvider 的get方法

5.ViewModelProvider的构造方法,以及 get 方法

5.1ViewModelProvider构造方法

先看ViewModelProvider构造方法,传入的参数为当前的 AppCompatActivity

//ViewModelProvider.java

private final Factory mFactor
private final ViewModelStore mViewModelStore;

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}

  • 通过 ViewModelStoreOwner获取ViewModelStore对象并给 mViewModelStore赋值
  • 给mFactory赋值,这里赋值的是NewInstanceFactory这个对象
5.2.ViewModelProvider的 get 方法
//ViewModelProvider.java

private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
  	//1
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

注释1:

  • 调用了两个参数的 get 方法
  • 第一个参数是字符串的拼接,用来以后获取对应 ViewModel 实例的,保证了同一个 Key 取出是同一个 ViewModel
  • 第二参数是 UserViewModel 的字节码文件对象

看下两个参数的get方法

//ViewModelProvider.java
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);//1
		//2
    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
  	//3
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
  	//4
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

注释 1:从ViewModelStore中,根据 key,取一个 ViewModel,ViewModelStore源码下文分析

注释 2:判断取出来的 ViewModel 实例和传进来的是否是一个,是同一个,直接返回此缓存中实例

注释 3:通过Factory创建一个ViewModel

注释 4:把新创建的ViewModel用ViewModelStore存储起来,以备下次使用,最后返回新创建的ViewModelStore

这里看一下ViewModel是怎么通过Factory创建出来的

通过 5.1 小节可以知道,这个Factory的实例是NewInstanceFactory

5.3.NewInstanceFactory的create方法
//ViewModelProvider.java 中的 AndroidViewModelFactory.java
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    //noinspection TryWithIdenticalCatches
    try {
        return modelClass.newInstance();
    } catch (InstantiationException e) {
        throw new RuntimeException("Cannot create an instance of " + modelClass, e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Cannot create an instance of " + modelClass, e);
    }
}

简单粗暴,通过反射,直接创建了ViewModel对象。

这里扩展一个,在实例UserViewModel的时候

private val userViewModel by lazy { ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(application))[UserViewModel::class.java] }

也可以通过两个参数的构造方法,来实例化,其中第二个参数就是Factory类型。然后就会用 AndroidViewModelFactory来实例化UserViewModel,我们来具体看下代码

AndroidViewModelFactory是NewInstanceFactory的子类

//ViewModelProvider.java 中的 AndroidViewModelFactory
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.getConstructor(Application.class).newInstance(mApplication);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
    return super.create(modelClass);
}

如果我们创建的UserViewModel当初继承的是AndroidViewModel类就走modelClass.getConstructor(Application.class).newInstance(mApplication);实例化方法,否则就走父类的实例化方法,也就是NewInstanceFactory的create方法

在开发中建议使用AndroidViewModel类,它会提供给一个Application级别的 Context。

接下来看一下ViewModelStoreOwner是什么,以及它的具体实现

6.ViewModelStoreOwner

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

  • 一个接口,里面一个方法返回了ViewModelStore对象
  • 它的实现类在 AndroidX 中ComponentActivity和 Fragment

ComponentActivity的关键代码

//ComponentActivity.java
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner,XXX{
   
    private ViewModelStore mViewModelStore;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    } 
}

  • 创建了一个ViewModelStore并返回了

来看下这个ViewModelStore类

7.ViewModelStore

7.1.ViewModelStore的源码

我下面贴的是完整代码,对你没看错。

public class ViewModelStore {
		//1
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
		//2
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }
		//3
    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    //4
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

注释 1 :声明一个 Map 来存储ViewModel

注释 2:存储ViewModel,这个方法我们在7.2 小节ViewModelProvider的 get 方法中用到过

注释 3:取出 ViewModel,这个方法我们在7.2 小节ViewModelProvider的 get 方法中用到过。注意在从 Map中去 ViewModel 的时候是根据 Key,也就是7.2小节注释 1 拼接的那个字符串DEFAULT_KEY + “:” + canonicalName。这也就解释了第 4 节的疑问 为什么在对应的作用域内,保正只生产出对应的唯一实例

注释 4:这个是一个重点方法了,表明要清空存储的数据,还会调用到ViewModel的 clear 方法,也就是最终会调用带 ViewModel 的onCleared()方法

那么这个ViewModelStore的 clear 方法,什么时候会调用呢?

7.2.ComponentActivity的构造方法

//ComponentActivity.java
public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
   	
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
          	//1
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });

}

在ComponentActivity的构造方法中,我们看到,在 Activity 的生命周期为 onDestory的时候,并且当前不是,配置更改(比如横竖屏幕切换)就会调用ViewModelStore 的 clear 方法,进一步回调用 ViewModel 的onCleared方法。

这就回答了第四节提出的问题onCleared方法在什么调用

最后看一下 ViewModel 的源码,以及其子类AndroidViewModel

8.ViewModel 的源码

ViewModel类其实更像是更规范化的抽象接口

public abstract class ViewModel {
    private volatile boolean mCleared = false;

    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }

    @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {
            synchronized (mBagOfTags) {
                for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();
    }

}

ViewModel 的子类AndroidViewModel

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

提供了一个规范,提供了一个 Application 的 Context

到现在整个源码过程就看了,包括前面,我们提到的那几个关键类的源码。

到目前为止,我们第 4 节抛出的问题,已经解决了,两个了,还有一个ViewModel为什么不会随着Activity的屏幕旋转而销毁;

9.分析为啥ViewModel不会随着Activity的屏幕旋转而销毁

首先知道的是 ViewModel 不被销毁,是在一个 ViewModelStore 的 Map 中存着呢,所以要保证ViewModelStore不被销毁。

首先得具备一个前置的知识

在 Activity 中提供了 onRetainNonConfigurationInstance 方法,用于处理配置发生改变时数据的保存。随后在重新创建的 Activity 中调用 getLastNonConfigurationInstance 获取上次保存的数据。

10.1.onRetainNonConfigurationInstance方法

//ComponentActivity.java
/**
 * Retain all appropriate non-config state.  You can NOT
 * override this yourself!  Use a {@link androidx.lifecycle.ViewModel} if you want to
 * retain your own non config state.
 */
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    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;
    }
		//1
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

注意看下方法上的注释

  • 不需要也不能重写此方法,因为用 final 修饰
  • 配置发生改变时数据的保存,用ViewModel就行
  • 注释 1:把ViewModel存储在 NonConfigurationInstances 对象中

现在再看下ComponentActivity 的 getViewModelStore方法

//ComponentActivity.java
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
      	//1
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

注释 1:获取了NonConfigurationInstances一个对象,不为空从其身上拿一个ViewModelStore,这个就是之前保存的ViewModelStore

当 Activity 重建时还会走到getViewModelStore方法,这时候就是在NonConfigurationInstances拿一个缓存的ViewModelStore。


ViewModel的高级应用

随着Android应用变得越来越复杂,ViewModel的使用也不再局限于简单的UI数据存储。它的高级用法包括与LiveData结合使用以及实现Repository模式,这些都是提升应用架构清晰度和维护性的关键技术。

结合LiveData使用

LiveData是一个可观察的数据持有者类,它遵循观察者模式,允许数据变化可以通知到观察者组件。与ViewModel结合使用时,LiveData提供了一种响应式方式来更新UI,这样可以确保UI组件总是展示最新的数据。

class UserViewModel : ViewModel() {
    private val userRepository = UserRepository()
    val userData: LiveData<User> = userRepository.getUserData()

    fun updateUser(userData: User) {
        userRepository.updateUser(userData)
    }
}

在Activity或Fragment中,您可以观察这些LiveData对象:

class UserActivity : AppCompatActivity() {
    private lateinit var viewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)

        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        viewModel.userData.observe(this, Observer { user ->
            // 更新UI
        })
    }
}

实现Repository模式

Repository模式是一种将数据源管理逻辑从UI逻辑中分离的设计模式。ViewModel通过使用Repository类,可以从不同的数据源(如网络API、本地数据库)抽象地获取数据,同时保持UI组件的简洁和聚焦。

class UserRepository {
    fun getUserData(): LiveData<User> {
        // 实现从数据库或网络获取用户数据
    }

    fun updateUser(user: User) {
        // 实现更新用户数据到数据库或网络
    }
}

通过这种方式,ViewModel不仅作为数据的直接管理者,同时也协调不同后端服务或数据库的数据流,显著增强了应用的灵活性和可扩展性。


总结

在本文中,我们详细探讨了ViewModel的基本使用、工作原理及其在复杂应用中的高级应用。通过这些分析和代码示例,我们看到ViewModel不仅能够有效管理UI相关的数据,保持数据在配置变化时的持久性,而且通过与LiveData和Repository模式的结合使用,显著提高了应用的响应性和数据处理能力。

关键优点回顾
  • 生命周期意识:ViewModel的设计允许它自然地处理用户界面所需的数据与Activity或Fragment生命周期的关联问题,从而简化了开发者的工作。
  • 数据管理与隔离:通过将数据处理逻辑从UI逻辑中解耦,ViewModel帮助开发者实现更清晰和可维护的代码结构。
  • 增强的应用稳定性和用户体验:保持数据在配置更改时的一致性,避免了应用在如屏幕旋转等情况下的数据丢失,提升了用户体验。

鼓励所有Android开发者实践ViewModel的使用,深入探索其与其他Jetpack组件的集成,以充分利用这些工具提供的强大功能,为用户创造出更流畅、更可靠的应用体验。

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


在这里插入图片描述
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

暴力法解决最近对问题和凸包问题-实现可视化

目录 最近对问题 凸包问题 最近对问题 顾名思义就是采用蛮力法求出所有点之间的距离&#xff0c;然后进行比较找出第一个最近对&#xff0c;一个一个进行比较。 大概思路就是如图&#xff08;每个圈代表一个数对&#xff09; 第一个和其他四个比较 第二个和其他三个比较 …

C++类和对象下——实现日期类

前言 在学习了类和对象的六大成员函数后&#xff0c;为了巩固我们学习的知识可以手写一个日期类来帮助我们理解类和对象&#xff0c;加深对于其的了解。 默认函数 构造函数 既然是写类和对象&#xff0c;我们首先就要定义一个类&#xff0c;然后根据实际需要来加入类的数据与函…

计算机Java项目|Springboot房产销售系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、Python项目、前端项目、人工智能与大数据、简…

Windows下安装Node.js、npm和electronic,并运行一个Hello, World!脚本程序

20240510 By wdhuag 目录 简介&#xff1a; 参考&#xff1a; 安装Node.js 安装npm 配置npm&#xff1a; 修改包存放目录和缓存目录 切换镜像源 使用 nrm 切换镜像源 安装Electron 运行一个Hello, World!脚本程序 安装Yarn JavaScript 指南 简介&#xff1a; Nod…

flash attention的CUDA实现探讨-V3

之前关于flash attention的实现参考添加链接描述,添加链接描述,添加链接描述 lash attention的数学变换:给定三个矩阵Q,K,V,形状都是[N,d],计算S=QK.T,然后针对dim=1做softmax,然后和V继续做矩阵乘法得到形状为[N,d]的输出矩阵O,即O=softmax(QK.T,dim=1)V。 下面本人的…

物联网设计竞赛_2_Jetson Nano中文输入法配置安装vscode

1、装ibus和ibus依赖框架 sudo apt-get install ibus ibus-clutter ibus-gtk ibus-gtk3 ibus-qt4 2、启动ibus im-config -s ibus 3、安装拼音引擎 sudo apt-get install ibus-pinyin 4、重启linux系统 shutdown -r now 5、进入ibus设置添加中文 ibus-setup 插入中文p…

四川汇昌联信:拼多多网点怎么开?大概需要多少钱?

想要开一家拼多多网点&#xff0c;你肯定很关心需要准备多少资金。下面&#xff0c;我们就来详细解答这个问题&#xff0c;并从多个角度分析开设网点的要点。 一、 开设拼多多网点&#xff0c;首要任务是确定启动资金。根据不同的经营模式和地区差异&#xff0c;成本会有所不同…

基于SpringBoot + Vue的兼职网站管理系统设计与实现+毕业论文+答辩PPT

系统介绍 本系统包含管理员、用户、企业三个角色。 管理员角色&#xff1a;前台首页、个人中心、用户管理、企业管理、兼职信息管理、职位申请管理、留言板管理、系统管理。 用户角色&#xff1a;前台首页、个人中心、职位申请管理。 企业角色&#xff1a;前台首页、个人中心、…

JUC下的ThreadLocalRandom详解

ThreadLocalRandom 是Java并发包&#xff08;java.util.concurrent&#xff09;中提供的一个随机数生成器类&#xff0c;它是从Java 7开始引入的。相较于传统的Math.random()或Random类&#xff0c;ThreadLocalRandom更适用于多线程环境&#xff0c;因为它为每个线程维护了一个…

【spring】application.yml导入额外配置文件

有时候application.yml 已经配置很多配置已经很大很乱了想把他们拆出去放在一个独立的XX.yml文件管理。这时候就用到了 spring.config.import 属性。 spring.config.import spring.config.import 是 Spring Boot 2.4 版本引入的一个配置属性&#xff0c;用于导入额外的配置数…

Java入门基础学习笔记21——Scanner

在程序中接收用户通过键盘输入的数据&#xff1a; 需求&#xff1a; 请在程序中&#xff0c;提示用户通过键盘输入自己的姓名、年龄、并能在程序中收到这些信息&#xff0c;怎么解决&#xff1f; Java已经写好了实现程序&#xff0c;我们调用即可。 API&#xff1a;Applicat…

解决 XXL-Job 端口额外占用问题 小结

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java技术栈笔记 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. 问题解决 2.1. 下载源码 2.2. 启动admin服务器 2.3. 项目引入c…

MySQL-InnoDB数据存储结构

1、存储结构-页 索引结构提供了高效的索引方式&#xff0c;索引信息以及数据记录都保存在数据文件或索引文件中&#xff08;本质存储在页结构中&#xff09; 1.1、磁盘与内存交互的基本单位&#xff1a;页 在InnoDB中将数据划分为若干页&#xff0c;页的默认大小为&#xff…

OpenCV-基于累计直方图的中值滤波算法

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 实现原理 基于累计直方图的中值滤波算法是一种图像处理技术&#xff0c;用于去除图像中的噪声。它利用了像素值的频数分布&#…

LeetCode/NowCoder-链表经典算法OJ练习2

最好的&#xff0c;不一定是最合适的&#xff1b;最合适的&#xff0c;才是真正最好的。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;分割链表 题目二&#xff1a;环形链表的约瑟夫问题 SUMUP结尾 说在前面 dear朋友们大家好&#xff01;&…

卷积神经网络边缘识别

为什卷积神经网络能够识别图片呢&#xff1f;是基于图片相似度比较&#xff0c;两张图片的点击越大说明两张图片越像&#xff0c;比如我们那狗胡子的图片去比较&#xff0c;如果相似度很高&#xff0c;就是认为这个动物更像狗。点积越大&#xff0c;图片越相似&#xff0c;这个…

Windows2016系统禁止关闭系统自动更新教程

目录 1.输入cmd--适合系统2016版本2.输入sconfig&#xff0c;然后按回车键3.输入5&#xff0c;然后按回车键4.示例需要设置为手动更新&#xff0c;即输入M&#xff0c;然后按回车键 1.输入cmd–适合系统2016版本 2.输入sconfig&#xff0c;然后按回车键 3.输入5&#xff0c;然后…

揭秘APP广告变现:轻松赚取收益的秘密武器,你还在等什么?

在移动互联网时代&#xff0c;APP广告变现已成为许多开发者和公司获取收益的重要方式。它如同一把秘密武器&#xff0c;帮助那些掌握了其使用技巧的人轻松赚取收益。那么&#xff0c;究竟什么是APP广告变现&#xff1f;又如何通过它轻松赚取收益呢&#xff1f;接下来&#xff0…

对中介者模式的理解

目录 一、场景1、题目 【[来源](https://kamacoder.com/problempage.php?pid1094)】1.1 题目描述1.2 输入描述1.3 输出描述1.4 输入示例1.5 输出示例 二、不采用中介者设计模式1 代码2 问题 三、中介者设计模式1 代码2 更好的例子 四、个人思考 一、场景 设计模式不是银弹&am…

STM32(开篇总结)

STM32介绍 STM32是意法半导体公司基于ARM Cortex-M内核开发的32位微控制器 STM32常应用在嵌入式领域&#xff0c;如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等 STM32功能强大、性能优异片上资源丰富、功耗低&#xff0c;是一款经典的嵌入式微控制器…