主頁 > 軟體設計 > 【Android Jetpack高手日志】ViewModel 從入門到精通

【Android Jetpack高手日志】ViewModel 從入門到精通

2021-04-29 14:10:30 軟體設計

背景

上一篇介紹了 Android Jetpack 組件 LiveData,LiveData是在Lifecycle 的幫助下,實作了生命周期管理的一致性,將資料變更的通知控制在生命周期活躍狀態 STARTED、RESUMED(注意這是Lifecycle 中的 State)時進行通知,該通知成為資料的唯一可信來源,也就是視圖獲取資料的唯一入口,LiveData 經常和 ViewModel 一起配合使用,

定義

下面我們來介紹下一個 Android Jetpack 的下一個組件 ViewModel,先來看官方的定義:ViewModel 類旨在以注重生命周期的方式存盤和管理界面相關的資料,ViewModel類讓資料可在發生螢屏旋轉等配置更改后繼續留存

在我看來,ViewModel類讓資料可在螢屏發生等配置更改后繼續留存,比如在界面因配置改變重新創建后 ViewModel 依舊持有原先的資料,這個功能當然很重要,但還有一個同樣重要的功能是在 Fragment 之間共享資料,最后由于其管理方式(單向依賴,只有 Activity/Fragment 持有 ViewModel),也避免了記憶體泄漏的發生,同時 ViewModel 配合 kotlin 協程,將加載器替換為 ViewModel,這些也是 ViewModel 能發揮重要作用的地方,

基本使用

匯入依賴
// LiveData & ViewModel 因為這兩者通常都一起使用
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
簡單使用

Android Jetpack 架構組件為界面控制器提供了 ViewModel 輔助程式類,該類負責為界面準備資料,在配置更改期間會自動保留 ViewModel 物件,以便他們存盤的資料立即可供下一個 Activity/Fragment 實體使用,下面來看個例子:

ViewModel 代碼如下:

class AccountViewModel: ViewModel() {

    private val _userAgeLiveData: MutableLiveData<String> = MutableLiveData()
    val userAgeLiveData: LiveData<String>
        get() = _userAgeLiveData

    fun loadUserName(userId: String){
        val accountRepository = AccountRepository()
				Log.i("ViewModel=====", "loadUserName: ")
        viewModelScope.launch {
            //suspend 方法,2s 后獲取用戶資訊
            val result = accountRepository.requestUserInfo(userId)
            // 賦值
            _userAgeLiveData.value = result
        }
    }

      override fun onCleared() {
        super.onCleared()
        Log.i("ViewModel=====", "onCleared: ")
    }
}

Activity 代碼如下:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        txtUserName = findViewById(R.id.txtUserName)
        Log.i(TAG, "onCreate: ")
        val accountViewModel = ViewModelProvider(this).get(AccountViewModel::class.java)

        findViewById<View>(R.id.btnGetUserInfo).setOnClickListener {
            accountViewModel.loadUserName("jackie")
        }

        accountViewModel.userAgeLiveData.observe(this, Observer {
            //通知UI,UI 進行操作
            Log.i(TAG, "onCreate: ========observe change=========")
            txtUserName.text = it
        })
    }

點擊按鈕后獲取用戶資訊,執行了 ViewModel 中的方法loadUserName

jetpackdemo I/ViewModel=====: loadUserName: 
jetpackdemo I/AccountActivity=====: onCreate: ========observe change=========

配置變化后(螢屏旋轉后),日志列印如下:

jetpackdemo I/AccountActivity=====: onPause: 
jetpackdemo I/AccountActivity=====: onDestroy: 
jetpackdemo I/AccountActivity=====: onCreate: 
jetpackdemo I/AccountActivity=====: onStart: 
jetpackdemo I/AccountActivity=====: onCreate: ========observe change=========

可以看到 Activity 重新創建了,我們并沒有點擊按鈕執行loadUserName方法,但是卻回呼了Observer 的 onChange 方法;而且 ViewModel 的 onClear方法并沒有執行,說明 ViewModel 并沒有銷毀重建,

注意

ViewModel 的創建方式使用的是

val accountViewModel = ViewModelProvider(this).get(AccountViewModel::class.java)

而不是

val accountViewModel = AccountFactory().create(AccountViewModel::class.java)

