如何等待所有FCM主题订阅完成后再启动新Activity?
优化FCM主题批量订阅后跳转Activity的实现
问题背景
从服务器获取需订阅的主题数组,循环逐一订阅FCM主题,希望等待所有订阅完成后再启动新Activity。当前通过判断循环索引是否为数组最后一位来触发启动操作,现有代码如下:
int length = subscribeTo.length(); for (int i = 0; i < length; i++) { String sub = null; try { sub = subscribeTo.get(i).toString(); } catch (JSONException e) { e.printStackTrace(); } int finalI = i; JSONObject finalData = data; String finalSub = sub; FirebaseMessaging.getInstance().subscribeToTopic(sub).addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { Log.e("sub", finalSub); if (!task.isSuccessful()) { Toast.makeText(MainActivity.this, "can't login in", Toast.LENGTH_SHORT).show(); return; } Log.e("test", "sub" + finalI); if (finalI == length - 1) { try { editor.putInt("sub_length", length); editor.putString("restaurant_name", finalData.get("restaurant_name").toString()); editor.putString("username", finalData.get("username").toString()); editor.putString("password", finalData.get("password").toString()); editor.putString("sub" + finalI, finalSub); } catch (JSONException e) { e.printStackTrace(); } Intent intent = new Intent(MainActivity.this, MainActivity2.class); startActivity(intent); editor.apply(); finish(); } } }); }
现有代码的问题
- 逻辑依赖漏洞:仅靠最后一个任务完成触发跳转,若最后一个订阅失败,即使前面所有任务成功也无法跳转,不符合需求。
- 数据存储不完整:仅存储了最后一个订阅的主题,前面的主题未存入SharedPreferences,属于功能bug。
- 异常处理缺失:JSON解析失败时仅打印日志,未处理该任务的后续逻辑,可能导致流程阻塞或计数错误。
- 代码冗余:匿名内部类需要复制多个final变量,代码可读性差。
更优实现方案
方案一:利用Firebase Tasks.whenAll批量等待任务
Firebase的Tasks类提供的whenAll方法可批量等待所有异步任务完成,逻辑清晰且贴合Firebase生态:
// 收集所有订阅任务 List<Task<Void>> taskList = new ArrayList<>(); int length = subscribeTo.length(); for (int i = 0; i < length; i++) { try { String sub = subscribeTo.get(i).toString(); Task<Void> subscribeTask = FirebaseMessaging.getInstance().subscribeToTopic(sub); taskList.add(subscribeTask); // 单独处理单个任务的失败 subscribeTask.addOnFailureListener(e -> { Log.e("sub", "订阅主题[" + sub + "]失败: " + e.getMessage()); Toast.makeText(MainActivity.this, "部分主题订阅失败", Toast.LENGTH_SHORT).show(); }); } catch (JSONException e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "主题解析失败", Toast.LENGTH_SHORT).show(); } } // 等待所有任务完成后统一处理 Tasks.whenAll(taskList) .addOnCompleteListener(combinedTask -> { if (combinedTask.isSuccessful()) { // 所有订阅成功,执行存储与跳转 try { editor.putInt("sub_length", length); editor.putString("restaurant_name", data.get("restaurant_name").toString()); editor.putString("username", data.get("username").toString()); editor.putString("password", data.get("password").toString()); // 存储所有订阅主题 for (int i = 0; i < length; i++) { String sub = subscribeTo.get(i).toString(); editor.putString("sub" + i, sub); } editor.apply(); } catch (JSONException e) { e.printStackTrace(); } Intent intent = new Intent(MainActivity.this, MainActivity2.class); startActivity(intent); finish(); } else { // 至少一个任务失败 Toast.makeText(MainActivity.this, "部分主题订阅失败,无法进入应用", Toast.LENGTH_SHORT).show(); } });
方案二:使用Java CountDownLatch(并发场景方案)
若不想依赖Firebase的Tasks类,可使用Java并发包的CountDownLatch实现等待逻辑:
int length = subscribeTo.length(); CountDownLatch latch = new CountDownLatch(length); boolean[] hasFailure = {false}; // 数组用于在匿名内部类中修改失败状态 for (int i = 0; i < length; i++) { try { String sub = subscribeTo.get(i).toString(); FirebaseMessaging.getInstance().subscribeToTopic(sub) .addOnCompleteListener(task -> { if (!task.isSuccessful()) { Log.e("sub", "订阅主题[" + sub + "]失败"); hasFailure[0] = true; } latch.countDown(); // 任务完成,计数减一 }); } catch (JSONException e) { e.printStackTrace(); hasFailure[0] = true; latch.countDown(); // 解析失败也要减少计数,避免阻塞 } } // 开启线程等待所有任务完成 new Thread(() -> { try { latch.await(); // 阻塞直到所有任务完成 // 回到UI线程处理界面逻辑 runOnUiThread(() -> { if (!hasFailure[0]) { // 所有订阅成功,存储数据并跳转 try { editor.putInt("sub_length", length); editor.putString("restaurant_name", data.get("restaurant_name").toString()); editor.putString("username", data.get("username").toString()); editor.putString("password", data.get("password").toString()); for (int i = 0; i < length; i++) { String sub = subscribeTo.get(i).toString(); editor.putString("sub" + i, sub); } editor.apply(); } catch (JSONException e) { e.printStackTrace(); } Intent intent = new Intent(MainActivity.this, MainActivity2.class); startActivity(intent); finish(); } else { Toast.makeText(MainActivity.this, "部分主题订阅失败", Toast.LENGTH_SHORT).show(); } }); } catch (InterruptedException e) { e.printStackTrace(); } }).start();
内容的提问来源于stack exchange,提问作者ABDULLAH SH
相关产品推荐
相关产品推荐

