7. Android jetpack全家桶, 三劍客之ViewModel輕松掌握

目錄:
1. ViewModel是解決什么問題的?
2. ViewModel的特點(diǎn)
3. ViewModel的特點(diǎn)1生命周期
4. ViewModel的特點(diǎn)2不持有UI層引用
5. ViewModel的特點(diǎn)3.Fragment間數(shù)據(jù)共享
6. ViewModel的使用
7. ViewModel原理源碼分析(ViewModel為什么在旋轉(zhuǎn)屏幕后不會(huì)丟失狀態(tài))
8. ViewModel對(duì)比onSaveInstanceState()
9. ViewModel內(nèi)存泄露

viewmodel.jpg

ViewModel相關(guān)的問 題:

1.ViewModel為什么在旋轉(zhuǎn)屏幕后不會(huì)丟失狀態(tài)

2.ViewModel的原理,為什么可以在Activity銷毀后保存數(shù)據(jù)

3.ViewModel怎么實(shí)現(xiàn)自動(dòng)處理生命周期?ViewModel的生命周期是怎么樣的?

4.為什么不同的Fragment使用相同的Activity對(duì)象來獲取ViewModel,可以輕易的實(shí)現(xiàn)ViewModel共享?

5.ViewModel在Activity初始化與在Fragment中初始化,有什么區(qū)別?

6.ViewModel是如何創(chuàng)建出來的?

7.ViewModel是怎么實(shí)現(xiàn)雙向數(shù)據(jù)綁定的。這個(gè)vm指的是mvvm里面的?

1. ViewModel是解決什么問題的?

在詳細(xì)介紹ViewModel前,先來看下背景和問題點(diǎn)。

1). 數(shù)據(jù)持久保持: Activity可能會(huì)在某些場景(例如屏幕旋轉(zhuǎn))銷毀和重新創(chuàng)建界面,那么存儲(chǔ)在其中的界面相關(guān)數(shù)據(jù)都會(huì)丟失。例如,界面含用戶信息列表,因配置更改而重新創(chuàng)建 Activity 后,新 Activity 必須重新請(qǐng)求用戶列表,這會(huì)造成資源的浪費(fèi)。能否直接恢復(fù)之前的數(shù)據(jù)呢?對(duì)于簡單的數(shù)據(jù),Activity 可以使用 onSaveInstanceState() 方法保存 然后從 onCreate() 中的Bundle恢復(fù)數(shù)據(jù),但此方法僅適合可以序列化再反序列化的少量數(shù)據(jù)(IPC對(duì)Bundle有1M的限制),而不適合數(shù)量可能較大的數(shù)據(jù),如用戶信息列表或位圖。 那么如何做到 因配置更改而新建Activity后的數(shù)據(jù)恢復(fù)呢?

2). 內(nèi)存泄露: UI層(如 Activity 和 Fragment)經(jīng)常需要通過邏輯層(如MVP中的Presenter)進(jìn)行異步請(qǐng)求,可能需要一些時(shí)間才能返回結(jié)果,如果邏輯層持有UI層應(yīng)用(如context),那么UI層需要管理這些請(qǐng)求,確保界面銷毀后清理這些調(diào)用以避免潛在的內(nèi)存泄露,但此項(xiàng)管理需要大量的維護(hù)工作。 那么如何更好的避免因異步請(qǐng)求帶來的內(nèi)存泄漏呢?

這時(shí)候ViewModel就閃亮出場了——ViewModel用于代替MVP中的Presenter,為UI層準(zhǔn)備數(shù)據(jù),用于解決上面兩個(gè)問題。

  • ViewModel : 從界面控制中分離出視圖數(shù)據(jù)所有權(quán)的操作更容易且更高效。
  • Activity 的 onSaveInstanceState() 方法從 onCreate() 捆綁包恢復(fù)其數(shù)據(jù),僅限支持序列化的少量數(shù)據(jù)。
  • ViewModel 非常適合在用戶正活躍地使用應(yīng)用時(shí)存儲(chǔ)和管理界面相關(guān)數(shù)據(jù)。它支持快速訪問界面數(shù)據(jù),并且有助于避免在發(fā)生旋轉(zhuǎn)、窗口大小調(diào)整和其他常見的配置變更后從網(wǎng)絡(luò)或磁盤中重新獲取數(shù)據(jù)。