class AccountFactory: ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(AccountViewModel::class.java)){
            return AccountViewModel() as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
Fragment 間資料共享

Activity 中的兩個或更多 Fragment 互相通信是一種很常見的需求,假設你有一個 Fragment,在該 Fragment 中,用戶從串列中選擇一項,還有另一個 Fragment,用于顯示選定項的內容,這種情況不太容易處理,因為這兩個 Fragment 都需要定義某種介面描述,并且所有者 Activity 必須將兩者系結在一起,此外,這兩個 Fragment 都必須處理另一個 Fragment 尚未創建或不可見的情況,

可以使用 ViewModel物件解決這個常見的難點,這兩個 Fragment 可以使用其 Activity 范圍共享 ViewModel來處理此類通信,代碼如下:

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

class MasterFragment : Fragment() {

    private lateinit var itemSelector: Selector

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
            // Update the UI
        })
    }
}

這兩個 Fragment 都會檢索包含它們的 Activity,這樣,當這兩個 Fragment 各種獲取 ViewModelProvider時,他們會收到相同的SharedViewModel實體(其范圍限定為該 Activity),

此方法具有以下優勢:

  • Activity 不需要執行任何操作,也不需要對此通信有任何了解,
  • 除了 SharedViewModel 約定之外,Fragment 不需要相互了解,如果其中一個 Fragment 消失,另一個 Fragment 將繼續照常作業,
  • 每個 Fragment 都有自己的生命周期,而不受另一個 Fragment 的生命周期的影響,如果一個 Fragment 替換另一個 Fragment,界面將繼續作業而沒有任何問題,

ViewModel 的生命周期

ViewModel物件存在的時間范圍是獲取ViewModel時傳遞給 VIewModelProvider 的 Lifecycle,也就是上面我們呼叫的代碼:

val accountViewModel = ViewModelProvider(this).get(AccountViewModel::class.java)

這里的thisActivity實體物件,因為我們的Activity實作了 Lifecycle 的關聯,ViewModel將一直留在記憶體中,直到限定其存在時間范圍的 Lifecycle永久消失:對于 Activity,是在 Activity 完成時;而對于 Fragment,是在 Fragment 分離時,

下圖是 Activity 在螢屏旋轉而后結束時所處的各種生命周期狀態,該圖還在關聯 Activity 生命周期的旁邊顯示了 ViewModel的生命周期,這些基本狀態同樣適用于 Fragment 的生命周期,

通常是在系統首次呼叫 Activity 物件的 onCreate()時請求ViewModelViewModel存在的時間范圍是從首次請求ViewModel直到 Activity 完成并銷毀

原始碼分析

在分析原始碼前,我先提出兩個問題,ViewModel 是如何做到讓資料可在發生螢屏旋轉等配置更改后繼續留存(也就是說ViewModel 實體依然存在)?Fragment 之間是如何通過 ViewModel 共享資料的?

先來看看ViewModel

public abstract class ViewModel {
		···
    private volatile boolean mCleared = false;

