乐趣区

关于android:Android-Jetpack架构组件三之ViewModel

ViewModel 简介

在晚期的 Android 开发中,因为利用绝对较小,页面绝对简略,咱们会将数据申请、页面 UI 解决和数据加载全副放在 Activity 或 Fragment 中进行,然而随着我的项目的迭代,这种开发方式显得越来越臃肿,并且也不易于我的项目的保护和扩大。

此时,借鉴后端的后端程序的开发思路,咱们对 Android 我的项目进行了分层,典型的有 MVC,MVP 和 MVVM 等我的项目分层,而后每层负责本人的事件即可。以当初风行的 MVVM 模式为例。

  • Model 层:数据层,次要负责数据实体和对数据实体的操作。
  • View 层:视图层,对应 Android 的 Activity、Fragment 和 View 等,负责数据的显示以及与用户的交互。
  • ViewModel 层:关联层,用于将 Model 和 View 进行绑定,当 Model 产生更改时,即时告诉 View 进行刷新,当然,也能够反向告诉。

在 JetPack 架构中,ViewModel 组件是一个能够感知生命周期的模式来存储和治理视图相干的数据的组件,因而它适宜以下场景。

  • 适宜须要保留大量数据的场景。例如,对于须要保留小量数据的场景,咱们能够应用 Activity/ Fragment 的 onSaveInstanceState 办法保留数据,而后在 onCreate 办法中利用 onRestoreInstanceState 进行还原。然而,onSaveInstanceState 只适宜用来存储数据量少且序列化或者反序列化不简单的数据,如果被序列化的对象简单的话,序列化会耗费大量的内存,进而造成丢帧和视觉卡顿等问题。而 ViewModel 不仅反对数据量大的状况,还不须要序列化、反序列化操作。
  • 在 Android 中,Activity/Fragment 次要用于显示视图数据,如果它们也负责数据库或者网络加载数据等操作,那么势必造成代码臃肿,而将逻辑代码放到 ViewModel 之后,能够更无效的将视图数据相干逻辑和视图控制器拆散开来。

除此之外,ViewModel 的益处还有很多,然而最终的目标就是为了让代码可维护性更高,升高代码的冗余水平。

生命周期

咱们晓得,Android 的 Activity/Fragment 是有生命周期的,咱们能够在不同的生命周期函数中执行不同的操作来达到不同的目标。因为 ViewModel 是保留在内存中的,所以 ViewModel 的生命周期并不会随 Activity/Fragment 的生命周期发生变化。

下图是官网给出的 ViewModel 与 Activity 的生命周期的对应关系示意图。

从上图能够看出,ViewModel 会随同着 Activity/Fragment 的整个生命周期,直到 ViewModel 绑定的 Activity/Fragment 执行 onDestroy()办法之后才会被销毁。

根本应用

1,增加 gradle 以来,如下所示。

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

2,创立一个继承自 ViewModel 类的 MyViewModel 类,创立 ViewModel 类千万不能持有 Context 的援用,否则会引起内存透露,如果须要应用 Context 能够继承 AndroidViewModel。

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

    private void loadUsers() {user.setValue("Android 利用开发实战");
    }
}

3,为了防止内存透露,咱们能够在 onCleared()办法中进行资源开释操作。而后,咱们在 Activity 中就能够应用 MyViewModel,如下所示。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {Log.d(TAG, "LiveData 监听数据返回:"+s);
            }
        });
    }
}

在 Fragment 之间共享数据

    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 onViewCreated(@NonNull View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);
            model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);
            SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), { item ->
               // Update the UI.
            });
        }
    }
    

源码剖析

ViewModel 源码

ViewModel 类是一个形象接口,其局部源码如下。

public abstract class ViewModel {
    
    @Nullable
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    private volatile boolean mCleared = false;

    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {}

    @MainThread
    final void clear() {
        mCleared = true;
        if (mBagOfTags != null) {synchronized (mBagOfTags) {for (Object value : mBagOfTags.values()) {
                    // see comment for the similar call in setTagIfAbsent
                    closeWithRuntimeException(value);
                }
            }
        }
        onCleared();}

   
    @SuppressWarnings("unchecked")
    <T> T setTagIfAbsent(String key, T newValue) {
        T previous;
        synchronized (mBagOfTags) {previous = (T) mBagOfTags.get(key);
            if (previous == null) {mBagOfTags.put(key, newValue);
            }
        }
        T result = previous == null ? newValue : previous;
        if (mCleared) {closeWithRuntimeException(result);
        }
        return result;
    }

    
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    <T> T getTag(String key) {if (mBagOfTags == null) {return null;}
        synchronized (mBagOfTags) {return (T) mBagOfTags.get(key);
        }
    }

    private static void closeWithRuntimeException(Object obj) {if (obj instanceof Closeable) {
            try {((Closeable) obj).close();} catch (IOException e) {throw new RuntimeException(e);
            }
        }
    }
}

能够发现,ViewModel 抽象类的次要作用就是应用 HashMap 存储数据。ViewModel 有一个子类 AndroidViewModel,它的源码如下。

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

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

    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {return (T) mApplication;
    }
}

与继承 ViewModel 不同,AndroidViewModel 须要提供一个 Application 的 Context。

ViewModelProvider

在后面的示例代码中,咱们在 Activity 中应用 ViewModelProviders.of 办法来获取 ViewModel 实例,如下所示。

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

关上 ViewModelProviders 类的源码,能够发现 ViewModelProviders 一共有四个构造方法,都是用来创立 ViewModelProvider 对象,只不过参数不同而已。

public static ViewModelProvider of(@NonNull Fragment fragment) {return of(fragment, null);
    }
    
public static ViewModelProvider of(@NonNull FragmentActivity activity) {return of(activity, null);
    }
    
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);
    }
    
 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 的时候须要用到 ViewModelStore 和 Factory,上面咱们来别离介绍一下它们。

ViewModelStore

ViewModelStore 次要作用是存储 ViewModel 的容器,当咱们关上 ViewModelStore 的源码时会发现 ViewModelStore 是通过 HashMap 来存储 ViewModel 的数据的。并且,ViewModelStore 还提供了一个 clear 办法,用来清空 Map 汇合外面的 ViewModel,咱们能够在 Activity/Fragment 的 onDestroy 办法执行 clear 办法执行 ViewModel 数据的革除。

 protected void onDestroy() {super.onDestroy();
        if (mViewModelStore != null && !isChangingConfigurations()) {mViewModelStore.clear();
        }
    }

Factory

当咱们应用 ViewModelProvider 获取 ViewModel 实例时,ViewModelProvider 一共提供了 4 个构造函数,另一个比拟重要的构造函数是

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory{
        mFactory = factory;
        mViewModelStore = store;
    }

ViewModelProvider 的第二个参数是 factory,它的子类有 NewInstanceFactory 和 AndroidViewModelFactory 两个,咱们能够应用 ViewModelProvider.AndroidViewModelFactory.getInstance 获取单例的 Factory 对象,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;

       
        @NonNull
        public static AndroidViewModelFactory getInstance(@NonNull Application application) {if (sInstance == null) {sInstance = new AndroidViewModelFactory(application);
            }
            return sInstance;
        }

        private Application mApplication;

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

AndroidViewModelFactory 实例化构造方法外面有个参数 class,能够援用 Context,其实是 Appplication 实例。在下面的代码中,如果是有 application 参数,则通过 newInstance(application)实例化,否则调用父类的 create 办法而后通过 newInstance()实例化。如果通过 newInstance(application)实例化,就能够在 ViewModel 外面拿到 Context,因为 Application 是 APP 全局的生命周期最长,所以就不存在内存泄露问题。

ViewModel 是如何实现状态保留的

后面说过,ViewModel 是不会随着 Activity/Fragment 的销毁而销毁的,因为 ViewModel 是将数据应用 ViewModelStore 保留在 HashMap 中,所以只有 ViewModelStore 不被销毁,则 ViewModel 的数据就不会被销毁。

家喻户晓,Android 在横竖屏切换时会触发 onSaveInstanceState,而后在还原时则会触发 onRestoreInstanceState。除此之外,Android 的 Activity 类还提供了 onRetainNonConfigurationInstance 和 getLastNonConfigurationInstance 两个办法,当设施状态产生扭转时,下面的两个办法就会被零碎调用。

其中,onRetainNonConfigurationInstance 办法用于解决配置产生扭转时数据的保留,而 getLastNonConfigurationInstance 则用于复原创立 Activity 时获取上次保留的数据。首先,咱们来看一下 onRetainNonConfigurationInstance 办法,如下所示。

public final Object onRetainNonConfigurationInstance() {Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        ... 
        
        if (fragments == null && mViewModelStore == null && custom == null) {return null;}

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

能够发现,ViewModel 会将数据存储在 NonConfigurationInstances 对象中,而 NonConfigurationInstances 是定义在 Activity 外面的一个类,如下所示。

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

再来看一下 getLastCustomNonConfigurationInstance 办法,
getLastNonConfigurationInstance 办法返回的数据就是 NonConfigurationInstances.activity 属性,如下所示。

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

当初,咱们再看一下 ComponentActivity 的 getViewModelStore 办法,如下所示。

@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.");
    }
    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;
}

能够发现,在 getViewModelStore 办法中咱们首先会获取 NonConfigurationInstances 对象,不为空则从其身上拿到 ViewModelStore,也就是之前保留的 ViewModelStore,而后当 Activity 被再次创立的时候复原数据。

须要阐明的是,onRetainNonConfigurationInstance 办法会在 onSaveInstanceState 办法之后被调用,即调用程序同样在 onStop 办法和 onDestroy 办法之间。

退出移动版