Jetpakc LiveData ViewMode详解

2023-05-16

前言:

本文不定时更新,有问题欢迎在评论区提出~

最近更新时间:2022-06-21

介绍

在2017年,那时,观察者模式有效的简化了开发,但是诸如RxJava一类的库有一些太过复杂,学习成本太高,为此,LiveData出现了,一个专用于Android的,具备自主生命周期感知能力的,可观测的数据存储类。同时也出现了ViewModel这个组件,配合LiveData,更方便的实现MVVM模式中Model与View的分离。那么就让本文来带大家来学习LiveData与ViewModel的使用吧。

LiveData和ViewModel的关系:

请添加图片描述

本文的案例代码:https://github.com/taxze6/Jetpack_learn/tree/main/Jetpack_basic_learn/livedata_viewmodel

LiveData

参考资料:

🌟官方文档:https://developer.android.google.cn/topic/libraries/architecture/livedata

🌟LiveData postValue详解:https://www.cnblogs.com/button123/p/14871526.html

LiveData是一种可观察的数据存储器类(响应式编程,类似Vue)。与常规的可观察类不同,LiveData 具有生命周期感知能力。LiveData最重要的是它了解观察者的生命周期,如ActivityFragment。

因此,当LiveData发送变化时,UI会收到通知,然后UI可以使用新数据重新绘制自己。换句话说,LiveData可以很容易地使屏幕上发生的事情与数据保持同步(响应式编程的核心)

使用 LiveData 具有以下优势:

  • UI与数据状态匹配

    • LiveData 遵循观察者模式。当底层数据发生变化时,LiveData 会通知Observer对象。您可以整合代码以在这些 Observer对象中更新界面。这样一来,您无需在每次应用数据发生变化时更新界面,因为观察者会替您完成更新。
  • 提高代码的稳定性

    代码稳定性在整个应用程序生命周期中增加:

    • 活动停止时不会发生崩溃。如果应用程序组件处于非活动状态,则这些更改不受影响。因此,您在更新数据时无需担心应用程序组件的生命周期。对于后台堆栈中的活动,它不会接受任何LiveData事件
    • 内存泄漏会减少,观察者会绑定到Lifecycle对象,并在其关联的生命周期遭到销毁后进行自我清理
    • 取消订阅任何观察者时无需担心
    • 如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
  • 不再需要手动处理生命周期

    界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。

  • 数据始终保持最新状态

    如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

  • 共享资源

    像单例模式一样,我们也可以扩展我们的LiveData对象来包装系统服务,以便它们可以在我们的应用程序中共享。一旦LiveData对象连接到系统服务,任何需要资源的观察者可以轻松地观看LiveData对象。

在以下情况中,不要使用LiveData:

  • 您需要在信息上使用大量运算符,尽管LiveData提供了诸如转换之类的工具,但只有Map和switchMap可以帮助您
  • 您没有与信息的UI交互
  • 您有一次性的异步操作
  • 您不必将缓存的信息保存到UI中

如何使用LiveData

一般来说我们会在 ViewModel 中创建 Livedata 对象,保证app配置变更时,数据不会丢失,然后再 Activity/Fragment 的 onCreate 中注册 Livedata 监听(因为在 onStart 和 onResume 中进行监听可能会有冗余调用)

基础使用流程:

1.创建一个实例LiveData来保存某种类型的数据。一般在你创建的ViewModel类中完成

class MainViewModel : ViewModel() {
    var mycount: MutableLiveData<Int> = MutableLiveData()
}

2.在Activity或者Fragment中获取到ViewModel,通过ViewModel获取到对应的LiveData

class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        /**记住绝对不可以直接去创建ViewModel实例
        一定要通过ViewModelProvider(ViewModelStoreOwner)构造函数来获取。
        因为每次旋转屏幕都会重新调用onCreate()方法,如果每次都创建新的实例的话就无法保存数据了。
        用上述方法后,onCreate方法被再次调用,
        它会返回一个与MainActivity相关联的预先存在的ViewModel,这就是保存数据的原因。*/
        viewModel = ViewModelProvider(this@MainActivity,ViewModelProvider.
                        NewInstanceFactory()).get(MainViewModel::class.java)
    }
}

