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活在当下3 小时前
【现代 Android APP 架构】09. 聊一聊依赖注入在 Android 开发中的应用
java·架构·android jetpack
bqliang11 小时前
Jetpack Navigation 3:领航未来
android·android studio·android jetpack
用户69371750013843 天前
🚀 Jetpack MVI 实战全解析:一次彻底搞懂 MVI 架构,让状态管理像点奶茶一样丝滑!
android·android jetpack
俩个逗号。。6 天前
ViewPager+Fragment 切换主题崩溃
android·android studio·android jetpack
alexhilton8 天前
Compose CameraX现已稳定:给Composer的端到端指南
android·kotlin·android jetpack
在狂风暴雨中奔跑10 天前
使用 Compose 权限请求模板高效搭建应用权限流程
android jetpack
H10015 天前
SharedFlow和StateFlow的方案选择-屏幕旋转设计
android jetpack
alexhilton15 天前
理解retain{}的内部机制:Jetpack Compose中基于作用域的状态保存
android·kotlin·android jetpack
Coffeeee16 天前
Labubu很难买?那是因为还没有用Compose来画一个
前端·kotlin·android jetpack
我命由我1234518 天前
Android 对话框 - 对话框全屏显示(设置 Window 属性、使用自定义样式、继承 DialogFragment 实现、继承 Dialog 实现)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime