ViewModel的使用和源码分析

ViewModel概述

ViewModel是一个状态存储器,它的主要优势是可以缓存状态,让ViewModel中的数据不受Configuration Change的影响。这意味着当你切换页面,或者屏幕旋转的时候,不需要重新获取数据。

ViewModel的优势

我们先来看看屏幕旋转时遇到的问题,如果在AndroidManifest.xml中没有配置configChanges="orientation|screenSize",系统会销毁并重建Activity,我们一般使用onSaveInstanceState()方法保存数据,然后使用onCreate()中的Bundle恢复数据。但是这个方法只能恢复序列化的数据,如果要序列化的对象很复杂,序列化会占用大量内存。由于这个过程发生在主线程,耗时的序列化可能导致掉帧和页面卡顿,并且这个方法不能用于恢复Bitmap这种大容量的数据(IPC对Bundle有1M的限制)。

使用ViewModel可以解决这个问题,ViewModel可以缓存数据,不受Configuration Change的影响。ViewModel的优势主要有2个:

  • 更便于保存数据
  • 更方便UI组件之间的通信

更便于保存数据

我们通常在Activity的onCreate()方法中请求ViewModel,在旋转设备屏幕时,系统会多次调用onCreate()方法,而ViewModel从你首次请求ViewModel直到Activity执行onDestroy()方法期间一直存在。如下图左侧展示了旋转屏幕到最终退出页面,Activity经历的多个生命周期状态,右侧是对应ViewModel的生命周期:

Activity销毁后,ViewModel的onCleared()方法才会最终执行释放ViewModel。它不会像Activity那样反复重建,从而节省了用于状态维护(数据的存储和获取、序列化和反序列化)的代码。

ViewModel在其生命周期内类似一个单例,这就引发了一个更好用的特性,那就是UI组件间的通信。

更方便UI组件之间的通信

由于Activity销毁后,ViewModel中的资源才会释放,就可以很方便地运用在一个Activity中有多个Fragment的场景,多个Fragment可以持有同一个ViewModel的实例,再结合LiveData,这也就意味着数据状态的共享

比如下面这个示例:

java 复制代码
public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
    }
}
xml 复制代码
//activity_my.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <fragment
        android:name="com.example.test.viewmodel.ListFragment"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <fragment
        android:name="com.example.test.viewmodel.DetailFragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@+id/fragment_list"
        app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
java 复制代码
public class ShareViewModel extends ViewModel {

    private MutableLiveData<String> selected = new MutableLiveData<>();

    public void setSelectedItem(String item){
        selected.setValue(item);
    }

    public LiveData<String> getSelectedLiveData() {
        return selected;
    }
}
java 复制代码
public class ListFragment extends Fragment {
    private ShareViewModel shareViewModel;
    private List<String> list = new ArrayList<>();
    private RecyclerView.Adapter adapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        RecyclerView recyclerView = view.findViewById(R.id.rv);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext());
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        adapter = new RecyclerView.Adapter<ItemViewHolder>() {
            @NonNull
            @Override
            public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
                return new ItemViewHolder(new TextView(parent.getContext()));
            }

            @Override
            public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) {
                String s = list.get(position);
                if(holder.itemView instanceof TextView){
                    ((TextView)holder.itemView).setText(s);
                    (holder.itemView).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            shareViewModel.setSelectedItem(s);
                        }
                    });
                }
            }

            @Override
            public int getItemCount() {
                return list.size();
            }
        };
        recyclerView.setAdapter(adapter);

        for(int i = 0; i < 100; i++){
            list.add("item " + i);
        }
        adapter.notifyDataSetChanged();
        return view;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        shareViewModel = new ViewModelProvider(requireActivity()).get(ShareViewModel.class);
    }

    static class ItemViewHolder extends RecyclerView.ViewHolder{
        TextView tv;
        public ItemViewHolder(@NonNull TextView itemView) {
            super(itemView);
            tv = itemView;
        }
    }
}
java 复制代码
public class DetailFragment extends Fragment {

