新Places SDK中PendingResult await()等效方案及循环阻塞问题
你的痛点我太懂了——旧SDK里用PendingResult.await()可以很轻松地在循环里逐个同步查询Place,结果新SDK全改成异步回调了,直接导致循环跑完了,查询结果还没回来,功能完全失效。不过别担心,我们可以用几种方式把异步操作“转”成同步等待的逻辑,让你的代码按预期执行。
方案一:用Java的CompletableFuture包装异步回调(Java项目适用)
我们可以把fetchPlace的异步回调塞进CompletableFuture里,然后用get()方法阻塞当前线程,直到拿到Place结果再继续循环。
给你写个示例代码:
// 遍历你的Office Place ID列表 for (String officePlaceId : officePlaceIdList) { // 创建一个CompletableFuture来承接异步结果 CompletableFuture<Place> placeFuture = new CompletableFuture<>(); // 构建查询请求,这里只请求经纬度字段就够了 FetchPlaceRequest request = FetchPlaceRequest.newInstance(officePlaceId, Arrays.asList(Place.Field.LAT_LNG)); placesClient.fetchPlace(request) .addOnSuccessListener(response -> { // 拿到结果后,把它传给Future placeFuture.complete(response.getPlace()); }) .addOnFailureListener(exception -> { // 处理失败情况,比如把异常传给Future placeFuture.completeExceptionally(exception); }); try { // 阻塞当前线程,直到Future拿到结果 Place officePlace = placeFuture.get(); // 这里执行你的距离判断逻辑 LatLng officeLatLng = officePlace.getLatLng(); float distance = SphericalUtil.computeDistanceBetween(userSelectedLatLng, officeLatLng); if (distance < 50) { // 找到符合条件的Office,你可以在这里做后续处理,比如跳出循环 break; } } catch (InterruptedException | ExecutionException e) { // 处理异常,比如打印日志或者跳过当前这个Place e.printStackTrace(); } }
⚠️ 重要提醒:绝对不要在UI线程跑这段代码! get()方法会阻塞线程,UI线程被阻塞超过5秒就会触发ANR(应用无响应)。你得把这段逻辑放到后台线程里,比如用ExecutorService、AsyncTask或者WorkManager来执行。
方案二:用Kotlin协程实现同步写法(Kotlin项目适用)
如果你的项目是用Kotlin写的,那协程简直是解决这个问题的完美工具——它能让你用同步的代码风格写异步逻辑,完全不用回调嵌套。
示例代码如下:
// 先切换到IO调度器的协程里执行,避免阻塞UI CoroutineScope(Dispatchers.IO).launch { for (officePlaceId in officePlaceIdList) { val request = FetchPlaceRequest.newInstance(officePlaceId, listOf(Place.Field.LAT_LNG)) try { // 用await()等待查询结果,这会挂起协程但不会阻塞线程 val response = placesClient.fetchPlace(request).await() val officePlace = response.place val distance = SphericalUtil.computeDistanceBetween(userSelectedLatLng, officePlace.latLng) if (distance < 50) { // 找到目标Office后,如果需要更新UI,切换回主线程 withContext(Dispatchers.Main) { // 这里写UI相关的逻辑,比如提示用户找到附近办公室 } break } } catch (e: Exception) { // 处理查询失败的情况 e.printStackTrace() } } }
要注意,这里用到的await()是Google为Places SDK的Task类提供的Kotlin扩展函数,你需要确保项目里引入了正确的依赖:
implementation "com.google.android.gms:play-services-places:19.0.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
备选方案:不阻塞,改用批量异步处理
如果你不想阻塞线程,也可以一次性发起所有Place查询,等所有结果都回来后再统一判断距离。这种方式速度更快,但需要处理多个异步任务的结果:
用Java的CountDownLatch实现的示例:
// 初始化计数器,数值等于Place ID的数量 CountDownLatch latch = new CountDownLatch(officePlaceIdList.size()); // 用线程安全的列表存结果 List<Place> officePlaces = Collections.synchronizedList(new ArrayList<>()); for (String officePlaceId : officePlaceIdList) { FetchPlaceRequest request = FetchPlaceRequest.newInstance(officePlaceId, Arrays.asList(Place.Field.LAT_LNG)); placesClient.fetchPlace(request) .addOnSuccessListener(response -> { officePlaces.add(response.getPlace()); latch.countDown(); // 每完成一个请求,计数器减1 }) .addOnFailureListener(exception -> { latch.countDown(); // 失败也要减计数器,避免一直阻塞 }); } try { // 阻塞直到所有请求完成 latch.await(); // 现在遍历所有拿到的Place,判断距离 for (Place officePlace : officePlaces) { float distance = SphericalUtil.computeDistanceBetween(userSelectedLatLng, officePlace.getLatLng()); if (distance < 50) { // 找到符合条件的Office break; } } } catch (InterruptedException e) { e.printStackTrace(); }
这种方式的好处是所有请求并行发起,效率更高;缺点是必须等所有请求都完成才能开始判断,而且要注意线程安全问题。
最后再强调一遍:不管用哪种方案,都不能在UI线程执行阻塞操作,一定要放到后台线程或者协程的IO调度器里!
内容的提问来源于stack exchange,提问作者Ziggy

