Android Jetpack系列——ViewModel源碼分析

本文章已授權微信公衆號郭霖(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的生命週期:
在這裏插入圖片描述

示例代碼

項目加上如下依賴:

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha02'

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

dataBinding {
    enabled = true
}

項目結構如下圖:
在這裏插入圖片描述
我這邊定義了一個繼承了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:譚嘉俊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章