    private ShareViewModel shareViewModel;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_detail, container, false);
        TextView tv = view.findViewById(R.id.tv);
        shareViewModel.getSelectedLiveData().observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                tv.setText(s);
            }
        });
        return view;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        shareViewModel = new ViewModelProvider(requireActivity()).get(ShareViewModel.class);
    }
}

ListFragment和DetailFragment的requireActivity()方法返回的是同一个宿主Activity,因此两个Fragment之间返回的是同一个ViewModel。再结合LiveData,把LiveData放到ViewModel中,这样在ListFragment中点击了某个item,DetailFragment马上就可以监听到并显示相应的字符串,如下图所示:

源码分析

ViewModel的核心就是因配置更改,系统重建Activity或Fragment后,ViewModel的实例仍然存在,这是怎么实现的呢?下面我们就通过源码来进行分析。

自定义ViewModel需要继承ViewModel类,我们先来看看ViewModel类的代码:

java 复制代码
public abstract class ViewModel {

    private volatile boolean mCleared = false;

    //clear()方法中会调用onCleared(),当ViewModel观察了一些数据,可以使用这个方法
    //清除订阅来避免ViewModel的内存泄露
    protected void onCleared() {
    }

    @MainThread
    final void clear() {
        mCleared = true;
        ...
        onCleared();
    }
}

ViewModel类是一个抽象类,内部逻辑比较简单。onCleared()方法是一个空方法,可以重写这个方法清除订阅来避免ViewModel的内存泄露

获取ViewModel的实例前我们先要获取ViewModelProvider实例,一般我们在Activity中使用new ViewModelProvider(this)来获取ViewModelProvider实例:

java 复制代码
public class ViewModelProvider {

    private final Factory mFactory;
    
    private final ViewModelStore mViewModelStore;

    //一个参数ViewModelStoreOwner
    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;
    }

}

可以看到this实际上是ViewModelStoreOwner,它是一个接口:

java 复制代码
public interface ViewModelStoreOwner {
    
    @NonNull
    ViewModelStore getViewModelStore();
}

在ComponentActivity中对这个接口进行了实现:

java 复制代码
public class ComponentActivity implements ViewModelStoreOwner{
    
    private ViewModelStore mViewModelStore;

    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;
    }

}

创建ViewModelProvider实例最终走的是ViewModelProvider类的第3个构造方法,其中ViewModelStore为ViewModel的存储器,Factory为创建ViewModel的工厂。ViewModelStore来自ComponentActivity中的getViewModelStore()方法,Factory来自NewInstanceFactory.getInstance()

我们先来看看如何从ViewModelStore中获取ViewModel,获取ViewModel调用了ViewModelProvider的get()方法:

java 复制代码
public class ViewModelProvider {

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

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        //获取canonicalName,其中包含包路径和类名
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        //通过key从mViewModelStore中获取viewModel
        ViewModel viewModel = mViewModelStore.get(key);
        //判断viewModel是否是modelClass或其子类的实例
        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 {
            //没有获取到,使用mFactory创建
            viewModel = mFactory.create(modelClass);
        }
        //存入mViewModelStore
        mViewModelStore.put(key, viewModel);
        //返回
        return (T) viewModel;
    }

}

从上面的代码可以看到,ViewModel以key-value的形式存储在mViewModelStore中,如果mViewModelStore中找不到ViewModel,就使用mFactory创建,然后再存入mViewModelStore。

mFactory来自NewInstanceFactory.getInstance(),NewInstanceFactory代码如下:

java 复制代码
public class ViewModelProvider {

    public static class NewInstanceFactory implements Factory {

        private static NewInstanceFactory sInstance;

        @NonNull
        static NewInstanceFactory getInstance() {
            if (sInstance == null) {
                sInstance = new NewInstanceFactory();
            }
            return sInstance;
        }

        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                //直接通过newInstance()创建类的实例
                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);
            }
        }
    }
}

如果mViewModelStore中找不到ViewModel,会直接反射创建ViewModel的实例存入mViewModelStore。

ViewModelStore类的代码如下:

java 复制代码
public class ViewModelStore {

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

