Android Architecture Componets - ViewModel

ViewModel

/**
ViewModel是一個負責準備和管理Activity或Fragment數據的類。
它還處理Activity / Fragment與其他應用程序的通信 (例如調用業務邏輯類)
ViewModel會在一個關聯的范圍內(activity 或者 fragment)創建,只要activity或fragment是活著的,它將被保留.
換句話說,這意味著如果它的所有者由于配置改變被銷毀了(例如旋轉), ViewModel不會被銷毀,所有者的新實例將重新連接到現有的ViewModel。
ViewModel的目的是獲取并保存Activity或Fragment所需的信息. Activity或Fragment應該能夠觀察ViewModel中的變化。ViewModel通常通過LiveData或Android DataBinding公開這些信息。 你也可以使用你喜歡的框架的任何可觀察性構造。
ViewModel唯一的職責就是管理用戶界面的數據, 它不應該訪問您的視圖層次結構或持有引用回到Activity或Fragment.

典型的用法:
 * public class UserActivity extends Activity {
 *
 *     {@literal @}Override
 *     protected void onCreate(Bundle savedInstanceState) {
 *         super.onCreate(savedInstanceState);
 *         setContentView(R.layout.user_activity_layout);
 *         final UserModel viewModel = ViewModelProviders.of(this).get(UserModel.class);
 *         viewModel.userLiveData.observer(this, new Observer<User>() {
 *            {@literal @}Override
 *             public void onChanged(@Nullable User data) {
 *                 // update ui.
 *             }
 *         });
 *         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
 *             {@literal @}Override
 *             public void onClick(View v) {
 *                  viewModel.doAction();
 *             }
 *         });
 *     }
 * }
 
 ViewModel:
  * public class UserModel extends ViewModel {
 *     public final LiveData&lt;User&gt; userLiveData = new LiveData<>();
 *
 *     public UserModel() {
 *         // trigger user load.
 *     }
 *
 *     void doAction() {
 *         // depending on the action, do necessary business logic calls and update the
 *         // userLiveData.
 *     }
 * }
ViewModels也可以用作Activity中的不同Fragment之間的通信層。 每個Fragment可以通過他們的Activity使用相同的密鑰來獲取ViewModel。這允許Fragment之間以分離的方式進行通信,使得它們不需要直接與另一個Fragment進行通信。
 * public class MyFragment extends Fragment {
 *     public void onStart() {
 *         UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);
 *     }
 * }
*/
public abstract class ViewModel {
    /**
     * 這個ViewModel被銷毀時調用
     * <p>
     * It is useful when ViewModel observes some data and you need to clear this subscription to
     * prevent a leak of this ViewModel.
     */
    @SuppressWarnings("WeakerAccess")
    protected void onCleared() {
    }
}
ViewModelProviders.of(this).get(MyViewModel::class.java)
// ViewModelProviders
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    // 確保單例對象 sDefaultFactory 實例存在
    initializeFactoryIfNeeded(checkApplication(activity));
    return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
// ViewModelStores
@MainThread
public static ViewModelStore of(@NonNull FragmentActivity activity) {
    // 創建HolderFragment 并且添加到Activity當中, 返回ViewModelStore
    return HolderFragment.holderFragmentFor(activity).getViewModelStore();
}
// HolderFragment

// getViewModelStore() 返回該實例
private ViewModelStore mViewModelStore = new ViewModelStore();

private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static HolderFragment holderFragmentFor(FragmentActivity activity) {
    return sHolderFragmentManager.holderFragmentFor(activity);
}

// HolderFragmentManager
HolderFragment holderFragmentFor(FragmentActivity activity) {
    FragmentManager fm = activity.getSupportFragmentManager();
    // 根據 HOLDER_TAG 尋找 HolderFragment
    HolderFragment holder = findHolderFragment(fm);
    if (holder != null) {
        return holder;
    }
    // mNotCommittedActivityHolders 保存已經執行了add操作, 但是沒有添加進去的 Fragment
    holder = mNotCommittedActivityHolders.get(activity);
    if (holder != null) {
        return holder;
    }

    if (!mActivityCallbacksIsAdded) {
        mActivityCallbacksIsAdded = true;
        // 在activity的 onDestroy()方法中 mNotCommittedActivityHolders釋放引用
        activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
    }
    // 創建 HolderFragment 添加到當前 Activity 當中
    holder = createHolderFragment(fm);
    // 上面方法執行了 fragment 的 add以及commit 操作, 到HolderFragment 真正添加到activity之前記錄
    mNotCommittedActivityHolders.put(activity, holder);
    return holder;
}
// HolderFragment
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    sHolderFragmentManager.holderFragmentCreated(this);
}
// HolderFragmentManager
void holderFragmentCreated(Fragment holderFragment) {
    // 如果是在Fragment中嵌套Fragment, 該返回不為空, 這里只討論Activity的情況
    Fragment parentFragment = holderFragment.getParentFragment();
    if (parentFragment != null) {
        mNotCommittedFragmentHolders.remove(parentFragment);
        parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
                mParentDestroyedCallback);
    } else {
        // 執行 onCreate() 說明HolderFragment以及添加到Activity, 可以釋放引用了
        mNotCommittedActivityHolders.remove(holderFragment.getActivity());
    }
}
@NonNull
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);
}
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    // 根據反射創建實例
    viewModel = mFactory.create(modelClass);
    // 存入 viewModel實例
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

到這里會發現只是將一個毫不起眼的HolderFragment添加到Activity,并且利用HolderFragment中的ViewModelStore中的Map保存了 自定義ViewModel實例.

HolderFragment是如何在在Activity配置變更被銷毀時繼續持有viewModel的?

// HolderFragment
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 這行代碼上面已經分析過了
    sHolderFragmentManager.holderFragmentCreated(this);
}
@Override
public void onDestroy() {
    super.onDestroy();
    // 調用ViewModel的
    mViewModelStore.clear();
}

// ViewModelStore
public final void clear() {
    for (ViewModel vm : mMap.values()) {
        // 空方法, 繼承給開發者用, 做釋放等操作
        vm.onCleared();
    }
    // 清空map, 也就是 釋放ViewModel數據
    mMap.clear();
}

上面也并沒有什么特殊的操作, 并且在onDestroy() 中釋放了 viewModel, 那么在 Activity例如旋轉的時候會調用onDestroy(), 跟著 Fragment的 onDestroyView(), onDestroy(), onDetach() 會依次觸發. HolderFragment為什么不會調用 onDestroy()?

關鍵來了!!

// 關鍵就在 HolderFragment的構造方法中
public HolderFragment() {
    setRetainInstance(true);
}
// Fragment
/**
 控制是否在重新創建Activity(例如配置更改)中保留Fragment實例。這只能用于不在back stack中的Fragment。 如果設置,則在 recreate Activity時,Fragment生命周期將略有不同:
 onDestroy() 將不會被調用 (但onDetach() 仍然會,因為片段正在從它當前的活動分離). onCreate(Bundle)將不會被調用,因為片段不被重新創建。onAttach(Activity) 和 onActivityCreated(Bundle)仍然被調用。
 */
public void setRetainInstance(boolean retain) {
    mRetainInstance = retain;
}

所以在ViewModel類的注釋中有一段

ViewModel唯一的職責就是管理用戶界面的數據, 它不應該訪問您的視圖層次結構或持有引用回到Activity或Fragment.

一旦ViewModel持有了Activity, 并且在旋轉時, 新的Activity需要創建, 而舊的Activity需要被釋放. 但是被ViewModel持有而釋放不了就有可能造成內存泄漏.

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

推薦閱讀更多精彩內容