一道面試題:ViewModel爲何橫豎屏切換時不銷燬?

# 又到金三銀四

往年面試中有關Jetpack的考察能夠算是加分項,隨着官方對Modern Android development (MAD) 的大力推廣,今年基本上都是必選題了。java

不少候選人對Jetpack各組件的功能及用法如數家珍,但一問及到原理每每卡殼。原理不清雖不影響API的使用,但也正由於如此,若是能對源碼有必定了解,也許能夠脫穎而出獲得加分。android

本文分享一個入門級的源碼分析,也是在面試中常常被問到的問題面試


# ViewModel

ViewModel是Android Jetpack中的重要組件,其優點是具備下圖這樣的生命週期、不會由於屏幕旋轉等Activity配置變化而銷燬,是實現MVVM架構中UI狀態管理的重要基礎。 在這裏插入圖片描述markdown

class MyActivity : AppCompatActivity {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Log.d(TAG, "onCreate")

    val activity: FragmentActivity = this
    val factory: ViewModelProvider.Factory = ViewModelProvider.NewInstanceFactory()

    // Activity因爲橫豎品切換銷燬重建,此處的viewModel 仍然是重建前的實例
    val viewModel = ViewModelProvider(activity, factory).get(MyViewModel::class.java)
    // 若是直接new實例則會建立新的ViewModel實例
	// val viewModel = MyViewModel()

    Log.d(TAG, " - Activity :${this.hashCode()}")
    Log.d(TAG, " - ViewModel:${viewModel.hashCode()}")
  }
}
複製代碼

上面代碼在橫豎屏切換時的log以下:架構

#Activity初次啓動
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

#屏幕旋轉
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713  #Activity實例不一樣
  - ViewModel:249530701  #ViewModel實例相同
onStart
onResume
複製代碼

下面代碼是保證屏幕切換時ViewModel不銷燬的關鍵,咱們依次爲入口看一下源碼app

val viewModel = ViewModelProvider(activity, factory).get(MyViewModel::class.java)
複製代碼

# ViewModelProvider

ViewModelProvider源碼很簡單,分別持有一個ViewModelProvider.FactoryViewModelStore實例ide

package androidx.lifecycle;

public class ViewModelProvider {

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

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

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

    ...
}
複製代碼

get()返回ViewModel實例源碼分析

package androidx.lifecycle;

public class ViewModelProvider {
    ...

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();

        ...

        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

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

    ...
}
複製代碼

邏輯很是清晰:this

  1. ViewModelProvider經過ViewModelStore獲取ViewModel
  2. 若獲取失敗,則經過ViewModelProvider.Factory建立ViewModel

# ViewModelStore

package androidx.lifecycle;

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

    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}
複製代碼

可見,ViewModelStore就是一個對Map的封裝。spa

val viewModel = ViewModelProvider(activity, factory).get(FooViewModel::class.java)
複製代碼

上面代碼ViewModelProvider()構造參數1中傳入的FragmentActivity(基類是ComponentActivity)其實是ViewModelStoreOwner的一個實現。

package androidx.lifecycle;

public interface ViewModelStoreOwner {
    @NonNull
    ViewModelStore getViewModelStore();
}
複製代碼

ViewModelProvider中的ViewModelStore正是來自ViewModelStoreOwner。

public class ViewModelProvider {

    private final ViewModelStore mViewModelStore;

    public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
        this(owner.getViewModelStore(), factory);
    }

    public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        this.mViewModelStore = store;
    }
複製代碼

Activity在onDestroy會嘗試對ViewModelStore清空。若是是因爲ConfigurationChanged帶來的Destroy則不進行清空,避免橫豎屏切換等形成ViewModel銷燬。

//ComponentActivity.java
getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });
複製代碼

# FragmentActivity#getViewModelStore()

FragmentActivity實現了ViewModelStoreOwnergetViewModelStore方法

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        ...

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

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

    ...
}
複製代碼

經過getLastNonConfigurationInstance() 獲取 NonConfigurationInstances 實例,從而獲得真正的viewModelStoregetLastNonConfigurationInstance()又是什麼?

# Activity#getLastNonConfigurationInstance()

package android.app;

public class Activity extends ContextThemeWrapper implements ... {

    /* package */ NonConfigurationInstances mLastNonConfigurationInstances;

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

Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate(Bundle) and onStart() calls to the new instance, allowing you to extract any useful dynamic state from the previous instance.

經過官方文檔咱們知道,屏幕旋轉前經過onRetainNonConfigurationInstance()返回的Activity實例,屏幕旋轉後能夠經過getLastNonConfigurationInstance()獲取,所以屏幕旋轉先後不銷燬的關鍵就在onRetainNonConfigurationInstance


# Activity#onRetainNonConfigurationInstance()

#Activity初次啓動
onCreate
  - Activity :132818886
  - ViewModel:249530701
onStart
onResume

#屏幕旋轉
onPause
onStop
onRetainNonConfigurationInstance
onDestroy
onCreate
  - Activity :103312713  #Activity實例不一樣
  - ViewModel:249530701  #ViewModel實例相同
onStart
onResume
複製代碼

屏幕旋轉時,onRetainNonConfigurationInstance()onStoponDestroy之間調用

package android.app;

public class Activity extends ContextThemeWrapper implements ... {

    public Object onRetainNonConfigurationInstance() {
        return null;
    }

    ...
}
複製代碼

onRetainNonConfigurationInstance在Activity中只有空實現,在FragmentActivity中被重寫

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner, ... {

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

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

    ...
}
複製代碼

FragmentActivity 經過 onRetainNonConfigurationInstance() 返回 了存放ViewModelStore的NonConfigurationInstances 實例。 值得一提的是onRetainNonConfigurationInstance提供了一個hook時機:onRetainCustomNonConfigurationInstance,容許咱們像ViewModel同樣使得自定義對象不被銷燬

NonConfigurationInstances會在attach中由系統傳遞給新重建的Activity:

final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) 複製代碼

而後在onCreate中,經過getLastNonConfigurationInstance()獲取NonConfigurationInstances中的ViewModelStore

package androidx.fragment.app;

public class FragmentActivity extends ComponentActivity implements ViewModelStoreOwner ... {

    private ViewModelStore mViewModelStore;

    @SuppressWarnings("deprecation")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        ...
    }
}
複製代碼

# 總結

Activity首次啓動

  • FragmentActivity#onCreate()被調用
    • 此時 FragmentActivity 的 mViewModelStore 尚爲 null
  • HogeActivity的onCreate() 被調用
    • ViewModelProvider 實例建立
    • FragmentActivity#getViewModelStore() 被調用,mViewModelStore被建立並賦值

發生屏幕旋轉

  • FragmentActivity#onRetainNonConfigurationInstance() 被調用
    • 持有mViewModelStore 的NonConfigurationInstances 實例被返回

Activity重建

  • FragmentActivity#onCreate() 被調用
    • 從Activity#getLastNonConfigurationInstance() 獲取 NonConfigurationInstances 實例
    • NonConfigurationInstances 中保存了屏幕旋轉前的 FragmentActivity 的 mViewModelStore,將其賦值給重建後的FragmentActivity 的 mViewModelStore
  • HogeActivity#onCreate() 被調用
    • 經過ViewModelProvider#get() 獲取 ViewModel 實例
相關文章
相關標籤/搜索