Android ViewModel,Lifecycles和LiveData组件讲解

news2024/10/6 20:32:49

文章目录

      • 一、ViewModel
        • ViewModel基本用法
        • 向ViewModel传递参数
      • 二、Lifecycles
      • 三、LiveData
        • LiveData的基本用法
        • map和switchMap

JetPack是一个开发组件工具集,他的主要目的是帮助我们编写出更加简洁的代码,并简化我们的开发过程。JetPack中的组件有一个特点,它们大部分不依赖于任何Android系统版本,这意味者这些组件通常是定义在AndroidX库当中的,并且拥有非常好的向下兼容性。

一、ViewModel

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。
ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
简单的说就是,在android中,当Activity重建或销毁时,页面上的数据会丢失。为了保存页面的数据,我们以前通常的做法是在 onSaveInstanceState 中,将数据保存到 bundle 中,再在 onCreate 中将 bundle 中的数据取出来。
而使用 ViewModel,我们就无需再用这种方法保存,因为 ViewModel 会自动感知生命周期,处理数据的保存与恢复。即数据可在发生屏幕旋转等配置(其它例如分辨率调整、权限变更、系统字体样式、语言变更等)更改后继续留存

对于横竖屏生命周期的总结是:先销毁掉原来的生命周期,然后再重新跑一次。
但是,这样子是不是会有问题呢?有些场景下: 比如说,做游戏开发 。横竖屏的切换,生命周期重新加载,那么当前页面的数据也会重新开始了。但是ViewModel会保存里面的数据。
在切换语言的时候ViewModel也会保存数据
Activity等视图文件中不保存数据,在ViewModel里面保存数据
当Activity或fragment被Destory或onCreate时ViewModel数据不会丢失
在这里插入图片描述
在这里插入图片描述

ViewModel基本用法

想要使用ViewModel组件,还需要添加如下依赖:

dependencies {
    //ViewModel是LifeCycle的一个组件库,所以只需要添加LifeCycle库的依赖即可
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}

通常来讲,我们需要给每个Activity和Fragment都创建一个对应的ViewModel,因此为MainActivity创建一个对应的MainViewModel类,并让他继承自ViewModel,代码如下所示:

class MainViewModel :ViewModel(){
    var counter=0
}

现在我们在界面上添加一个按钮,每点击一次按钮就让计数器加1,并且把最新的计数显示到界面上。修改布局代码:

package com.example.JetPackTest

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import com.example.kotlintext.R
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel
   private val TAG:String="MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d(TAG, "onCreate: ")
        //viewModel= ViewModelProviders.of(this).get(MainViewModel::class.java) 
        viewModel=ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
             viewModel.counter++;
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter() {
        infoText.text=viewModel.counter.toString()
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy: ")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart: ")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop: ")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart: ")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause: ")
    }

}

首先我们要通过ViewModelProvider来创建ViewModel的实例,之所以这么写是ViewModel有独立的生命周期,并且其生命周期要长于Activity。如果我们在onCreate()方法中创建ViewModel的实例,那么每次onCreate()方法执行时候,ViewModel都会创建一个新的实例,这样当手机屏幕发生变化时候,就无法保留其中的数据了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当我们旋转一下屏幕,你会发现Activity虽然重新被创建了,但计数器的数据没有丢失

向ViewModel传递参数

如果我们需要通过构造函数来传递一些参数,需要借助ViewModelProvider.Factory就可以实现。虽然计数器在屏幕旋转的时候不会丢失数据,但是如果退出程序之后又重新打开,那么之前的计数就会被清零。这个时候我们就需要在退出程序的时候对当前的计数进行保存,然后在重新打开程序的时候读取之前保存的计数,并传递给MainViewModel。修改MainViewModel中的代码,如下所示

class MainViewModel(countReserved:Int) :ViewModel(){
    var counter=countReserved
}

我们通过给MainViewModel的构造函数添加了一个countReserved参数,这个参数用于记录之前保存的计数值,并在初始化的时候赋值给counter变量。
新建一个MainViewModelFactory类,并让它实现ViewModelProvider.Factory接口,代码如下:

class MainViewModelFactory(private val countReserved:Int):ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(countReserved)as T
    }
}

可以看到MainViewModel.Factory的构造函数中也接收了一个countReserved参数,另外ViewModelProvider.Factory接口要求我们必须实现create()方法,因此这里在create方法中我们创建了MainViewModel的实例,并将countReserved参数传了进去。为什么这里就可以创建MainViewModel的实例了呢?因为create()方法的执行时机和Activity的生命周期无关,所以不会产生之前提到的问题。
另外,我们在界面上添加一个清零按钮,方便用户手动将计数器清零。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
>
 <TextView
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:id="@+id/infoText"
     android:layout_gravity="center_horizontal"
     android:textSize="32sp"
     />
    <Button
        android:id="@+id/plusOneBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Plus One"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/clearBtn"
        android:layout_gravity="center_horizontal"
        android:text="clear"
        />
</LinearLayout>

最后修改MainActivity中的代码

package com.example.JetPackTest

import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.lifecycle.ViewModelProvider
import com.example.kotlintext.R
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel
    lateinit var sp:SharedPreferences
   private val TAG:String="MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d(TAG, "onCreate: ")
        sp=getPreferences(Context.MODE_PRIVATE)
        val countReserved = sp.getInt("count_reserved", 0)
viewModel=ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
             viewModel.counter++;
            refreshCounter()
        }
        clearBtn.setOnClickListener {
            viewModel.counter=0
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter() {
        infoText.text=viewModel.counter.toString()
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy: ")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart: ")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop: ")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart: ")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause: ")
        sp.edit {
            putInt("count_reserved",viewModel.counter)
        }
    }
}

在onCreate()方法中,我们首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读到的话,就使用0作为默认值。接下来在ViewModelProvider方法传入MainViewModelFactory(countReserved)作为参数,将读取到的计数值传给了MainViewModelFactory的构造函数。
并在onPause()方法中对当前的计数进行保存,这样可以保证不管程序是退出还是进入后台,计数都不会丢失
在这里插入图片描述

二、Lifecycles

在编写Android应用程序的时候,可能经常遇到需要感知Activity生命周期的情况。比如,某个页面中发起了一条网络请求,但是当请求得到响应的时候,界面或许已经关闭了,这个时候就不应该继续对响应的结果进行处理。因此我们需要能够时刻感知到Activity的生命周期,以便在适当的时候进行相应的逻辑控制。
比如有个问题,如果要在一个非Activity的类中去感知Activity的声明周期,应该怎么办?
可以通过在Activity中嵌入一个隐藏的Fragment来进行感知,或者通过手写监听器的方式来进行感知。
下面通过监听器的方式来对Activity的生命周期进行感知

class MyObserver{
fun activityStart(){
}
fun activityStop(){
}
}
class MainActivity:AppCompatActivity(){
lateinit var observer:MyObserver
override fun onCreate(savedInstanceState:Bundle?){
observer=MyObserver()
}
override fun onStart(){
super.onStart()
observer.activityStart()
}
override fun onStop(){
super.onStop()
observer.activityStop()
}
}

这里我们为了让MyObserver能够感知到Activity的生命周期,需要专门在MainActivity中重写相应的生命周期方法,然后再通知给MyObserver。这种实现方式需要在Activity中编写太多额外的逻辑。
Lifecycles组件就可以在任何一个类中都能轻松感知到Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理
新建一个MyObserver类,并让它实现LifecycleObserver接口,代码如下所示:

class MyObserver:LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun  activityStart(){
        Log.d("MyObserver", "activityStart")
    }
    
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop(){
        Log.d("MyObserver", "activityStop")
    }
}

可以看到,我们在方法上使用了@OnLifecycleEvent注解,并传入了一种生命周期事件。生命周期事件的类型一共有7种:ON_CREATE、ON_START、ON_STOP、ON_RESUEM、ON_DESTORY分别匹配Activity中相应的声明周期回调。另外还有一种ON_ANY类型,表示可以匹配Activity的任何生命周期回调
因此,上述代码中的activityStart()和activityStop()方法就应该分别在Activity的onStart()和onStop()触发的时候执行。
接下来借助LifecycleOwner,可以使用如下的语法结构让MyObserver得到通知:

lifecycleOwner.lifecycle.addObserver(MyObserver)

首先调用lifecycleOwner的getLifecycle()方法,得到一个Lifecycle对象,然后调用它的addObserver()方法来观察LifecyclerOwner的生命周期,再把MyObserver的实例传进去就可以了。
又因为我们的Activity是继承自AppCompatActivity的,或者Fragment继承自androidx.fragment.app.Fragment,他们本身就是一个LifecycleOwner的实例,这部分工作AndroidX库自动帮我们完成。所以可以这么写

class MainActivity : AppCompatActivity() {
  private val TAG:String="MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycle.addObserver(MyObserver())
    }
    }

加上这一行,MyObserver就能自动感知到Activity的生命周期了。不仅在Activity适用,Fragment也适用。
运行程序,然后切到后台,再回来的打印输出
在这里插入图片描述
当然MyObserver除了感知Activity的生命周期发生变化,也能够获知当前的生命周期状态。只需要在MyObserver的构造函数中将Lifecycle对象传进来,如下所示:

class MyObserver(val lifecycle:Lifecycle):LifecycleObserver{
}

有了lifecycle对象之后,我们就可以在任何地方调用lifecycle.currentState来主动获知当前的生命周期状态。lifecycle.currentState返回的生命周期状态是一个枚举类型,一共有DESTROYED,INITIALIZED,CREATED,STARTED,RESUMED这五种状态。
也就是说,当获取的生命周期状态是CREATED的时候,说明onCreate()方法已经执行了,但是onStart()方法还没有执行。当获取的生命周期状态是STARTED的时候,说明onStart()方法已经执行了,但是onResume()方法还没有执行。

三、LiveData

LiveData是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者

LiveData的基本用法

之前编写的计数器虽然功能简单,但还是有问题。当点击Plus One按钮时,都会先给ViewModel中的计数加1,然后立即获取最新的计数。这种方式虽然可以在单线程中正常工作,但如果ViewModel的内部开启了线程去执行一些耗时逻辑,那么在点击按钮后就立即去获取最新的数据,得到的肯定还是之前的数据。
之前我们使用都是在Activity中手动获取ViewModel中的数据这种交互方式,但是ViewModel却无法将数据的变化主动通知给Activity。
或许你会把Activity的实例传给ViewModel,这样ViewModel不就能主动对Activity进行通知了吗?但是要知道ViewModel的生命周期是长于Activity的,如果把Activity的实例传给ViewModel,就很有可能就因为Activity无法释放而造成内存泄露。
如果我们将计数器的计数使用LiveData来包装,然后在Activity中去观察它,就可以主动将数据变化通知给Activity了。
修改MainViewModel中的代码,如下所示:

class MainViewModel(countReserved:Int) :ViewModel(){
    var counter=MutableLiveData<Int>()
    init {
        counter.value=countReserved
    }
    fun plusOne(){
        val count=counter.value?:0
        counter.value=count+1
    }
    fun clear(){
        counter.value=0
    }
}

这里我们将counter变量修改成了一个MutableLiveData对象,并指定它的泛型为Int,表示它包含的是整型数据。MutableLiveData是一种可变的LiveData,用法很简单,主要有3种读写数据的方法,分别是getValue()、setValue()和postValue()方法。getValue()方法用于获取LiveData中包含的数据;setValue()方法用于给LiveData设置数据,但是只能在主线程中调用;postValue()方法用于在非主线程中给LiveData设置数据
这里在init结构体中给counter设置数据,这样之前保存的计数值剧可以在初始化的时候得到恢复。接下来新增了plusOne()和clear()这两个方法,分别用于给计数加1以及将计数清零。plusOne()方法中的逻辑是先获取counter中包含的数据,然后给它加1,再重新设置到counter中。调用LiveData的getValue()方法获得的数据是可能为空的,因此这里使用了一个?:操作符,当获取到的数据为空时,就用0来作为默认计数。
修改MainActivity

