Android Jetpack系列——ViewModel原始碼分析

譚嘉俊發表於2019-12-15

本文章已授權微信公眾號郭霖(guolin_blog)轉載。

本文章主要是對ViewModel進行原始碼分析,建議對著示例程式碼閱讀文章,示例程式碼如下:

ViewModelDemo

本文章使用的是Android SDK 29的原始碼分析。

定義

Android框架管理UI控制器的生命週期(例如:ActivityFragment),Framework可能決定銷燬或者重新建立一個UI控制器,以響應某些使用者操作或者裝置事件,這些操作或者事件完全超出你的控制。

如果系統銷燬或者重新建立一個UI控制器,那麼你儲存在其中的任何與UI相關的臨時資料丟失,例如:你的應用程式在某個Activity中包含一個使用者列表,當配置資訊更改重新建立Activity時,新的Activity必須重新獲取使用者列表。對於簡單資料,Activity可以使用onSaveInstanceState()方法,並且在onCreate()方法中從Bundle中恢復資料,但是這種方法只適用於少量的、可以序列化和反序列化的資料,而不是潛在的大量資料的使用者列表或者是很多的Bitmap

另外一個問題是UI控制器經常需要進行非同步呼叫,這可能需要一些時間才能返回,UI控制器需要管理這些呼叫,並確保系統在銷燬後對其進行清理,以避免潛在的記憶體洩露,這種管理需要大量的維護,並且為了配置更改重新建立物件的情況下,這是對資源的浪費,因為物件可能不得不重新發出它已經發出的呼叫。

UI控制器(例如:ActivityFragment)主要用於顯示UI資料響應使用者操作或者處理作業系統通訊(例如:許可權請求),要求UI控制器也負責從資料庫或者網路載入資料會使類膨脹,將過多的責任分配給UI控制器會導致單個類檢視自己處理應用程式的所有工作,而不是將工作委託給其他類,這樣也會使測試變得更加困難。

檢視資料所有權UI控制器的邏輯中分離出來會更加簡單、更有效,所以官方推出這樣一個元件:ViewModel

ViewModel是一個負責準備和管理Activity或者Fragment的類,它還可以處理ActivityFragment與應用程式其餘部分的通訊(例如:呼叫業務邏輯類)。

ViewModel總是在一個Activity或者一個Fragment建立的,並且只要對應的Activity或者Fragment處於活動狀態的話,它就會被保留(例如:如果它是個Activity,就會直到它finished)。

換句話說,這意味著一個ViewModel不會因為配置的更改(例如:旋轉)而被銷燬,所有的新例項將被重新連線到現有的ViewModel

ViewModel的目的是獲取儲存Activity或者Fragment所需的資訊,Activity或者Fragment應該能夠觀察到ViewModel中的變化,通常通過LiveData或者Android Data Binding公開這些資訊。

注意的是,ViewModel的唯一職責是管理UI的資料,它不應該訪問你的檢視層次結構或者保留對Activity或者Fragment的引用

以下這張圖片表示Activity經歷螢幕旋轉而後結束的過程中所處的各種生命週期狀態,還在關聯的Activity生命週期的旁邊顯示了ViewModel的生命週期:

ViewModelLifecycle.png

示例程式碼

專案加上如下依賴:

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha02'
複製程式碼

由於我這邊用到了DataBinding,所以加上如下程式碼:

dataBinding {
    enabled = true
}
複製程式碼

專案結構如下圖:

ViewModelDemoProjectStructureDiagram.png

我這邊定義了一個繼承了ViewModel,並且實現了ObservableObservableViewModel類,來通知控制元件資料的變化,也可以使用LiveData來實現這樣的功能,程式碼如下:

package com.tanjiajun.viewmodeldemo.viewmodel

import androidx.databinding.Observable
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.ViewModel

/**
 * Created by TanJiaJun on 2019-11-24.
 */
open class ObservableViewModel : ViewModel(), Observable {