3.给LiveData添加观察者监听,用来监听LiveData中的数据变化,在Observer的onChanged中使用监听回调数据

/**
 *  订阅 ViewModel,mycount是一个LiveData类型 可以观察
 * */        
viewModel.mycount.observe(this@MainActivity) {
    countTv.text = viewModel.mycount.value.toString()
}
// LiveData onchange会自动感应生命周期 不需要手动
//        viewModel.mycount.observe(this, object : Observer<Int> {
//            override fun onChanged(t: Int?) {
//
//            }
//        })

进阶用法:

Transformations.map

现在有一个场景:我们通过网络请求,获得了一个User类数据(LiveData),但是,我们只想把User.name暴露给外部观察者,这样我们就可以通过Transformations.map来转化LiveData的数据类型,从而来实现上述场景。这个函数常用于对数据的封装。

//实体类
data class User(var name: String)
...
//Transformations.map接收两个参数,第一个参数是用于转换的LiveData原始对象,第二个参数是转换函数。
private val userLiveData: MutableLiveData<User> = MutableLiveData()
    val userNames: LiveData<String> = Transformations
        .map(userLiveData) { user ->
            user.name
}

Transformations.switchMap

switchMap是根据传入的LiveData的值,然后判断这个值,然后再去切换或者构建新的LiveData。比如我们有些数据需要依赖其他数据进行查询,就可以使用switchMap。

例如,有一个学生,他有两门课程的成绩,但是在UI组件中,我们一次只能显示一门课的成绩,在这个需要判断展示哪门课程成绩的需求下,我们就可以使用switchMap。

data class Student
    (var englishScore: Double, var mathScore: Double, val scoreTAG: Boolean).....
class SwitchMapViewModel:ViewModel {
    var studentLiveData = MutableLiveData<Student>()
    val transformationsLiveData = Transformations.switchMap(studentLiveData) {
        if (it.scoreTAG) {
            MutableLiveData(it.englishScore)
        } else {
            MutableLiveData(it.mathScore)
        }
    }
}//使用时:
var student = Student()
person.englishScore = 88.2
person.mathScore = 91.3
//判断显示哪个成绩
person.condition = true
switchMapViewModel.conditionLiveData.postValue(person)

MediatorLiveData

MediatorLiveData继承于MutableLiveData,在MutableLiveData的基础上,增加了合并多个LiveData数据源的功能。其实就是通过addSource()这个方法去监听多个LiveData。

例如:现在有一个存在本地的dbLiveData,还有一个网络请求来的LiveData,我们需要讲上面两个结果结合之后展示给用户,第一种做法是我们在Activity中分别注册这两个LiveData的观察者,当数据发生变化时去更新UI,但是我们其实使用MediatorLiveData可以简化这个操作。

class MediatorLiveDataViewModel : ViewModel() {
    var liveDataA = MutableLiveData<String>()
    var liveDataB = MutableLiveData<String>()var mediatorLiveData = MediatorLiveData<String>()
    
    init {
        mediatorLiveData.addSource(liveDataA) {
            Log.d("This is livedataA", it)
            mediatorLiveData.postValue(it)
        }
​
        mediatorLiveData.addSource(liveDataB) {
            Log.d("This is livedataB", it)
            mediatorLiveData.postValue(it)
        }
    }
}

解释:

如果是第一次接触到LiveData的朋友可能会发现,我们虽然一直在提LiveData,但是用的时候却是MutableLiveData,这两个有什么关系呢,既然都没怎么用LiveData,那么把标题直接改成MutableLiveData吧
其实,LiveData与MutableLiveData在概念上是一模一样的。唯一的几个区别分别是:

💡“此处引用:LiveData与MutableLiveData的区别文章中的段落”

  • MutableLiveData的父类是LiveData
  • LiveData在实体类里可以通知指定某个字段的数据更新
  • MutableLiveData则是完全是整个实体类或者数据类型变化后才通知.不会细节到某个字段。

