Android MVVM架构下屏幕旋转重复请求问题排查
首先,你的问题核心有两个:ViewModel没有被正确复用,以及每次调用数据获取方法都会重复发起请求,导致屏幕旋转时重复触发整个流程。下面一步步拆解问题并给出修复:
1. ViewModel实例化错误(最关键的问题)
你当前的ViewModel初始化方式:
val viewModel: Fragment1ViewModel = ViewModelProviders.of(this).get(Fragment1ViewModel(Application())::class.java)
这种写法会每次都创建新的ViewModel实例,包括屏幕旋转后。因为ViewModelProviders.get()如果传入带参数的构造器,它无法复用已有的ViewModel,而是会每次调用构造函数生成新对象。正确的做法是让ViewModel依托自身的AndroidViewModel特性持有Application,不需要手动传参初始化。
修复后的Fragment初始化代码:
val viewModel: Fragment1ViewModel = ViewModelProvider(this).get(Fragment1ViewModel::class.java)
注:如果用的是旧版依赖,可能还是
ViewModelProviders,但新版已推荐使用ViewModelProvider。修改后,屏幕旋转时ViewModel会被系统自动复用,不会重新创建。
2. Repository与ServerFetch的重复请求问题
即使ViewModel被复用了,你的getData()方法每次调用都会触发新请求:
- ViewModel的
getData()每次调用都会调用repository.getListItem() - Repository的
getListItem()每次都会重新初始化serverFetch - ServerFetch的
getData()每次都会调用connect()发起新的Volley请求
修复方案:缓存LiveData结果,避免重复请求
我们需要让Repository只在第一次请求时发起网络调用,后续复用已有的LiveData结果。
修改Repository类:
class Repository(private val context: Context) { private val serverFetch: ServerFetch = ServerFetch(context) // 缓存第一次请求的LiveData结果 private var cachedLiveData: LiveData<String>? = null fun getListItem(): LiveData<String> { if (cachedLiveData == null) { cachedLiveData = serverFetch.getData() } return cachedLiveData!! } }
这里移除了静态的serverFetch(避免上下文泄漏和重复覆盖),改为实例变量,并且缓存第一次请求的LiveData,后续调用直接返回缓存实例,避免重复触发请求。
修改ServerFetch类:
确保connect()只被调用一次,而不是每次getData()都执行:
class ServerFetch(private val context: Context) { private val api: String = "https://jsonplaceholder.typicode.com/posts" private val result = MutableLiveData<String>() // 标记是否已经发起过请求 private var isRequested = false private fun connect() { if (isRequested) return isRequested = true val request = StringRequest(Request.Method.GET, api, Response.Listener { Log.i("Log", "ServerFetch Successfully") result.value = it }, Response.ErrorListener { Log.i("Log", "ServerFetch field: ${it.toString()}") }) val queue = Volley.newRequestQueue(context) queue.add(request) } fun getData(): LiveData<String> { connect() return result } }
添加isRequested标记,确保connect()只会执行一次,避免重复发起Volley请求。
3. 额外优化:ViewModel中缓存LiveData
为了进一步确保ViewModel复用后不会重复调用Repository方法,也可以在ViewModel中缓存LiveData:
class Fragment1ViewModel(application: Application) : AndroidViewModel(application) { private val repository = Repository(application) private var dataLiveData: LiveData<String>? = null fun getData(): LiveData<String> { if (dataLiveData == null) { Log.i("Log","get data in view model") dataLiveData = repository.getListItem() } return dataLiveData!! } }
这样就算多次调用getData(),也只会在第一次时初始化LiveData,后续直接返回缓存实例。
验证修复后的流程
- 首次运行:ViewModel初始化 → 调用
getData()→ 发起网络请求 → 返回结果 - 屏幕旋转:ViewModel被复用 → 调用
getData()直接返回缓存的LiveData → 不会发起新的网络请求,Observer会收到之前缓存的结果
内容的提问来源于stack exchange,提问作者Mohammad Derakhshan

