Android中Retrofit调用Spring REST API获取数据异常求助
嘿,这个问题我太熟了——本质就是异步请求的时序问题,咱们一步步拆解清楚:
为什么会出现「首次点击无数据,二次点击才显示」?
你用的是Retrofit的异步回调模式,myWebService.getAllTeacher()发起网络请求后会立刻返回,不会等待请求完成。而你的getAllDepartments()方法在发起请求后马上就返回了departmentList(哦对了,你代码里还有个小bug:类里定义的是private List<Teacher> list;,但返回的是this.departmentList,这俩变量根本不搭边!)。
首次点击时:
- 调用
dataAPI.getAllDepartments(),发起异步请求 - 方法立刻返回未初始化的(或null的)列表,MainActivity拿到null,自然显示不出内容
- 等网络请求完成后,
success回调才执行,给list赋值
第二次点击时:
第一次请求的回调已经执行完了,list里有数据了,所以getAllDepartments()返回的是之前请求到的数据,就能显示了。
至于把DataAPI移到onClick里能首次加载?大概率是测试时的巧合,或者你潜意识里调整了代码逻辑——本质上异步请求的时序问题还是存在的,只是回调完成的时机刚好赶上了操作节奏而已。
怎么解决?
核心思路:不要试图从异步方法里直接返回数据,而是用回调接口让请求完成后主动通知UI更新,或者用更现代的响应式方案(比如LiveData)。
方案1:自定义回调接口(最直接的修复)
第一步:在DataAPI里定义一个回调接口,用来通知请求结果:
public interface OnDataLoadedListener { void onSuccess(List<Teacher> data); void onFailure(RetrofitError error); }
第二步:修改DataAPI的getAllDepartments方法,去掉返回值,改成传入回调:
@RequiresApi(api = Build.VERSION_CODES.N) public void getAllDepartments(OnDataLoadedListener listener) { try { myWebService.getAllTeacher(new Callback<List<Teacher>>() { @Override public void success(List<Teacher> data, Response response) { Log.d(CLASS_TAG, data.toString()); list = data; // 请求成功,通知UI层 if (listener != null) { listener.onSuccess(data); } } @Override public void failure(RetrofitError error) { Log.e(CLASS_TAG, "请求失败", error); if (listener != null) { listener.onFailure(error); } } }); } catch (Exception e) { Log.e(CLASS_TAG, "异常", e); if (listener != null) { listener.onFailure(null); } } }
第三步:修改MainActivity的点击事件,通过回调接收数据并更新UI:
@RequiresApi(api = Build.VERSION_CODES.N) @Override public void onClick(View v) { textView.setText("加载中..."); dataAPI.getAllDepartments(new OnDataLoadedListener() { @Override public void onSuccess(List<Teacher> data) { if (data == null || data.isEmpty()) { textView.setText("暂无数据"); return; } // 用StringBuilder拼接,避免多次setText影响性能 StringBuilder content = new StringBuilder(); data.forEach(teacher -> content.append(teacher.toString()).append("\n")); textView.setText(content.toString()); } @Override public void onFailure(RetrofitError error) { String errorMsg = error != null ? error.getMessage() : "未知错误"; textView.setText("加载失败:" + errorMsg); } }); }
额外要修复的小问题
- 删掉DataAPI里的
return this.departmentList;(因为现在用回调传递数据了),同时修正变量名不一致的问题(之前的list和departmentList混淆了)。 - 不要在循环里多次调用
textView.setText(),用StringBuilder拼接后一次性设置,性能更好。
方案2:用LiveData(更符合Android架构的方式)
如果你的项目用了Jetpack,可以把DataAPI改成用LiveData来暴露数据,自动处理生命周期,避免内存泄漏:
public class DataAPI { private static final String CLASS_TAG = "DataAPI"; RestAdapter retrofit; MyWebService myWebService; private MutableLiveData<List<Teacher>> teacherListLiveData = new MutableLiveData<>(); public DataAPI() { String url = "http://10.101.12.31:8080/"; retrofit = new RestAdapter.Builder() .setEndpoint(url) .build(); myWebService = retrofit.create(MyWebService.class); } @RequiresApi(api = Build.VERSION_CODES.N) public LiveData<List<Teacher>> getAllDepartments() { try { myWebService.getAllTeacher(new Callback<List<Teacher>>() { @Override public void success(List<Teacher> data, Response response) { Log.d(CLASS_TAG, data.toString()); teacherListLiveData.postValue(data); } @Override public void failure(RetrofitError error) { teacherListLiveData.postValue(null); } }); } catch (Exception e) { Log.d(CLASS_TAG, e.toString()); teacherListLiveData.postValue(null); } return teacherListLiveData; } }
然后在MainActivity里观察LiveData:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); dataAPI = new DataAPI(); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); findViewById(R.id.button_get).setOnClickListener(new View.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onClick(View v) { textView.setText("加载中..."); dataAPI.getAllDepartments().observe(MainActivity.this, teachers -> { if (teachers == null || teachers.isEmpty()) { textView.setText("暂无数据"); return; } StringBuilder content = new StringBuilder(); teachers.forEach(teacher -> content.append(teacher.toString()).append("\n")); textView.setText(content.toString()); }); } }); }
内容的提问来源于stack exchange,提问作者Kubek