原理探究:

对于LiveData的基础使用我们就讲到这里,想要探索LiveData原理的朋友可以从下面几个角度:

  • LiveData的工作原理
  • LiveData的observe方法源码分析
  • LifecycleBoundObserver源码分析
  • activeStateChanged源码分析(用于粘性事件)
  • postValue和setValue
  • considerNotify判断是否发送数据分析
  • 粘性事件的分析

相信大家从以上几个角度去分析LiveData会有不小的收获💪

ViewModel

官方文档:https://developer.android.google.cn/topic/libraries/architecture/viewmodel

官方简介

ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel类让数据可在发生屏幕旋转等配置更改后继续留存。

生命周期

请添加图片描述

ViewModel的生命周期会比创建它的Activity、Fragment的生命周期都要长。所以ViewModel中的数据会一直存活在Activity/Fragment中。

基础使用流程:

1.构造数据对象

自定义ViewModel类,继承ViewModel,然后在自定义的ViewModel类中添加需要的数据对象

class MainViewModel : ViewModel() {
    ...
}

2.获取数据

有两种常见的ViewModel创建方式,第一种是在activity或fragment种直接基于 ViewModelProvider 获取。第二种是通过ViewModelFactory 创建

//第一种 ViewModelProvider直接获取
ViewModelProvider(this@MainActivity).get(MainViewModel::class.java)//第二种 通过 ViewModelFactory 创建
class TestViewModelFactory(private val param: Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return TestViewModel(param) as T
    }
}ViewModelProvider(this@MainActivity,TestViewModelFactory(0)).get(TestViewModel::class.java)

使用ViewModel就是这么简单🚢

ViewModel常见的使用场景

  • 使用ViewModel,在横竖屏切换后,Activity重建,数据仍可以保存
  • 同一个Activity下,Fragment之间的数据共享
  • 与LiveData配合实现代码的解耦

ViewModel和onSaveInstanceState的区别

我相信大家一定知道onSaveInstanceState,它也是用来保存UI状态的,你可以使用它保存你所想保存的东西,在Activity被杀死之前,它一般在onStop或者onPause之前触发。虽然ViewModel被设计为应用除了onSaveInstanceState的另一个选项,但是还是有一些明显的区别。由于资源限制,ViewModel无法在进程关闭后继续存在,但onSaveInstance包含执行此任务。ViewModel是存储数据的绝佳选择,而onSaveInstanceState bundles不是用于该目的的合适选项。

ViewModel用于存储尽可能多的UI数据。因此,在配置更改期间不需要重新加载或重新生成该数据。

另一方面,如果该进程被框架关闭,onSaveInstanceState应该存储回复UI状态所需的最少数据量。例如,可以将所有用户的数据存放在ViewModel中,而仅将用户的数据库ID存储在onSaveInstanceState中。

android onSaveInstanceState调用时机详细总结

onSaveInstanceState用法及源码分析

ViewModel和Context

ViewModel不应该包含对Activity,Fragment或context的引用,此外,ViewModel不应包含对UI控制器(如View)的引用,因为这将创建对Context的间接引用。当您旋转Activity被销毁的屏幕时,您有一个ViewModel包含对已销毁Activity的引用,这就是内存泄漏。因此,如果需要使用上下文,则必须使用应用程序上下文 (AndroidViewModel)

LiveData和ViewModel的基本用法我们已经介绍完了,现在用几个例子带大家来更好的使用它们

案例一:计数器 — 两个Activity共享一个ViewModel

话不多说,先上效果图:
在这里插入图片描述

虽然这个案例是比较简单的,但是我相信可以帮助你更快的熟悉LiveData和ViewModel

想要实现效果图的话需要从下面几步来写(只讲解核心代码,具体代码请自己查看仓库):

