數據共享與持久化——ViewModel 的使用與原理

介紹

ViewModel屬于ACC框架組件之一,用以解決數據持久與共享問題,此外,也將數據的相關行為從UI中分離出來。

前言

對于ViewModel的使用以及原理,可能需要對Lifecycle和LiveData有一些理解,不然可能會影響對某些內容的理解。以下為可參考資料。

正文

案例

public class MyData extends LiveData<String> {

    private static final String TAG = "T-MyData"; 

    public  MyData(){
        setValue("hi");
        Log.d(TAG, "create new liveData ");
    }

    @Override
    protected void onActive() {
        super.onActive();
        Log.d(TAG, "onActive ");
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        Log.d(TAG, "onInactive ");
    }

    public void changeValue(String value){
        setValue(value);
    }

}
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "T-MainActivity";

    private TabLayout nav;
    private Fragment nowFragment;

    private Fragment[] fs = new Fragment[]{
            new AFragment(),
            new BFragment()};

    private MViewModel mViewModel;
    MyData data;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "activity onCreate ");

        nav = findViewById(R.id.nav);
        nav.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                if (tab.getText().equals("A")){
                    nowFragment = fs[0];
                }else {
                    nowFragment = fs[1];
                }
                getSupportFragmentManager().beginTransaction().replace(R.id.container, nowFragment).commit();
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
        nav.addTab(nav.newTab().setText("A"));
        nav.addTab(nav.newTab().setText("B"));

        mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
        findViewById(R.id.attack).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyData data = mViewModel.getLiveData();
                data.changeValue(data.getValue() + "~");
            }
        });
    }
}
public class AFragment extends Fragment {

    View mainView;
    private TextView text;

    private MViewModel mViewModel;

    public AFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        mainView = inflater.inflate(R.layout.fragment_a, container, false);
        text = mainView.findViewById(R.id.A_text);

        mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
        mViewModel.getLiveData().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                text.setText("A--" + s);
            }
        });

        return mainView;
    }

}
public class BFragment extends Fragment {

    private View mainView;
    private TextView text;

    private MViewModel mViewModel;

    public BFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        mainView = inflater.inflate(R.layout.fragment_b, container, false);
        text = mainView.findViewById(R.id.B_text);

        mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
        mViewModel.getLiveData().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                text.setText("B--" + s);
            }
        });
        return mainView;
    }
}
public class MViewModel extends AndroidViewModel {

    private MyData data;

    public MViewModel(Application application) {
        super(application);
        data = new MyData();
    }

    public MyData getLiveData(){
        return data;
    }
}

頁面如下圖


ViewModel.jpg

描述:LiveData持有String數據初始為hi,A和Bfragment分別從ViewModel中獲取LiveData并監聽其中數據,在Activity上有一按鈕,每次點擊更新String數據為其本身加上"~"。
行為:單機幾次按鈕,來回切換A和B按鈕,可以看到數據在Fragment間都是最新的(圖不貼,懶),翻轉屏幕,再次觀察,日志如下圖


viewmodel日志.jpg

從日志圖中可得到的信息如下:

  • Activity與Fragment被重建
  • LiveData對Fragment的綁定關系被重建
  • ViewModel沒有被重建,持有原來的數據對象
    從運行情況可以得知:
  • 數據保持共享

ViewModel是如何做到數據持久化以及數據共享的?以下為講解

提醒

由于版本問題,ViewModel對較低版本的SDK做了兼容,因此在實現原理上分有兩種做法。在此提前點明情況,方便以下的敘述順序流暢。

原理一(此SKD為27)

入口

mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        // 獲取當前程序所依托的Application
        Application application = checkApplication(activity);
        if (factory == null) {
            // 獲取AndroidViewModelFactory,單例
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        // ViewModelStores.of()返回了ViewModelStore
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }

這里只要注意,of()返回了ViewModelProvider,其持有ViewModelStore信息和AndroidViewModelFactory。 而ViewModelStore其實規劃了自身將被如何存儲

當前位置
ViewModelProviders.of()
- ViewModelProvider()
-- ViewModelStores.of()

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        // 當前SDK下,運行到這里就返回了,證據在下一張代碼引用圖
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }
public class FragmentActivity extends BaseFragmentActivityApi16 implements
        ViewModelStoreOwner,

既然FragmentActivity實現了ViewModelStoreOwner,那么對應的獲取方式如下

當前位置
FragmentActivity. getViewModelStore()

    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) {
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }

可見,FragmentActivity自身是持有ViewModelStore

以上構造出了ViewModelProvider,緊接著去獲取具體的ViewModel

當前位置ViewModelProvider
    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);
    }

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 從ViewModelStore中獲取viewModel
        ViewModel viewModel = mViewModelStore.get(key);
        
        // 獲取到,返回
        if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
        } else {
            if (viewModel != null) {
            }
        }
        // 創建viewModel,factory為AndroidViewModelFactory
        viewModel = mFactory.create(modelClass);
        // 將ViewModel與類名綁定,并保存
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

以上代碼就獲取到了具體的ViewModel,其中AndroidViewModelFactory.create()代碼僅僅是通過反射創建了ViewModel實例并捕捉了異常。先做個小結:

  • ViewModelProvider持有ViewModelStore信息
  • VIewModelStore規劃自身的提取方式并持有ViewModel信息
  • 通過類作為key獲取具體的ViewModel實現共享

我們知道,在屏幕旋轉時,如果沒有對Activity做相應的配置更變設置,Activity是會被重建的,而Activity被銷毀時,相應持有的數據理應被釋放。那ViewModel是如何逃過一劫的?

