Java如何在后台Runnable中执行API调用并向多个Fragment返回响应
问题根因
你现有代码存在两个核心缺陷,直接导致了异常:
- 每个Fragment都独立
new了专属的RunnableDemo实例,实例间状态、逻辑完全隔离,FragmentA启动的轮询任务和FragmentB没有任何关联,B自然收不到A启动任务的返回结果;两个实例各自启动轮询,必然触发重复API请求。 RunnableDemo仅持有单个监听器引用,就算把实例做成全局共享,也只有最后一个注册的Fragment能收到回调,之前注册的回调会被直接覆盖。
实现方案
采用全局单例+多监听器注册的模式改造即可,保证全局仅运行一个轮询任务,所有注册的Fragment都能同步收到响应,同时自动绑定生命周期避免内存泄漏。
第一步:改造RunnableDemo为全局单例,支持多监听器分发
public class RunnableDemo implements Runnable{ // 全局单例实例 private static volatile RunnableDemo INSTANCE; // 用线程安全的集合存储所有注册的监听器 private final Set<RunnableDemoListener> listeners = new CopyOnWriteArraySet<>(); private final Handler myHandler = new Handler(Looper.getMainLooper()); private Runnable myRunnable; private boolean shouldRun = false; private boolean running = false; // 轮询间隔,替换为实际业务需要的时间值 private final long updateInterval = 5000L; // 私有构造方法,禁止外部直接new实例 private RunnableDemo() {} public static RunnableDemo getInstance() { if (INSTANCE == null) { synchronized (RunnableDemo.class) { if (INSTANCE == null) { INSTANCE = new RunnableDemo(); } } } return INSTANCE; } // 注册监听器,Fragment进入可见状态时调用 public void registerListener(RunnableDemoListener listener) { listeners.add(listener); // 注册后如果轮询没启动,自动启动,不需要外部手动判断 if (!isRunning()) { start(); } } // 解绑监听器,Fragment销毁时必须调用,避免内存泄漏 public void unregisterListener(RunnableDemoListener listener) { listeners.remove(listener); // 所有监听器都解绑后自动停止轮询,避免空跑耗流量 if (listeners.isEmpty()) { stop(); } } private void start() { shouldRun = true; run(); } @Override public void run() { if (myRunnable == null) { // 首次0延迟执行,需要等一个间隔再发首次请求就把0替换为updateInterval myHandler.postDelayed(myRunnable = new Runnable() { public void run() { if(shouldRun) { running = true; loop(); } myHandler.postDelayed(myRunnable, updateInterval); } }, 0); } } private void loop() { // 保留原有的retrofit请求逻辑 // ...retrofit.... call.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) { // 遍历所有已注册的监听器,分发成功结果 for (RunnableDemoListener listener : listeners) { listener.onDataSuccess(response); } } @Override public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) { // 遍历所有已注册的监听器,分发失败结果 for (RunnableDemoListener listener : listeners) { listener.onDataError("error"); } } }); } public void stop() { shouldRun = false; running = false; if (myRunnable != null) { myHandler.removeCallbacks(myRunnable); myRunnable = null; } // 通知所有监听器轮询已停止 for (RunnableDemoListener listener : listeners) { listener.onStopped(); } } public final boolean isRunning() { return running; } public interface RunnableDemoListener { void onDataError(String title); void onDataSuccess(Response<ResponseBody> responseBody); void onStopped(); } }
第二步:简化Fragment逻辑,统一做注册/解绑
FragmentA和FragmentB逻辑完全一致,不需要自己创建RunnableDemo实例,也不需要手动判断轮询是否运行,在对应生命周期做注册和解绑即可:
public class FragmentA extends Fragment implements RunnableDemo.RunnableDemoListener{ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 获取全局单例注册自身,轮询未启动时会自动启动 RunnableDemo.getInstance().registerListener(this); } @Override public void onDestroyView() { super.onDestroyView(); // 页面销毁时解绑,避免内存泄漏 RunnableDemo.getInstance().unregisterListener(this); } @Override public void onDataError(String title) { Log.i("TEST", "FragmentA receive error: "+ title); } @Override public void onDataSuccess(Response<ResponseBody> responseBody) { Log.i("TEST", "FragmentA receive success, code: "+ responseBody.code()); } @Override public void onStopped() { Log.i("TEST", "FragmentA receive stopped callback"); } }
FragmentB只需要复制上面的代码,把类名和日志标记替换成FragmentB即可。改造后无论多少个Fragment同时在前台,都只会运行一个轮询任务发API请求,所有注册的Fragment能同步收到返回结果,所有Fragment销毁后轮询会自动停止,不会出现后台空跑的问题。
内容的提问来源于stack exchange,提问作者MitGuru
相关产品推荐
相关产品推荐