class MainActivity : AppCompatActivity() {
    //对变量进行延迟初始化,这样在就不用先给全局变量赋值,而且在赋值的时候赋值为null,后面还要进行判空
    // 如果变量多会比较麻烦
  lateinit var viewModel: MainViewModel
    lateinit var sp: SharedPreferences
  private val TAG:String="MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
       sp=getPreferences(Context.MODE_PRIVATE)
      val countReserved = sp.getInt("count_reserved", 0)
      viewModel= ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
        viewModel.plusOne()
        }
        clearBtn.setOnClickListener {
        viewModel.clear()
        }
        viewModel.counter.observe(this, Observer { count->
            infoText.text=count.toString()
        })
}

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause: ")
        sp.edit {
            putInt("count_reserved",viewModel.counter.value?:0)
        }
    }
}

这里调用了ViewModel.counter的observe()方法来观察数据的变化。经过对MainViewModel的改造,现在counter变量已经变成了一个LiveData对象,任何LiveData对象都可以调用它的observe()方法来观察数据的变化。observe()方法接口接收两个参数,第一个是一个LifecycleOwner对象,Activity本身就是一个LifecycleOwner对象,因此直接传this就好;第二个参数是一个Observer接口,当counter中包含的数据发生变化时,就会回调这里,因此这里将最新的数据更新到界面。
为了在非ViewModel中就只能观察LiveData的数据变化,而不能给LiveData设置数据,下面改造MainViewModel:

class MainViewModel(countReserved:Int) :ViewModel(){
    val counter:LiveData<Int> get() = _counter
    private val _counter=MutableLiveData<Int>()
    init {
        _counter.value=countReserved
    }
    fun plusOne(){
        val count=_counter.value?:0
        _counter.value=count+1
    }
    fun clear(){
        _counter.value=0
    }
}

将原来的counter变量改名为_counter变量,并给它加上private修饰符,这样_counter变量就对外部不可见了。然后又定义了一个counter变量,将它的类型声明为不可变的LiveData,并在它的get()属性方法中返回_counter变量。
这样,当外部调用counter变量时,实际上获得的就是_counter的实例,但是无法给counter设置数据,从而保证了ViewModel的数据封装性。

map和switchMap

LiveData为了能够应对各种不同的需求场景,提供了两种转换方法:map()和switchMap()方法。
map()方法,这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换
那么什么情况下,会用到这个方法呢?
比如说有个User类,User类包含用户的姓名和年龄,定义如下:

data class User(var firstName:String,var lastName:String,var age:Int)

我们可以在ViewModel中创建一个相应的LiveData来包含User类型的数据,如下所示:

class MainViewModel(countReserved:Int):ViewModel(){
val userLiveData=MutableLiveData<User>()
}

如果MainActivity中明确只会显示用户的姓名,而完全不关心用户的年龄,这个时候还将User类型的LiveData暴露给外部就不合适了。
而map()方法就是专门解决这个问题,它可以将User类型的LiveData自由地转型成任意其他类型的LiveData

class MainViewModel(countReserved:Int):ViewModel(){
private val userLiveData=MutableLiveData<User>()
val userName:LiveData<String> =Transformations.map(userLiveData){user->
"${user.firstName}${user.lastName}"
}
...
}

这里我们调用了Transformations的map()方法来对LiveData的数据类型进行转换。map()方法接收两个参数:第一个参数是原始的LiveData对象;第二个参数是一个转换函数,我们在转换函数中编写具体的转换逻辑即可。这里的逻辑就是将user对象转换为一个只包含用户姓名的字符串。
另外还将userLiveData声明成了private,以保证数据的封装性。外部使用的时候只要观察userName这个LiveData就可以了。当userLiveData的数据发生变化时,map()方法会监听到变化并执行转换函数的逻辑,然后再将转换之后的数据通知给userName的观察者。
switchMap()方法的使用场景比较固定:如果ViewModel中的某个LiveData对象是调用另外的方法获取的,那么我们就可以借助switchMap()方法,将这个LiveData对象转换成另一个可观察的LiveData对象
比如:LiveData对象的实例都是在ViewModel中创建的,然而在实际的项目中,不可能一直都是这种理想情况,很有可能ViewModel中的某个LiveData对象是调用另外的方法获取的。
新建一个Repository单例类,代码如下所示:

