Android泛型参数化BaseViewModel为何需Handler延迟?原因及解决方案
崩溃根因
这是Kotlin类初始化顺序规则与泛型基类设计冲突导致的空指针问题,具体执行链路如下:
- Kotlin的类初始化严格遵循「父类构造逻辑全部执行完成后,才会执行子类的成员初始化、子类自身init块」的规则
- 当前基类的init块中直接调用了
sendRequest()方法,该方法内部会读取基类定义的抽象属性requestObservable - 实际执行时,构造
MainViewModel会先触发父类BaseViewModel的构造流程,运行到父类init块时,子类MainViewModel的所有成员(包括绑定api的requestObservable属性)还没开始初始化 - 此时读取
requestObservable,本质是触发未初始化完成的api字段的取值,自然会返回null,调用接口时直接触发空指针崩溃 - 之前不使用泛型基类时,请求触发逻辑写在子类自身的初始化流程里,所有成员已经完成赋值,自然不需要额外延迟
- 现有代码中的1ms延迟能暂时生效,本质是通过
Handler.postDelayed把请求任务切到了主线程消息队列的下一轮循环,等子类构造流程全部走完、所有成员初始化完成后再执行请求,完全属于依赖时序的临时hack,不具备稳定性,一旦消息队列调度顺序变化,随时可能复现崩溃。
无延迟的可靠解决方案
- 方案一:将请求触发逻辑的执行权交还给子类
删除基类init块中自动触发请求的代码,保留sendRequest()方法的访问权限,让子类在自身init块中主动调用请求方法,从时序上保证所有成员完成初始化后再发起请求。
基类调整示例:
子类调整示例:abstract class BaseViewModel<T, R>(private val schedulerProvider: BaseSchedulerProvider) : ViewModel() { // 删除原init块中带Handler延迟的自动调用逻辑 protected abstract val requestObservable: Observable<R> // 保留原有LiveData封装、composeObservable、资源回收等逻辑 }class MainViewModel( api: PokemonService, schedulerProvider: BaseSchedulerProvider ) : BaseViewModel<List<Pokemon>, List<NamedResponseModel>>(schedulerProvider) { override val requestObservable: Observable<List<NamedResponseModel>> = api.getPokemonList(LIMIT).map { it.results } init { // 子类执行到该块时,所有成员已完成初始化,直接调用即可 sendRequest() } } - 方案二:将抽象属性替换为延迟创建的高阶函数
把基类中需要提前初始化的抽象requestObservable属性,改成抽象的无参函数,等到真正需要发请求时,再通过函数回调拿到对应的Observable实例,彻底避免父类init阶段读取未初始化的子类成员。这种写法可以保留基类自动触发请求的逻辑,不需要每个子类都手动写init块调用请求。
基类调整示例:
子类调整示例:abstract class BaseViewModel<T, R>(private val schedulerProvider: BaseSchedulerProvider) : ViewModel() { // 原抽象属性替换为抽象方法 protected abstract fun createRequestObservable(): Observable<R> // 基类init块可以直接调用sendRequest,因为此时createRequestObservable还未执行,不会触发子类成员读取 init { sendRequest() } fun sendRequest() { _liveData.value = Resource.Loading() // 发请求时才回调获取Observable,此时子类所有成员已完成初始化 composeObservable { createRequestObservable() } .subscribe({ _liveData.postValue(Resource.Success(getSuccessResult(it))) }) { _liveData.postValue(Resource.Failure(it.localizedMessage)) Timber.e(it) }.also { compositeDisposable.add(it) } } }class MainViewModel( private val api: PokemonService, schedulerProvider: BaseSchedulerProvider ) : BaseViewModel<List<Pokemon>, List<NamedResponseModel>>(schedulerProvider) { override fun createRequestObservable(): Observable<List<NamedResponseModel>> { return api.getPokemonList(LIMIT).map { it.results } } }
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

