Android Jetpack 之 ViewModel

这是我参与更文挑战的第15天,活动详情查看: 更文挑战

ViewModel 概览

简介

ViewModel是一种被设计为通过能感知生命周期的方式来存储和管理 UI 相关的数据的类。它让数据能存活过配置变更(如屏幕旋转)而不被杀死。

定义

ViewModel 是一个抽象类,类中只定义了一个空实现的 onCleared() 方法。

public abstract class ViewModel {
    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}
复制代码

ViewModel 里面不要引用 View、或者任何持有 Activity 类的 context , 否则会引发内存泄漏问题。

ViewModel 需要 Application 类的 context 来获取资源、查找系统服务等,可以继承 AndroidViewModel类。

public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}
复制代码

ViewModel 使用demo

看下源码中的一个例子

public class UserActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.user_activity_layout);
        final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
        viewModel.userLiveData.observer(this, new Observer<User>() {
           @Override
            public void onChanged(@Nullable User data) {
                // update ui.
            }
        });
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                 viewModel.doAction();
            }
        });
    }
}
复制代码
public class UserModel extends ViewModel {
    public final LiveData<User> userLiveData = new LiveData<>();
    public UserModel() {
        // trigger user load.
    }
    void doAction() {
        // depending on the action, do necessary business logic calls and update the
        // userLiveData.
    }
}
复制代码

源码分析

上面已经看过了ViewModel的源码,很简单,就是一个抽象类,还有一个onCleared()方法

接下来看下获取ViewModel的代码

ViewModelProviders.of(this).get(UserModel.class)

ViewModelProviders

ViewModelProviders 类提供了4个静态工厂方法 of() 创建新的 ViewModelProvider 对象。

ViewModelProviders.of(Fragment) 
ViewModelProviders.of(FragmentActivity)
ViewModelProviders.of(Fragment, Factory) 
ViewModelProviders.of(FragmentActivity, Factory)
复制代码
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
    Application application = checkApplication(checkActivity(fragment));
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
复制代码
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}
复制代码

ViewModelProvider

ViewModelProvider 负责提供 ViewModel 对象

public class ViewModelProvider {

    public interface Factory {
       
        <T extends ViewModel> T create(@NonNull Class<T> modelClass);
    }

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;
    
    //...省略其他无关代码
}
复制代码

Factory 接口定义了一个创建 ViewModel 的接口 create()ViewModelProvider 在需要时调用该方法新建 ViewModel 对象。

Android 已经内置了2个 Factory 实现类,分别是:

  • AndroidViewModelFactory 实现类,可以创建 ViewModelAndroidViewModel 子类对象。
  • NewInstanceFactory 类,只可以创建 ViewModel 子类对象。

它们的实现都是通过反射机制调用 ViewModel 子类的构造方法创建对象。

public static class NewInstanceFactory implements Factory {
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        //noinspection TryWithIdenticalCatches
        try {
            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);
        }
    }
}
复制代码

AndroidViewModelFactory 继承 NewInstanceFactory 类,是个单例,支持创建 AndroidViewModel 子类对象。

public static class AndroidViewModelFactory extends ViewModelProvider.NewInstanceFactory {

    private static AndroidViewModelFactory sInstance;

    /**
     * Retrieve a singleton instance of AndroidViewModelFactory.
     *
     * @param application an application to pass in {@link AndroidViewModel}
     * @return A valid {@link AndroidViewModelFactory}
     */
    @NonNull
    public static AndroidViewModelFactory getInstance(@NonNull Application application) {
        if (sInstance == null) {
            sInstance = new AndroidViewModelFactory(application);
        }
        return sInstance;
    }

    private Application mApplication;

    /**
     * Creates a {@code AndroidViewModelFactory}
     *
     * @param application an application to pass in {@link AndroidViewModel}
     */
    public AndroidViewModelFactory(@NonNull Application application) {
        mApplication = application;
    }

    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.getConstructor(Application.class).newInstance(mApplication);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
        return super.create(modelClass);
    }
}
复制代码

ViewModelStore

再看下实例化ViewModelProvider中还需要的ViewModelStore对象

ViewModelStore 类中维护一个 Map<String, ViewModel> 对象存储已创建的 ViewModel 对象,并提供 put()get() 方法。

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

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}
复制代码

而这个ViewModelStore通过之前的代码知道,是通过传入的Fragment/FragmentActivity中拿,这2个类都实现了ViewModelStoreOwner 接口,返回当前 UI 作用域里的 ViewModelStore 对象。

ViewModelStoreOwner

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

实现如下:

androidx.fragment.app.Fragment#getViewModelStore