object  Repository {
    fun getUser(userId:String):LiveData<User>{
        val liveData=MutableLiveData<User>()
        liveData.value=User(userId,userId,0)
        return liveData
    }
}

这里在Repository类中添加了一个getUser()方法,这个方法接收一个userId参数。每次将传入的userId当做用户姓名来创建一个新的User对象。
getUser()方法返回的是一个包含User数据的LiveData对象,而且每次调用getUser()方法都会返回一个新的LiveData实例。
然后再MainViewModel中也定义一个getUser()方法,并且让它调用Repository的getUser()方法来获取LiveData对象:

class MainViewModel(countReserved:Int) :ViewModel(){
   fun getUser(userId:String):LiveData<User>{
        return Repository.getUser(userId)
    }    
}

接下来的问题是,在Activity中如何观察LiveData的数据变化呢?既然getUser()方法返回的是一个LiveData对象,那么我们可不可以直接在Activity中使用如下写法呢?

viewModel.getUser(userId).observe(this)
{user->
}

因为每次调用getUser()返回的都是一个新的LiveData实例,而上述写法会一直观察老的LiveData实例,从而根本无法观察到数据的变化,会发现这种情况下LiveData是不可观察的。
这个时候switchMap()方法就可以派上用场了。
修改MainViewModel中的代码:

class MainViewModel(countReserved:Int) :ViewModel(){
    private val userIdLiveData=MutableLiveData<String>()
    val user:LiveData<User> =Transformations.switchMap(userIdLiveData){
        userId ->  Repository.getUser(userId)//此时的userId就是userIdLiveData的类型对象String
    }
    fun getUser(userId:String){
        userIdLiveData.value=userId
    }
}

定义了一个新的userIdLiveDat对象,用来观察userId的数据变化,然后调用了Transformations的switchMap()方法,用来对另一个可观察的LiveData对象进行转换。
switchMap()方法同样接收两个参数:第一个参数传入我们新增的userIdLiveData,switchMap()方法会对它进行观察;第二个参数是一个转换函数,注意:我们必须在这个转换函数中返回一个LiveData对象,因为switchMap()方法的工作原理就是将转换函数中返回LiveData对象转换为另一个可观察的LiveData对象。我们只需要在转换函数中调用Respository的getUser()方法来得到LiveData对象,将其返回。
首先,当外部调用MainViewModel的getUser()方法来获取用户数据时,并不会发起任何请求或者函数调用,只会传入userId的值设置到userIdLiveData中。一旦userIdLiveData的数据发生变化,那么观察userIdLiveData的switchMap()方法就会执行,并且调用我们编写的转换函数。然后在转换函数中调用Repository.getUser()方法获取真正的用户数据。同时,switchMap()方法会将Repository.getUser()方法返回的LiveData对象转换成一个可观察的LiveData对象。对于Activity只需要观察这个LiveData对象就可以了
修改activity_main.xml文件,新增一个Get User按钮

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/getUserBtn"
        android:layout_gravity="center_horizontal"
        android:text="Get User"
        />
</LinearLayout>

修改MainActivity的代码:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel= ViewModelProvider(this,MainViewModelFactory(countReserved)).get(MainViewModel::class.java)
        getUserBtn.setOnClickListener {
            val userId=(0..10000).random().toString()
            viewModel.getUser(userId)
        }
        viewModel.user.observe(this, Observer { user ->
            infoText.text=user.firstName
        })
    }
}