    /**
     * This method will be called when this ViewModel is no longer used and will be destroyed.
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    //該方法將會在 ViewModel 被清除時呼叫,可以在這個方法里做一些取消注冊,防止記憶體泄漏 
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
    @MainThread
    final void clear() {
        mCleared = true;
				···
        onCleared();
    }
	···
}

ViewModel 只是一個抽象類,clear()方法會在 ViewModel 被清除時呼叫有,用戶可以通過重寫 onCleared()方法來處理一些額外的操作,

再從呼叫處開始分析

val accountViewModel = ViewModelProvider(this).get(AccountViewModel::class.java)

我們再來看看ViewModelProvider這個類:

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

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

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

最終呼叫到的構造器都是第三個構造器,再來看看ViewModelStoreFactory是什么,因為我們傳入的是 Activity(是ViewModelStoreOwnerHasDefaultViewModelProviderFactory的實作者),所以才會有owner.getViewModelStore(),第二個引數是getDefaultViewModelProviderFactory()

ViewModelStoreOwner可以理解為 ViewModelStore(ViewModel 存盤器)的擁有者,也就是說我們的 Activity/Fragment 是 ViewModel 存盤器的擁有者,

然后我們來看看看ViewModelStore是什么?

// 存盤 ViewModel
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();
    }
}

可以看到ViewModelStore的代碼很簡單,就是用一個 HashMap 存盤了key(String)value(ViewModel),這里的 key 的名字為

// key 的名字
DEFAULT_KEY + ":" + canonicalName

private static final String DEFAULT_KEY =
            "androidx.lifecycle.ViewModelProvider.DefaultKey";
String canonicalName = modelClass.getCanonicalName();            

因為第二個引數是通過getDefaultViewModelProviderFactory()獲取到的,前面說過ActivityHasDefaultViewModelProviderFactory的實作類,我們再來看看Activity中的getDefaultViewModelProviderFactory()方法

    @NonNull
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        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 (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

可以看到是通過一個SavedStateViewModelFactory來獲取ViewModelProvider.Factory,命名也很清晰直觀,就是保存狀態的 ViewModel 工廠,

下面我們再來看看get(AccountViewModel::class.java)方法

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);  //key的名字
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key); //從 hashmap 中獲取 viewmodel 實體

        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.
            }
        }
     		//使用 Factory 創建
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass); 
        }
      	//存入 viewModelStore
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

簡單來說就是從mViewModelStore獲取 ViewModel,如果沒有獲取到,就使用 Factory 創建,然后存入mViewModelStore

這樣邏輯就清楚了,ViewModelProvider(this).get(AccountViewModel::class.java)會把ViewModel存入ViewModelStore

因為 Activity 實作了ViewModelStoreOwner 介面,可以理解為 ViewModelStore(ViewModel 存盤器)的擁有者,也就是說我們的 Activity/Fragment 是 ViewModel 存盤器的擁有者,

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

然后我們來看看該方法在 Activity 中的實作

    @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) {
          	//從getLastNonConfigurationInstance 獲取
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
              	//恢復viewmodelstore
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;  
            }
          	//如果還是獲取不到,就新建一個
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }

從該方法中可以看到,Activity(ViewModelStoreOwner)內部最侄訓創建一個ViewModelStore,用來存盤ViewModel,接下來我們來看getLastNonConfigurationInstance方法

//ComponentActivity.java
NonConfigurationInstances mLastNonConfigurationInstances;
@Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

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

為什么使用 getLastNonConfigurationInstance 方法呢,我們先來看看 Activity 狀態保存和恢復:

onSaveInstanceStateonRetainNonConfigurationInstance 的使用場景區別

我們知道,在螢屏旋轉的時候會執行保存狀態和恢復狀態的方法

	@Override
	protected void onSaveInstanceState(Bundle outBundle) {  //保存
		super.onSaveInstanceState(outBundle);
	}

	@Override 
	protected void onRestoreInstanceState(Bundle savedInstanceState) { //恢復
		super.onRestoreInstanceState(savedInstanceState);
	}

//注意,如果你是繼承 AppcompactActiviy,該方法已經在它的父類 ComponentActivity 定義為 final,子類無法重寫
//繼承 Activity 就可以
public Object onRetainNonConfigurationInstance() {
    // TODO Auto-generated method stub
    // 在這里設定需要保存的內容,在切換時不是bundle了,我們可以直接通過object來代替,          
  return super.onRetainNonConfigurationInstance();
} 

@Nullable
@Override
public Object getLastNonConfigurationInstance() {
  return super.getLastNonConfigurationInstance();
}

在 Android 10,他們的執行順序都在onStoponDestory之間,而且onSaveInstanceStateonRetainNonConfigurationInstance先執行,一般情況我們保存的資料不是太大,適合放在 Bundle 中,這個時候使用onSaveInstanceState比較合適,如果要保存的資料不適合放在 Bundle 中(比如: 一個socket)或是資料比較大(比如 Bitmap),那么這個時間我們就應該使用onRetainNonConfigurationInstance(),而且我們使用onRetainNonConfigurationInstance()可以保存任何型別的物件,像AsyncTaskSQLiteDatabse,我們都可以進行保存,這些型別的資料可能會被一個新的Activity重新使用,

也就是說 Bundle 中只能放一些特定的型別,比如基本資料型別,陣列,Serialable 物件,而onRetainNonConfigurationInstance中只要是個 Object 物件就可以了

同時當某個activity變得“容易”被系統銷毀時,該activityonSaveInstanceState就會被執行,而onRetainNonConfigurationInstance更多的是時候是在配置改變時操作的,這個時候保存一些不會因為配置改變而發生改變的東西,而且onSaveInstanceState資料是序列化保存到磁盤中,而onRetainNonConfigurationInstance保存的資料是存在記憶體中

所以這里我們的 ViewModel 肯定是放在onRetainNonConfigurationInstance方法中,再來看看

   //ComponentActivity.java
   @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,將 viewModelStore 保存
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore; 
        return nci;
    }

可以看到在該方法中保存了viewModelStore,保存在了NonConfigurationInstances,而該getLastNonConfigurationInstance的真正實作是在 Activity.java 類中

    //Activity.java
     //靜態內部類   
     static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
    }
    NonConfigurationInstances mLastNonConfigurationInstances;
    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

mLastNonConfigurationInstances的賦值是在attach方法中的mLastNonConfigurationInstances = lastNonConfigurationInstances;

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) {
        attachBaseContext(context);
  			···
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
  			···
為什么ViewModel 在配置變化后依舊存在?

attach中的傳入的引數lastNonConfigurationInstancesActivityClientRecord的一個變數,而ActivityClientRecord是存在ActivityThreadmActivities(ArrayMap)中,ActivityThread中的ActivityClientRecord不受activity重建的影響,所以ActivityThread中的lastNonConfigurationInstances同樣也不受影響,所以ComponentActiviy中的NonConfigurationInstances也無影響,所以ViewModelStore不受影響,最終ViewModelActivity重新創建后呼叫getLastNonConfigurationInstance獲取得到,這也是ViewModel一直存在的原因

在更早之前版本保存 ViewModel 是使用HolderFragment的,Fragment 中的setRetainInstance(boolean)在設定為 true 時可以是當前的 Fragment 在 Activity 重建時存盤下來,所以可以在 Activity 中注入一個 Fragment,這樣就可以達到保存 ViewModel 的功能了,詳情可以參考這篇文章,

Fragment 之間是如何通過 ViewModel 共享資料的?

從上面的分析,我們知道ViewModel存在ActivityViewModelStore中,多個 Fragment 依賴于同一個 Activity,這個時候拿到同一個ViewModel自然就不是問題了,

為什么旋轉后所有的 LiveData 會重新執行一次通知

原因很簡單,因為 LiveData 的事件是粘性事件,也就是說當 Activity 銷毀后,因為 ViewModel 中的 LiveData 并沒有銷毀(有具體的值),在 Activity 重新創建后,LiveData 會將該值發送給當前 Activity 界面,達到恢復 Activity 界面狀態的效果,

ViewModel 如何避免記憶體泄漏

ComponentActivity的構造器中可以看出,getLifecycle().addObserver添加了一個觀察者觀察界面是否銷毀,一旦銷毀,就清空ViewModelStore中的所有ViewModel

    public ComponentActivity() {
        Lifecycle lifecycle = getLifecycle();
        //noinspection ConstantConditions
        if (lifecycle == null) {
            throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
                    + "constructor. Please make sure you are lazily constructing your Lifecycle "
                    + "in the first call to getLifecycle() rather than relying on field "
                    + "initialization.");
        }
        if (Build.VERSION.SDK_INT >= 19) {
            getLifecycle().addObserver(new LifecycleEventObserver() {
                @Override
                public void onStateChanged(@NonNull LifecycleOwner source,
                        @NonNull Lifecycle.Event event) {
                    if (event == Lifecycle.Event.ON_STOP) {
                        Window window = getWindow();
                        final View decor = window != null ? window.peekDecorView() : null;
                        if (decor != null) {
                            decor.cancelPendingInputEvents();
                        }
                    }
                }
            });
        }
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear(); //界面執行 onDestroy 方法是,清空 viewmodel
                    }
                }
            }
        });

        if (19 <= SDK_INT && SDK_INT <= 23) {
            getLifecycle().addObserver(new ImmLeaksCleaner(this));
        }
    }

ViewModel 和協程一起使用

ViewModel 支持協程,ViewMdoelScope 為應用中的每個ViewModel定義了ViewMdoelScope,如果 ViewModel 已清除,則在此范圍內啟動的協程都會自動取消,如果您具有僅在 ViewModel 處于活動狀態時才需要完成的作業,此時協程非常有用,例如,如果要為布局計算某些資料,則應將作業范圍限定至 ViewModel,以便在 ViewModel 清除后,系統會自動取消作業以避免消耗資源,

viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }

Android Jetpack 的這些架構組件配合起來會非常強大,大大節省開發者的開發時間,同時還能輕松避免記憶體泄漏,簡直就是開發利器,

總結

ViewModel 是個非常好用的 Android Jetpack 組件,可在發生螢屏旋轉等配置更改后繼續留存,同時還能在不同 Fragment 之間共享,在配合協程使用時也非常方便,還能輕松避免記憶體泄漏,

需要注意的是,不少開發者會將 ViewModel 實作 LifecycleObserver 介面,把 ViewModel 當做一個能感應生命周期變化的組件,在感知到生命周期的方法中執行loadData之類的操作,然后通過 LiveData 去通知 UI 做出對應的改變,這樣的做法喪失了官方為我們設計的 ViewModel 的初衷,反而有點不倫不類了,而且 ViewModel 配合協程也非常方便,也有很多人將網路請求也放到 ViewModel 去呼叫,個人感覺也是很不合適的,可以參考官方的Demo進行設計,

文末的話

最近火熱的Jetpack Compose是谷歌在2019Google i/o大會上發布的新的庫,是用于構建原生Android UI的現代工具包,他有強大的工具和直觀的Kotlin API,簡化并加速了Android上的UI開發,可以幫助開發者用更少更直觀的代碼創建View,還有更強大的功能,以及還能提高開發速度,

客觀地講,Compose 確實是一套比較難學的東西,因為它畢竟太新也太大了,它是一個完整的、全新的框架,確實讓很多人感覺「學不動」,這也是個事實,

如果你是因為缺少學習資料,而我正好薅到這本谷歌內部大佬根據實戰撰寫的《Jetpack Compose最全上手指南》,從入門到精通,教程通俗易懂,實體豐富,既有基礎知識,也有進階技能,能夠幫助讀者快速入門,是你學習Jetpack Compose的葵花寶典,快收藏起來!!!

由于篇幅原因,如有需要以下完整學習筆記PDF,可以點擊我的GitHub免費下載獲取!

第一章 初識 Jetpack Compose

1. 為什么我們需要一個新的UI 工具?
2. Jetpack Compose的著重點

  • 加速開發
  • 強大的UI工具
  • 直觀的Kotlin API

3. API 設計

4. Compose API 的原則

  • 一切都是函式
  • 頂層函式(Top-level function)
  • 組合優于繼承
  • 信任單一來源

5. 深入了解Compose

  • Core
  • Foundation
  • Material

  1. 插槽API

第二章 Jetpack Compose構建Android UI

1. Android Jetpack Compose 最全上手指南

  • Jetpack Compose 環境準備和Hello World
  • 布局
  • 使用Material design 設計
  • Compose 布局實時預覽
    ……

2. 深入詳解 Jetpack Compose | 優化 UI 構建

  • Compose 所解決的問題
  • Composable 函式剖析
  • 宣告式 UI
  • 組合 vs 繼承
  • 封裝
  • 重組
    ……

3. 深入詳解 Jetpack Compose | 實作原理

  • @Composable 注解意味著什么?
  • 執行模式
  • Positional Memoization (位置記憶化)
  • 存盤引數
  • 重組
    ……

第三章 Jetpack Compose 專案實戰演練(附Demo)

1. Jetpack Compose應用1

  • 開始前的準備
  • 創建DEMO
  • 遇到的問題

2. Jetpack Compose應用2

3. Jetpack Compose應用做一個倒計時器

  • 資料結構
  • 倒計時功能
  • 狀態模式
  • Compose 布局
  • 繪制時鐘

4. 用Jetpack Compose寫一個玩安卓App

  • 準備作業
  • 引入依賴
  • 新建 Activity
  • 創建 Compose
  • PlayTheme
  • 畫頁面
  • 底部導航欄
  • 管理狀態
  • 添加頁面

5. 用Compose Android 寫一個天氣應用

  • 開篇
  • 畫頁面
  • 畫背景
  • 畫內容
    ……

6. 用Compose快速打造一個“電影App”

  • 成品
  • 實作方案
  • 實戰
  • 不足
    ……

由于篇幅原因,如有需要以上完整學習筆記PDF,可以點擊我的GitHub免費下載獲取!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/281309.html

標籤:其他

上一篇:負載均衡演算法居然有這么多種!!!負載均衡演算法總結

下一篇:二叉樹系列匯總,持續更新!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more