第一步:创建ViewModel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
​
class MainViewModel : ViewModel() {
    private var _mycount: MutableLiveData<Int> = MutableLiveData()
    //只暴露不可变的LiveData给外部
    val mycount: LiveData<Int> get() = _mycount
    init {
        //初始化
        _mycount.value = 0
    }
    /**
     * mycount.value若为空就赋值为0,不为空则加一
     * */
    fun add() {
        _mycount.value = _mycount.value?.plus(1)
    }
    /**
     * mycount.value若为空就赋值为0,不为空则减一,可以为负数
     * */
    fun reduce() {
        _mycount.value = _mycount.value?.minus(1)
    }
    /**
     * 随机参数
     * */
    fun random() {
        val random = (0..100).random()
        _mycount.value = random
    }
    /**
     * 清除数据
     * */
    fun clear() {
        _mycount.value = 0
    }
}

第二步:标记ViewModel的作用域

因为,我们是两个Activity共享一个ViewModel,所以我们需要标记ViewModel的作用域

import androidx.lifecycle.*/**
 * 用于标记viewmodel的作用域
 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation
class VMScope(val scopeName: String) {}private val vMStores = HashMap<String, VMStore>()fun LifecycleOwner.injectViewModel() {
    //根据作用域创建商店
    this::class.java.declaredFields.forEach { field ->
        field.getAnnotation(VMScope::class.java)?.also { scope ->
            val element = scope.scopeName
            var store: VMStore
            if (vMStores.keys.contains(element)) {
                store = vMStores[element]!!
            } else {
                store = VMStore()
                vMStores[element] = store
            }
            val clazz = field.type as Class<ViewModel>
            val vm = ViewModelProvider(store, ViewModelProvider.NewInstanceFactory()).get(clazz)
            field.set(this, vm)
        }
    }
}class VMStore : ViewModelStoreOwner {
    private var vmStore: ViewModelStore? = null
    override fun getViewModelStore(): ViewModelStore {
        if (vmStore == null)
            vmStore = ViewModelStore()
        return vmStore!!
    }}

第三步:在Activity中使用(都是部分代码)

class MainActivity : AppCompatActivity() {
    @VMScope("count") //设置作用域
    lateinit var viewModel: MainViewModel
​
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        injectViewModel()
        initEvent()
    }private fun initEvent() {
        val cardReduce: CardView = findViewById(R.id.card_reduce)
        .....
        cardReduce.setOnClickListener {
            //调用自定义ViewModel中的方法
            viewModel.reduce()
        }
        
        .....
        
        /**
         *  订阅 ViewModel,mycount是一个LiveData类型 可以观察
         * */
        viewModel.mycount.observe(this@MainActivity) {
            countTv.text = viewModel.mycount.value.toString()
        }
}
    
在第二个Activity中也是类似...

这样就可以实现效果图啦🏀

案例二:同一个Activity下的两个Fragment共享一个ViewModel

话不多说,先上效果图

请添加图片描述

这个效果就很简单了,在同一个Activity下,有两个Fragment,这两个Fragment共享一个ViewModel

这个案例主要是想带大家了解一下ViewModel在Fragment中的使用

第一步:依旧是创建ViewModel

class BlankViewModel : ViewModel() {
    private val numberLiveData = MutableLiveData<Int>()private var i = 0
    fun getLiveData(): LiveData<Int> {
        return numberLiveData
    }fun addOne(){
        i++
        numberLiveData.value = i
    }
}

非常简单的一个ViewModel

第二步:在Fragment中使用

//左Fragment
class LeftFragment : Fragment() {
    
    private val viewModel:BlankViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_left, container, false)
    }override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //对+1按钮监听
        left_button.setOnClickListener {
            viewModel.addOne()
        }
        activity?.let {it ->
            viewModel.getLiveData().observe(it){
                left_text.text = it.toString()
            }
        }
    }
}//右Fragment
class RightFragment : Fragment() {
    private val viewModel: BlankViewModel by activityViewModels()
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_right, container, false)
    }override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        right_button.setOnClickListener {
            viewModel.addOne()
        }
        activity?.let { it ->
            viewModel.getLiveData().observe(it) {
                right_text.text = it.toString()
            }
        }
    }
}

这样,这个简单的案例就实现啦。

