百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

Jetpack-VM再不懂你就out了(vm client)

liuian 2025-04-30 18:01 14 浏览

作者:heiyulong

授权原文:https://mp.weixin.qq.com/s/yZ7ZQYn7KjYxQiZT956NqQ

前言

ViewModel 作为 Jetpack 组件库组件之一,它的出现释放了 Activity/Fragment 管理数据的压力,ViewModel 经常会搭配 LiveData 一起用于 MVVM 的开发模式。

一、认识ViewModel

1、什么是 ViewModel?

  • ViewModel 具备宿主生命后期感知能力的数据存储与数据管理组件。
    • 使用 ViewModel 保存的数据,在页面因配置变更(如,发生屏幕旋转等配置更改后)导致页面销毁重建之后依然也是存在的;
    • ViewModel还处理Activity/Fragment与应用程序其余部分的通信(例如,调用业务逻辑类);
    • ViewModel的目的是获取并保留Activity或Fragment所需的信息。Activity或Fragment应能够观察ViewModel中的更改;
    • ViewModel通常通过LiveData或DataBinding公开此信息;
    • ViewModel的唯一责任是管理UI的数据。它永远都不应访问您的视图层次结构或保留对Activity或Fragment的引用。

2、ViewModel 的优势

页面配置更改数据不丢失

  • 当设备因配置更改导致 Activity/Fragment 重建,ViewModel 中的数据并不会因此而丢失,配合 LiveData 可以在页面重建后立马能收到最新保存的数据用以重新渲染页面。

Android的Activity的生命周期有很多状态,并且由于配置更改,单个Activity可能会在这些不同状态之间循环多次。参照下面Activity生命周期图:

Activity生命周期

当一个Activity经历了所有这些状态,您可能还需要将瞬态UI数据保存在内存中。我将瞬态UI数据定义为UI所需的数据,包括用户输入的数据,运行时生成的数据或从数据库加载的数据。这些数据可以是图片位图、用于RecyclerView的对象列表等。

如,用户旋转屏幕时,以前我们通过onSaveInstanceState()和onRestoreInstanceState()在配置更改期间保存或还原此数据。

用户旋转屏幕时如下图:当系统开始停止您的Activity时:

  • (1)它会调用onSaveInstanceState(),以便您可以指定要保存的其他状态数据,以防Activity必须重新创建实例。
  • (2)如果Activity被破坏并且必须重新创建相同的实例,则系统将(1)中定义的状态数据传递给onCreate()方法和onRestoreInstanceState()方法。

屏幕旋转:Avtivity生命周期流程

但是,如果您的数据不需要知道或管理“Activity”所处于的生命周期状态,或者不将其存储在Activity内该怎么做呢?这就是ViewModel类的目的

  • ViewModel 和 onSaveIntanceState 方法区别?
    • onSaveIntanceState 只能存储轻量级的 key-value 键值对数据,非配置变更导致的页面被回收时才会触发,此时数据存储在 ActivityRecord 中;
    • ViewModel 可以存放任意 Object 数据,因配置变更导致的页面被回收才有效。此时存在ActivityThread#ActivityClientRecord 中。

生命周期感应

  • 在 ViewModel 中难免会做一些网络请求或数据的处理,可以复写 onCleared() 方法,终止清理一些操作,释放内存。该方法在宿主 onDestroy 时被调用。

如下图:

你可以看到Activity的生命周期在旋转过程中的状态流转直到finish。ViewModel的生命周期显示在关联的Activity的生命周期的旁边。请注意,这个ViewModels可以简单轻松地与Activities/Fragments结合使用。

您通常在系统首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 Activity 完成并销毁。

数据共享

  • 对于单 Activity 对 Fragment 的页面,可以使用 ViewModel 实现页面之间的数据共享,实际上不同的 Activity也可以实现数据共享。

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的情况。

想象一下主从 Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。

这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。

此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。

可以使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //!!!请注意:这两个 Fragment 都会检索包含它们的 Activity
        model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //!!!请注意:这两个 Fragment 都会检索包含它们的 Activity
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), { item ->
           // Update the UI.
        });
    }
}   
  • 通过ViewModel在 Fragment 之间共享数据的优势:
    • 1、Activity 不需要执行任何操作,也不需要对此通信有任何了解。
    • 2、除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
    • 3、每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

二、ViewModel 的使用

使用ViewModel的三个步骤:

  1. 通过创建一个继承自ViewModel的类来从你的UI控制器(Activity或Fragment)中分离出你的ViewModel
  2. 在你的UI控制器和ViewModel之间建立通讯
  3. 在你的UI控制器中使用ViewModel

LiveData , ViewModel 组件都是基于Lifecycle来实现的,使用 ViewModel 之前需要先添加依赖:

声明依赖项

dependencies {
        def lifecycle_version = "2.2.0"

        // ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
}

1、创建一个ViewModel类

