• Android


    通过源码我们能知道些什么内容

    • 生命周期比组件的长如何实现?
    • 数据在发生屏幕旋转等配置更改时如何保存数据?(注意是配置文件更改而不是所有的activity销毁都保存数据)
    • 如何避免内存泄漏?
    • 如何在 Activity 中的两个或更多 Fragment 共享数据?

    Tip:
    源码:Android API 29

    ViewModel 的使用

    根据 sunFlower示例,我们写个简单的示例demo如下:

    class GardenActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_garden)
    
            val model: MyViewModel by viewModels{
                InjectorUtils.provideUserViewModelFactory()
            }
    
            // val model = ViewModelProvider(this,InjectorUtils.provideUserViewModelFactory())
    
            model.getUserInfo()?.observe(this, Observer<User>{ user ->
                // update UI
            })
        }
    }
    
    object InjectorUtils {
    
    	// 注入数据参数
        fun provideUserViewModelFactory(): MyViewModelFactory {
            val repository = UserRepository.getInstance()
            return MyViewModelFactory(repository)
        }
    }
    
    // ViewModel 工厂类
    class MyViewModelFactory(
        private val userRepository: UserRepository,
    ) : ViewModelProvider.Factory {
    
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return MyViewModel(userRepository) as T
        }
    }
    
    // ViewModel
    class MyViewModel(private val userRepository: UserRepository ):ViewModel() {
        private var userInfo: MutableLiveData<User>? = null
    
        fun getUserInfo(): LiveData<User>? {
            if (userInfo == null) {
                userInfo = MutableLiveData()
                loadUserInfo()
            }
            return userInfo
        }
    
        private fun loadUserInfo() {
            this.userInfo = userRepository.getUserInfo()
        }
    }
    
    // User 仓库
    class UserRepository {
    
       fun getUserInfo():MutableLiveData<User>{
           val user = MutableLiveData<User>()
           user.value = User("张三","18")
           return user
       }
    
        companion object {
    
            // For Singleton instantiation
            @Volatile
            private var instance: UserRepository? = null
    
            fun getInstance() =
                instance ?: synchronized(this) {
                    instance ?: UserRepository().also { instance = it }
                }
        }
    }
    
    // User 实体
    data class User(val name:String,val age:String)
    
    

    以上示例实现了数据和 UI 分离,并且ViewModel中没有持有View
    接下来我们带着开头的几个问题,深入源码看看它是如何实现的。

    (1) 生命周期比组件的长如何实现

    来张官方配图

    上图是ActivityViewModel 生命周期对比图,从图中可看出 ViewModel 的生命周期确实比Activity长。那么 ViewModel 它是如何实现的呢?

    其实主要用到了Jetpack 中的 LifeCycle库,当然不用该库也是可以的,下篇文章我们再分析该库的实现。

    首先我们要明确知道 ViewModel 的作用之一就是是:通过关联生命周期的方式来存储和管理跟UI相关的数据
    LifeCycle库是专门用来处理生命周期的库,也就是该库可以感知Activity的生命周期,并在不同的生命周期处理相关的逻辑。

    上图也看出了 ViewModel的生命周期比ActivityonDestory生命周期还长并且多了个方法onCleared
    既然是在 Activity 的 onDestory 生命周期之后,那我们跟进源码看看它是怎么处理的。

    源码: Android API 29
    查看顺序:AppCompatActivity--->FragmentActivity--->ComponentActivity

    AppCompatActivity.java 中只是委托了事件,具体的处理逻辑要在不同的 Android API 版本中处理,这就不在本文的介绍范围了,可搜索 Activity 的加载流程详细了解
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        getDelegate().onDestroy();
    }
    
    FragmentActivity.java 中处理了 ON_DESTROY 事件
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mFragments.dispatchDestroy();
        mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
    }
    
    ComponentActivity.java 的构造函数里面观察了 Lifecycle.Event.ON_DESTROY 事件,并获取 ViewModelStore 调用 clear 方法清除数据
    
    public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        ...
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
    
                	// isChangingConfigurations 方法是检查配置文件是否修改,如果修改则返回true,没有修改则返回false
                	// 这里取反,就是保证了在配置文件修改的时候不会清除数据
                    if (!isChangingConfigurations()) {
    
                    	// 清除数据
                        getViewModelStore().clear();
                    }
                }
            }
        });
    }
    

    从上面的源码分析也看出了ViewModel确实是在Activity的生命周期onDestory之后监听到Lifecycle.Event.ON_DESTROY再去解决ViewModel中的数据清除问题。
    而源码中也做了判断,在页面配置文件修改后并不会清除数据。这也进一步说明了ViewModel能解决因页面配置文件修改后清除数据的问题。

    具体如何保存和如何恢复数据,我们知道在配置文件后页面会销毁和重建,这个过程中会使用到下面两个方法。

    • onSaveInstanceState
    • onRestoreInstanceState

    那么我们去源码中找找看有没有什么蛛丝马迹

    (2) 页面配置修改后如何保存数据

    源码: Android API 29
    查看顺序:AppCompatActivity--->FragmentActivity--->ComponentActivity

    AppCompatActivity.java 同样的这里也只是委托了事件
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        getDelegate().onSaveInstanceState(outState);
    }
    
    FragmentActivity.java 中处理了数据的保存
    
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        markFragmentsCreated();
        mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        if (mPendingFragmentActivityResults.size() > 0) {
            outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
    
            int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
            String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
            for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
                requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
                fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
            }
            outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
            outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
        }
    }
    
    
    ComponentActivity.java 的 onSaveInstanceState 方法中还是没有 ViewModel 的身影,
    但是紧接着的方法 onRetainNonConfigurationInstance 的注释有点意思,它是 final 所以不能重写,
    但是它里面又有一个公开的 onRetainCustomNonConfigurationInstance 方法,所以说我们可以重写该方法返回一些 Object 的对象,
    但是该方法过时了。
    
    推荐我们使用 ViewModel 来实现。
    
    @CallSuper
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        Lifecycle lifecycle = getLifecycle();
        if (lifecycle instanceof LifecycleRegistry) {
            ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
        }
        super.onSaveInstanceState(outState);
        mSavedStateRegistryController.performSave(outState);
    }
    
    /**
     * Retain all appropriate non-config state.  You can NOT
     * override this yourself!  Use a {@link androidx.lifecycle.ViewModel} if you want to
     * retain your own non config state.
     */
    @Override
    @Nullable
    public final Object onRetainNonConfigurationInstance() {
        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;
        }
    
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
    }
    
    所以我们还是回到 ComponentActivity.java 的构造方法中找到 Lifecycle.Event.ON_DESTROY 的处理,
    在里面有个方法 getViewModelStore 获取到了 ViewModel 的数据,它的实现如下:
    
    @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;
    }
    
    

    如果不使用 ViewModel,在页面销毁和重建时可以用重写下面两个方法保存和恢复数据

    • onSaveInstanceState
    • onRestoreInstanceState

    但是我们知道这两个方法只能保存 Bundle 支持的数据,要想保存其它数据就得用到下面的方法了

    • onRetainNonConfigurationInstance:final 所以不能重写,作用是保存数据,调用时机是onStop()onDestroy()之间
    • onRetainCustomNonConfigurationInstance:@Deprecated 过时了,推荐使用 ViewModel
    • getLastNonConfigurationInstance:作用是取出数据,调用时机是onCreate()之后
    • getLastCustomNonConfigurationInstance:@Deprecated 过时了,推荐使用 ViewModel

    如果使用ViewModel,在页面销毁和重建时就不需要你操心数据的保存和恢了。

    ViewModel会在onRetainNonConfigurationInstance 方法中保存数据,如下:

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

    如果Activity重写了onRetainCustomNonConfigurationInstance方法还可以将自定义的Object类型的对象保存到NonConfigurationInstances 的 custom属性中。如下:

    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    

    并且还会在ActivityonDestory生命周期判断页面是否重建,重建则不删除ViewModel中的数据

    (3) 如何避免内存泄漏?

    源码: Android API 29

    首先我们要知道内存为何会泄漏。

    短生命周期的对象被长生命周期的对象一直持有,导致短生命周期的对象在本该被回收的时候没有及时回收,导致内存泄漏。

    比如:

    内部类导致的内存泄漏如:非静态内部类 Handler、匿名内部类 Thread 中存在业务处理的时间比 Activity 的生命周期还长,这就会导致内存泄漏
    因为内部类默认会持有外部类的引用,当外部类 Activity 关闭需要回收的时候,它的内部类还在持有它,导致它没有被回收。

    在文章开头ViewModel的使用中用到了LiveData库。
    它是具有生命感知的数据存储类,这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

    活跃生命周期状态也就是用户可见的状态下才会更新数据。详细的实现后面深入源码查看并写篇文章记录。

    又因为ViewModel会在onDestory之后会自动清除相关数据,结合这两点即可避免内存泄漏。

    (4) 如何在 Activity 中的两个或更多 Fragment 共享数据?

    在创建 ViewModel 的时候需要传入this上下文,这个上下文是个LifecycleOwner,
    AppCompatActivity 和 Fragment都默认实现了LifecycleOwner接口,因此可以在Activity或者Fragment中直接传递this

    在传递上下文给 ViewModel之后,会通过ViewModelStore里面的HashMap去根据不同的key取出不同的ViewModel
    而这里的 keyandroidx.lifecycle.ViewModelProvider.DefaultKey:加上ViewModel类所在的包含路径的全名称字符串。

    Activity中的ViewModel,除非被杀死或者关闭,否则一直存在。
    这也就是为什么Activity中的多个Fragment能共享ViewModel中的数据。

    主要代码如下:

    /**
     * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
     * an activity), associated with this {@code ViewModelProvider}.
     * <p>
     * The created ViewModel is associated with the given scope and will be retained
     * as long as the scope is alive (e.g. if it is an activity, until it is
     * finished or process is killed).
     *
     * @param key        The key to use to identify the ViewModel.
     * @param modelClass The class of the ViewModel to create an instance of it if it is not
     *                   present.
     * @param <T>        The type parameter for the ViewModel.
     * @return A ViewModel that is an instance of the given type {@code T}.
     */
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);
    
        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 {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }
    

    总结

    ViewModel的主要作用:

    • 以注重生命周期的方式存储和管理界面相关的数据
    • 让数据可在发生屏幕旋转等配置更改后继续留存
    • Activity下的所有Fragment可以共享ViewModel中的数据
    • 结合LiveData使用可避免内存泄漏

    参考

  • 相关阅读:
    Solidworks如何设置零件材料,如何评估零件质量
    Office 如何双面打印Word文档
    Discuz常见小问题2-如何在新建的页面上只显示一部分板块
    Discuz常见小问题2-如何在数据库搜索指定关键字
    Discuz常见小问题2-如何修改整个网站的默认字体为微软雅黑
    Discuz常见小问题2-如何修改管理员密码,修改admin账户密码
    php使用include报错require_once(../include.php): failed to open stream: No such file or directo
    PHP中的__get()和__set()方法获取设置私有属性
    php->是什么意思
    PHP中require(),include(),require_once()和include_once()有什么区别
  • 原文地址:https://www.cnblogs.com/gdragon/p/13887188.html
Copyright © 2020-2023  润新知