    private val callbacks = PropertyChangeRegistry()

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) =
        callbacks.add(callback)

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) =
        callbacks.remove(callback)

    fun notifyChange() =
        callbacks.notifyCallbacks(this, 0, null)

    fun notifyPropertyChanged(fieldId: Int) =
        callbacks.notifyCallbacks(this, fieldId, null)

}
複製程式碼

第一個例子:ViewModel不會因為配置更改而被銷燬

MainActivity中建立MainViewModel,程式碼如下:

// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main).also {
        it.viewModel = ViewModelProviders.of(this)[MainViewModel::class.java].apply {
            name = "譚嘉俊"
            age = 25
            gender = "男"
        }
        it.handlers = this
    }
}
複製程式碼

這個Activity所對應的介面可以跟隨手機螢幕旋轉,而且沒有通過android:configChanges指定屬性,讓Activity在指定屬性變化的時候,只會呼叫ActivityonConfigurationChanged()方法,而不會被銷燬重建,程式碼如下:

android:configChanges="orientation|screenSize|keyboardHidden"
複製程式碼

當我們旋轉手機螢幕的時候,發現這個Activity的內容沒有發生變化,符合我們的預期。

第二個例子是:Fragment使用Activity共享的ViewModel處理資料

定義NameViewModel,並且繼承我在上面說的ObservableViewModel,程式碼如下:

package com.tanjiajun.viewmodeldemo.viewmodel

import androidx.databinding.Bindable
import androidx.databinding.library.baseAdapters.BR

/**
 * Created by TanJiaJun on 2019-11-24.
 */
class NameViewModel : ObservableViewModel() {

    @get:Bindable
    var name = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.name)
        }

}
複製程式碼

FirstNameFragment使用一個和NameActivity生命週期相同的NameViewModel,程式碼如下:

// FirstNameFragment.kt
private var viewModel: NameViewModel? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // retainInstance方法下面會解析
    retainInstance = true
    viewModel = activity?.let {
        ViewModelProviders.of(it)[NameViewModel::class.java].apply {
            name = "譚嘉俊"
        }
    }
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? =
    DataBindingUtil.inflate<FragmentFirstNameBinding>(
        inflater,
        R.layout.fragment_first_name,
        container,
        false
    )
        .also {
            // 使用NameActivity共享的NameViewModel
            it.viewModel = viewModel
            it.handlers = this
        }
        .root
複製程式碼

retainInstance方法控制在Activity重新建立(例如:配置更改)期間是否重新建立Fragment例項,如果設為true的話,在Activity重新建立的時候,Fragment的生命週期會有點不一樣,onCreate(Bundle)方法將不會被呼叫,因為Fragment沒有重新建立,onDestroy()不會被呼叫,但是onDetach()方法會被呼叫,因為Fragment只是從它附加的Activity分離而已,**onAttach(Activity)方法和onActivityCreated(Bundle)**方法仍然會被呼叫。

// SecondNameFragment.kt
override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? =
    DataBindingUtil.inflate<FragmentSecondNameBinding>(
        inflater,
        R.layout.fragment_second_name,
        container,
        false
    )
        .also { it.handlers = this }
        .root

// 點選按鈕後會改變NameViewModel中name的屬性值
override fun onChangeNameToAppleClick(view: View) {
    activity?.let { ViewModelProviders.of(it)[NameViewModel::class.java].name = "蘋果" }
}
複製程式碼

點選SecondFragment的按鈕後,我們再按後退鍵,退回到上個Fragment,可以看到name已經已經從**”譚嘉俊“變成”蘋果“了,這裡的NameViewModel的生命週期是和NameActivity的生命週期一樣,也就是這兩個Fragment拿到的都是同一個ViewModel**,所以我們可以這樣處理附加在同一個Activity多個Fragment之間的資料。

原始碼分析

我們看下ViewModelProviders這個類,程式碼如下:

// 建立一個ViewModelProvider,在Fragment處於活動狀態時保留ViewModel
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
    return of(fragment, null);
}