2. ViewModel的特點(diǎn)

ViewModel可以這么理解: 它是介于View(視圖)和Model(數(shù)據(jù)模型)之間的一個(gè)東西。它起到了橋梁的作用,使視圖和數(shù)據(jù)既能分離,也能保持通信

  1). 保存數(shù)據(jù) ! ViewModel 主要用于存儲(chǔ) UI 數(shù)據(jù)

  2). 自動(dòng)管理: 生命周期感知的數(shù)據(jù)。

  3). **共享 ViewModel 定義, **Fragment 共享數(shù)據(jù)

3.生命周期長于Activity

Android中的ViewModel是一個(gè)可以用來存儲(chǔ)UI相關(guān)的數(shù)據(jù)的類。ViewModel的生命周期會(huì)比創(chuàng)建它的Activity、Fragment的生命周期長

看到在因屏幕旋轉(zhuǎn)而重新創(chuàng)建Activity后,ViewModel對(duì)象依然會(huì)保留。 只有Activity真正Finish的時(shí)ViewModel才會(huì)被清除。

也就是說,因系統(tǒng)配置變更Activity銷毀重建,ViewModel對(duì)象會(huì)保留并關(guān)聯(lián)到新的Activity。而Activity的正常銷毀(系統(tǒng)不會(huì)重建Activity)時(shí),ViewModel對(duì)象是會(huì)清除的。

生命周期.jpg

4. 不持有UI層引用

問題: viewModel怎么實(shí)現(xiàn)自動(dòng)處理生命周期?

我們知道,在MVP的Presenter中需要持有IView接口來回調(diào)結(jié)果給界面。

而ViewModel是不需要持有UI層引用的,那結(jié)果怎么給到UI層呢?答案就是使用上一篇中介紹的基于觀察者模式的LiveData。 并且,ViewModel也不能持有UI層引用,因?yàn)閂iewModel的生命周期更長。

所以,ViewModel不需要也不能 持有UI層引用,那么就避免了可能的內(nèi)存泄漏,同時(shí)實(shí)現(xiàn)了解耦。這就解決了第二個(gè)問題。

5. ViewModel的特點(diǎn)3之Fragment間數(shù)據(jù)共享

Activity 中的多個(gè)Fragment需要相互通信是一種很常見的情況。假設(shè)有一個(gè)ListFragment,用戶從列表中選擇一項(xiàng),會(huì)有另一個(gè)DetailFragment顯示選定項(xiàng)的詳情內(nèi)容。在之前 你可能會(huì)定義接口或者使用EventBus來實(shí)現(xiàn)數(shù)據(jù)的傳遞共享。

在上面ViewMoel的生命周期也提到了 ViewModel只會(huì)在Activity存活時(shí),只會(huì)創(chuàng)建一次,因此在同一個(gè)Activity中可以在多個(gè)Fragment中共享ViewModel中數(shù)據(jù)。

現(xiàn)在就可以使用 ViewModel 來實(shí)現(xiàn)。這兩個(gè) Fragment 可以使用其 Activity 范圍共享 ViewModel 來處理此類通信,如以下示例代碼所示:

//ViewModel
public class SharedViewModel extends ViewModel {
//被選中的Item
    private final MutableLiveData<UserContent.UserItem> selected = new MutableLiveData<UserContent.UserItem>();

    public void select(UserContent.UserItem user) {
        selected.setValue(user);
    }
    public LiveData<UserContent.UserItem> getSelected() {
        return selected;
    }
}

//ListFragment
public class MyListFragment extends Fragment {
   ...
    private SharedViewModel model;
   ...
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        //獲取ViewModel,注意ViewModelProvider實(shí)例傳入的是宿主Activity
        model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        adapter.setListner(new MyItemRecyclerViewAdapter.ItemCLickListner(){
            @Override
            public void onClickItem(UserContent.UserItem userItem) {
                model.select(userItem);
            }
        });
    }
}

//DetailFragment
public class DetailFragment extends Fragment {

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextView detail = view.findViewById(R.id.tv_detail);
        //獲取ViewModel,觀察被選中的Item
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), new Observer<UserContent.UserItem>() {
            @Override
            public void onChanged(UserContent.UserItem userItem) {
                //展示詳情
                detail.setText(userItem.toString());
            }
        });
    }
}