尾述

终于把LiveData和ViewModel的大致使用讲解了一遍,但仅仅这样还是不够的,你还需要在更多更多的实践中去熟悉,去深入学习…

关于我

Hello,我是Taxze,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客。

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?😝

基础系列:

2022 · 让我带你Jetpack架构组件从入门到精通 — Lifecycle

学会使用LiveData和ViewModel,我相信会让你在写业务时变得轻松🌞 (本文🌟)

以下部分还在码字,赶紧点个收藏吧🔥

2022 · 让我带你Jetpack架构组件从入门到精通 — DataBinding

2022 · 让我带你Jetpack架构组件从入门到精通 — Navigation

2022 · 让我带你Jetpack架构组件从入门到精通 — Room

2022 · 让我带你Jetpack架构组件从入门到精通 — Paging3

2022 · 让我带你Jetpack架构组件从入门到精通 — WorkManager

2022 · 让我带你Jetpack架构组件从入门到精通 — ViewPager2

2022 · 让我带你Jetpack架构组件从入门到精通 — 登录注册页面实战(MVVM)

进阶系列:

协程 + Retrofit网络请求状态封装

Room 缓存封装

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Jetpakc LiveData ViewMode详解 的相关文章

  • 解决R/RStudio中安装包“无法与服务器建立连接”

    这几天在用RStudio写程序的时候突然出现无法从网络直接安装r包的问题 xff0c 也无法从本地直接安装 通过多方尝试找到了问题解决的办法 xff0c 现在记录在这里 程序报错如下 xff1a gt install packages sp
  • [https]解决apache 403 权限问题

    最近需要发布一个网站 xff0c 购买了腾讯云服务器 xff0c 使用oneinstack搭建了LAMP环境 将网站文件上传到服务器后从地址栏访问 xff0c 出现403权限问题 xff1a 403 xff1a You don t have
  • 解决python安装后cmd报错Fatal Python error: Py_Initialize: unable to load the file system codec

    因为我的电脑中以前装有python xff0c 这次重新从网上安装python3后与之前残留的设置产生了冲突 xff0c 打开cmd输入python出现错误 xff1a Fatal Python error Py Initialize un
  • 模型效果验证方法

    对于回归问题 xff0c 一般采用均方误差验证 对于分类问题 xff0c 首先要明确两个概念 xff1a 查准率和查全率 数据可以分成四种类别 xff1a 真正例TP 真反例TN 假正例FP 假反例FN TP是指预测结果是正例 xff0c
  • leetcode:167. Two Sum II - Input array is sorted

    目录 题目描述 实现 1 哈希表 2 二分查找法 3 双指针法 题目描述 Given an array of integers that is already sorted in ascending order find two numbe
  • leetcode:206 - Reverse Linked List

    目录 题目描述 实现 1 迭代法 2 递归法 题目描述 Reverse a singly linked list Example Input 1 gt 2 gt 3 gt 4 gt 5 gt NULL Output 5 gt 4 gt 3
  • 解决idea运行Tomcat报错:Unable to ping server at localhost:1099

    方法一 xff1a 网上有人说修改hosts xff0c 目录定位到 C windows system32 drivers etc hosts xff0c 在里面添加 xff1a 127 0 0 1 localhost 计算机名 这个方法实
  • spring中alias标签

    在对bean进行定义时 xff0c 除了使用id属性来指定名称之外 xff0c 为了提供多个名称 xff0c 可以使用alias标签来指定 而所有的这些名称都指向同一个bean xff0c 在某些情况下提供别名非常有用 xff0c 比如为了
  • ActiveMQ高并发处理方案

    高并发发送消息异常解决方法 xff1a 现象 xff1a 使用10个线程每100ms发送一条消息 xff0c 大约3000多条后 xff0c 出现异常 xff0c 所有线程停 止 xff1a javax jms JMSException C
  • Unable to get package info for com.example.sampletest; is package not installed?

    Unable to instantiate application com android activity BaseApplication java lang IllegalStateException Unable to get pac
  • 5种最著名的人脸识别算法和关键特征介绍

    人脸识别算法是任何人脸检测和识别系统或软件的基础组件 专家将这些算法分为两种核心方法 xff1a 几何方法侧重于区分特征 xff0c 光度统计方法用于从图像中提取值 然后将这些值与模板进行比较以消除差异 这些算法还可以分为两个更一般的类别
  • 请多多指教

    我是菜鸟 xff0c 还请各位大虾多多指教 xff01
  • golang-vue实现微信小程序分享到朋友圈

    最近涉及到微信小程序分享到朋友圈 xff0c 不知道微信为什么不直接接口分享 xff0c 咱也不敢佛 xff0c 咱也不敢问 xff0c 只能百度问度娘 xff0c 看官方文档 xff0c 网上的一些分享五花八门 xff0c 每一个重点的
  • Archlinux 命令行模式-安装 BIOS 非 UEFI(实装笔记)(系统安装完成,系统美化使用未完成)

    前言 截至2021年02月28日 xff0c 官网iso安装镜像已经提供有脚本安装方式 当下谨以命令行安装方式做记录 xff0c 共同学习 主要知识来源于archwiki https wiki archlinux org index php
  • Arch 基本安装后的使用配置

    A 参考借用整理 1 Arch wiki https wiki archlinux org index php Installation guide B 注意 1 选择有很多 xff0c 可以根据自己需求来 xff0c 如速度 xff0c
  • 【IP技术】网络安全防护措施

    网络安全威胁造成的形式主要包含运用系统软件缺点或侧门 xff0c 运用网络防火墙安全隐患 xff0c 内部结构客户的泄密 泄露和毁坏 xff0c 动态口令进攻和拒绝服务式攻击等 针对该网络安全威胁 xff0c 现阶段的预防措施主要有五种 x
  • 如何搭建本地yum仓库

    一 yum简介 yum xff08 Yellow dog Updater Modified xff09 是一个在 Fedora 和 RedHat 以及 SUSE 中的 Shell 前端软件包管理器 基于 RPM 包管理 xff0c 能够从指

