CompletableFuture致UI冻结,Unirest异步请求实现方案合理性咨询
API异步请求实现问题解答
原始代码的核心问题
你的代码结构思路没问题,但executeAsync方法里调用req.asObjectAsync(responseType).get()是错误的——get()会阻塞当前线程(也就是UI线程),直接导致UI冻结,完全违背了异步请求的初衷。
结构是否过于复杂?
你想引入Executor的修改思路反而增加了不必要的复杂度,还存在隐患:每次调用Executors.newSingleThreadScheduledExecutor()都会新建一个线程池,频繁调用会导致线程资源耗尽,甚至出现内存泄漏。
是否必须使用线程/Executor?
不需要。Unirest的asObjectAsync本身就返回CompletableFuture,它内部已经封装了异步执行的线程池,你不需要手动创建额外线程,只需要正确使用CompletableFuture的异步回调方法即可。
更优的实现方式
利用CompletableFuture的whenComplete、thenAccept等异步回调方法,避免调用阻塞式的get(),同时保证UI操作在UI线程执行。
修改后的Request类核心代码
fun executeAsync(action: (HttpResponse<T>) -> Unit, onError: (Throwable) -> Unit = {}) { prepareRequest { req -> // 直接使用Unirest返回的CompletableFuture,添加异步回调 req.asObjectAsync(responseType) .whenComplete { res, exception -> if (exception != null) { onError(exception) } else { action(res) } } } }
ApiClient类优化(增加错误处理)
abstract class ApiClient { protected fun <T> executeAsync( req: Request<T>, onSuccess: (T) -> Unit, onError: (Throwable) -> Unit = {} ) { req.executeAsync({ res -> if (res.isSuccess) { onSuccess(res.body) } else { onError(RuntimeException("请求失败,状态码:${res.status}")) } }, onError) } }
AuthClient类(确保UI操作在UI线程)
以Android客户端为例,需要把onSuccess切换到UI线程执行:
class AuthClient : ApiClient() { fun signin( email: String, password: String, onSuccess: () -> Unit, onError: (Throwable) -> Unit = {} ) { executeAsync( Request.builder(TokenModel::class.java) .post("/signin") .body(SignInModel(email, password)) .build(), { // 切换到UI线程执行刷新操作 runOnUiThread { onSuccess() } }, onError ) } // 非Android环境可替换为对应平台的UI线程调度方法 }
进阶:结合Kotlin协程(更简洁的异步处理)
如果项目使用Kotlin协程,可以进一步简化代码,摆脱回调地狱:
- 将
Request的异步方法封装为挂起函数:
suspend fun execute(): HttpResponse<T> { return prepareRequest { req -> req.asObjectAsync(responseType).await() // 使用kotlinx-coroutines-jdk8的await() } }
- ApiClient的执行方法改为挂起函数:
abstract class ApiClient { protected suspend fun <T> execute(req: Request<T>): T { val res = req.execute() if (res.isSuccess) { return res.body } else { throw RuntimeException("请求失败,状态码:${res.status}") } } }
- AuthClient的调用逻辑:
class AuthClient : ApiClient() { fun signin( email: String, password: String, onSuccess: () -> Unit, onError: (Throwable) -> Unit = {} ) { // 在IO协程执行网络请求,切换到Main线程处理UI CoroutineScope(Dispatchers.IO).launch { try { execute( Request.builder(TokenModel::class.java) .post("/signin") .body(SignInModel(email, password)) .build() ) withContext(Dispatchers.Main) { onSuccess() } } catch (e: Exception) { withContext(Dispatchers.Main) { onError(e) } } } } }
内容的提问来源于stack exchange,提问作者Thomas Thaler
相关产品推荐
相关产品推荐

