使用Volley单次方法发起两次远程请求出现数据混淆问题求助
问题:Volley异步请求导致电影列表数据混乱,热门列表混入两类数据,高分列表为空
我在用Volley调用MovieDB API获取数据时遇到了一个奇怪的问题:我调用同一个方法分别传入热门电影和高分电影的URL,本应该把结果分别存入popularMoviesList和topRatedMoviesList,但实际情况是popularMoviesList里混进了两类电影的数据,而topRatedMoviesList却一直是空的。相关代码如下:
MovieDataSource类代码
import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.JsonObjectRequest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; public class MovieDataSource { private final static String MOVIE_ID="id"; private final static String MOVIE_RATING = "vote_average"; private final static String MOVIE_TITLE = "original_title"; private final static String MOVIE_POSTERPATH = "poster_path"; private final static String MOVIE_DESCRIPTION = "overview"; private final static String MOVIE_RELEASEDATE = "release_date"; private final static String API_KEY = "";// paste api key inside the double quote private RequestQueue mRequestQueue = VolleySingleton.getmInstance().getmRequestQueue(); public ArrayList<Movie> movieArrayList(String URL){ final ArrayList<Movie> listOfMovies = new ArrayList<>(); String url= URL + API_KEY; JsonObjectRequest jsonObjectRequest= new JsonObjectRequest (Request.Method.GET, url, null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { int movieId; double movieRating; String movieTitle, imagePath, movieDescription,movieReleaseDate; Log.i("successful", response.toString()); try { JSONArray results = response.getJSONArray("results"); // get json array from the object JSONObject object; // to get objects from result array Movie movie; for(int i=0; i<results.length(); i++) { object = results.getJSONObject(i);// get objects in the array based on their position/index in the array //get values from the objects that will be used to populate instantiated movies movieId = object.getInt(MOVIE_ID); movieRating = object.getDouble(MOVIE_RATING); movieTitle = object.optString(MOVIE_TITLE); imagePath = object.optString(MOVIE_POSTERPATH); movieDescription = object.optString(MOVIE_DESCRIPTION); movieReleaseDate = object.optString(MOVIE_RELEASEDATE); //// for every element in the array( index i), create a new movie movie = new Movie(movieId,movieRating,movieTitle,imagePath,movieDescription, movieReleaseDate); // add each movie created to the arrayList listOfMovies.add(movie); } } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { error.printStackTrace(); Log.i("error", "onErrorResponse: something went wrong "); } }); mRequestQueue.add(jsonObjectRequest); return listOfMovies; } }
MainActivity调用代码
MovieDataSource movieDataSource = new MovieDataSource(); final ArrayList<Movie>popularMoviesList = movieDataSource.movieArrayList(popularMoviesUrl) final ArrayList<Movie> topRatedMoviesList = movieDataSource1.movieArrayList(topRatedMovieUrl);
问题原因分析
这个问题的核心是你误解了Volley请求的异步特性,具体来说:
异步请求与同步返回的矛盾:Volley的网络请求是在后台线程异步执行的,你的
movieArrayList方法在发起请求后立刻就返回了空的listOfMovies——这时候网络请求甚至还没开始处理,更别说拿到数据了。你返回的只是一个空容器,真正的数据要等网络请求完成后,在onResponse回调里才会被添加进去。数据混乱的具体原因:
- 你看到
popularMoviesList混入两类数据,大概率是因为两次请求的回调执行时机和变量引用出现了混乱(比如你可能在后续代码中错误地把两个列表合并,或者因为Java的引用传递特性,让两个回调意外地指向了同一个列表); - 而
topRatedMoviesList为空,要么是高分电影的请求还没完成回调你就去检查列表了,要么是请求过程中出现了错误但你没及时发现,或者是变量引用错误导致数据没被正确添加到这个列表里。
- 你看到
解决方案:改用回调接口处理异步结果
要彻底解决这个问题,你需要放弃“同步返回列表”的错误思路,改用回调接口来传递异步请求的结果。这样能确保数据在请求真正完成后才被传递到对应的列表中,避免混乱。
步骤1:定义回调接口
在MovieDataSource类中(或者单独创建一个接口文件)定义一个回调接口,用来传递请求成功或失败的结果:
public interface OnMoviesLoadedListener { // 请求成功时调用,返回电影列表 void onMoviesLoaded(ArrayList<Movie> movies); // 请求失败时调用,返回错误信息 void onLoadFailed(VolleyError error); }
步骤2:修改MovieDataSource的请求方法
把原来的movieArrayList方法改成接收回调接口的形式,不再直接返回列表:
public void getMovies(String URL, final OnMoviesLoadedListener listener) { String url = URL + API_KEY; JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { ArrayList<Movie> listOfMovies = new ArrayList<>(); try { JSONArray results = response.getJSONArray("results"); for (int i = 0; i < results.length(); i++) { JSONObject object = results.getJSONObject(i); int movieId = object.getInt(MOVIE_ID); double movieRating = object.getDouble(MOVIE_RATING); String movieTitle = object.optString(MOVIE_TITLE); String imagePath = object.optString(MOVIE_POSTERPATH); String movieDescription = object.optString(MOVIE_DESCRIPTION); String movieReleaseDate = object.optString(MOVIE_RELEASEDATE); Movie movie = new Movie(movieId, movieRating, movieTitle, imagePath, movieDescription, movieReleaseDate); listOfMovies.add(movie); } // 请求成功,通过回调把数据传出去 listener.onMoviesLoaded(listOfMovies); } catch (JSONException e) { e.printStackTrace(); // 解析失败也触发错误回调 listener.onLoadFailed(new VolleyError("JSON解析失败")); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { error.printStackTrace(); listener.onLoadFailed(error); } }); mRequestQueue.add(jsonObjectRequest); }
步骤3:在MainActivity中使用回调获取数据
现在你可以在MainActivity中通过回调分别获取热门和高分电影的数据,确保数据准确对应:
MovieDataSource movieDataSource = new MovieDataSource(); ArrayList<Movie> popularMoviesList; ArrayList<Movie> topRatedMoviesList; // 获取热门电影 movieDataSource.getMovies(popularMoviesUrl, new OnMoviesLoadedListener() { @Override public void onMoviesLoaded(ArrayList<Movie> movies) { popularMoviesList = movies; // 这里可以更新UI,比如刷新RecyclerView // popularAdapter.setMovies(popularMoviesList); // popularAdapter.notifyDataSetChanged(); } @Override public void onLoadFailed(VolleyError error) { // 处理加载失败的情况,比如弹出提示 Toast.makeText(MainActivity.this, "热门电影加载失败", Toast.LENGTH_SHORT).show(); } }); // 获取高分电影 movieDataSource.getMovies(topRatedMovieUrl, new OnMoviesLoadedListener() { @Override public void onMoviesLoaded(ArrayList<Movie> movies) { topRatedMoviesList = movies; // 更新高分电影的UI // topRatedAdapter.setMovies(topRatedMoviesList); // topRatedAdapter.notifyDataSetChanged(); } @Override public void onLoadFailed(VolleyError error) { Toast.makeText(MainActivity.this, "高分电影加载失败", Toast.LENGTH_SHORT).show(); } });
为什么这样能解决问题?
- 这种方式完全适配了Volley的异步特性,只有当网络请求真正完成并解析完数据后,才会通过回调把数据传递给对应的列表;
- 每个请求的回调都是独立的,数据只会被添加到对应的目标列表中,不会出现数据混乱的情况;
- 你还能清晰地处理请求失败的场景,更容易排查问题(比如API_KEY是否正确、URL是否错误等)。
内容的提问来源于stack exchange,提问作者user7609274
相关产品推荐
相关产品推荐

