IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Jetpack 架构组件:ViewModel -> 正文阅读

[移动开发]Jetpack 架构组件:ViewModel

简介

在Activity/Fragment中,通常会将UI交互、与数据获取等相关的业务逻辑全部写在页面中,但是在页面功能复杂的情况下,代码量会变的非常多。页面只应该负责处理用户与UI控件的交互,并将数据展示到屏幕上,而数据获取相关的业务逻辑应该单独处理和存放。谷歌为了解决这种问题,推出了ViewModel组件。

ViewModel 是以感知生命周期的形式来存储和管理视图相关的数据

使用

在app的build.gradle中添加依赖

dependencies {
	implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
}

在这里实现一个简单计数功能,首先在ViewModel 定义一个计数变量num

public class ExViewModel extends ViewModel {

    private  int  num;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

所有与界面相关的数据应该放在ViewModel中。接着,在Activity中获取ViewModel对象。

private  int  i=0;
    private ExViewModel model;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ex_jetpack);
        // 获取ExViewModel 对象
         model=new ViewModelProvider(this).get(ExViewModel.class);
         refreshData();
         btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                i++;
                model.setNum(i);
                refreshData();
            }
        });
    }
    
    public void refreshData(){
        tv_num.setText(model.getNum()+"");
    }

效果如下:
在这里插入图片描述

生命周期

在旋转设备屏幕时,Activity会被销毁重新创建,但ViewMode不会被销毁。效果如下图
在这里插入图片描述
当我们旋转屏幕导致Activity重建时,计数值并没有改变。这意味着横/竖屏状态下的Activity所对应的ViewModel是同一个,并没有被销毁,所持有的数据也一直都存在。

ViewModel生命周期

在这里插入图片描述
可以看到,Activity的生命周期不断变化,经历了被销毁重新创建,而ViewModel的生命周期没有发生变化。

小结:

  • ViewModel只会在Activity存活,且只会创建一次。当销毁时,会主动调用onCleared()方法。
  • 在此Activity下的所有Fragment都可以共享一个ViewModel。
  • 因为ViewModel 生命周期可能长于Activity,所以避免内存泄漏禁止在ViewModel中持有Context或activity或view的引用。如果非得使用Context, 可以继承AndroidViewModel 类中获取ApplicationContext。

与onSaveInstanceState()对比

应用场景

  • onSaveInstanceState

当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用。

  • ViewModel

因配置更改界面销毁重建情况下,则ViewModel恢复数据。

存储方式

  • onSaveInstanceState

onSaveInstanceState是在 序列化到磁盘中。

  • ViewModel

ViewModel是存在内存中,读写速度快。

存储数据的限制

  • onSaveInstanceState

只能存可序列化和反序列化的对象,且大小有限制(一般Bundle限制大小1M)。

  • ViewModel

可以存复杂数据,大小限制就是App的可用内存。

源码分析

在页面中通过ViewModelProvider类来实例化ViewModel

ExViewModel model=new ViewModelProvider(this).get(ExViewModel.class);

接下来,查看ViewModelProvider源码:

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

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }
    
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

在代码中,调用了只需传ViewModelStoreOwner的构造方法,最后会走到两个参数的构造方法。ViewModelStoreOwner是ViewModel存储器拥有者;ViewModelStore是ViewModel存储器;Factory是创建ViewModel实例的工厂。

接下来,查看ViewModelStoreOwner

// ViewModelStoreOwner

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

ViewModelStoreOwner是个接口,其实现类是Activity/Fragment,也就是说Activity/Fragment都是ViewModel存储器的拥有者。

// ComponentActivity.java

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller {
            ....
    @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.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }    
    
}

从ComponentActivity 源码来看,实现了 ViewModelStoreOwner接口。

ViewModel 存取和创建

