放一张官图

- 先上代码
/**
* 提供自定义 Factory 和 viewModel 扩展,使得定义 ViewModel 时可以传参
*
* <code>
* private val model by viewModel { XxxViewModel(1, 2, 3) }
* </code>
*/
class ParamViewModelFactory<VM : ViewModel>(
private val factory: () -> VM,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T = factory() as T
}
inline fun <reified VM : ViewModel> AppCompatActivity.viewModel(
noinline factory: () -> VM,
): Lazy<VM> = viewModels { ParamViewModelFactory(factory) }
class ViewModelActivity : BaseActivity() {
companion object {
const val TAG = "hehe"
}
// 1. 使用 viewModels() 扩展实例化 ViewModel,
// 在 implementation "androidx.activity:activity-ktx:1.3.1" 该库中
private val model by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 2. 打印 Activity 实例,旋转屏幕后实例发生变化
Log.d(TAG, "" + this)
// 3. 打印 ViewModel 实例,旋转屏幕后实例未发生变化
Log.d(TAG, "" + model)
// 4. 使用另一种方式实例化 ViewModel
val model = ViewModelProvider(this).get(MyViewModel::class.java)
model.text.observe(this) {
// 打印并显示 ViewModel 中的数据
Log.d(TAG, "text: $it")
findViewById<TextView>(R.id.hello_world).text = it
}
// 5. 再次打印 ViewModel 实例,表明两种方式得到的是同一个实例,细节参看 ViewModelProvider 具体实现
Log.d(TAG, "" + model)
// 6. 开始请求数据
model.update()
}
}
class MyViewModel : ViewModel() {
val text = MutableLiveData("No book")
fun update() {
// 7. 模拟延时返回数据
thread {
Thread.sleep(1000L * 5)
text.postValue("This is book " + (Random(System.currentTimeMillis()).nextInt(10)))
}
}
override fun onCleared() {
// 8. 需要清理资源的回调,Activity#onDestroy() 时会调过来(旋转屏幕导致的 onDestroy 不会)
super.onCleared()
Log.d(ViewModelActivity.TAG, "MyViewModel#onCleared()")
}
}
- 所使用的依赖
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.activity:activity-ktx:1.3.1"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
- 需要注意的点
a. 不能使用 new XxxViewModel() 的方式实例化 ViewModel。因为这样实例化出来的 ViewModel 不能再屏幕旋转后仍然保持有效;同时也不会使得 onCleared() 方法被回调;再次,如果使用了 viewModelScope 发起协程的话,也不会使得协程被取消。
1. 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()
}
}
2. 可以看到在 close() 方法中取消了协程,那么 close() 方法在哪儿被调用了呢?
3. 看 ViewModel#clear() 方法和 ViewModel#closeWithRuntimeException() 方法
final void clear() {
mCleared = true;
// Since clear() is final, this method is still called on mock objects
// and in those cases, mBagOfTags is null. It'll always be empty though
// because setTagIfAbsent and getTag are not final so we can skip
// clearing it
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException(value);
}
}
}
onCleared();
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
4. 可以看到在 closeWithRuntimeException() 方法调用了 CloseableCoroutineScope#close() 方法
5. ViewModelStore#clear() 会调用 ViewModel#clear() 方法,再往上请参考源码
b. 初始值最好放在 MutableLiveData 的构造方法里,不要放在 Activity 里,这样屏幕旋转时界面不会跳变(否则每次都会先闪现一下 Activity 里的初始值)
错误用法:
package top.gangshanghua.xiaobo.helloworld
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlin.concurrent.thread
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.hello_world).text = "No book"
val model = ViewModelProvider(this).get(MyViewModel::class.java)
model.text.observe(this) {
// 为了效果更加明显,假设渲染任务较重,延时模拟
findViewById<TextView>(R.id.hello_world).post {
Thread.sleep(1000L)
findViewById<TextView>(R.id.hello_world).text = it
}
}
model.update()
}
}
class MyViewModel : ViewModel() {
val text = MutableLiveData<String>()
fun update() {
thread {
Thread.sleep(1000L * 5)
text.postValue("This is book " + (Random(System.currentTimeMillis()).nextInt(10)))
}
}
}

c. 请您补充~
网友评论