通过Get User按钮的点击事件中使用随机函数生成一个userId,然后调用MainViewModel的getUser()方法来获取用户数据,但是这个方法不会有返回值。等数据获取完后,可观察LiveData对象的observe()方法将会得到通知,我们在这里将获取的用户名显示到界面上。
在这里插入图片描述
LiveData内部不会判断即将设置的数据和原有数据是否相同,只是调用了setValue()或postValue()方法,就一定会触发数据变化事件。
如果Activity处于不可见状态的时候(手机息屏,或者被其他的Activity遮挡),LiveData发生了多次数据变化,当Activity恢复可见状态时,只有最新的那份数据才会通知给观察者,前面的数据在这种情况下相当于已经过期了,会被直接丢弃。

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

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

相关文章

【C++11】右值引用

右值引用是C11中才被提出来的新概念&#xff0c;而以前的版本中也有引用&#xff0c;但是是指的左值引用。归根结底&#xff0c;左右值引用都是给对象取别名。 1.区分左值和右值 提起左值和右值很多小伙伴可能第一时间会有点小蒙圈&#xff0c;敲了好长时间代码了&#xff0c;对…

【Java基础】—— Java简介(超详细整理,适合新手入门)

​ “作者 久绊A” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴。 写给小白看的入门级 Java 基本语法&#xff0c;需要掌握哪些知识点? Java涵盖的知识点…

1个 30多年程序员的生涯经验总结

有人说&#xff1a;一个人从1岁活到80岁很平凡&#xff0c;但如果从80岁倒着活&#xff0c;那么一半以上的人都可能不凡。 生活没有捷径&#xff0c;我们踩过的坑都成为了生活的经验&#xff0c;这些经验越早知道&#xff0c;你要走的弯路就会越少。 在我30多年的程序员生涯里…

部分iphone、安卓手机打开微信小程序不请求、白页问题

前言&#xff1a; 最近项目上发现用户测试小程序体验版打开一直白页&#xff0c;请求没反应&#xff0c;页面不渲染。开始以为是微信小程序某api问题&#xff0c;或者用户微信版本过低&#xff0c;或者用户网络不好&#xff0c;甚至考虑是不是服务器问题&#xff01;因为后端是…

vue3中如何使用JSX?

在绝大多数情况下&#xff0c;Vue 推荐使用模板<template>语法来创建应用。 在 Vue 3 的项目开发中&#xff0c;template 是 Vue 3 默认的写法。虽然 template 长得很像 HTML&#xff0c;但 Vue 其实会把 template 解析为 render 函数&#xff0c;之后&#xff0c;组件运…

【Java多线程】线程的优先级

线程的优先级等级 MAX_PRIORITY&#xff1a;10 MIN _PRIORITY&#xff1a;1 NORM_PRIORITY&#xff1a;5 涉及的方法 getPriority() &#xff1a;返回线程优先值 setPriority(int newPriority) &#xff1a;改变线程的优先级 例&#xff1a; 我们将分线程的优先级设置为…

Java设计模式-迭代器模式Iterator

介绍 根据GoF的定义&#xff0c;迭代器模式提供了一种顺序访问聚合对象的元素而不暴露其底层表示的方法。这是一种行为设计模式。 顾名思义&#xff0c;迭代器有助于以定义的方式遍历对象集合&#xff0c;这对客户端应用程序很有用。在迭代期间&#xff0c;客户端程序可以根据需…

SciPy 教程与安装

SciPy 教程SciPy 是一个开源的 Python 算法库和数学工具包。Scipy 是基于 Numpy 的科学计算库&#xff0c;用于数学、科学、工程学等领域&#xff0c;很多有一些高阶抽象和物理模型需要使用 Scipy。SciPy 包含的模块有最优化、线性代数、积分、插值、特殊函数、快速傅里叶变换、…

小场景解决大问题|明道云在京东方的落地实践

我是来自京东方集团京东方晶芯科技有限公司的季旭。很荣幸给各位分享我们京东方集团和明道云之间的合作情况。 关于京东方晶芯 在分享之前&#xff0c;我首先给各位介绍一下我们公司。京东方集团是1993年4月成立的&#xff0c;以半导体显示为核心技术&#xff0c;在物联网创新…

聚观早报 | 硅谷大数据龙头Palantir扩招;滴滴出行恢复新用户注册