問題: ViewModel在Activity初始化與在Fragment中初始化,有什么區(qū)別?

共享的基礎(chǔ):

Activity和Fragment創(chuàng)建ViewModel時(shí)的區(qū)別在于:

分別會(huì)創(chuàng)建一個(gè)ViewModelProvider對(duì)象,這個(gè)不同的ViewModelProvider對(duì)象中又封裝了相同的ViewModelStore對(duì)象和factory對(duì)象,

通過相同的ViewModelStore,又會(huì)獲取到相同的ViewModel對(duì)象,這也是Activity和Fragment通過ViewModel通訊的基礎(chǔ)。

生命周期不一樣,ViewModel和Activity綁定過程:

                        ViewModel和Fragment綁定過程:

問題: ViewModel 如何實(shí)現(xiàn)不同的作用域

ViewModel 內(nèi)部會(huì)為不同的 ViewModel 宿主分配不同的 ViewModelStore 映射表,不同宿主是從不同的數(shù)據(jù)源來獲取 ViewModel 的實(shí)例,因而得以區(qū)分作用域。

6. ViewModel的使用

6.1 ViewModel 的創(chuàng)建三種方式

方法 1: ViewModelProvider 是創(chuàng)建 ViewModel 的工具類

方法 2: 使用 Kotlin by 委托屬性,本質(zhì)上是間接使用了 ViewModelProvider:

方法 3: Hilt 提供了注入部分 Jetpack 架構(gòu)組件的支持

** ViewModel 定義,組合了 LiveData**

class NameViewModel : ViewModel() {
   val currentName: MutableLiveData<String> by lazy {
       MutableLiveData<String>()
   }
}

class MainActivity : AppCompatActivity() {

   private val model: NameViewModel by viewModels()

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

       // LiveData 觀察者
       val nameObserver = Observer<String> { newName ->
           // 更新視圖
           nameTextView.text = newName
       }

       // 注冊 LiveData 觀察者,this 為生命周期宿主
       model.currentName.observe(this, nameObserver)

       // 修改 LiveData 數(shù)據(jù)
       button.setOnClickListener {
           val anotherName = "John Doe"
           model.currentName.value = anotherName
       }
   }
}

問題: 4.viewModel是怎么實(shí)現(xiàn)雙向數(shù)據(jù)綁定的?

一般是databing實(shí)現(xiàn)雙向數(shù)據(jù)

ViewModel里面存放MutableLiveData(model)

然后更新view:post。然后Acrtivity通過觀察者模式監(jiān)聽!??!

7. ViewModel源碼分析

7.1 ViewModel 結(jié)構(gòu)

  • ViewModel:抽象類,有 onClear() 方法以供重寫,自定義數(shù)據(jù)清空。
  • ViewModelStore: 持有管理 ViewModel 的 HashMap。
  • ViewModelStoreOwner: ComponentActivityFragment 實(shí)現(xiàn)此接口,約束ViewModelStore 的作用域。
  • ViewModelProvider: 創(chuàng)建 ViewModel

獲取ViewModel:

ActivityThread

   ActivityClientRecord

       ViewModelProvider

           NonConfigurationInstance

                ViewModelStore 

                              HashMap

7.2 Activity 流程

7.2.1 ?Activity 關(guān)聯(lián) ViewModel 類圖

viewmodel架構(gòu).jpg

Activity 重建 ViewModel 依然存在

深度的原理:

ActivityThread 中的 ActivityClientRecord 的屬性 lastNonConfigurationInstances 不受重建影響,在新建 activity 時(shí),將 lastNonConfigurationInstances 入?yún)⒌?attach() 方法中,所以 activity 重建后,依然能獲取到之前的配置緩存 lastNonConfigurationInstances ,拿到其中的緩存 ViewModelStore

總結(jié): ActivityThread 中的 ActivityClientRecord 是不受 activity 重建的影響,那么ActivityClientRecord中l(wèi)astNonConfigurationInstances也不受影響,那么其中的Object activity也不受影響,那么ComponentActivity中的NonConfigurationInstances的viewModelStore不受影響,那么viewModel也就不受影響了。

