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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:03:24