思考

在源碼看到這的時候,實際上正面線索已無法跟蹤,因為在龐大的Activity架構之中,很難快速地找到事件發源地。那現在,如何找到ViewModel持久的線索呢?在當前條件下,FragmentActivty是直接持有了ViewModelStore的信息,那么在配置更變需要重建時,必定要對ViewModelStore做一些處理,因此選擇了跟蹤FragmentActity.mViewModelStore。

果然,定位到了以下位置

FragmentActivity

    public final Object onRetainNonConfigurationInstance() {
       .......
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

在配置更改需要重建頁面的時候,系統會去保存現場以便恢復,在這個函數中,ViewModelStore被作為狀態之一被保存在NonConfigurationInstances之中。依次類推,有保存就有取出,繼續跟蹤,定位到了以下位置

FragmentActivity

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        .....
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
      .....
  }

可見,在生命周期onCreate,備份的ViewModelStore被取出。因此,當重建后,再次通過ViewModelProvider.get()去獲取ViewModel時候,會直接獲取到此ViewModelStore并取出ViewModel,不會再通過AndroidVIewModelFactory重建ViewModel。

這里小結一下:

  • 在配置更變需要重建頁面時,ViewModelStore會在重建前交由NonConfigurationInstances保管,并在重建后取出恢復。

以上,就是ViewModel在高SDK下的數據共享與持久化的原理。

接下來,是適配低版本的。

原理二 (實例SDK為25)

之前說過,ViewModelStore規劃了自身將被如何存儲,而且差異也在于低版本的SDK的Activity的各父類并不是ViewModelStoreOwner,回看代碼ViewModelStore.of()

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        // 當前SDK下,運行到此返回
        return holderFragmentFor(activity).getViewModelStore();
    }

以上代碼從HolderFragment取得ViewModelStore

當前位置
HolderFragment

    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

當前位置
HolderFragement.HolderFragmentManager

        HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            // 查找合適的HolderFragment
            HolderFragment holder = findHolderFragment(fm);
            // 查找到返回
            if (holder != null) {
                return holder;
            }
            // 通過key(activity)取出HolderFragment
            holder = mNotCommittedActivityHolders.get(activity);
           // 取到返回
            if (holder != null) {
                return holder;
            }
            
            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            // 注入HolderFragment
            holder = createHolderFragment(fm);
            // 綁定HolderFragment和key
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
        }

以上代碼代碼可知,HolderFragment靜態對象HolderFragmentManager,持有HolderFragment對象與key的對應管理。這里因為HolderFragment被注入,需要看一下初始工作。

當前位置HolderFragment
    private ViewModelStore mViewModelStore = new ViewModelStore();

    public HolderFragment() {
        setRetainInstance(true);
    }

新的HolderFragment新建時自身持有了ViewMolderStore,之前通過ViewModelStores.of()獲取的,就是這個ViewMolderStore。

能區別出,在原理一種,具有生命周期的對象,本身會持有ViewModelStore,而在原理二中,會通過注入HolderFrament,去間接持有ViewModelStore。其他流程是一致的。

現在,就還剩一個問題,簡介持有的ViewModelStore,如何保持持久化?

注意到,在初始化HolderFragment是,設置了mRetainInstance,如下

    /**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated
     */
    public void setRetainInstance(boolean retain) {
        mRetainInstance = retain;
    }

注釋大意為:在Activity 銷毀-重建時控制是否是有fragment實例。僅在fragment不在back stack時生效。當mRetainInstance設置為trues時,生命周期表現行為與重建時有輕微不同。

簡單來說,HolderFragment并沒有被銷毀,而當再次通過key去取出對應的HolderFragment時,就能取出。

至于HolderFragment為什么沒有被銷毀,那就需要了解FragmentManager如何去管理Fragment了,這就扯遠了。

總結

通過以上的梳理分析,算是講明了ViewModel的數據如何共享以及持久化,一下為要點:

  • 通過ViewModelProvider持有ViewModelStore和Factory,并主要用來獲取對應的ViewModelStore
  • ViewModelStore存儲了key-value形勢的類與ViewModel的對應,實現了數據的共享,Factory負責在需要時創建出ViewModel
  • ViewModelStore被Activity或Fragment持有, 或通過注入HolderFragment間接持有
  • Activity因配置原因銷毀-重建時,ViewModelStore被NonConfigurationInstances保存或被HolderFragment保存,再此需求時從從保存處恢復。

簡單原理圖


ViewModel原理-2.png

細節

當前位置
HolderFragment

    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }


當前位置
HolderFragment.HolderFragmentManager

        void holderFragmentCreated(Fragment holderFragment) {
            // 獲取作為依托的父fragment
            Fragment parentFragment = holderFragment.getParentFragment();
            if (parentFragment != null) {
                // 釋放父fragment
                mNotCommittedFragmentHolders.remove(parentFragment);
                parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
                        mParentDestroyedCallback);
            } else {
                //釋放Activity
                mNotCommittedActivityHolders.remove(holderFragment.getActivity());
            }
        }

在Activity銷毀-重建狀態下,雖然ViewModelStore跟隨HolderFragment被保存了,但是此時的與HolderFragment綁定的Activity或Fragment已不再是當時候的對象,因此,會存在內存泄漏問題。因此,在HolderFragment生命周期onCreate()里解決這一問題。

注意到,HolderFragment與Activity或Fragment間的對應關系鏈已不存在,那么再去獲取對應的HolderFragment是,會通過holderFragmentFor() ->findHolderFragment() 找到,如下圖


viewModel獲取Holder.jpg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容