Jetpack之ViewMode初识,使用和源码讲解

今天是2022年最后一天,祝大家元旦快乐。


image.png

ViewModel初识

ViewModelJetpack组件之一,它注重生命周期的方式存储和管理界面的数据,它是让数据在屏幕旋转等配置更改后继续留存。通俗一点就是:手机屏幕发生旋转后,数据依然还在。

ViewModel没出来之前,怎么存取数据?

ViewModel出现之前,一般屏幕发生旋转时候,Activity生命周期会重新创建,我们会在onSaveInstance()里面保存数据,然后在onCreate(Bundle saveInstance)里面取数据,但是这个数据有弊端,必须要实现序列化和反序列化,并且这个数据不能太大。

class ViewModelActivity : AppCompatActivity() {

     override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
               //(2)取数据
     }

     override fun onSaveInstanceState(outState: Bundle) {
          super.onSaveInstanceState(outState)
               //(1)存数据
     }
}

所以ViewModel就是为了解决这个问题的。

ViewModel的使用
(1)添加ViewModel依赖

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

(2)定义一个类继承ViewModel(AndroidViewModel)即可

class MyViewModel : ViewModel() {
      val updateLiveData by lazy { MutableLiveData<String>() }
}

(3)Activity创建ViewModel实例

class ViewModelActivity : AppCompatActivity() {

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)

          var myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
          myViewModel.updateLiveData.observe(this) {
           
             //update  data
          }
     }
}

Activity中,我们可以使用ViewModelProvider来得到 ViewMode的实例。

如果在ViewModel中,我们需要使用到上下文Context对象(toast 或者获取系统服务等等),我们可以继承AndroidViewModel来构建ViewModel

class AndroidModel(app:Application): AndroidViewModel(app) {

}

此时这个AndroidModel的创建和上面的类似,也是用ViewModelProvider来获得。

ViewModel常常结合LiveData使用,然后在我们的Activity或者Fragemnt中去监听LiveData的改变

然后去在Activity中做UI的更新逻辑,例如,我们需要根据网络请求来决定是否弹出一个DialogFragment,我们的网络请求放在ViewModel中,DialogFragment必须在Activity或者Fragment中弹出来(因为DialogFragment的弹出不能使用Application作为Context),所以此时我们必须使用LiveData。当网络请求完成之后,我们改变LiveData的值,并且在Activity或者Fragment监听LiveData的变化,然后作出弹出DailogFragment的操作

Fragment之间共享数据

现在的App中使用Fragment是很常见的,之前我们从Activity中向Fragment中传递数据,我们使用Bundle来传递(在创建Fragment的时候),但是在Activity中如果需要动态传递(随时传递)数据给Fragment,我们平常的做法可能是在Activity中持有Fragment的应用,然后在Activity中去调用对于Fragment的某些方法传递数据,或者利用通知系统(EventBus),但是我们如果在BFragment中促使Activity中的数据改变,要通知到CFragment的Ui修改的话,目前的场景只能使用EventBus。当然也可以使用我们这里的ViewModel

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

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

class ListFragment : Fragment() {

    private lateinit var itemSelector: Selector

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model = ViewModelProvider(requireActivity()).get(ShareViewModel::class.java)
        itemSelector.setOnClickListener { item ->
            // Update the UI
     }
}

class DetailFragment : Fragment() {

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

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

在上面的两个Fragment 在获取ViewModel的时候 传递的都是requireActivity(),那么获取到的ViewModel的实例其实就是同一个SharedViewModel,所以当Activity 或者任何一个Fragment中 改变了SharedViewModel中LiveData的数据,都会及时的通知到。这种方法有以下好处:

Activity 不需要执行任何操作,也不需要对此通信有任何了解。
除了 SharedViewModel之外,Fragment不需要相互了解。如果其中一个 Fragment消失,另一个 Fragment将继续照常工作。
每个 Fragment都有自己的生命周期,而不受另一个 Fragment的生命周期的影响。如果一个Fragment替换另一个Fragment,界面将继续工作而没有任何问题。

ViewModel的源码解析

image.png

这里基本只需要知道onCleared()方法就行了,自定义ViewModel并重写这个方法,讲释放资源的逻辑放在这个方法中就行

ViewModelProvider

获取ViewModel的实例时,我们是使用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;
}

创建ViewModelProvider的时候需要传递一个ViewModelStore 和一个Factory,而我们构建的时候只传递了一个this(Activity),其实就是一个ViewModelStoreOwner

AppCompatActivity -->FragmentActivity -->ComponentActivity --> ViewModelStoreOwner

有上面这样一个继承实现关系,我们的AppCompatActivity其实可以说是实现了ViewModelStoreOwner的,最终返回的是ComponentActivity中的mViewModelStore 关于Factory后面再讲

@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.");
    }
    ensureViewModelStore();
    return mViewModelStore;
}