問題: 修改系統(tǒng)語言會(huì)發(fā)生什么?

進(jìn)程會(huì)被殺死**

問題: 為什么說旋轉(zhuǎn)屏幕ViewModel 不會(huì)丟數(shù)據(jù)。因?yàn)殡m然走了onDestroy 但是內(nèi)部判斷了是否旋轉(zhuǎn)屏幕

在Android中,當(dāng)設(shè)備的屏幕方向(橫屏與豎屏)發(fā)生變化時(shí),當(dāng)前的Activity會(huì)經(jīng)歷一系列的生命周期回調(diào)。這主要是因?yàn)槠聊恍D(zhuǎn)通常會(huì)導(dǎo)致當(dāng)前的Activity被銷毀并重新創(chuàng)建,以適應(yīng)新的屏幕配置

  • 為啥旋轉(zhuǎn)還能保存數(shù)據(jù)?
判斷了是配置變化,如旋轉(zhuǎn)屏幕等,等到真正銷毀才清空

答案:這里我們又學(xué)到了Activity的兩個(gè)跟生命周期相關(guān)的函數(shù)調(diào)用:onRetainNonConfigurationInstance和getLastNonConfigurationInstance。

  1. Activity實(shí)現(xiàn)了ViewModelStoreOwner接口,創(chuàng)建了ViewModelStore對(duì)象。
  2. 當(dāng)Activity意外銷毀時(shí),onRetainNonConfigurationInstance函數(shù)被回調(diào),在此函數(shù)中對(duì)ViewModelStore對(duì)象進(jìn)行了保存。
  3. 當(dāng)Activity重建時(shí),onCreate方法中會(huì)先獲取getLastNonConfigurationInstance,如果其中的ViewModelStore對(duì)象不為空,就直接引用,不再重新創(chuàng)****建ViewModelStore對(duì)象了。

橫豎平:能夠保存數(shù)據(jù)的原理:ViewModelStore,里面用的hashmap


viewmodel原理.png

=============================================================

問題: 旋轉(zhuǎn)屏幕會(huì)執(zhí)行ondestory方法嗎?會(huì)執(zhí)行

根本原因:AMS有關(guān),因?yàn)樵趫?zhí)行onDestroy時(shí),ActivityClientRecord的lastNonConfigurationInstances從哪里來,答案是在onDestroy時(shí),會(huì)保存Activity的retainNonConfigurationInstances()方法返回值。

從ActivityClientRecord持有一條到ViewModelStore引用鏈****,所以當(dāng)Activity被銷毀時(shí),ViewModelStore不會(huì)被銷毀,

而Activity的reLaunch并不會(huì)銷毀對(duì)應(yīng)的ActivityClientRecord,下次仍然會(huì)復(fù)用ActivityClientRecord,進(jìn)而復(fù)用保存的ViewModelStore,這樣就解釋通了;

Activity是不同的實(shí)例。但是獲取到的ViewModelStore是同一個(gè)。


答案總結(jié):1).進(jìn)來創(chuàng)建了ViewModel,如果ViewModelStore還在,用同一個(gè)

2).onstop的時(shí)候,viewModelStore保存了數(shù)據(jù),通過一個(gè)新方法

3).destory的時(shí)候,viewModelstore是保存在ActivityClientRecord里面,viewModelStore在橫豎平的時(shí)候不會(huì)銷毀,但是在正常destory的時(shí)候會(huì)清除

和上面一樣,AMS的ActivityClientRecord

setRetainInstance (true)

那我有個(gè)問題,Actiivty銷毀之后,一直用的是第一次的VIewModel。那么它們的數(shù)據(jù)豈不是用舊數(shù)據(jù).

只要在切換橫豎屏才保持,否則ondestory的時(shí)候clear。把VIewModerStore清除了。

FragmentManager類的destroy()方法里面清除

Activity在onDestroy會(huì)嘗試對(duì)ViewModelStore清空。如果是由于ConfigurationChanged帶來的Destroy則不進(jìn)行清空


當(dāng) Activity 處于前臺(tái)的時(shí)候被銷毀了,那么得到的 ViewModel 是之前實(shí)例過的 ViewModel;

