简介

ViewModel在架构中用于承载业务逻辑和作为容器保存屏幕状态,它可以缓存界面的状态,并且能在配置变更后持久保留相应的界面状态。

在jetpack套件中,ViewModel随lifecycle一起提供。

优势

可以持久的保持界面状态:一是界面因配置变更导致的重建,不会销毁内存中的;二是可以借助SavedStateHandle在进程销毁-重建过程中恢复数据。ViewModel具有作用域(如:Activity、Fragment等),ViewModel中的异步工作将被限定在这个Lifecycle上执行。ViewModel可用用来承载之前处于界面层的部分业务逻辑:将数据层传递的数据处理成界面状态。可以作为桥梁在Activity与Fragment、Fragment与Fragment之间共享数据。

使用

引入

参考lifecycle官方文档。

定义

// 直接继承ViewModel

class DemoViewModel : ViewModel() {

private val api = MyService()

// 通常配合LiveData、StateFlow这些可感知对象为界面提供状态。

private val _uiState = MutableLiveData("")

val uiState: LiveData = _uiState

// 使用SharedFlow为界面提供事件回调

private val _uiEvent = MutableSharedFlow()

val uiEvent = _uiEvent.asSharedFlow()

fun reqData(param: String) {

// ViewModel能自动处理协程scope的生命周期

viewModelScope.launch(Dispatchers.IO) {

_uiEvent.emit(DemoEvent.Loading)

try {

val data = api.reqData(param)

_uiState.postValue("rsp: $data")

} finally {

_uiEvent.emit(DemoEvent.Completed)

}

}

}

}

基本用法

在androidx的Activity中使用:

class ViewModelDemoActivity : AppCompatActivity() {

private lateinit var viewModel: DemoViewModel

// viewmodel的ktx扩展库中提供了委托方式获取viewmodel实例

// private val viewModel: DemoViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_view_model_demo)

// ViewModelProvider是获取viewmodel的基础工具,它需要一个ViewModelStoreOwner实例

// 这个实例就是用来存储和管理viewmodel的,androidx的ComponentActivity

// 实现了这个接口,因此可以直接使用AppCompatActivity来初始化ViewModelProvider。

viewModel = ViewModelProvider(this).get(DemoViewModel::class.java)

// 监听界面状态以及事件,并做出响应

viewModel.uiState.observe(this) {

Log.d(TAG, "received response: $it")

}

viewModel.uiEvent

.onEach { showLoading(it == DemoEvent.Loading) }

.launchIn(lifecycleScope)

viewModel.reqData(param)

}

}

在androidx的Fragment中使用:

class DemoFragment : Fragment() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

// jetpack fragment也实现了ViewModelStoreOwner接口,因此也可以用于获取和管理viewmodel

val selfViewModel = ViewModelProvider(this).get(FragmentViewModel::class.java)

}

override fun onAttach(context: Context) {

super.onAttach(context)

// 可以获取到Activity或者其它Fragment的ViewModel,只需要在构造ViewModelProvider

// 时传递了对应的ViewModelStoreOwner。这个实例和DemoActivity

// 中获取到的是同一个实例,因此你可以通过这个实例实现和Activity的通信。

val parentViewModel = ViewModelProvider(requireActivity())

.get(DemoViewModel::class.java)

}

}

SavedStateHandle

SavedStateHandle主要用于在进程销毁-重建过程中恢复数据,它可以将数据持久化到存储中,并在重建后恢复数据。

class DemoViewModel(val savedState: SavedStateHandle) : ViewModel() {

// 可以使用getLiveData将要获取的数据转为LiveData

private val _savedData = savedState.getLiveData(DATA_KEY)

val savedData: LiveData = _savedData

fun saveData(data: String) {

savedState[DATA_KEY] = data

}

fun readData(): String? {

// 也可以直接获取

return savedState[DATA_KEY]

}

companion object {

private const val DATA_KEY = "data"

}

}

AndroidViewModel

有时候ViewModel中可能会需要使用到Android Context(获取文本、颜色等资源),此时可以使用AndroidViewModel,它提供了一个getApplication()方法,可以很方便的获取上下文实例。使用方式如下:

class DemoViewModel(application: Application)

: AndroidViewModel(application) {

fun getString() = application.getString(R.string.hint_txt)

}

带参数的ViewModel

前面几个小节我们都假定了使用androidx的组件作为ViewModelStoreOwner来构造ViewModelProvider。这些androidx的组件会帮助我们自动提供ViewModel所依赖的SavedStateHandle和Application 。

然而,当我们使用自定义ViewModelStoreOwner时,或者想向ViewModel传递其它类型的参数时,就需要自定义ViewModeProvider.Factory了。

假如我们有如下ViewModel,它需要接收一个Repository作为参数:

class MyViewModel(

private val myRepository: MyRepository

) : ViewModel() { }

为了实例化MyViewModel,我们需要再定义一个Factory,然后在create方法中获取依赖对象,构造ViewModel实例:

val factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {

override fun create(

modelClass: Class,

extras: CreationExtras

): T {

val repo = MyRepository(extras[MY_URL])

return MyViewModel(repo) as T

}

}

上面的CreationExtras用于从外界向ViewModel构造过程传递参数,它在ViewModelProvider构造时与factory实例一起传递给ViewModelProvider:

val extras = MutableCreationExtras().apply {

this[MY_URL] = "https://..."

}

val viewModel = ViewModelProvider(this, factory, extras)

.get(MyViewModel::class.java)

原理分析

ViewModel的获取过程

ViewModelProvider的构造

顾名思义,ViewModelProvider就是用于提供ViewModel实例的类,它在构造时需要接受三个参数:

public open class ViewModelProvider

constructor(

private val store: ViewModelStore,

private val factory: Factory,

private val defaultCreationExtras: CreationExtras = CreationExtras.Empty,

)

ViewModelStore:用于存储ViewModel实例的类,内部持有一个HashMap保存实例,ViewModelProvider会将创建好的ViewModel实例保存到ViewModelStore中,之后再需要此类ViewModel的实例时就直接从中读取。ViewModelProvider.Factory:前文已经提到,这是用于创建ViewModel实例的工厂,ViewModelProvider当需要ViewModel的实例又在ViewModelStore中没有找到对应实例时就会调用工厂的create方法创建。CreationExtras:前文也已提到,它用于在创建ViewModel实例时从外界向构造过程传递参数,内部持有一个MutableMap,以key-value的形式存储和查找参数。

虽然ViewModelProvider需要三个参数来构造,但在实际使用中我们往往只在构造时传递了一个ViewModelStoreOwner,ViewModelStoreOwner很好理解,可以用来提供ViewModelStore,而剩下两个参数,框架则提供了一系列的默认规则。

public constructor(

owner: ViewModelStoreOwner

) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

ViewModelStore的获取

通常情况下ViewModelStore由ViewModelStoreOwner提供,ViewModelStoreOwner是一个接口,里面只声明了一个getViewModelStore函数。androidx里的ComponentActivity、Fragment、FragmentViewLifecycleOwner等都实现了这个接口,下面我们看一看ComponentActivity中是如何实现的:

public ViewModelStore getViewModelStore() {

ensureViewModelStore();

return mViewModelStore;

}

void ensureViewModelStore() {

if (mViewModelStore == null) {

NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();

// 这里会尝获取配置变更前保存的实例,这是ViewModel在配置变更后仍能保持数据的关键

if (nc != null) {

mViewModelStore = nc.viewModelStore;

}

if (mViewModelStore == null) {

mViewModelStore = new ViewModelStore();

}

}

}

默认工厂和及构造参数

defaultFactory, defaultCreationExtras用于提供默认的ViewModelProvider.Factory与CreationExtras。

internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =

if (owner is HasDefaultViewModelProviderFactory)

owner.defaultViewModelProviderFactory

else instance

internal fun defaultCreationExtras(owner: ViewModelStoreOwner): CreationExtras =