@SuppressWarnings("WeakerAccess") /* synthetic access */
void ensureViewModelStore() {
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
            (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
}

这里就是获取ViewModelStore的方法,可以看到在ensureViewModelStore方法中,我们会首先判断mViewModelStore 是否为null,然后通过getLastNonConfigurationInstance()得到一个NonConfigurationInstances实例,这里其实就是当Activity旋转的时候ViewModel中的数据还会存在的奥秘,通过nc可以获取重建之前的mViewModelStore,然后从ViewModelStore里面根据类名获取ViewModel的实例,所以获取到的ViewModel在旋转前后其实是同一个实例,在我们的App系统configuration发生改变的时候 就会回调onRetainNonConfigurationInstance()这个方法

@SuppressWarnings("deprecation")
public final Object onRetainNonConfigurationInstance() {
    // Maintain backward compatibility.
    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 nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

//Activity中 的方法
@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
        ? mLastNonConfigurationInstances.activity : null;
}

这里就是将ViewModelStore进行保存。 这里getLastNonConfigurationInstance方法 最后其实是返回Activity中的mLastNonConfigurationInstances 变量的activity对象,我们看看这mLastNonConfigurationInstances 在哪里赋值,我们知道,我们的Activity的启动其实最终都会走到ActivityThread类中,当我们启动一个Activity的时候会执行其中的 performLaunchActivity方法最终会调用到 Activityattach方法,lastNonConfigurationInstances是存在ActivityClientRecord中的一个组件信息

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
    int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    Class<? extends Activity> activityClass = null;
    if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
    if (r != null) {
        activityClass = r.activity.getClass();
        r.activity.mConfigChangeFlags |= configChanges;
        if (finishing) {
            r.activity.mFinished = true;
        }

        performPauseActivityIfNeeded(r, "destroy");

        if (!r.stopped) {
            callActivityOnStop(r, false /* saveState */, "destroy");
        }
        if (getNonConfigInstance) {
            try {
                r.lastNonConfigurationInstances
                    = r.activity.retainNonConfigurationInstances();
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to retain activity "
                        + r.intent.getComponent().toShortString()
                        + ": " + e.toString(), e);
                }
            }
        }
        try {
            r.activity.mCalled = false;
            mInstrumentation.callActivityOnDestroy(r.activity);
            if (!r.activity.mCalled) {
                throw new SuperNotCalledException(
                "Activity " + safeToComponentShortString(r.intent) +
                " did not call through to super.onDestroy()");
            }
            if (r.window != null) {
                r.window.closeAllPanels();
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException(
                    "Unable to destroy activity " + safeToComponentShortString(r.intent)
                    + ": " + e.toString(), e);
            }
        }
        r.setState(ON_DESTROY);
    }
    schedulePurgeIdler();
    // updatePendingActivityConfiguration() reads from mActivities to update
    // ActivityClientRecord which runs in a different thread. Protect modifications to
    // mActivities to avoid race.
    synchronized (mResourcesManager) {
        mActivities.remove(token);
    }
    StrictMode.decrementExpectedActivityCount(activityClass);
    return r;
}

在屏幕旋转造成的的Activity重建的时候 就会给lastNonConfigurationInstances这个变量赋值,这样就能够在Activity重建的时候 获取到之前的ViewModel了。而我们的ViewModelonClear方法什么时候执行呢,在我们的ComponetActivity构方法中

public ComponentActivity() {
    // ......省略部分代码
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                // Clear out the available context
                mContextAwareHelper.clearAvailableContext();
                // And clear the ViewModelStore
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
}

上面注册了一个Lifecycle的监听,在我们的ActivityonDestory之后 并且isChangingConfigurations()为false的时候,才会去执行getViewModelStore().clear(); 的操作间接调用到ViewModel的onCleared()方法

get()获取ViewModel
在这里通过get方法创建ViewModel传入的参数是 所需要创建的ViewModelClass对象

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);
}


@SuppressWarnings("unchecked")
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    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.
        }
    }
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
    } else {
        viewModel = mFactory.create(modelClass);
    }
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

最终通过下面的get方法获取ViewModel,当我们从ViewModelStore 根据key值去获取ViewModel为null的时候,如果为null 就是用Factory进行创建。所以后面我们也可以定义自己的Factory 来创建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();
    }
}

这里其实就是一个 HashMap存放了ViewModel,主要是ViewModel的存取.

总结

1.现在的Activity底层已经继承了 ComponentActivity ,并实现了 ViewModelStoreOwner 接口,通过ViewModelProvider使用默认工厂 创建了 viewModel,并通过唯一Key值 进行标识,
存储到了 ViewModelStore中。等下次需要的时候即可通过唯一Key值进行获取。
2.由于ComponentActivity 实现了ViewModelStoreOwner接口,实现了 getViewModelStore方法,当屏幕旋转的时候,会先调用 onRetainNonConfigurationInstance()方法
viewModelStore保存起来,当屏幕旋转之后,会在ensureViewModel()方法中再调用 getLastNonConfigurationInstance方法将数据恢复,如果为空的话,会重新创建viewModelStore
并存储在全局中,以便以下次发生变化的时候,能够通过onRetainNonConfigurationInstance 保存起来。
3.最后当页面销毁并且没有配置更改的时候,会将viewModelStore中的数据 进行清除操作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容