如果 Activity 處于后臺(tái)時(shí)被銷毀了,那么得到的 ViewModel 不是同一個(gè)。舉例說,

如果 Activity 因?yàn)榕渲冒l(fā)生變化而被重建了,那么當(dāng)重建的時(shí)候,ViewModel 是之前的實(shí)例;如果因?yàn)殚L期處于后臺(tái)而被銷毀了,那么重建的時(shí)候,ViewModel 就不是之前的實(shí)例了。

viewmodel整體存在.jpg

問題: 怎么自動(dòng)管理的?

  • ComponentActivity 監(jiān)聽onDestroy() ,清理
  • FragmentFragmentActivityonDestroy() 會(huì)清理。

Fragment能拿 ActivityViewModel么?

  • 能 ,畢竟FragmentManger 那向上管理,其實(shí)取的就是上層FragmentActivityViewModelStore

問題: ViewModelStore是個(gè)啥?

  • ViewModelStore 知道么?

    • 知道 ,一個(gè)map 就是干。
  • 知道怎么創(chuàng)建的么?

    • 內(nèi)部 factory 反射就是干。

8. ViewModel對(duì)比onSaveInstanceState()

對(duì)比.jpg

9.ViewModel 的內(nèi)存泄漏問題

ViewModel 的內(nèi)存泄漏是指 Activity 已經(jīng)銷毀,但是 ViewModel 卻被其他組件引用。這往往是因?yàn)閿?shù)據(jù)層是通過回調(diào)監(jiān)聽器的方式返回?cái)?shù)據(jù),并且數(shù)據(jù)層是單例對(duì)象或者屬于全局生命周期,所以導(dǎo)致 Activity 銷毀了,但是數(shù)據(jù)層依然間接持有 ViewModel 的引用。

如果 ViewModel 是輕量級(jí)的或者可以保證數(shù)據(jù)層操作快速完成,這個(gè)泄漏影響不大可以忽略。但如果數(shù)據(jù)層操作并不能快速完成,或者 ViewModel 存儲(chǔ)了重量級(jí)數(shù)據(jù),就有必要采取措施。例如:

  • 方法 1: 在 ViewModel#onCleared() 中通知數(shù)據(jù)層丟棄對(duì) ViewModel 回調(diào)監(jiān)聽器的引用;
  • 方法 2: 在數(shù)據(jù)層使用對(duì) ViewModel 回調(diào)監(jiān)聽器的弱引用(這要求 ViewModel 必須持有回調(diào)監(jiān)聽器的強(qiáng)引用,而不能使用匿名內(nèi)部類,這會(huì)帶來編碼復(fù)雜性);
  • 方法 3: 使用 EventBus 代替回調(diào)監(jiān)聽器(這會(huì)帶來編碼復(fù)雜性);
  • 方法 4: 使用 LiveData 的 Transformations.switchMap() API 包裝數(shù)據(jù)層的請(qǐng)求方法,這相當(dāng)于在 ViewModel 和數(shù)據(jù)層中間使用 LiveData 進(jìn)行通信

第二種泄露的情況:

 使用ViewModel的時(shí)候,需要注意的是ViewModel不能夠持有View、Lifecycle、Acitivity引用,而且不能夠包含任何包含前面內(nèi)容的類。因?yàn)檫@樣很有可能會(huì)造成內(nèi)存泄漏。

這張圖也解釋了為什么ViewModel中不能持有Activity、Fragment、view的引用。因?yàn)锳ctivity在重建后是一個(gè)新的對(duì)象,如果ViewModel中持有舊對(duì)象的引用,這個(gè)舊對(duì)象可能就等不到釋放,造成泄漏。

如果確實(shí)需要,應(yīng)該使用applicationcontext,或者使用含有上下文的AndroidViewModel。

產(chǎn)生的原因:數(shù)據(jù)永久保存,shareperfece用的話要context.所以產(chǎn)生了AndroidViewModel

那如果需要使用Context對(duì)象改怎么辦。這時(shí)候我們可以給ViewModel一個(gè)Application。Application是一個(gè)Context,而且一個(gè)應(yīng)用也只會(huì)有Application。

我們自己添加Application?其實(shí)沒必要Google還有一個(gè)AndroidViewModel。這是一個(gè)包含Application的ViewModel。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容