2.1.2.Architecture components_ViewModel

Posted muouren

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.1.2.Architecture components_ViewModel相关的知识,希望对你有一定的参考价值。

参考

https://developer.android.com/topic/libraries/architecture/viewmodel

官方例子

https://github.com/android/architecture-components-samples/tree/master/BasicSample

ViewModel

ViewModel类旨在以生命周期感知的方式存储和管理与UI相关的数据。

ViewModel类允许数据幸免于配置更改(例如屏幕旋转)。

ViewModel其实算是Presenter。

对于少量的数据可以在activity的onSaveInstanceState保存,在下次重建activity的onCreate中恢复,但对于大量数据(比如bitmap)是不能在onSaveInstanceState中保存的,因为会造成卡顿,甚至anr,所以可以用全局的缓存位置来保存此类数据,而ViewModel就是官方提供给我们使用的一个这样的库。

简单使用:

1. 继承ViewModel,假设是MyViewModel,在其中写入你自己的逻辑

2. 通过ViewModelProviders.of(this).get(MyViewModel.class),来获取此ViewModel的实例。

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity‘s onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

 

l 如果重新创建了Activity,则它将收到与第一次Activity创建的相同的MyViewModel实例。

l 当Activity不是由于系统原因,即用户明确finish时,系统将调用ViewModel对象的onCleared()方法,以便清理资源。这个在后边会进行一些源码分析。

l ViewModel对象可以包含LifecycleObservers,例如LiveData对象。 但是,ViewModel对象绝不能观察对生命周期感知型的可观察对象的更改,例如LiveData对象。

注意:ViewModel不要引用view,lifecycle,或者任何持有activity context的对象,否则会导致内存泄漏。

AndroidViewModel

如果ViewModel需要Application context(例如,查找系统服务),则可以扩展AndroidViewModel类。

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

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

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

 

ViewModel的生命周期

l ViewModel是不能跨activity的,是和当前activity关联的,不过可以在此activity内的fragment之间共享。

l 如果想跨activity,则可以用AndroidViewModel。

ViewModel保留在内存中,直到其生命周期范围永久消失:

l 对于activity而言,它finish时,

l 对于fragment而言,当它detached时。

图1说明了activity经历屏幕旋转,重建,然后结束时的各种生命周期状态。

该图还在关联的activity生命周期旁显示了ViewModel的生命周期。 相同的基本状态适用于Fragment的生命周期。

技术图片

当Activity不是由于系统原因,即用户明确finish时,系统将调用ViewModel对象的onCleared()方法,以便清理资源。这个在后边会进行一些源码分析。

在fragment之间共享数据

ViewModel是和当前activity关联的,是不能跨activity的。

一个activity中的两个或更多fragment需要相互通信是很常见的。 想象一下主从fragment的一种常见情况,您有一个fragment,用户在其中从列表中选择一个item,另一个fragment显示了所选item的内容。 处理这种情况绝非易事,因为两个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 onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
                // Update the UI.
        });
    }
}

 

注意,两个Fragment都使通过获取activity来获得ViewModelProvider时,它们将收到相同的SharedViewModel实例,该实例的作用范围是此activity。

这种方法提供了下边的优点:

l 该activity不需要执行任何操作,也无需了解此通信。

l 除SharedViewModel外,Fragment不需要彼此了解。 如果其中一个Fragment消失了,则另一个继续照常工作。

l 每个Fragment都有自己的生命周期,并且不受另一个Fragment的生命周期影响。 如果一个Fragment替换了另一个Fragment,则UI可以继续工作而不会出现任何问题。

全局范围的ViewModel

在Application子类中创建一个ViewModelStore对象,然后用ViewModelProvider的

public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) (Application需要implements ViewModelStoreOwner)
或者
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory)

 

创建一个ViewModelProvider,

然后通过ViewModelProvider.get来获取对应的全局ViewModel。

ViewModel替换loader

诸如CursorLoader之类的Loader类经常用于使应用程序UI中的数据与数据库保持同步。 您可以使用ViewModel和其他一些类来替换Loader。 使用ViewModel可将UI控制器与数据加载操作分开,这意味着类之间的强引用更少。

在使用Loader的一种常见方法中,应用程序可能使用CursorLoader来观察数据库的内容。 当数据库中的值更改时,Loader会自动触发数据的重新加载并更新UI:

技术图片

ViewModel与Room和LiveData一起使用以替换Loader。 ViewModel确保数据在设备配置更改后仍然存在。 当数据库更改时,Room会通知您的LiveData,然后LiveData会使用修改后的数据更新UI。

技术图片

源码分析

MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);

ViewModel

是一个abstract类,其中只有一个方法:

protected void onCleared() {
}

 

当不再使用这个ViewModel并将其销毁时,将调用此方法。

当ViewModel观察到一些数据时,您需要清除这个订阅来防止这个ViewModel的泄漏,这是很有用的。

activity和fragment中会在销毁时自动调用关联的ViewModel的onCleared方法。

AndroidViewModel

继承自ViewModel,

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

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

    /**
     * Return the application.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @NonNull
    public <T extends Application> T getApplication() {
        //noinspection unchecked
        return (T) mApplication;
    }
}

 

ViewModelProviders

//获取application
private static Application checkApplication(Activity activity) {
    Application application = activity.getApplication();
    if (application == null) {
        throw new IllegalStateException("Your activity/fragment is not yet attached to "
                + "Application. You can‘t request ViewModel before onCreate call.");
    }
    return application;
}

//获取fragment关联的activity
private static Activity checkActivity(Fragment fragment) {
    Activity activity = fragment.getActivity();
    if (activity == null) {
        throw new IllegalStateException("Can‘t create ViewModelProvider for detached fragment");
    }
    return activity;
}

/**
 * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
 * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
 * <p>
 * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
 *
 * @param fragment a fragment, in whose scope ViewModels should be retained
 * @return a ViewModelProvider instance
 */
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
    return of(fragment, null);
}

/**
 * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
 * is alive. More detailed explanation is in {@link ViewModel}.
 * <p>
 * It uses {@link ViewModelProvider.AndroidViewModelFactory} to instantiate new ViewModels.
 *
 * @param activity an activity, in whose scope ViewModels should be retained
 * @return a ViewModelProvider instance
 */
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    return of(activity, null);
}

/**
 * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
 * {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
 * <p>
 * It uses the given {@link Factory} to instantiate new ViewModels.
 *
 * @param fragment a fragment, in whose scope ViewModels should be retained
 * @param factory  a {@code Factory} to instantiate new ViewModels
 * @return a ViewModelProvider instance
 */
@NonNull
@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);
}

/**
 * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given Activity
 * is alive. More detailed explanation is in {@link ViewModel}.
 * <p>
 * It uses the given {@link Factory} to instantiate new ViewModels.
 *
 * @param activity an activity, in whose scope ViewModels should be retained
 * @param factory  a {@code Factory} to instantiate new ViewModels
 * @return a ViewModelProvider instance
 */
@NonNull
@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);
}

 

l 其中的Factory是用来创建ViewModel的工厂类,在ViewModelProvider中定义的。

l 可以看到如果没有传递Factory,那么就会使用ViewModelProvider.AndroidViewModelFactory。

l 可以看到ViewModelProvider在创建时除了需要传递Factory,还有一个ViewModelStore,它就是用来管理activity重建时能够获取相同实例ViewModel的,而ViewModelStore在fragment和fragmentActivity中都会创建。

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

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

@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");
    }
    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)) {
        //noinspection unchecked
        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);
    //noinspection unchecked
    return (T) viewModel;
}

 

  • l public <T extends ViewModel> T get(@NonNull Class<T> modelClass)
  • l public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass)

modelClass:就是ViewModel的class。

key:要获取的ViewModel实例对应的key,如果没有传递则会使用modelClass.getCanonicalName()来当key,key相同则获取的ViewModel的实例就相同。

返回一个现有的ViewModel或在范围内创建一个新的ViewModel(通常是一个Fragment或一个activity),与这个ViewModelProvider相关联。

创建的ViewModel与给定的范围相关联,并且只要该范围是活动的,它就会被保留(例如,如果它是一个activity,直到它finish或者进程被终止)。

HasDefaultViewModelProviderFactory

public interface HasDefaultViewModelProviderFactory {
    /**
     * Returns the default {@link ViewModelProvider.Factory} that should be
     * used when no custom {@code Factory} is provided to the
     * {@link ViewModelProvider} constructors.
     *
     * @return a {@code ViewModelProvider.Factory}
     */
    @NonNull
    ViewModelProvider.Factory getDefaultViewModelProviderFactory();
}

 

Fragment和ComponentActivity都实现了此接口,

public ViewModelProvider(@NonNull ViewModelStoreOwner owner)用到了

ViewModelStoreOwner

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

 

Fragment和ComponentActivity都实现了此接口,

另外在

l ViewModelProviders.of(this).get(MyViewModel.class);

l public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory)

都用到了

Factory

内部有一些Factory,就是创建ViewModel的工厂类

public interface Factory {
    /**
     * Creates a new instance of the given {@code Class}.
     * <p>
     *
     * @param modelClass a {@code Class} whose instance is requested
     * @param <T>        The type parameter for the ViewModel.
     * @return a newly created ViewModel
     */
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

 

NewInstanceFactory

public static class NewInstanceFactory implements Factory {

    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @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

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

来存储ViewModels的Class。

  • l 必须通过配置更改来保留ViewModelStore的一个实例:如果这个ViewModelStore的所有者由于配置更改而被销毁并重新创建,那么所有者的新实例应该仍然拥有ViewModelStore的相同旧实例。
  • l 如果这个ViewModelStore的所有者被销毁,并且不打算重新创建,那么它应该在这个ViewModelStore上调用clear(),以便通知ViewModels它们不再被使用。
  • l 也就是说ViewModel能够在configuration change之间也能保留的原因就是在configuration change之间保留了ViewModelStore。

使用ViewModelStoreOwner.getViewModelStore()来检索activity和fragment的ViewModelStore。

每个FragmentActivity和Fragment都在内存创建了自己的ViewModelStore,并且在configuration change时保留。

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

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

 

以上是关于2.1.2.Architecture components_ViewModel的主要内容,如果未能解决你的问题,请参考以下文章

qml 怎样改变component的颜色

[Vue @Component] Simplify Vue Components with vue-class-component

[Vue @Component] Dynamic Vue.js Components with the component element

5.8 Components — Composing Components(组合组件)

注解service和component的区别

5.3 Components — Passing Properties to A Component