【问题标题】:Accessing Firebase Database from AsyncTask's Transaction从 AsyncTask 的事务中访问 Firebase 数据库
【发布时间】:2016-09-27 13:07:20
【问题描述】:

我需要从正在进行事务的异步任务访问 Firebase 数据库。

所以我的设置是,我有一个 AsyncTask。在其doInBackground 方法中,我调用了另一个方法,其中包含我运行事务的逻辑。在事务的doTransaction 方法中,我需要从不同的路径访问数据并生成新数据(如一些统计信息)。

看起来像这样:

public class MyAsyncTask extends AsyncTask<String,Void,Void> {

    @Override
    protected Void doInBackground(String... params) {
        String para1 = params[0];
        String para2 = params[1];

        performCalculations(para1,para2);

        return null;
    }

    public void performCalculation(String para1, String para2) {
        DatbaseReference ref = FirebaseDatabase.getInstance().getReference().child(para1);

        // Run the transaction here
        ref.runTransaction(new Transaction.Handler() {
            @Override
            public Transaction.Result doTransaction(MutableData mutableData) {

                 TaskCompletionSource<DataSnapshot> taskCompletionSource = new TaskCompletionSource<>();

                 DatabaseReference ref2 = FirebaseDatabase.getInstance().getReference().child(param2);

                 ref2.addListenerForSingleValueEvent(new MyValueEventListener(taskCompletionSource));

                 try {
                    dataSnapshot = Tasks.await(taskCompletionSource.getTask());

                    // DOING SOME AWESOME THINGS HERE 
                    // And updating the mutable Data


                } catch (ExecutionException | InterruptedException e) {
                    Log.v(TAG, "Exception Catched");
                    e.printStackTrace();
                }


                mutableData.setValue(stats);
                return Transaction.success(mutableData);
            }

            @Override
            public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
                if (databaseError != null) {
                    Log.d(TAG, "onComplete: Error: " + databaseError.getMessage());
                } else {
                    Log.d(TAG, "onComplete: Transaction was successful");

                }
            }

        }
    }

    private class MyValueEventListener implements ValueEventListener {

        private final TaskCompletionSource<DataSnapshot> taskSource;

        public MyValueEventListener(TaskCompletionSource<DataSnapshot> taskSource) {
            this.taskSource = taskSource;
        }

        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            Log.v(TAG, "Looks like MyValueEventListener received the data");
            taskSource.setResult(dataSnapshot);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            Log.v(TAG, "Looks like MyValueEventListener thrown an error");
            taskSource.setException(databaseError.toException());
        }

    }

}

将日志放在各处,我遇到了它到达Tasks.await 语句。但是,onDataChanged 回调永远不会被调用。

它卡在那个点上。没有错误。没有例外。

最大的问题是,从那时起,它会停止整个应用程序的所有回调。我所有的活动都被阻止了,因为它从不调用onDataChanged。看起来它在内部崩溃或被线程阻塞。

发生了什么事?谢谢。

编辑:

因此,为了从事务中的 ref 获取最新数据,我将变量作为类的一部分。并将值事件侦听器附加到它。所以它会一直保持更新。但是,如果离线排队事务在触发值事件侦听器之前开始运行怎么办?离线一段时间后,上线后先执行什么优先级?

这是我更新的代码:

public class MyAsyncTask extends AsyncTask<String,Void,Void> {

    DataSnapshot mDataSnapshot;

    ValueEventListener mListener;

    DatabaseReference ref2;

   @Override
    protected Void doInBackground(String... params) {
        String para1 = params[0];
        String para2 = params[1];

        performCalculations(para1,para2);

        return null;
    }

    public void performCalculation(String para1, String para2) {

            TaskCompletionSource<DataSnapshot> taskCompletionSource = new TaskCompletionSource<>();

            ref2 = FirebaseDatabase.getInstance().getReference().child(param2);

            ref2.addListenerForSingleValueEvent(new MyValueEventListener(taskCompletionSource));

            try {
                    // Get the data first time before you proceed
                    dataSnapshot = Tasks.await(taskCompletionSource.getTask());

             } catch (ExecutionException | InterruptedException e) {
                    Log.v(TAG, "Exception Catched");
                    e.printStackTrace();
             }

             // Register the value event listener to keep the data updated
             mListener = new ValueEventListener() {
             @Override
             public void onDataChange(DataSnapshot dataSnapshot) {
                mDataSnapshot = dataSnapshot
                Log.v(TAG, "Data updated");
             }

             @Override
             public void onCancelled(DatabaseError databaseError) {

             }
         };

        ref2.addValueEventListener(mListener);


        DatbaseReference ref = FirebaseDatabase.getInstance().getReference().child(para1);

        // Run the transaction here
        ref.runTransaction(new Transaction.Handler() {
            @Override
            public Transaction.Result doTransaction(MutableData mutableData) {

                 // Perform some stuff based on mDataSanpshot



                mutableData.setValue(stats);
                return Transaction.success(mutableData);
            }

            @Override
            public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
                if (databaseError != null) {
                    Log.d(TAG, "onComplete: Error: " + databaseError.getMessage());
                } else {
                    Log.d(TAG, "onComplete: Transaction was successful");
                    ref2.removeEventListener(mListener);
                }
            }

        }
    }