随机推荐

  • python_tweets.json (python数据挖掘入门与实践数据集下载)

    最近在看python数据挖掘入门与实践一书 xff0c 书不错 xff0c 有个不好的地方是 xff0c 书上所用的数据集 xff0c 有几个测试数据在网上非常不好找 下面几个资源是我自己整理出来的 xff0c 上传到CSDN xff0c
  • ios UILabel显示html文本

    let attrContent 61 try NSAttributedString data htmlContent options NSDocumentTypeDocumentAttribute NSHTMLTextDocumentTyp
  • 转行的辛苦

    我是2004年毕业的 xff0c 学的专业是市场营销 xff0c 毕业后来到深圳 xff0c 换了很多工作 xff0c 一直都无法找到令自己满意的工作 因为我非常喜欢计算机 xff0c 从中学到大学 xff0c 一直是班级里公认的计算机高手
  • 内存优化 和 性能优化 的总结

    从 检查内存 xff0c 减少使用 xff0c 复用 xff0c 以及及时释放几个维度去考虑 1 检查 可以ddms查看内存使用情况 xff0c 可以使用 adb shell dumpsys meminfo 查看 xff0c 也可以使用 l
  • ubuntu16.04 安装gnome经典桌面

    一直比较喜欢旧版本Ubuntu的Gnome风格的菜单栏 xff0c 在Ubuntu16 0 4中可以执行指令 xff1a sudo apt get install gnome session flashback 安装完成 xff0c 注销一
  • Gson在序列化反序列化中的TypeAdapter

    1 package waf json adatpter 2 3 import java io IOException 4 import java util ArrayList 5 import java util List 6 import
  • 技术泡妹子二:篡改百度首页,惊呆女神

    大多数网民上网的入口都是先打开百度 xff0c 然后再搜索xxx 进入 xff0c 为了给女神惊喜 xff0c 决定篡改百度首页让女神惊呆 xff0c 当然不是黑了百度 xff0c 目前没这个实力 xff0c 但是我们可以修改host文件
  • VC多线程中控制界面控件的几种方法

    转 http hi baidu com magicyang87 blog item 23bbf2fd72d6b81108244d73 html 为了保证界面的用户体验经常要把数据处理等放到子线程中进行 xff0c 然后把结果更新到主界面 x
  • 一次性打包学透 Spring

    不知从何时开始 xff0c Spring 这个词开始频繁地出现在 Java 服务端开发者的日常工作中 xff0c 很多 Java 开发者从工作的第一天开始就在使用 Spring Framework xff0c 甚至有人调侃 不会 Sprin
  • 关于产品的一些思考——写在前面的话

    自己是一个十足的Geek xff0c 喜欢使用各种新奇的东西 xff0c 包括软件 硬件 技术 xff0c 又因为自己一点点轻微的强迫症和完美主义 xff0c 在这个过程中总会有自己的一些思考 xff0c 又因为技术出身 xff0c 总会考
  • mybatis映射文件mapper.xml的写法。

    在学习mybatis的时候我们通常会在映射文件这样写 xff1a lt xml version 61 34 1 0 34 encoding 61 34 UTF 8 34 gt lt DOCTYPE mapper PUBLIC 34 myba
  • layer的弹出层的简单的例子

    如果不了级的基本的清楚官网查看api网址为 http layer layui com 我用的是iframe 如果是iframe层 layer open type 2 content 39 http sentsin com 39 这里cont
  • 左链接Column 'id' in field list is ambiguous

    如题错误如左链接Column 39 id 39 in field list is ambiguous 今天在写sm的时候 xff0c 用到两个表的联合查询出现的如下的错误 xff0c 仔细查找才发现原来两个表的id重复了 xff0c use
  • maven出现:Failed to execute goal on project ...: Could not resolve dependencies for project ...

    1 我的项目结构是一个父项目 xff0c 多个子项目目录如下 xff1a 2 我这里就举个例子 xff0c 所以应用的也就是core和domain这两个项目 3 两个项目都继承父项目 4 在模块中domain依赖于core xff0c 在c
  • EOS的CPU危机:BM的租赁模式或只是乌托邦

    摘要 xff1a 继RAM内存之后 xff0c EOS的CPU危机也爆发了 昨日 xff0c 由于BetDice和EOSBET为了保证游戏的运行 xff0c 占用了过多的主网CPU xff0c 导致用户资源紧张 xff0c 甚至无法转账 昔
  • 有关Shiro中Principal的使用

    1 定义 principal代表什么那 xff1f 如果阅读官方文档或者源码你会得到如下的定义 xff1a 解释 xff1a 1 xff09 可以是uuid 2 xff09 数据库中的主键 3 xff09 LDAP UUID或静态DN 4
  • 关于shiro的 subject.getPrincipal()方法

    1 说明 上一篇文章说明了 principal xff0c 而subject getPrincipal 是用来干嘛的 xff0c 他就是来获取你存储的principal xff0c 内部是怎么获取的那 xff0c 多个principal怎么
  • CentOS7 64位安装solr7.2.0

    声明 xff1a 本人为学习solr的新手 xff0c 如编写过程中有部队的地方还请各位大佬指正 本文为原创 xff0c 如要转载请注明出处 你能学到 xff1a 1 linux上solr的安装部署 xff0c 官方给出的运行方式 2 添加
  • 阿里巴巴20121009 研发/算法工程师 笔试试题【修正】

    第19题 a i 在排序后的位置是 i k i 43 k xff0c a i 43 2k 在排序后的位置是 i 43 k i 43 3k xff0c 必然有a i lt 61 a i 43 2k 所以数组a里实际上有2k个各自有序的 交错的
  • Jetpakc LiveData ViewMode详解

    前言 xff1a 本文不定时更新 xff0c 有问题欢迎在评论区提出 最近更新时间 xff1a 2022 06 21 介绍 在2017年 xff0c 那时 xff0c 观察者模式有效的简化了开发 xff0c 但是诸如RxJava一类的库有一