使用同步块等待Task结果致应用冻结,该如何解决?
嗨,这个问题我之前也踩过坑,核心问题出在主线程阻塞和回调线程的冲突上,咱们慢慢捋清楚:
为什么你的代码会导致应用冻结?
- 首先,你大概率是在Android主线程里调用了
synchronized(lock){ lock.wait(); }——主线程一旦进入等待状态,就会彻底卡住,没法处理任何UI事件、回调逻辑,整个应用自然就失去响应了。 - 其次,Google Play Services的
Task默认情况下,onSuccess()这类回调是在主线程执行的。这就形成了死锁:主线程等着被notifyAll()唤醒,但负责执行唤醒逻辑的onSuccess()根本没机会运行,因为主线程已经挂起了,循环往复,应用就彻底冻结了。
正确处理Task完成的几种方式
1. 优先用Task的链式回调(非阻塞,推荐)
这是Google设计Task的初衷,完全不用阻塞线程,直接通过回调链处理结果,代码更清晰也更安全:
yourTask.addOnSuccessListener(result -> { // 在这里处理任务成功的结果,比如更新UI、传递数据 processSuccessResult(result); }).addOnFailureListener(exception -> { // 处理任务失败的情况 handleFailure(exception); }).addOnCompleteListener(task -> { // 不管成功失败都会执行,适合做收尾工作,比如关闭加载弹窗 finishLoading(); });
这种方式下,所有回调会在合适的线程执行(默认主线程,也可以通过addOnSuccessListener(Executor, OnSuccessListener)指定后台线程),完全不会阻塞主线程,自然不会出现冻结问题。
2. 必须同步获取结果?放到后台线程!
如果你确实需要同步拿到Task的结果(比如在某个后台逻辑里),绝对不能在主线程操作,可以用Tasks.await()方法,但一定要放在后台线程中执行:
// 示例:用Thread开启后台线程 new Thread(() -> { try { // 阻塞当前后台线程,直到Task完成 ResultType result = Tasks.await(yourTask); // 拿到结果后,回到主线程处理UI(如果需要) runOnUiThread(() -> updateUIWithResult(result)); } catch (ExecutionException | InterruptedException e) { // 处理异常情况,比如任务失败、线程被中断 e.printStackTrace(); } }).start();
⚠️ 注意:Tasks.await()会阻塞调用它的线程,所以一定要确保这个线程不是主线程,否则还是会导致应用冻结。
3. Kotlin项目?用协程更优雅
如果是Kotlin开发,可以借助kotlinx-coroutines-play-services库提供的扩展函数,用协程以同步风格处理异步任务,同时避免阻塞主线程:
// 比如在Activity/Fragment的lifecycleScope中执行 lifecycleScope.launch(Dispatchers.IO) { try { // 协程挂起,不会阻塞线程 val result = yourTask.await() // 回到主线程处理UI withContext(Dispatchers.Main) { updateUI(result) } } catch (e: Exception) { // 处理异常 showError(e.message) } }
总结一下:永远不要在Android主线程使用wait()/notify()这类阻塞方法,Task的设计核心就是异步回调,优先用链式回调处理;如果一定要同步获取结果,务必放到后台线程执行。
内容的提问来源于stack exchange,提问作者AndreaF
相关产品推荐
相关产品推荐