// 建立一個ViewModelProvider,在Activity處於活動狀態時保留ViewModel
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    return of(activity, null);
}

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
    // 檢查Fragment是否附加在Application
    Application application = checkApplication(checkActivity(fragment));
    // 在上面的方法中factory是傳null
    if (factory == null) {
        // 建立一個單例的AndroidViewModelFactory
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    // 建立ViewModelProvider,這裡會拿到Fragment的ViewModelStore,下面會分析
    return new ViewModelProvider(fragment.getViewModelStore(), factory);
}

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    // 檢查Activity是否附加在Application
    Application application = checkApplication(activity);
    // 在上面的方法中factory是傳null
    if (factory == null) {
        // 建立一個單例的AndroidViewModelFactory
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    // 建立ViewModelProvider,這裡會拿到Activity的ViewModelStore,下面會分析
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}
複製程式碼

我們看下Activity的**getViewModelStore()**方法,程式碼如下:

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    // 檢查Activity是否附加在Application
    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) {
        // 通過getLastNonConfigurationInstance()方法得到NonConfigurationInstances,下面會分析
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // 從NonConfigurationInstances恢復ViewModelStore
            mViewModelStore = nc.viewModelStore;
        }
        // 如果是空的話建立ViewModelStore物件
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}
複製程式碼

在分析getLastNonConfigurationInstances()方法之前,我們看下onRetainNonConfigurationInstance()方法,它在Activity類中是一個返回null的方法,我們可以找到Activity的子類ComponentActivity,可以看到它重寫了這個方法,程式碼如下:

// ComponentActivity.java
// NonConfigurationInstances是一個final的靜態類,裡面有兩個變數:custom和viewModelStore
static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    // onRetainCustomNonConfigurationInstance()方法已棄用
    Object custom = onRetainCustomNonConfigurationInstance();

    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // 如果viewModelStore是null的話,證明沒人呼叫getViewModelStore(),所以看看我們最後一個NonConfigurationInstance是否存在ViewModelStore
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // 如果有的話,就從NonConfigurationInstances取出ViewModelStore
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        // 如果ViewModelStore還是null而且custom也是null的話,證明沒有NonConfigurationInstances
        return null;
    }

    // 如果有ViewModelStore或者有custom的話,就建立NonConfigurationInstances物件,並且對其進行賦值
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}
複製程式碼

onRetainNonConfigurationInstance()方法是在一個Activity因為配置改變被銷燬時被呼叫,這時就會建立一個新的例項,它會在onStop()方法和onDestroy()方法兩者之間呼叫,我們可以在這裡返回物件,甚至是Activity的例項也可以,之後我們可以在新的Activity的例項通過**getLastNonConfigurationInstance()**方法來檢索,拿到我們想要的物件。

我們之前也用過onSaveInstanceState方法,呼叫這個方法可以在Activity被終止之前檢索每個例項的狀態,以便可以在onCreate方法或者onRestoreInstanceState方法中恢復狀態,兩個方法都會傳入我們之前想要保留的Bundle物件,注意你還要給你的View設定id,因為它是通過id儲存當前有焦點View,在Android P版本中,這個方法將在onStop()方法之後呼叫,在之前的版本中,這個方法將在onStop()方法之前呼叫,並不能保證它將在onPause()方法之前或之後呼叫,這個方法預設實現儲存了關於Activity的檢視層次狀態的臨時資訊,例如:EditText中的文字和ListView或者RecyclerView中的滾動條位置。

onSaveInstanceState方法和onRetainNonConfigurationInstance()方法還有什麼區別呢?其中一點是前者是儲存到BundleBundle是有型別限制大小限制的,而且也要在主執行緒序列化和反序列化資料,而後者是儲存到Object型別和大小都沒有限制

我們繼續看原始碼,從上面分析可知,會建立ViewModelStore物件,我們看下ViewModelStore的原始碼,程式碼如下:

public class ViewModelStore {

    // 建立一個key為String,value為ViewModel的HashMap物件
    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());
    }

    // 清除內部儲存並且通知儲存在這個HashMap中的所有的ViewModel不再被使用
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}
複製程式碼

它是通過HashMap存放ViewModel的,然後我們回到上面ViewModelProvidersof方法,可以看到它建立了ViewModelProvider物件,看下它的構造方法,程式碼如下:

// ViewModelProvider.java
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    mViewModelStore = store;
}
複製程式碼

構造方法就兩個引數,第一個是ViewModelStore,用於存放ViewModel;第二個引數是Factory,用於例項化多個ViewModel工廠

在我們的示例程式碼中,我們呼叫了ViewModelProviderget方法,傳入的是我們建立的ViewModelClass物件,看下相關的程式碼,程式碼如下:

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

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    // 得到ViewModel的Class物件的Java語言規範定義的底層類的規範名稱
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    // 呼叫下面的get方法,傳入“DEFAULT_KEY:modelClass的規範名稱”字串和ViewModel的Class物件
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}

@SuppressWarnings("unchecked")
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    // 通過key從ViewModelStore中的HashMap中得到ViewModel
    ViewModel viewModel = mViewModelStore.get(key);

    // 判斷從ViewModelStore中得到的ViewModel是否是Class物件的一個例項,也就是說判斷ViewModelStore中是否存在我們想要的ViewModel
    if (modelClass.isInstance(viewModel)) {
        // 如果有的話就返回對應的ViewModel
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    // 如果沒有的話就建立ViewModel
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        // 根據上面的程式碼可知,mFactory是Factory的實現類NewInstanceFactory的子類AndroidViewModelFactory,所以我們呼叫的是這段邏輯
        viewModel = (mFactory).create(modelClass);
    }
    // 將建立好的ViewModel存放到ViewModelStore
    mViewModelStore.put(key, viewModel);
    // 返回ViewModel
    return (T) viewModel;
}
複製程式碼

我們看下AndroidViewModelFactorycreate方法,程式碼如下:

// ViewModelProvider中的靜態內部類AndroidViewModelFactory
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    // 判斷AndroidViewModel所表示的類或介面是否與modelClass所表示的類或介面相同,或者是否為其超類或超介面
    if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
        //noinspection TryWithIdenticalCatches
        try {
            // 建立ViewModel物件
            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);
        }
    }
    // 根據我們的示例程式碼,我們傳入的modelClass不是AndroidViewModel,而且也不是為其超類或者超介面,所以會執行以下邏輯
    return super.create(modelClass);
}
複製程式碼

ViewModel是不能傳入任何有Context引用的物件,這樣導致記憶體洩露,如果需要使用的話,可以使用AndroidViewModel

然後會呼叫它的父類NewInstanceFactorycreate方法,程式碼如下:

@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    //noinspection TryWithIdenticalCatches
    try {
        // 建立由modelClass類物件表示的類的新例項
        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);
    }
}
複製程式碼

剛才我們看的是ActivitygetViewModelStore()方法,現在看下Fragment的**getViewModelStore()**方法,程式碼如下:

// Fragment.java
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    // 判斷Fragment是否已經與Activity分離
    if (mFragmentManager == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    return mFragmentManager.getViewModelStore(this);
}
複製程式碼

呼叫了FragmentManagerImplgetViewModelStore方法,程式碼如下:

// FragmentManagerImpl.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    return mNonConfig.getViewModelStore(f);
}
複製程式碼

成員變數mNonConfigFragmentManagerVIewModel的引用,我們看下FragmentManagerViewModelgetViewModelStore方法,程式碼如下:

// FragmentManagerViewModel.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}
複製程式碼

成員變數mViewModelStoreskeyStringvalueViewModelStoreHashMap的引用,它是跟隨Fragment的生命週期,根據Frament的內部唯一名稱從這個HashMap中得到ViewModelStore,如果是空的話,就建立一個新的ViewModelStore物件,並且放入mViewModelStores,然後返回這個物件;如果不是空的話,就返回剛才從HashMap取得的ViewModelStore

到這裡,ViewModel建立得到的原始碼就分析得差不多了,然後我們看下ViewModel什麼時候被銷燬,在上面分析ViewModelStore原始碼的時候,我們看到有個clear方法,這個方法用來清除內部儲存並且通知儲存在這個HashMap中的所有的ViewModel不再被使用,如果ViewModel跟隨的Activity的生命週期的話,它會在如下程式碼呼叫這個方法:

public ComponentActivity() {
    // 省略部分程式碼
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            // 判斷是否接收到Activity的destroy狀態
            if (event == Lifecycle.Event.ON_DESTROY) {
                // 如果接收到,判斷是否因為配置更改導致的destroy
                if (!isChangingConfigurations()) {
                    // 如果不是,呼叫ViewModelStore的clear方法
                    getViewModelStore().clear();
                }
            }
        }
    });
    // 省略部分程式碼
}
複製程式碼

如果ViewModel跟隨的是Fragment的生命週期的話,它會在如下程式碼呼叫這個方法:

// FragmentManagerViewModel.java
void clearNonConfigState(@NonNull Fragment f) {
    if (FragmentManagerImpl.DEBUG) {
        Log.d(FragmentManagerImpl.TAG, "Clearing non-config state for " + f);
    }
    // 清除並且刪除Fragment的子配置狀態
    FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
    if (childNonConfig != null) {
        childNonConfig.onCleared();
        mChildNonConfigs.remove(f.mWho);
    }
    // 清除並且刪除Fragment的ViewModelStore
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore != null) {
        viewModelStore.clear();
        mViewModelStores.remove(f.mWho);
    }
}
複製程式碼

在如下程式碼呼叫clearNonConfigState這個方法:

// FragmentManagerImpl.java
@SuppressWarnings("ReferenceEquality")
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                 boolean keepActive) {
    // 省略部分程式碼
    if (f.mState <= newState) {
        // 省略部分程式碼
    } else if (f.mState > newState) {
        switch (f.mState) {
            // 省略部分程式碼
            case Fragment.CREATED:
                if (newState < Fragment.CREATED) {
                    // 省略部分程式碼
                    if (f.getAnimatingAway() != null || f.getAnimator() != null) {
                        // 省略部分程式碼
                    } else {
                        if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
                        boolean beingRemoved = f.mRemoving && !f.isInBackStack();
                        // 判斷Fragment是否正在remove,同時還沒放入後退棧,或者判斷是否FragmentManagerViewModel是否應該銷燬
                        if (beingRemoved || mNonConfig.shouldDestroy(f)) {
                            boolean shouldClear;
                            // 判斷mHost是否是ViewModelStoreOwner的例項
                            if (mHost instanceof ViewModelStoreOwner) {
                                // 如果是,shouldClear的值就是FragmentManagerViewModel是否已經清除
                                shouldClear = mNonConfig.isCleared();
                            } else if (mHost.getContext() instanceof Activity) {
                                Activity activity = (Activity) mHost.getContext();
                                shouldClear = !activity.isChangingConfigurations();
                            } else {
                                shouldClear = true;
                            }
                            // 根據beingRemoved或者shouldClear的值來判斷是否需要清除ViewModel
                            if (beingRemoved || shouldClear) {
                                // 如果是,呼叫clearNonConfigState方法
                                mNonConfig.clearNonConfigState(f);
                            }
                            // 執行Fragment的onDestroy()方法
                            f.performDestroy();
                            dispatchOnFragmentDestroyed(f, false);
                        } else {
                            f.mState = Fragment.INITIALIZING;
                        }
                        // 省略部分程式碼
                    }
                }
        }
    }

    // 省略部分程式碼
}
複製程式碼

到這裡,ViewModel銷燬的原始碼分析得差不多了。

onSaveInstanceState和ViewModel

onSaveInstanceState是生命週期的一個回撥方法,用來儲存以下兩種狀態下的少量UI相關的資料:

  • 應用的程式在後臺的時候由於記憶體限制而被終止。
  • 配置更改。

onSaveInstanceState不是被設計用來儲存類似Bitmap這樣大的資料,而是儲存小的與UI相關的能夠被序列化和反序列化的資料,上面也提及過了,這裡就不再贅述了。

