You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从Volley请求返回列表结果并传递至其他类?

解决Volley异步响应返回列表的问题

你遇到的核心问题是Volley的请求是异步执行的,所以你不能像同步方法那样直接在getMovies里立即返回结果——当你执行return results的时候,Volley的请求还没完成,结果根本没被赋值,这就是为什么会出现"result must be initialized"的错误,同时响应会滞留在回调里。

下面我给你两种可行的解决方案,优先推荐用Kotlin协程的方式,因为它能让异步代码看起来像同步代码,更易维护:

方案一:用Kotlin协程+CompletableDeferred包装Volley请求

我们可以用CompletableDeferred把Volley的回调式异步操作转换成挂起函数,这样就能在suspend fun getMovies里直接获取结果:

改造后的完整代码

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class RemoteDataSourceManager: ServiceCall {
    override suspend fun getMovies(context: Context): Result<List<MovieEntity>> {
        // 在IO线程执行网络请求,避免阻塞主线程
        return withContext(Dispatchers.IO) {
            getRemoteMovies(context)
        }
    }

    // 把这个方法改成挂起函数,用CompletableDeferred等待Volley响应
    private suspend fun getRemoteMovies(context: Context): Result<List<MovieEntity>> {
        val deferred = CompletableDeferred<Result<List<MovieEntity>>>()
        val listOfMovies = ArrayList<MovieEntity>()
        val url = "https://api.themoviedb.org/3/movie/popular?api_key=f1e256985ebc2be710bf1f4ed754da11&language=en-US&page=1"
        
        val request: JsonObjectRequest = JsonObjectRequest(
            Request.Method.GET, url, null,
            Response.Listener { response ->
                try {
                    val results = response.getJSONArray("results")
                    for (movieIndex in 0 until results.length()) {
                        val eachMovie = results.getJSONObject(movieIndex)
                        val movieImage = "https://image.tmdb.org/t/p/w500${eachMovie.getString("poster_path")}"
                        val title = eachMovie.getString("title")
                        val rating = eachMovie.getInt("vote_average")
                        val overView = eachMovie.getString("overview")
                        val releaseDate = eachMovie.getString("release_date")
                        val movieId = eachMovie.getInt("id").toString()
                        
                        val newMovie = MovieEntity(
                            title, movieImage, rating, overView, releaseDate
                        ).apply {
                            this.movieId = movieId
                            this.id = movieId.toInt()
                        }
                        listOfMovies.add(newMovie)
                    }
                    // 请求成功,把结果传给deferred
                    deferred.complete(Result.Success(listOfMovies.toList()))
                } catch (e: Exception) {
                    Log.i("error", "Response $e")
                    // 请求失败,传递异常
                    deferred.completeExceptionally(e)
                }
            },
            Response.ErrorListener { error ->
                val errorMessage = VolleyErrorHandler.instance.erroHandler(error)
                Log.i("VolleyError", errorMessage)
                // 错误处理,传递异常
                deferred.completeExceptionally(error)
            })
        
        context.applicationContext.let { 
            VolleySingleton.getInstance(it).addToRequestQueue(request) 
        }
        
        // 挂起当前协程,直到Volley请求完成并返回结果
        return deferred.await()
    }

    override suspend fun saveMovies(movieEntity: MovieEntity) {
        TODO("not implemented")
    }

    // 原来的VolleyCallBack接口可以移除了,因为我们用协程替代了回调
}

关键说明:

  1. CompletableDeferred:它相当于一个异步结果的"容器",Volley请求成功时调用deferred.complete()传递结果,失败时调用deferred.completeExceptionally()传递异常。
  2. withContext(Dispatchers.IO):网络请求属于IO操作,我们在IO调度器执行,避免阻塞主线程。
  3. deferred.await():挂起当前协程,直到Volley请求完成,然后返回结果——这就实现了你想要的"同步式"获取异步结果的效果。

方案二:改进原有回调方式(适合不想用协程的场景)

如果你暂时不想用协程,那需要调整回调的逻辑,让getMovies不直接返回结果,而是通过回调把结果传递给调用方:

改造示例

class RemoteDataSourceManager: ServiceCall {
    // 修改getMovies为接收回调,而不是返回结果
    fun getMovies(context: Context, callback: (Result<List<MovieEntity>>) -> Unit) {
        getRemoteMovies(context) { result ->
            callback(result)
        }
    }

    private fun getRemoteMovies(context: Context, callback: (Result<List<MovieEntity>>) -> Unit) {
        val listOfMovies = ArrayList<MovieEntity>()
        val url = "https://api.themoviedb.org/3/movie/popular?api_key=f1e256985ebc2be710bf1f4ed754da11&language=en-US&page=1"
        
        val request: JsonObjectRequest = JsonObjectRequest(
            Request.Method.GET, url, null,
            Response.Listener { response ->
                try {
                    // ... 解析数据的逻辑和之前一样 ...
                    callback(Result.Success(listOfMovies.toList()))
                } catch (e: Exception) {
                    Log.i("error", "Response $e")
                    callback(Result.Error(e)) // 假设你的Result有Error类型
                }
            },
            Response.ErrorListener { error ->
                val errorMessage = VolleyErrorHandler.instance.erroHandler(error)
                Log.i("VolleyError", errorMessage)
                callback(Result.Error(error))
            })
        
        context.applicationContext.let { 
            VolleySingleton.getInstance(it).addToRequestQueue(request) 
        }
    }

    // ... 其他方法 ...
}

// 调用方式
remoteDataSourceManager.getMovies(context) { result ->
    when(result) {
        is Result.Success -> {
            // 拿到列表,存入数据库
            saveToDatabase(result.data)
        }
        is Result.Error -> {
            // 处理错误
        }
    }
}

关键说明:

这种方式下,你不能让getMovies直接返回结果,而是要把结果通过回调函数传递给调用方,因为Volley是异步的,结果只能在回调里拿到。

总结

优先选择方案一的协程方式,因为它能让你的异步代码逻辑更清晰,避免回调嵌套(回调地狱),而且符合Kotlin现代异步编程的最佳实践。

内容的提问来源于stack exchange,提问作者Darotudeen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 17:57:44