揭秘ViewModel怎样在屏幕旋转时保存数据

本文深入解析Android中的ViewModel,阐述其在MVVM架构中的作用,特别是如何结合LiveData实现视图和逻辑的解耦。ViewModel的主要职责是管理界面数据,确保在配置变化(如屏幕旋转)时数据不丢失。文章详细介绍了ViewModel的创建、使用过程,以及Activity如何通过ViewModelStore保存和恢复ViewModel,确保数据持久性。此外,还探讨了onSaveInstanceState方法和ViewModelProvider的内部机制。

ViewModel的作用及怎样使用

ViewModel使用在MVVM框架中,结合LiveData实现View层和控制层解耦。ViewModel类旨在以注重生命周期的方式存储和管理界面相关的数据,ViewModel作为一个类保存着LiveData类型的数据,这样做的好处在于当Activity因为配置的改变(如屏幕旋转)而重新创建时,界面中所持有的数据能够不被销毁。  对于简单的数据,Activity可以从onCreate()中利用Bundle对象恢复数据,但此方法仅适合可以序列化再反序列化的少量数据, 而不适合数量可能较大的数据,如列表或位图。

先回顾一下onSaveInstanceState方法怎样使用的:

Android中的activity通过onSaveInstanceState()方法保存activity的状态,在onCreate或者onRestoreInstanceState方法恢复状态。

public class SaveStateActivityTest extends Activity {
	public static final String TAG = "SaveStateActivity";
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	if (savedInstanceState!=null) {
    		Log.v(TAG, savedInstanceState.getString("data"));
		}
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.v(TAG, "onCreate");
    }
    
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		Log.v(TAG, savedInstanceState.getString("data"));
		super.onRestoreInstanceState(savedInstanceState);
		Log.v(TAG, "onRestoreInstanceState");
	}
	
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		outState.putString("data", "mzq");
		super.onSaveInstanceState(outState);
		Log.v(TAG, "onSaveInstanceState");
	}
}

ViewModel是怎样使用的

架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 activity 或 fragment 实例使用。例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel,而不是 activity 或 fragment。 

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

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

 然后,您可以从 Activity 访问该列表,如下所示:

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // 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.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

进入主题 

我们常用的创建ViewModel的方法: 

SearchViewModel viewModel = new ViewModelProvider(this).get(MzqViewModel.class);

我们看一下ViewModelProvider类的构造方法:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory)owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
}

我们看一下ViewModelStoreOwner是何方神圣:

它是Android.lifecycle包下的一个接口:

package androidx.lifecycle;

import androidx.annotation.NonNull;

/**
 * A scope that owns {@link ViewModelStore}.
 * <p>
 * A responsibility of an implementation of this interface is to retain owned ViewModelStore
 * during the configuration changes and call {@link ViewModelStore#clear()}, when this scope is
 * going to be destroyed.
 *
 * @see ViewTreeViewModelStoreOwner
 */
@SuppressWarnings("WeakerAccess")
public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

这个接口只有一个方法,getViewModelStore()方法,该方法返回ViewModelStore对象,从命名上可得知该对象上用于存储ViewModel的,一个Activity可能关联多个ViewModel,因此ViewModelStore应该持有集合的数据结构,我们继续验证一下:

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

 果真如我们的猜想,它内部是一个HashMap的键值对集合,值即为我们的ViewModel对象。

Activity实现了ViewModelStoreOwner,并且重写了getVeiwModelStore()的方法。我们来看一下具体实现。

我们验证一下Activity实现了ViewModelStoreOwner:

public class AppCompatActivity extends FragmentActivity ....
public class FragmentActivity extends ComponentActivity ...
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        ContextAware,
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner,
        ActivityResultRegistryOwner,
        ActivityResultCaller 

可以看到ComponentActivity类实现了ViewModelStoreOwner接口。

我们继续查看一下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.");
        }
        ensureViewModelStore();
        return mViewModelStore;  //是ViewModelStore类型的对象
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

这个方法当本地变量mViewModelStore为空时,会通过getLastNonConfigurationInstance()方法尝试获得ViewModeStore,如果没有的活就重新创建。而ViewModel不被销毁的玄机就在这里。

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

Activity类中: 

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

NonConfigurationInstaces是个很简单的类,包含了ViweModelStore,而getLastNonConfigurationInstace会尝试获取上次配置改变被保存的NonConfigurationInstaces对象

那么在哪里对NonConfigurationInstances中的ViewModel进行赋值存储呢?

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

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

viewModeStore不为null或者lastNonConfigurationInstacne不为null时,我们就把viewModelStore保存在NonConfigurationInstance对象中。当重新创建时,我们就能通过getLastNonConfigurationInstance()方法得到我们保存到的ViewModelStore了,而ViewModelStore里面就有我们的ViewMode。

总结


当Activity要销毁前回调onRetainNonConfigurationInstance()方法保存viewModelStore在lastNonConfigurationInstacne.activity(NonConfigurationInstance)中,然后在getViewModelStore方法中又会首先去获取lastNonConfigurationInstace.activity(NonConfigurationInstance)对象。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值