ViewModel有以下好處:

  • ViewModel可以架構設計更加良好,UI程式碼資料分離,使程式碼更加遵循單一職責原則更加模組化更易於測試
  • ViewModel能儲存更大更復雜的資料,而且資料型別也沒有限制,甚至可以儲存Activity例項

要注意的是,ViewModel只能在配置更改造成相關的銷燬下得到保留,而不能在被終止的程式中得到保留,也就是說在應用的程式在後臺的時候由於記憶體限制而被終止,ViewModel也會被銷燬

因此我們最好兩者結合來處理儲存和恢復UI狀態,如果要保證資料不丟失,就要對資料進行本地持久化

題外話

如果應用在特定配置更改期間無需更新資源,並且因效能限制你需要避免Activity重啟,則可宣告Activity自行處理配置更改,從而阻止系統重啟Activity

我們可以通過在AndroidManifest檔案中,找到相應**元素,新增android:configChanges屬性,在宣告多個配置值的時候,可以通過|**字元對其進行分隔。

Android官方不建議對大多數應用使用此方法,因為這樣做可能會提高使用備用資源的難度。

有如下屬性:

  • density:顯示密度發生變更,例如:使用者可能已指定不同的顯示比例,或者有不同的顯示現處於活躍狀態。**請注意:**此項為 API 級別 24 中的新增配置。

  • fontScale:字型縮放係數發生變更,例如:使用者已選擇新的全域性字號。

  • keyboard:鍵盤型別發生變更,例如:使用者插入外接鍵盤。

  • keyboardHidden:鍵盤無障礙功能發生變更,例如:使用者顯示硬鍵盤。

  • layoutDirection:佈局方向發生變更,例如:自從左至右 (LTR) 更改為從右至左 (RTL)。**請注意:**此項為 API 級別 17 中的新增配置。

  • locale:語言區域發生變更,例如:使用者已為文字選擇新的顯示語言。

  • mcc:IMSI 移動裝置國家/地區程式碼 (MCC) 發生變更,例如:檢測到 SIM 並更新 MCC。

  • mnc:IMSI 移動裝置網路程式碼 (MNC) 發生變更,例如:檢測到 SIM 並更新 MNC。

  • navigation:導航型別(軌跡球/方向鍵)發生變更。(這種情況通常不會發生。)

  • orientation:螢幕方向發生變更,例如:使用者旋轉裝置。請注意:如果應用面向 Android 3.2(API 級別 13)或更高版本的系統,則還應宣告screenSize配置,因為當裝置在橫向與縱向之間切換時,該配置也會發生變更。

  • screenLayout:螢幕佈局發生變更,例如:不同的顯示現可能處於活躍狀態。

  • screenSize:當前可用螢幕尺寸發生變更。該值表示當前可用尺寸相對於當前縱橫比的變更,當使用者在橫向與縱向之間切換時,它便會發生變更。**請注意:**此項為 API 級別 13 中的新增配置。

  • smallestScreenSize:物理螢幕尺寸發生變更。該值表示與方向無關的尺寸變更,因此它只有在實際物理螢幕尺寸發生變更(如切換到外部顯示器)時才會變化。對此配置所作變更對應smallestWidth配置的變化。**請注意:**此項為 API 級別 13 中的新增配置。

  • touchscreen:觸控式螢幕發生變更。(這種情況通常不會發生。)

  • uiMode:介面模式發生變更,例如:使用者已將裝置置於桌面或車載基座,或者夜間模式發生變更。如需瞭解有關不同介面模式的更多資訊,請參閱UiModeManager。**請注意:**此項為 API 級別 8 中的新增配置。

所有這些配置變更都可能影響到應用所看到資源值,因此呼叫**onConfigurationChanged()**方法時,通常有必要再次檢索所有資源(包括檢視佈局、可繪製物件等),以正確處理變更。

我的GitHub:TanJiaJunBeyond

Android通用框架:Android通用框架(Kotlin-MVVM)

我的掘金:譚嘉俊

我的簡書:譚嘉俊

我的CSDN:譚嘉俊

相關文章