引用官方:一个非常简单的例子

  • 通常情况下,你需要为你的app的每个页面创建一个ViewModel类。
  • 这个ViewModel类会掌控和管理所有的与页面相关数据,并且提供get/set方法用于存取数据。
  • 这会将显示Activity的UI代码与您的用于显示UI的数据分离开(显示UI的数据现在位于ViewModel中)。
public class ScoreViewModel extends ViewModel {
   // Tracks the score for Team A
   public int scoreTeamA = 0;

   // Tracks the score for Team B
   public int scoreTeamB = 0;
}

2、关联UI控制器和ViewModel

你的UI控制器(即Activity或Fragment)需要了解您的ViewModel。您的UI控制器就可以在发生UI交互时显示数据并更新数据

但是,ViewModels不应保留对Activity,Fragment或Context的引用。 此外,ViewModels不应包含那些拥有对UI控制器的引用的元素,例如Views,因为这将创建对Context的间接引用。

你不该保存这些对象的原因是, ViewModels的寿命超出了特定的UI控制实例之外——如果您将Activity旋转3次,则您刚刚创建了三个不同的Activity实例,但是只有一个ViewModel实例。

考虑到这一点,让我们来创建这个UI控制器/ViewModel关联。你想要为在UI控制器中的ViewModel创建一个成员变量。那么在onCreate中,你应该这样写:

ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)

引用最新版本2.2.0版本ViewModel你会发现,ViewModelProviders该类被弃用了。

参考一下lifecycle更新文档:

lifecycle更新文档

那么在onCreate中,你应该这样写:

 mViewModel = new ViewModelProvider(<Your UI controller>).get(<Your ViewModel>.class)
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
   // Other setup code below...
}

注意:“ViewModel中没有上下文”的规则有一个例外:

有时您可能需要一个Application的Context(而不是Activity的Context)来与诸如系统服务之类的东西一起使用。

那么,您可以将应用程序上下文储存在ViewModel中,因为应用程序上下文与应用程序生命周期相关联。

这不同于与Activity生命周期相关的Activity的Context。实际上如果需要Application的Context,则应该拓展AndroidViewModel,它只是一个包含Application引用的ViewModel。

3、在UI控制器中使用ViewModel

要访问或更改UI数据,现在可以在ViewModel中使用数据。这里有一个新的onCreate方法和一个为队伍A增加分数的更新方法的例子:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
   
   displayForTeamA(mViewModel.scoreTeamA);
   displayForTeamB(mViewModel.scoreTeamB);
}

// An example of both reading and writing to the ViewModel
public void addOneForTeamA(View v) {
   mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
   displayForTeamA(mViewModel.scoreTeamA);
}

ViewModel也可以很好地和其他架构组件一起工作。比如: LiveData,DataBinding等等。

三、ViewModel复用的实现原理

上面说到,ViewModel 可以实现因配置变更导致页面销毁重建之后依然可以复用。

准确点来说,应该是 页面恢复重建前后获取到的是同一个 ViewModel 实例对象,以至于页面恢复重建后还能接着复用。

通过ViewModelProvider获取 ViewModel 实例的方式入手:

mViewModel = new ViewModelProvider(this).get(ScoreViewModel.class);
//指定factory
mViewModel = new ViewModelProvider(this,factory).get(ScoreViewModel.class);

ViewModelProvider 本质是从传递进去的 ViewModelStore 来获取实例。如果没有传递,则利用 factory 去创建一个新的,并存储到 ViewModelStore。

1、ViewModelProvider获取ViewModel实例get()方法源码分析

public class ViewModelProvider {

    private static final String DEFAULT_KEY =
            "androidx.lifecycle.ViewModelProvider.DefaultKey";
            
    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;
    
    //1、        
    //ViewModelProvider 本质是从传递进去的 ViewModelStore 来获取实例。
    //如果没有传递,则利用 factory 去创建一个新的,并存储到 ViewModelStore。
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
    
    //5、ViewModelStore()的由来,通过owner.getViewModelStore()?
    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }
    
    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
    
    @NonNull
    @MainThread
    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");
        }
        //2、根据传递的modelClass 构建一个默认的Key
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    @NonNull
    @MainThread
    //3、获取viewmodel实例时,也可以自行指定Key
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //4、在ViewModelStore中拿到VM实例
        //ViewModelStore 一个真正用来存储ViewModel实例的集合。本质上是HashMap<String,ViewModel>
        ViewModel viewModel = mViewModelStore.get(key);

        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.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
}

2、ViewModelStore()的由来,通过owner.getViewModelStore()查看源码:

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner {
        
    static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }
    
    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`来获取`ViewModelStore`实例对象
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            //2、如果不为空岂不是获取到的是同一个 ViewModel 实例对象,
            //以至于页面恢复重建后还能接着复用。
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                //3、`ViewModelStore`何时被存储到`NonConfigurationInstances`里面的?
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
        
}

3、onRetainNonConfigurationInstance 源码

因系统原因页面被回收时,会触发onRetainNonConfigurationInstance方法,所以 viewModelStore 对象此时会被存储在NonConfigurationInstance 中。在页面恢复重建时,会再次把这个 NonConfigurationInstance 对象传递到新的Activity 中实现对象复用。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner {
    /**
     * 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
            //3、如果NonConfigurationInstance保存了viewModelStore,把它取出来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;
        //1、把viewModelStore放到NonConfigurationInstances中并返回
        nci.viewModelStore = viewModelStore;
        //2、当页面被销毁时ViewModelStore就被保存起来了。
        return nci;
    }
}

后台私信回复 1024 免费领取 SpringCloud、SpringBoot,微信小程序、Java面试、数据结构、算法等全套视频资料。

相关推荐

打开新世界,教你用RooCode+Copliot+Mcp打造一个自己的Manus

本文耗时两天打造,想要一遍走通需要花点时间,建议找个专注的时间开搞!这不仅是个免费使用claude3.5的方案,也是一个超级智能体方案,绝对值得一试!最近Manus真是赚足了眼球,然而我还是没有邀请码...

Git仓库(git仓库有哪些)

#Git仓库使用方法流程详解##一、环境搭建与基础配置###1.1安装与初始化-**安装Git**:官网下载安装包,默认配置安装-**配置全局信息**:```bashgitconfig...

idea版的cursor:Windsurf Wave 7(ideawalk)

在企业环境中,VisualStudioCode和JetBrains系列是最常用的开发工具,覆盖了全球绝大多数开发者。这两类IDE各有优势,但JetBrains系列凭借其针对特定语言和企业场景的深度...

Ai 编辑器 Cursor 零基础教程:推箱子小游戏实战演练

最近Ai火的同时,Ai编辑器Cursor同样火了一把。今天我们就白漂一下Cursor,使用免费版本搞一个零基础教程,并实战演练一个“网页版的推箱子小游戏”。通过这篇文章,让你真正了解cursor是什么...

ChatGPT深度集成于苹果Mac软件 编码能力得到提升

【CNMO科技消息】近日,OpenAI发布了针对MacOS的桌面应用程序,并宣布了一系列与各类应用程序的互操作性功能,标志着ChatGPT正在从聊天机器人向AI智能体工具进化。此次发布的MacOS桌面...

日常开发中常用的git操作命令和使用技巧

日常开发中常用的git操作命令,从配置、初始化本地仓库到提交代码的常用git操作命令使用git前的配置刚使用git,先要在电脑上安装好git,接着我们需要配置一下帐户信息:用户名和邮箱。#设置用户名...

Trae IDE 如何与 GitHub 无缝对接?

TraeIDE内置了GitHub集成功能,让开发者可以直接在IDE里管理代码仓库和版本控制。1.直接从GitHub克隆项目如果你想把GitHub上的代码拉到本地,Trae提供了...

China&#39;s diplomacy to further provide strong support for country&#39;s modernization: FM

BEIJING,March7(Xinhua)--ChineseForeignMinisterWangYisaidFridaythatChina'sdiplomacywil...

三十分钟入门基础Go(Java小子版)(java入门级教程)

前言Go语言定义Go(又称Golang)是Google的RobertGriesemer,RobPike及KenThompson开发的一种静态、强类型、编译型语言。Go语言语法与...

China will definitely take countermeasures in response to arbitrary pressure: FM

BEIJING,March7(Xinhua)--Chinawilldefinitelytakecountermeasuresinresponsetoarbitrarypre...

Go操作etcd(go操作docker实现沙箱)

Go语言操作etcd,这里推荐官方包etcd/clientv3。文档:https://pkg.go.dev/go.etcd.io/etcd/clientv3etcdv3使用gRPC进行远程过程调...

腾讯 Go 性能优化实战(腾讯游戏优化软件)

作者:trumanyan,腾讯CSIG后台开发工程师项目背景网关服务作为统一接入服务,是大部分服务的统一入口。为了避免成功瓶颈,需要对其进行尽可能地优化。因此,特别总结一下golang后台服务...

golang 之JWT实现(golang gin jwt)

什么是JSONWebToken?JSONWebToken(JWT)是一个开放标准(RFC7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON方式安全地传输信息。由于此信息是经...

一文看懂 session 和 cookie(session cookie的区别)

-----------cookie大家应该都熟悉,比如说登录某些网站一段时间后,就要求你重新登录;再比如有的同学很喜欢玩爬虫技术,有时候网站就是可以拦截住你的爬虫,这些都和cookie有关。如果...

有望取代 java?GO 语言项目了解一下

GO语言在编程界一直让人又爱又恨,有人说“GO将统治下一个十年”,“几乎所有新的、有趣的东西都是用Go写的”;也有人说它过于死板,使用感太差。国外有Google、AWS、Cloudflar...