接下来,先看下ViewModelStore 源码,如何存储ViewModel以及ViewModel实例如何获取的

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

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

    /**
     *  调用ViewModel的clear()方法,然后清除ViewModel;
     如果ViewModelStore的拥有者(Activity/Fragment)销毁后不会重建,那么就需要调用此方法
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

从ViewModelStore的源码可以看成,ViewModel实际上是以HashMap<String,ViewModel>的形式被缓存起来了。ViewModel与页面之间没有直接的关联,它们通过ViewModelProvider进行关联。当页面需要ViewModel时,会向ViewModelProvider索要,而ViewModelProvider会去HashMap中检查该ViewModel是否已经存在缓存中,若存在,则直接返回,否则,则实例化一个。因此,Activity由于屏幕旋转导致的销毁重建并不会影响ViewModel。

接下来,ViewModelProvider类get() 方法做了什么

@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");
        }
         // ViewModelStore中的Map的用于存 ViewModel的 Key
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
             //如果从ViewModelStore获取到,直接返回
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        //使用工厂模式创建 ViewModel 实例
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
        } else {
            viewModel = mFactory.create(modelClass);
        }
        //将创建的 ViewModel 实例放进 mViewModelStore 缓存中
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }    

先尝试从ViewModelStore获取ViewModel实例,如果没有获取到,就使用Factory创建,创建出的ViewModel最后都会存放到mViewModelStore中。

ViewModelStore 存取和创建

接下来,回到顶部 ComponentActivity如何实现ViewModelStoreOwner接口方法getViewModelStore()。

// ComponentActivity

   @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.");
        }
        ensureViewModelStore();
        return mViewModelStore;
    }
    
    void ensureViewModelStore() {
    //如果存储器是空,就先尝试 从lastNonConfigurationInstance从获取
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //  从 NonConfigurationInstances 恢复 ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
              //如果lastNonConfigurationInstance不存在,就new一个
                mViewModelStore = new ViewModelStore();
            }
        }
    }

先从NonConfigurationInstances中获取VIewModelStore实例,如果不存在,则会创建一个新的ViewModelSotre。从源码中可以看出,ViewModelStore会缓存到NonConfigurationInstances中。

接着,查看getLastNonConfigurationInstance()方法,NonConfigurationInstances如何会持有ViewModelStore。

// Activity

 @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
    
    public Object onRetainNonConfigurationInstance() {
        return null;
    }


// ComponentActivity

   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;
        }
        
        //new了一个NonConfigurationInstances
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        // 将viewModelStore交给 NonConfigurationInstances 对象保存
        nci.viewModelStore = viewModelStore;
        return nci;
    }

通过源码注释可以知道mLastNonConfigurationInstances是由onRetainNonConfigurationInstance方法返回的。在onRetainNonConfigurationInstance()方法中viewModelStore赋值 NonConfigurationInstances 对象。

onRetainNonConfigurationInstance() 方法在什么时候被调用的?

// ActivityThread

void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
       ......
      if (getNonConfigInstance) {
                try {
                    // 调用retainNonConfigurationInstances() 方法
                    r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
                    if (!mInstrumentation.onException(r.activity, e)) {
                        throw new RuntimeException("Unable to retain activity "
                                + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
                    }
                }
            }
        ......
}

在performDestroyActivity方法中会被调用,可以看到onRetainNonConfigurationInstance方法返回的Object会赋值给ActivityClientRecord的lastNonConfigurationInstances,这样子就保存了下来。

所以现在就可以明白,onRetainNonConfigurationInstance()方法就是Activity因配置改变而正要销毁时,且新Activity会立即创建,那么系统就会调用该方法。也就说,配置改变时 系统把viewModelStore存在了NonConfigurationInstances中,之后再重建的时候就可以通过getLastNonConfigurationInstance()方法来获取之前缓存的ViewModelStore实例。

在getLastNonConfigurationInstance()方法中,有个NonConfigurationInstances类型 变量mLastNonConfigurationInstances,NonConfigurationInstances

// Activity

    NonConfigurationInstances mLastNonConfigurationInstances;

    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

    static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }
    
//  ComponentActivity

   static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
    }

注意 : Activity 中静态内部类NonConfigurationInstances 和ComponentActivity中静态内部类NonConfigurationInstances是完全不同的类。

在Activity中,getLastNonConfigurationInstance()方法返回值是NonConfigurationInstances类中属性activity值, 该属性值也就是ComponentActivity中NonConfigurationInstances对象。这一点可以从上文中 ComponentActivity类中onRetainNonConfigurationInstance() 方法里面看出来。

下面,探究一下mLastNonConfigurationInstances 在哪里进行赋值的。

//Activity

final void attach(Context context, ActivityThread aThread, ...
            NonConfigurationInstances lastNonConfigurationInstances,... ) {
            ...
            //在attach方法中会进行赋值
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
            ...
       }

mLastNonConfigurationInstances是在Activity的attach方法中赋值。attach方法是为Activity关联上下文环境(是Activity启动的核心流程),在ActivityThread的performLaunchActivity方法中调用,这里的lastNonConfigurationInstances是存在ActivityClientRecord中的一个组件信息。

// ActivityThread

  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }
			······
        //由ActivityClientRecord中获得
        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.configCallback,
                        r.assistToken, r.shareableActivityToken);
  ......
}

在performLaunchActivity方法中,可以看到lastNonConfigurationInstances是保存在ActivityClientRecord中的;又因为界面在销毁的时候调用performDestroyActivity方法,内部又会调用Activity的retainNonConfigurationInstances方法将lastNonConfigurationInstances缓存到ActivityClientRecord中,也就是存到应用本身的进程中了。

而ActivityClientRecord是存在ActivityThread的mActivities中:

//ActivityThrtead.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

ActivityThread 中的 ActivityClientRecord 是不受activity重建的影响,所以ActivityClientRecord中lastNonConfigurationInstances也不受影响,那么ComponentActivity中的NonConfigurationInstances的viewModelStore不受影响,因此viewModel也就不受影响了。

ViewModel的销毁

在ViewModelStore中会调用ViewModel的clear方法

// ViewModelStore

private final HashMap<String, ViewModel> mMap = new HashMap<>();

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

遍历缓存中viewmodel依次调用,那ViewModelStore的clear方法在何时调用

// ComponentActivity

public ComponentActivity() {
  	......
      getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
              //activity生命周期处于destory状态
                if (event == Lifecycle.Event.ON_DESTROY) {
                  
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
}

调用ViewModelStore中的clear()方法,前提条件是activity生命周期处于destory状态,并且配置没有发生改变。

如果能帮助您,请点赞、关注

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-24 18:35:55  更:2021-12-24 18:36:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:36:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码