You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android调用Java Spring Boot REST服务:响应返回与循环阻塞问题

问题解答

问题B:为什么等待循环无法执行,还阻止REST调用完成?

这是典型的主线程死锁+线程调度冲突问题:

  • 你的while循环在主线程里执行,会一直占用主线程的CPU资源,没有任何让步操作(比如Thread.sleep())。
  • AsyncHttpClient默认会把onSuccess/onFailure这类回调调度到主线程执行(Android框架里网络请求回调通常绑定主线程,方便后续更新UI)。
  • 主线程被死循环死死卡住,根本腾不出时间处理回调任务,所以callResults.put()永远不会执行,循环也永远无法退出。甚至连System.out.print(".")的输出都没法及时刷新——因为主线程被占满了,连I/O操作的调度都被延迟了。
  • 最终结果就是:回调完全无法执行,看起来像服务器调用被"阻止",程序陷入永久等待。

问题A:如何将Login对象返回给初始调用的函数?

既然你不能用观察者模式(LiveData、RxJava等),那我们用正确的同步阻塞方式替代不靠谱的Map+死循环方案。推荐使用Java并发包的CountDownLatch——它是专门协调异步任务与等待线程的工具,比手动循环稳定得多。

修改后的代码示例:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

class MainController {
    private final AtomicLong counter;
    // 用线程安全容器存储每个请求的结果和等待锁
    private final ConcurrentHashMap<Long, AtomicReference<CallResult>> callResults;
    private final ConcurrentHashMap<Long, CountDownLatch> latches;

    public MainController(){
        counter = new AtomicLong();
        callResults = new ConcurrentHashMap<>();
        latches = new ConcurrentHashMap<>();
    }

    public Login getLoginByUsername(String username) {
        long callID = counter.incrementAndGet();
        // AtomicReference保证多线程下结果的可见性
        AtomicReference<CallResult> resultRef = new AtomicReference<>();
        // 初始化计数为1的Latch:回调完成后调用countDown()解锁
        CountDownLatch latch = new CountDownLatch(1);
        
        callResults.put(callID, resultRef);
        latches.put(callID, latch);

        System.out.println("Start call");
        ServerConnection.get("myURL", null, new JsonHttpResponseHandler(){
            @Override
            public void onSuccess(int statusCode, Header[] headers, JSONObject response){
                try {
                    System.out.println("Call success, enter result:");
                    CallResult result = new CallResult(Login.parseFromJson(response));
                    resultRef.set(result);
                    System.out.println(result);
                } catch (JSONException e) {
                    e.printStackTrace();
                } finally {
                    // 无论成功失败都要解锁,避免永久等待
                    latch.countDown();
                }
            }

            @Override
            public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
                // 必须处理失败场景!否则请求失败会导致线程永久阻塞
                throwable.printStackTrace();
                latch.countDown();
            }
        });

        System.out.println("start waiting");
        try {
            // 阻塞当前线程,直到Latch计数归0(回调执行完毕)
            latch.await();
        } catch (InterruptedException e) {
            // 恢复线程中断状态,避免异常丢失
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }

        System.out.println("waiting over, return result:");
        CallResult result = resultRef.get();
        // 清理资源,避免内存泄漏
        callResults.remove(callID);
        latches.remove(callID);

        return result != null ? result.content : null;
    }
}

方案说明:

  1. CountDownLatch的作用latch.await()会阻塞当前线程,直到回调里调用latch.countDown()将计数减到0,精准等待异步任务完成,不会像死循环那样空耗CPU。
  2. 线程安全保障:用ConcurrentHashMap存储请求对应的结果和Latch,避免多线程并发问题;AtomicReference保证结果在回调线程与等待线程之间的可见性。
  3. 异常处理:在onFailureonSuccess的finally块中调用countDown(),确保无论请求成功或失败,等待线程都能正常解除阻塞。
  4. 额外提醒:如果getLoginByUsername在AndroidUI线程调用,latch.await()会阻塞主线程导致ANR(应用无响应)。这种情况下要确保方法在子线程执行(比如线程池),若你无法修改调用逻辑,只能尽量避免在UI线程触发该方法。

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

火山引擎 最新活动