@Override
public ViewModelStore getViewModelStore() {
    if (getContext() == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    if (mViewModelStore == null) {
        mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}
复制代码

androidx.fragment.app.FragmentActivity#getViewModelStore

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.");
    }
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}
复制代码

获取ViewModel

final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
复制代码

ViewModelProviders.of(this)上面已经分析过了,这个方法返回一个ViewModelProvider实例,然后通过get()方法获取ViewModel

@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");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
复制代码
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}
复制代码

如果在 ViewModelStore 里不存在,则使用 Factory 创建一个新的对象并存放到 ViewModelStore 里。

以上便是 ViewModel 创建和获取的主要过程

通过 ViewModelProviders 创建 ViewModelProvider 对象,调用该对象的 get() 方法获取 ViewModel 对象。 当 ViewModelStore 里不存在想要的对象,ViewModelProvider 会使用 Factory 新建一个对象并存放到 ViewModelStore 里。

注:老版本的实现源码分析参见Android Jetpack之ViewModel

ViewModel 总结

Configuration Changes 存活原理

juejin.cn/post/5beccf…

总结里还有一段

销毁过程

androidx.fragment.app.FragmentActivity#onDestroy

@Override
protected void onDestroy() {
    super.onDestroy();

    if (mViewModelStore != null && !isChangingConfigurations()) {
        mViewModelStore.clear();
    }

    mFragments.dispatchDestroy();
}
复制代码

androidx.fragment.app.Fragment#onDestroy

@CallSuper
public void onDestroy() {
    mCalled = true;
    FragmentActivity activity = getActivity();
    boolean isChangingConfigurations = activity != null && activity.isChangingConfigurations();
    if (mViewModelStore != null && !isChangingConfigurations) {
        mViewModelStore.clear();
    }
}
复制代码

先判断是否有发生 Configuration Changes,如果没有则会调用 ViewModelStoreclear() 方法,再一一调用每一个 ViewModelonCleared() 方法。

public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.onCleared();
    }
    mMap.clear();
}
复制代码

生命周期

ViewModel 对象被限定为在获取自己时传入 ViewModelProviderLifecycle。在这个 Lifecycle 永久死去之前,ViewModel 都将留存在内存中:比如 activity 的 finish,或者 fragment 的 detach。

下图展示了一个 activity 在旋转和结束的过程中所经历的各种生命周期状态,以及与其关联的 ViewModel的生命周期。而同样的基本状态也适用于 fragment 的生命周期。

image.png

一般而言,您可以在系统第一次调用某个 activity 对象的 onCreate() 方法时请求特定的 ViewModel。系统可能会在整个 activity 的生命中多次调用 onCreate() 方法,例如当设备的屏幕旋转时。ViewModel 从您第一次请求它开始存在,直到 activity 被结束并销毁。

用途

  1. 在 Android 中,ActivityFragment 这类 UI 组件会被系统销毁或重建,未特殊处理的 UI 数据将会丢失。以往处理这类问题时,会使用 onSaveInstanceState() 保存 UI 数据,在 onCreate() 方法里恢复 UI 数据(通常还要保证其正确的序列化),但是数据的大小和类型有限制(比如:List、Bitmap…)

    当发生 Configuration Changes 时,可利用ViewModel恢复数据

  2. Activity/Fragment经常需要进行一些异步操作。一旦涉及到异步,我们都明白这里存在内存泄漏的可能性。因此我们保证Activity/Fragment在销毁后及时清理异步操作,以避免潜在的内存泄漏。

    ViewModel并没有自动帮我们解决这个问题,而是通过onCleared()交给我们业务自己重写去处理。

    ViewModel 所在的 UI 组件被真正销毁时,它的 onCleared() 方法会被调用,可以覆盖该方法清理资源。

  3. Fragment可以通过宿主Activity共享ViewModel来处理通信问题。

    FragmentActivityViewModelStore 提供的 ViewModel 可以存活至 FragmentActivity 销毁。 因此不同的Fragment实例,可以直接通过传入FragmentActivity,拿到同样的ViewModel实例,进而实现数据通讯。

其他

生命周期管理库 (Lifecycles) 由三个组件构成,包括 LifecycleLiveDataViewModel。它可以用来解决常见的生命周期问题,同时使您的应用程序易于测试且可维护。

推荐开发者同时使用 ViewModel 和另一个生命周期组件 LiveData 来实现响应式的 UI 界面。

注意事项:不要在 ViewModel 中引用 View,使用 LiveData 来通知界面更新;不要混淆 ViewModel 和 onSaveInstanceState 方法的用途。

配合Dagger/Koin

参见当Koin撞上ViewModel

参考

ViewModel Overview

剖析 Android 架构组件之 ViewModel

一点点入坑JetPack:ViewModel篇

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享