【问题标题】:How to continue Firebase Task on the same thread如何在同一线程上继续 Firebase 任务
【发布时间】:2018-02-26 19:14:35
【问题描述】:

我在后台线程的SyncAdapter 上执行了一些代码,看起来像这样

Task<DocumentSnapshot> docTask = FirebaseFirestore.
          getInstance().
          collection("users_dummy").
          document("ID1").
          get();

当这个任务完成后,我怎样才能在后台线程上继续?

我试过.addOnCompleteListener后得到

 .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>()
                {
                    @Override
                    public void onComplete(@NonNull Task<DocumentSnapshot> task)
                    {
                       // DOESN'T WORK, THIS IS THE MAIN THREAD

                    }
                });

但正如 here 所指出的,回调是在 UI 或主线程上完成的,实际上会抛出一个

java.lang.IllegalStateException: 不能在 main 上调用 应用线程

当我执行Tasks.await(some task)之类的代码时

此处的完整示例(随意忽略):

    private void performTheCodeThatDoesNotWork()
    {

        Task<DocumentSnapshot> docTask = FirebaseFirestore.getInstance().collection("users_dummy")
                .document("ID1").get()
                .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>()
                {
                    @Override
                    public void onComplete(@NonNull Task<DocumentSnapshot> task)
                    {

                        try
                        {
                            // Query the second one but block this time
                            Task<DocumentSnapshot> secondId = FirebaseFirestore.getInstance().collection("users_dummy")
                                    .document("ID2").get();

/* THIS WILL THROW THE EXCEPTION ->*/ Tasks.await(secondId);
                            DocumentSnapshot result = secondId.getResult();
                        }
                        catch (Exception ex)
                        {
                            ex.printStackTrace();
                        }

                        Log.i("TAG", "Result is there but never reached");
                    }
                });

        Log.i("TAG", "Im here quickly, because async");

    }

我尝试过Task.continueWith,但结果相同。我是否需要像在this doc stating leaking activities 中那样帮助执行者并用它调用addOnCompleteListener

或者我错过了关于这个问题的一些微不足道的事情?在某个线程上继续执行阻塞调用有那么难吗?

【问题讨论】:

    标签: android multithreading firebase task google-cloud-firestore


    【解决方案1】:

    如果您希望在主线程以外的线程上调用回调,则必须提供带有侦听器的 Executor,该侦听器将安排在非主线程上调用侦听器。这是您唯一的选择。

    Tasks.await() 几乎从来都不是正确的使用方法,尤其是在主线程上,因为它可能会阻塞。它仅用于后台线程,并且仅在您绝对知道需要阻塞行为时使用。

    【讨论】:

    • 你能详细说明为什么它实际上很糟糕吗?我的 SyncAdapter 在后台线程上,可以按照他的要求进行阻塞,对吗?恕我直言,我上面代码中的一个有效解决方案是对第一个和第二个任务使用两个Task.await,而不是在第一个任务上使用延续,在第二个任务上等待。如果两个任务都在等待,我总是留在 bg 线程上。使这两个任务异步只会使我的 SyncAdapter 变得复杂,因为 AFAIK onPerformSync 应该是阻塞的。如果没有 Task.await,我将不得不使用 CountDownLatch 来模仿这种行为,这看起来像是更复杂的解决方案
    • 它只在主线程外有效。 medium.com/google-developers/…
    • 我明白了。您有在 SyncAdapter 中使用 Firebase API 的经验吗? SyncAdapter.onPerformSync 在 BG 线程上调用,但方法调用本身必须是阻塞的。当我调用异步 API 时,我需要将它们的最终确定同步到 onPerformSync。您会建议在没有 addOnCompleteListener 的情况下一直使用 Task.await(及其在主线程上的处理),还是保持异步并使用 CountDownLatchonPerformSync 同步完成。还是别的什么?
    • 最能满足您需求的。
    【解决方案2】:

    我也遇到了这个问题,但是看到 firebase addOncompletion 监听器有一个你可以提供的执行器,这样你就可以与执行器保持在同一个线程上。所以我创建了以下内容:

    class ExecuteOnCaller: Executor {
         private val threadLocalHandler = object : ThreadLocal<Handler>() {
             override fun initialValue(): Handler {
                 var looper = Looper.myLooper()
                 if (looper == null)
                     looper = Looper.getMainLooper()
                 return Handler(looper)
             }
         }
    
         private val handler = threadLocalHandler.get()
         override fun execute(command: Runnable?) {
             handler?.post(command)
         }
     }
    

    然后你可以像这样使用执行器:

     FirebaseAuth.getInstance().currentUser?.getIdToken(true)?.addOnCompleteListener(ExecutOnCaller(),object: OnCompleteListener<GetTokenResult> {
                    override fun onComplete(task: Task<GetTokenResult>) {
                        when {
                                task.isSuccessful -> {
                                    task.result.token?.let {
                                       //do whatever you want
    
                                }
                                else -> {
                                    task.exception?.let {//some error occured}
                                }
                            }
                    }
    
                })
    

    注意我是如何将 executor 传递给 addoncompleteListener 的。如果我没有这样做,firebase 将在 mainThread 上运行它。小心不要在这里做 UI 工作。欲了解更多信息,请查看this post,这对我很有帮助

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-12
      • 2014-01-19
      • 1970-01-01
      • 2017-08-03
      • 2012-12-13
      • 1970-01-01
      • 1970-01-01
      • 2018-10-02
      相关资源
      最近更新 更多