    // My value event listener here ... 

}

【问题讨论】:

  • 这就像您在后台任务中使用事务进程(后台线程)。为什么你想要 MyAsync 任务? performCalculation 方法已经在后台线程中执行任务
  • 那么我可以阻塞事务线程直到它接收到来自 TaskCompletionSource 的数据吗?它不会影响我的 UI 线程吗?
  • 我不这么认为!试一次!
  • 天啊!我以前的结构只是performCalculation,但由于阻塞我将它转换为AsyncTask!我在努力。。
  • 我检查了这个文档firebase.google.com/docs/database/android/save-data它是回调方法,所以它不会影响UI线程

标签: android firebase android-asynctask transactions firebase-realtime-database


【解决方案1】:

您可能会从 FvP 等 Firebaser 获得更明智的答案。在那之前,我很确定你在runTransaction() 中等待的任务会造成死锁。您可以使用类似于此的 Log 语句查看回调运行的线程:

Log.i(TAG, "doTransaction: " + Thread.currentThread().getName());

doTransaction() 在FirebaseDatabaseWorker 线程上运行。侦听器回调在主 (UI) 线程上运行,并且(我猜想)由从 DB 工作线程调度的事件驱动。

阻塞 DB 工作线程将导致监听器回调永远不会触发。任务永远不会完成,所以Task.await() 永远不会被释放。

【讨论】:

  • 好的。所以 AsyncTask 中的运行导致我陷入僵局,对吧?我可以用 Tasks.await 阻止 runTransaction 线程吗?
  • @kirtan403:您无法控制runTransaction() 运行的线程。它始终是FirebaseDatabaseWorker 线程。使用 AsyncTask 没有帮助。而且你永远不应该阻止FirebaseDatabaseWorker线程。
  • 我已经检查了线程,它的 FirebaseDatabaseWorker,以及在事务之前(删除 AsyncTask 之后)它的主线程。我现在被锁定了!
【解决方案2】:

我还没有对 TaskCompletionSource 做任何事情,所以不要立即看到那里发生了什么。但总的来说,我会尽量避免使用这样的结构,因为它们会使代码复杂化而没有显着的好处。

Firebase 数据库客户端在单独的线程上执行所有网络和磁盘 I/O,然后在主线程上显示对您的方法的调用。通过使用它并重新排序一些调用,我认为您可以省去很多代码:

public void performCalculation(String para1, String para2) {
    DatbaseReference ref = FirebaseDatabase.getInstance().getReference().child(para1);
    DatabaseReference ref2 = FirebaseDatabase.getInstance().getReference().child(para2);

    ref2.addListenerForSingleValueEvent(new ValueEventListener() { }
        public void onDataChange(DataSnapshot dataSnapshot) {
            ref.runTransaction(new Transaction.Handler() {
                public Transaction.Result doTransaction(MutableData mutableData) {
                    // DOING SOME AWESOME THINGS HERE 
                    // And updating the mutable Data
                }
                public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
                    if (databaseError != null) {
                        Log.d(TAG, "onComplete: Error: " + databaseError.getMessage());
                    } else {
                        Log.d(TAG, "onComplete: Transaction was successful");
                    }
                }
            });
        }
    );

【讨论】:

  • 我也试过了。起初,我只是以这种方式实现的。但是重新运行时事务似乎没有更新数据。因为从 ref2 值访问数据时,它需要是 final 的,所以即使事务重新运行,它也会使用旧数据来计算我要写入的另一个数据。
  • 就是这个原因,我把数据库回调移到了runTransaction里面
  • 我正在使用TaskCompletionSource,这样我就可以简单地等待数据返回,然后继续执行。来自道格·史蒂文森:youtube.com/watch?v=AJqakuas_6g
  • @kirtan403 我认为您不想在事务中注册侦听器或阻止事务所在的线程。弗兰克的策略在我看来要可靠得多。我仍然不清楚您到底想要完成什么 - 我看到了您的代码,但我不知道您的意图是什么。
  • 您无法通过使用侦听器获取对 ref 的所有先前更改。侦听器最初只会看到该位置的最新值。如果一个位置被多次覆盖,并且您的听众没有一直注册,您将看不到所有这些单独的快照。如果您需要记录发生的事情的历史记录,则应改为使用 push() 将值放入队列中,然后在您完成该位置的数据后从该位置删除子节点。
猜你喜欢
  • 1970-01-01
  • 2017-04-18
  • 1970-01-01
  • 2010-10-23
  • 2018-07-28
  • 2019-04-13
  • 2019-03-18
  • 1970-01-01
  • 2019-02-16
相关资源
最近更新 更多