    final void put(String key, ViewModel viewModel) {
        //返回旧的ViewModel
        ViewModel oldViewModel = mMap.put(key, viewModel);
        //旧的ViewModel不为空,调用其onCleared()方法
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

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

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

    
    public final void clear() {
        //遍历ViewModel并调用其clear()方法
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        //mMap清空
        mMap.clear();
    }
}

ViewModelStore中使用HashMap存储ViewModel,由此可知,只要ViewModelStore不变,其中存储的ViewModel就不会变。

前面我们看到getViewModelStore()方法来自ComponentActivity:

java 复制代码
public class ComponentActivity implements ViewModelStoreOwner{
    
    private ViewModelStore mViewModelStore;

    public ViewModelStore getViewModelStore() {
        //判断getApplication()是否为null
        //给mApplication赋值在Activity的attach()方法中,由此可知
        //获取ViewModelStore需要在attach()方法之后
        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() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                //从NonConfigurationInstances中获取ViewModelStore
                mViewModelStore = nc.viewModelStore;
            }
            //没有获取到,新建一个
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
}

当我们调用getViewModelStore()获取ViewModelStore时,先会去getLastNonConfigurationInstance()中获取NonConfigurationInstances:

java 复制代码
public class Activity{

    NonConfigurationInstances mLastNonConfigurationInstances;
    
    final void attach(NonConfigurationInstances lastNonConfigurationInstances){ 
        ...
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        ...
    }

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

getLastNonConfigurationInstance()方法来自Activity,给mLastNonConfigurationInstances赋值在Activity的attach()方法中。

这里先不管,第一次调用getViewModelStore()时,mViewModelStore为null,新建一个ViewModelStore赋值给mViewModelStore。

mViewModelStore存储在哪里呢?

在onRetainNonConfigurationInstance()方法中,会将mViewModelStore存入NonConfigurationInstances,顾名思义,非配置实例,即不受配置更改影响的实例。这里NonConfigurationInstances是ComponentActivity的静态内部类:

java 复制代码
public class ComponentActivity{

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

    @Override
    @Nullable
    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;
        }

        //将viewModelStore存入NonConfigurationInstances,返回
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
}    

onRetainNonConfigurationInstance()方法又是在哪里调用的呢?

在这里添加断点,旋转屏幕,可以看到调用堆栈信息,如下:

Activity因配置更改而重建时,系统将执行Relaunch流程,系统在这个过程中通过同一个ActivityClientRecord来完成信息传递,会销毁当前Activity,紧接着再马上重建同一个Activity。我们先来看看handleRelaunchActivity()方法:

java 复制代码
public final class ActivityThread{

    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

    @Override
    public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
        ...
        //从mActivities中获取ActivityClientRecord
        ActivityClientRecord r = mActivities.get(tmp.token);
        ...
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
            pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
    }

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        ...
        //这里调用Activity的onPause()方法
        performPauseActivity(r, false, reason, null /* pendingActions */);
        ...
        //这里调用Activity的onStop()方法
        callActivityOnStop(r, true /* saveState */, reason);
        ...
        //这里调用Activity的onDestroy()方法
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        ...
        //这里调用Activity的attach()方法并传入ActivityClientRecord
        handleLaunchActivity(r, pendingActions, customIntent);
        ...
    }
}    

handleRelaunchActivity()方法从mActivities中获取ActivityClientRecord然后传给handleRelaunchActivityInner()方法,handleRelaunchActivityInner()方法中顺序调用了performPauseActivity()、callActivityOnStop()、handleDestroyActivity()、handleLaunchActivity(),其中handleLaunchActivity()方法中会调用Activity的attach()方法。

这里handleDestroyActivity()方法中又调用了performDestroyActivity()方法:

java 复制代码
public final class ActivityThread{
    @Override
    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        //调用performDestroyActivity()方法    
        ActivityClientRecord r = performDestroyActivity(token, finishing,
                configChanges, getNonConfigInstance, reason);
        ...        
    } 
}

performDestroyActivity()方法代码如下:

java 复制代码
public final class ActivityThread{

    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        ...
        if (getNonConfigInstance) {
            try {
                //在这里调用Activity的retainNonConfigurationInstances()方法,
                //获取NonConfigurationInstances存入r中
                r.lastNonConfigurationInstances
                        = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                ...
                }
            }
        }
        ...
        return r;
    }
}

在performDestroyActivity()中,通过Activity的retainNonConfigurationInstances()方法获取ActivityNonConfigurationInstances赋值给r.lastNonConfigurationInstances,就相当于修改了mActivities中对应的的ActivityClientRecord

而上面handleLaunchActivity()方法传入ActivityClientRecord跟这里是同一个,也就导致handleLaunchActivity()方法传入的ActivityClientRecord也同步修改了。

Activity的retainNonConfigurationInstances()方法代码如下:

java 复制代码
public class Activity{

    //注意这里的NonConfigurationInstances与ComponentActivity中的不一样
    static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }

    NonConfigurationInstances retainNonConfigurationInstances() {
        //ComponentActivity是其子类,调用ComponentActivity的
        //onRetainNonConfigurationInstance()方法
        Object activity = onRetainNonConfigurationInstance();
        ...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if (mVoiceInteractor != null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;
    }
}

由于ComponentActivity是Activity的子类,retainNonConfigurationInstances()方法调用了ComponentActivity的onRetainNonConfigurationInstance()方法,将ComponentActivity中的NonConfigurationInstances赋值给ActivityNonConfigurationInstances中的activity,注意这两个NonConfigurationInstances是不一样的。

总结一下:

大致流程是这样的,ViewModel存储在ViewModelStore中,Activity因配置更改销毁时,先将ViewModelStore存入ComponentActivity的NonConfigurationInstances,然后又把这个NonConfigurationInstances存入了Activity的NonConfigurationInstances,再存入ActivityClientRecord,最后存入ActivityThread的ArrayMap(mActivities)中。Activity重建时,调用Activity的attach()方法,传入的是同一个ActivityClientRecord,以后每次调用getViewModelStore()方法取的都是这里存储的ViewModelStore,这就是ViewModel不受配置更改影响的原因。

什么时候会清理ViewModelStore中的数据呢?

在Activity中,非配置变更触发的销毁会清除ViewModelStore中的数据:

java 复制代码
public class ComponentActivity{

    public ComponentActivity() {
        ...
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    mContextAwareHelper.clearAvailableContext();
                    //判断非配置变更,清理ViewModelStore
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
        ...
    }
}

使用onSaveInstanceState()方法保存数据与使用ViewModel保存数据的区别:

  1. onSaveInstanceState()是保存到Bundle中,只能保存Bundle能接收的数据类型,包括一些基本类型的数据和序列化的类型,而ViewModel可以保存任意类型的数据。

  2. onSaveInstanceState()受到Binder事务缓冲区大小限制,只可以存储小容量的数据。ViewModel可以存储大容量的数据,但是会受到App内存空间的限制。

  3. onSaveInstanceState()数据最终存储到ActivityManagerService的ActivityRecord中了,也就是存到系统进程中去了,所以App被杀之后还是能恢复。而ViewModel数据是存储到ActivityClientRecord中,也就是存到应用本身的进程中了,App被杀后没法恢复的。

相关推荐
Lei活在当下16 小时前
【业务场景架构实战】4. 支付状态分层流转的设计和实现
架构·android jetpack·响应式设计
天花板之恋1 天前
Compose之图片加载显示
android jetpack
消失的旧时光-19432 天前
Kotlinx.serialization 使用讲解
android·数据结构·android jetpack
Tans52 天前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Lei活在当下3 天前
【业务场景架构实战】2. 对聚合支付 SDK 的封装
架构·android jetpack
Tans55 天前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
alexhilton7 天前
runBlocking实践:哪里该使用,哪里不该用
android·kotlin·android jetpack
Tans59 天前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
ljt272496066110 天前
Compose笔记(四十九)--SwipeToDismiss
android·笔记·android jetpack
4z3312 天前
Jetpack Compose重组优化:机制剖析与性能提升策略
性能优化·android jetpack