今日要闻&#xff1a;硅谷大数据龙头Palantir扩招&#xff1b;美团无人机去年完成配送超10万单&#xff1b;滴滴出行恢复新用户注册&#xff1b;PS VR2将于2月22日全球同步上市&#xff1b;改款特斯拉 Model 3加州路测谍照曝光硅谷大数据龙头Palantir扩招 1 月 17 日消息&#…

【Javascript】面向对象编程,this,原型与原型链,类与实例

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录对象中的方法/thisthis使用bind函数原型原型链类与实例class对象中的方法/this 面向对象&#x…

【C语言】小王带您实现文件操作(简单图示讲解)

说到文件操作&#xff0c;大家会第一印象想到不就是电脑硬盘中创建文件&#xff0c;写入数据吗&#xff0c;键盘、鼠标就可以搞定&#xff0c;那么接下来我要告诉你的是C语言也可以实现文件操作哦&#xff01;&#xff01;&#xff01; 目录 前言 一、为什么要使用文件操作 …

模拟卷.C

1.分支_sine之舞 样例输入 3 样例输出 ((sin(1)+3)sin(1-sin(2))+2)sin(1-sin(2+sin(3)))+1 2.数组_和最大子序列 样例输入 5 3 -2 3 -5 4 样例输出 4 3.二维数组_星辰大海 样例输入 2 2 S. #T 2 RD DR 3 S.# .#. .T# 3 RL DDD DDRR 样例输出 I get …

文档管理系统采用电子签名的优势

DocuWare文档管理系统始终提供最高级的安全性&#xff0c;保护我们客户的机密文件和数据。 现在&#xff0c;我们与信任服务提供商ValidatedID 集成&#xff0c;电子签名又向前迈出了重要的一步。每当组织需要证明签名是真实的并确保文档未被更改时&#xff0c;都可以使用这些…

开源mybatis神器

什么是通用 Mapper&#xff1f; 它是一个可以方便的使用 Mybatis 进行单表的增删改查优秀开源产品。它使用拦截器来实现具体的执行 Sql&#xff0c;完全使用原生的 Mybatis 进行操作。在 Github 上标星 5.9K&#xff01; 为什么要用 Mapper&#xff1f; 它提供了所有单表的基…

《SQL基础》07. 约束

SQL-约束约束常见约束案例外键约束删除/更新行为约束 概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 目的&#xff1a;保证数据库中数据的正确性、有效性和完整性。 分类&#xff1a; 约束描述关键字非空约束限制该字段的数据不能为n…

动态内存管理(2)

TIPS 1. scanf读取与空格&#xff1a; 我们都知道&#xff0c;scanf()在从输入缓冲区里面读取数据的时候&#xff0c;如果中间碰到了空格&#xff0c;那么就会直接停下来&#xff0c;而如果在最前面有个空格&#xff0c;直接无视空格。 2. scanf()读取与\n&#xff0c;如果是…

【论文精选】TPAMI2020 - PFENet_先验引导的特征富集网络_小样本语义分割

【论文精选】TPAMI2020 - PFENet_先验引导的特征富集网络_小样本语义分割 精选精析&#xff1a; 【论文原文】&#xff1a; Prior Guided Feature Enrichment Network for Few-Shot Segmentation (当前引用次数&#xff1a;184) 【论文代码】&#xff1a; https://github.co…

【爪洼岛冒险记】第5站:多图解,超详细讲解Java中的数组、二维数组--建议收藏

&#x1f331;博主简介&#xff1a;是瑶瑶子啦&#xff0c;一名大一计科生&#xff0c;目前在努力学习JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛&#xff01; &#x1f4dc;所属专栏&#xff1a;爪洼岛冒险记【从小白到大佬之路】 ✈往期博文回顾: 【爪洼岛冒险记】…

【GD32F427开发板试用】RT-THREAD标准版 移植使用

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;打盹的消防车 前言&#xff1a; 无意在微信看到了GD做活动&#xff0c;想到了第一时间体验一下&#xff0c;搭配RT-THREAD&#xff0c;也很方…