if (owner is HasDefaultViewModelProviderFactory) {

owner.defaultViewModelCreationExtras

else CreationExtras.Empty

可以看到,两个方法首先都尝试将ViewModelStoreOwner实例转为HasDefaultViewModelProviderFactory,然后从中获取对应的默认值。如果没获取到,则返回ViewModelProvider自己提供的默认值。

先来看下ViewModelProvider提供的默认值:

public open class NewInstanceFactory : Factory {

override fun create(modelClass: Class): T {

return try {

modelClass.newInstance()

} catch (...) {

...

}

}

public companion object {

private var sInstance: NewInstanceFactory? = null

public val instance: NewInstanceFactory get() {

if (sInstance == null) sInstance = NewInstanceFactory()

return sInstance!!

}

}

}

可以看到,这个工厂通过直接调用Class的newInstance方法直接创建实例,这种情况下ViewModel必需要提供无参构造函数。

接下来我们看HasDefaultViewModelProviderFactory,这也是一个接口,里面声明了getDefaultViewModelProviderFactory与getDefaultViewModelCreationExtras两个方法,分别用于获取默认的工厂实例与默认的构造参数。androidx中的ComponentActivity与Fragment也实现了这个接口,以ComponentActivity中的实现为例:

public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {

if (mDefaultFactory == null) {

mDefaultFactory = new SavedStateViewModelFactory(

getApplication(),

this,

getIntent() != null ? getIntent().getExtras() : null);

}

return mDefaultFactory;

}

public CreationExtras getDefaultViewModelCreationExtras() {

MutableCreationExtras extras = new MutableCreationExtras();

if (getApplication() != null) {

extras.set(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY, getApplication());

}

extras.set(SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY, this);

extras.set(SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY, this);

if (getIntent() != null && getIntent().getExtras() != null) {

extras.set(SavedStateHandleSupport.DEFAULT_ARGS_KEY, getIntent().getExtras());

}

return extras;

}

ComponentActivity会提供一个SavedStateViewModelFactory实例,并且会提供一个预置了一些内容的CreationExtras实例,里面有Application实例、SavedStateRegistryOwner 的实例、ViewModelStoreOwner的实例,以及Intent中extras参数bundle。

然后是SavedStateViewModelFactory,我们直接看create方法:

override fun create(modelClass: Class, extras: CreationExtras): T {

val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]

?: throw IllegalStateException("VIEW_MODEL_KEY must always be provided by ViewModelProvider")

return if (extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&

extras[VIEW_MODEL_STORE_OWNER_KEY] != null) {

val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]

val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)

val constructor: Constructor? = if (isAndroidViewModel && application != null) {

findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)

} else {

findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)

}

// doesn't need SavedStateHandle

if (constructor == null) {

return factory.create(modelClass, extras)

}

val viewModel = if (isAndroidViewModel && application != null) {

newInstance(modelClass, constructor, application, extras.createSavedStateHandle())

} else {

newInstance(modelClass, constructor, extras.createSavedStateHandle())

}

viewModel

} else {

// 这里是为了兼容旧版本

...

}

}

除开旧版本的兼容逻辑,上面的代码根据是否使用SavedStateHandle分为两类:当不使用SavedStateHandle时,将ViewModel的构造请求发送给内部的AndroidViewModelFactory实例来处理;当使用SavedStateHandle时,则自己调用createSavedStateHandle方法创建SavedStateHandle实例,然后创建对应的ViewModel实例。关于SavedStateHandle的分析见后文。

获取ViewModel

回到开始,我们通过调用ViewModelProvider实例的get方法来获取ViewModel实例:

public open operator fun get(modelClass: Class): T {

val canonicalName = modelClass.canonicalName

?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")

return get("${ViewModelProvider.AndroidViewModelFactory.DEFAULT_KEY}:$canonicalName", modelClass)

}

public open operator fun get(key: String, modelClass: Class): T {

val viewModel = store[key]

if (modelClass.isInstance(viewModel)) {

(factory as? ViewModelProvider.OnRequeryFactory)?.onRequery(viewModel)

return viewModel as T

} else {

if (viewModel != null) { }

}

val extras = MutableCreationExtras(defaultCreationExtras)

extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY] = key

return try {

factory.create(modelClass, extras)

} catch (e: AbstractMethodError) {

factory.create(modelClass)

}.also { store.put(key, it) }

}

获取实例需要两个参数:key和要获取的ViewModel所属类的Class对象,ViewModelProvider会从ViewModelStore中根据key查找是否有现成的实例,有就直接使用,没有就调用Factory的create创建一个。

生命周期管理的实现

ViewModel的作用域会被限定为实例化时使用的ViewModelStoreOwner,ViewModelStoreOwner结束生命周期时,ViewModel就会自动回调onCleared方法用于清理依赖生命周期的工作或者对象。

class MyViewModel(

private val coroutineScope: CoroutineScope =

CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)

) : ViewModel() {

override fun onCleared() {

coroutineScope.cancel()

}

}

在2.5及更高版本的lifecycle库中,ViewModel提供了更多的支持:

ViewModel可以接受多个Closeable对象,ViewModel会在清除时自动调用这些对象的close方法。ViewModel提供了addCloseable、setTagIfAbsent等方法,这些方法允许在任意时刻添加Closeable对象到ViewModel中,这些对象同样会被自动清除。

下面还是以ComponentActivity为例看一下清理过程的实现:

public ComponentActivity() {

Lifecycle lifecycle = getLifecycle ();

...

getLifecycle().addObserver(new LifecycleEventObserver () {

@Override

public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {

if (event == Lifecycle.Event.ON_DESTROY) {

// Clear out the available context

mContextAwareHelper.clearAvailableContext();

// And clear the ViewModelStore

if (!isChangingConfigurations()) {

getViewModelStore().clear();

}

}

}

});

}

ComponentActivity会在构造时设置一个lifecycle监听,当activity onDestroy且并非配置改变引起的调用时,执行ViewModelStore的clear方法清空所有的ViewModel,在清空前,会调用每个ViewModel的clear方法。

// ViewModelStore

public final void clear() {

for (ViewModel vm : mMap.values()) {

vm.clear();

}

mMap.clear();

}

// ViewModel

final void clear() {

mCleared = true;

if (mBagOfTags != null) {

synchronized (mBagOfTags) {

for (Object value : mBagOfTags.values()) {

closeWithRuntimeException(value);

}

}

}

// We need the same null check here

if (mCloseables != null) {

synchronized (mCloseables) {

for (Closeable closeable : mCloseables) {

closeWithRuntimeException(closeable);

}

}

}

onCleared();

}

在ViewModel的clear方法中,对所有保存的Closeable执行close,然后调用onCleared。

再看看ktx中的viewModelScope:

public val ViewModel.viewModelScope: CoroutineScope

get() {

val scope: CoroutineScope? = this.getTag(JOB_KEY)

if (scope != null) return scope

return setTagIfAbsent(

JOB_KEY,

CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)

)

}

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {

override val coroutineContext: CoroutineContext = context

override fun close() {

coroutineContext.cancel()

}

}

其本质也是提供了一个实现Closeable接口的CoroutineScope,然后通过setTagIfAbsent设置给ViewModel。

配置变更后仍保持数据的原理

前面在分析ViewModelStore的获取时,我们知道ComponentActivity在初始化ViewModelStore时,会先调用getLastNonConfigurationInstance,尝试恢复配置未变更前保存的ViewModelStore。与之对应的也有配置变更时保存ViewModelStore的逻辑:

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;

}

onRetainNonConfigurationInstance会在Activity配置发生变更(如横竖屏切换)需要重建时,它会将返回的Object直接保存到ActivityClientRecord中:

// ActivityThread

void performDestroyActivity(ActivityClientRecord r, boolean finishing,

int configChanges, boolean getNonConfigInstance, String reason

) {

...

if (getNonConfigInstance) {

try {

r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();

} catch (Exception e) {

...

}

}

...

}

Activity重建时再设置回去:

// ActivityThread

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

...

activity.attach(appContext, this, getInstrumentation(), r.token,

r.ident, app, r.intent, r.activityInfo, title, r.parent,

r.embeddedID, r.lastNonConfigurationInstances, config,

r.referrer, r.voiceInteractor, window, r.activityConfigCallback,

r.assistToken, r.shareableActivityToken);

...

}

// Activity

final void attach(Context context, ActivityThread aThread,

...

NonConfigurationInstances lastNonConfigurationInstances,

...

) {

...

mLastNonConfigurationInstances = lastNonConfigurationInstances;

...

}

既然ViewModelStore实例在重建时被保存和恢复了,那么其中的ViewModel及其状态数据也自然不会变化。

SavedStateHandle实现原理

 SavedStateHandle机制是在jetpack-savedstate的基础上实现的,强烈建议先了解此组件的使用方法。

前面我们在分析ComponentActivity中提供的默认工厂SavedStateViewModelFactory时,提到了工厂会在需要使用SavedStateHandle调用createSavedStateHandle创建实例:

public fun CreationExtras.createSavedStateHandle(): SavedStateHandle {

val savedStateRegistryOwner = this[SAVED_STATE_REGISTRY_OWNER_KEY]

?: throw IllegalArgumentException(...)

val viewModelStateRegistryOwner = this[VIEW_MODEL_STORE_OWNER_KEY]

?: throw IllegalArgumentException(...)

val defaultArgs = this[DEFAULT_ARGS_KEY]

val key = this[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]

?: throw IllegalArgumentException(...)

return createSavedStateHandle(

savedStateRegistryOwner, viewModelStateRegistryOwner, key, defaultArgs

)

}

这里会从CreationExtras中获取一些必要参数:

savedStateRegistryOwner:SavedStateRegistryOwner是jetpack-savedstate库中的一个接口,实现此接口的类表明可以在应用意外销毁时支持保存/恢复状态。jetpack中的ComponentActivity与Fragment实现了它。viewModelStateRegistryOwner:这里实际获取的是当前的ViewModelStoreOwner,工厂会将创建出来的SavedStateHandle实例保存在一个专门的ViewModel—SavedStateHandlesVM中以加快访问。defaultArgs:传递给SavedStateHandle的默认参数。key:与待创建ViewModel相关联的键,用于从SavedStateHandlesVM中存取SavedStateHandle实例。

private fun createSavedStateHandle(

savedStateRegistryOwner: SavedStateRegistryOwner,

viewModelStoreOwner: ViewModelStoreOwner,

key: String, defaultArgs: Bundle?

): SavedStateHandle {

val provider = savedStateRegistryOwner.savedStateHandlesProvider

val viewModel = viewModelStoreOwner.savedStateHandlesVM

// If we already have a reference to a previously created SavedStateHandle

// for a given key stored in our ViewModel, use that. Otherwise, create

// a new SavedStateHandle, providing it any restored state we might have saved

return viewModel.handles[key]

?: SavedStateHandle.createHandle(

provider.consumeRestoredStateForKey(key), defaultArgs

).also { viewModel.handles[key] = it }

}

这里获取到的SavedStateProvider专用于保存/恢复SavedStateHandle中状态,这个Provider在ComponentActivity 的构造时通过enableSavedStateHandles创建。

当savedStateHandlesVM没有缓存的实例时,就创建一个新实例返回,这里会先根据key从SavedStateProvider中读取之前保存的状态作为SavedStateHandle构造过程的参数。这样就实现了数据的恢复。

接下来看看数据的保存,我们知道SDK中Activity的状态保存是靠onSaveInstanceState回调实现的,SavedStateHandle也不例外:

// ComponentActivity

protected void onSaveInstanceState(@NonNull Bundle outState) {

...

mSavedStateRegistryController.performSave(outState);

}

// SavedStateRegistryController

fun performSave(outBundle: Bundle) {

savedStateRegistry.performSave(outBundle)

}

//SavedStateRegistry

fun performSave(outBundle: Bundle) {

....

// 这里获取到所有注册的SavedStateProvider,调用他们的saveState获取到

// 需要保存的数据,统一的保存到onSaveInstanceState传入的Bundle中

val it: Iterator> =

this.components.iteratorWithAdditions()

while (it.hasNext()) {

val (key, value) = it.next()

components.putBundle(key, value.saveState())

}

if (!components.isEmpty) {

outBundle.putBundle(SAVED_COMPONENTS_KEY, components)

}

}

用于保存SavedStateHandle中状态的SavedStateHandlesProvider,则早在Activity初始化时注册到了SavedStateRegistry中。来看看它的saveState方法:

private val viewModel by lazy { viewModelStoreOwner.savedStateHandlesVM }

override fun saveState(): Bundle {

return Bundle().apply {

...

viewModel.handles.forEach { (key, handle) ->

val savedState = handle.savedStateProvider().saveState()

if (savedState != Bundle.EMPTY) {

putBundle(key, savedState)

}

}

}

}

这里的ViewModel就是之前提到的用来保存所有SavedStateHandle实例的SavedStateHandlesVM。

SavedStateHandle内部持有一个SavedStateProvider,在保存数据时,会将调用它的onSave方法将SavedStateHandle内部的状态打包成一个Bundle:

private val savedStateProvider = SavedStateRegistry.SavedStateProvider {

...

// Convert the Map of current values into a Bundle

val keySet: Set = regular.keys

val keys: ArrayList = ArrayList(keySet.size)

val value: ArrayList = ArrayList(keys.size)

for (key in keySet) {

keys.add(key)

value.add(regular[key])

}

bundleOf(SavedStateHandle.KEYS to keys, SavedStateHandle.VALUES to value)

}

总结

ViewModel是Android Jetpack架构组件之一,它可以帮助我们解决Activity/Fragment等组件在配置更改时数据丢失的问题。通过创建ViewModel对象,我们可以将数据存储在其中,从而实现数据的持久化。ViewModel的使用非常灵活,我们可以将其与LiveData、Kotlin协程等其他组件一起使用,以实现更加强大的功能。在本文中,我们介绍了ViewModel的优势、简单的使用方法,并对主要功能的实现原理进行了分析。如果你有任何疑问,欢迎评论我们一起讨论。

作者:碎星 链接:https://juejin.cn/post/7202557470342774839

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集 二、源码解析合集

三、开源框架合集

欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

推荐阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: