【问题标题】:Call Firebase database from Google App Engine从 Google App Engine 调用 Firebase 数据库
【发布时间】:2016-10-19 22:15:21
【问题描述】:

我按照tutorial 设置了我的 Google App Engine 实例,并且我也在使用 Firebase。我的目标是将所有“计算”放在 Google App Engine 上。我想在下面调用一个这样的函数:

我的端点:

package productions.widowmaker110.backend;

/** An endpoint class we are exposing */
@Api(
 name = "myApi",
 version = "v1",
 namespace = @ApiNamespace(
 ownerDomain = "backend.widowmaker110.productions",
 ownerName = "backend.widowmaker110.productions",
 packagePath=""
 )
)
public class MyEndpoint {

 /** A simple endpoint method that takes a name and says Hi back */
 @ApiMethod(name = "sayHi")
 public MyBean sayHi(@Named("name") String name) {

 // Write a message to the database
 FirebaseDatabase database = FirebaseDatabase.getInstance();
 DatabaseReference myRef = database.getReference("message");

 // Read from the database
 myRef.addValueEventListener(new ValueEventListener() {
 @Override
 public void onDataChange(DataSnapshot dataSnapshot) {
 // This method is called once with the initial value and again
 // whenever data at this location is updated.
 String value = dataSnapshot.getValue(String.class);
 Log.d(TAG, "Value is: " + value);
 }

 @Override
 public void onCancelled(DatabaseError error) {
 // Failed to read value
 Log.w(TAG, "Failed to read value.", error.toException());
 }
 });

 MyBean response = new MyBean();
 response.setData("Hi, " + name);

 return response;
 }

}

主活动:

package productions.widowmaker110.gpsweather;

// imports...

public class MainActivity extends AppCompatActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    new EndpointsAsyncTask().execute(new Pair<Context, String>(this, "Manfred"));
 }

 class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
    private MyApi myApiService = null;
  private Context context;

 @Override
 protected String doInBackground(Pair<Context, String>... params) {
   if(myApiService == null) { // Only do this once
      MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
      new AndroidJsonFactory(), null)
      // options for running against local devappserver
      // - 10.0.2.2 is localhost's IP address in Android emulator
      // - turn off compression when running against local devappserver
     .setRootUrl("http://10.0.2.2:8080/_ah/api/")
     .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
 @Override
 public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
    abstractGoogleClientRequest.setDisableGZipContent(true);
 }
 });
 // end options for devappserver

 myApiService = builder.build();
 }

 context = params[0].first;
 String name = params[0].second;

 try {
 return myApiService.sayHi(name).execute().getData();
 } catch (IOException e) {
 return e.getMessage();
 }
 }

 @Override
 protected void onPostExecute(String result) {
 Toast.makeText(context, result, Toast.LENGTH_LONG).show();
 }
 }
}

我了解 Firebase 的上述代码是特定于 Android 的,因此无法在 Google App Engine 实例上运行。我想知道是否有人知道如何执行 CRUD 从 Google App Engine 后端对 firebase 数据库进行操作。任何帮助表示赞赏。

【问题讨论】:

    标签: java android google-app-engine firebase


    【解决方案1】:

    您需要使用 Firebase Server SDK 进行服务器端调用。您可以在此处找到有关设置和使用它的信息:

    Add Firebase to your Server

    Firebase Server SDK Installation & Setup

    在将 Firebase 与 Google Cloud Endpoints 结合使用时,请注意您需要将 Firebase 方法与 Tasks API 结合使用。由于 Firebase 方法没有阻塞,如果您不使用 Tasks,您的 Endpoint 将在您对 Firebase 的调用有机会返回其结果之前返回。有关使用任务的简要介绍,请查看下面的链接。这是 Google I/O 2016 上的一次演讲。演讲者正在谈论 Android 上的 Tasks 和 Firebase,但在服务器上使用 Tasks 和 Firebase 时的概念是相同的。请注意,它们在 Firebase Server SDK 中包含了 Tasks API。我已经跳到直接处理任务的谈话部分。

    Firebase SDK for Android: A tech deep dive

    如果您需要在 Endpoint 返回值之前处理 Firebase 读/写操作的结果,或者其他代码依赖于 Firebase 读/写操作的结果,则以下示例适用。这些是服务器端示例。我假设你关心写操作是否成功。执行这些服务器端操作可能有更好的方法,但这是我目前所做的。

    使用 setValue() 的示例写入操作:

    DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
    YourModelClass obj = new YourModelClass();
    
    1. 当调用setValue() 方法时,它会返回一个Task 对象,保留对它的引用。

      Task<Void> setValueTask = ref.setValue(obj);
      
    2. 创建一个TaskCompletionSource 对象。该对象应使用您选择的结果类型进行参数化。我将使用Boolean 作为此示例的结果类型。

      final TaskCompletionSource<Boolean> tcs = new TaskCompletionSource<>();
      
    3. 从在第 2 步中创建的TaskCompletionSource 生成一个Task。同样,生成的Task 应该使用与TaskCompletionSource 对象相同的参数类型。

      Task<Boolean> tcsTask = tcs.getTask();
      
    4. 为调用setValue() 生成的Task 添加一个完成侦听器。在完成侦听器中,在步骤 3 中创建的 Task 上设置适当的结果。在您的 TaskCompletionSouce 对象上调用 setResult() 会将由此创建的 Task 标记为完成。这对于第 5 步很重要。

      setValueTask.addOnCompleteListener(new OnCompleteListener<Void>() {
         @Override
         public void onComplete(@NonNull Task<Void> task) {
            if(task.isSuccessful()){
               tcs.setResult(true);
            }else{
               tcs.setResult(false);
            }
         }
      });
      
    5. 调用Task.await() 阻塞当前线程,直到您感兴趣的Task 完成。我们正在等待由TaskCompletionSource 对象生成的@​​987654347@ 被标记为完成。当我们在用于生成TaskTaskCompletionSource 上调用setResult() 时,此Task 将被视为完成,就像我们在步骤4 中所做的那样。完成后它将返回结果。

      try {
          Boolean result = Tasks.await(tcsTask);
      }catch(ExecutionException e){
          //handle exception 
      }catch (InterruptedException e){
          //handle exception
      }
      

    就是这样,当前线程会阻塞直到Tasks.await()返回一个值。如果您想防止当前线程被无限期阻塞,您还可以(并且应该)在 Tasks.await() 方法上设置超时值。

    如果您只对setValue() 生成的Task 是否完成感兴趣,而不关心它是否成功,那么您可以跳过TaskCompletionSource 的创建,直接使用Tasks.await()直接在那Task上。 updateChildren() 也是如此。如果您愿意,可以只使用updateChilden()setValue() 的方法调用,它们使用DatabaseReference.CompletionListener 和TaskCompletionListener。

    等待读取操作完成也是类似的。

    使用 addListenerForSingleValueEvent() 的示例读取操作

    DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
    YourModelClass mModelClassObject;
    
    1. 创建一个TaskCompletionSource 对象,参数化为您期望从Task 生成的结果。

      final TaskCompletionSource<YourModelClass> tcs = new TaskCompletionSource<>();
      
    2. TaskCompletionSource 对象生成Task

      Task<YourModelClass> tcsTask = tcs.getTask();
      
    3. 在我们的DatabaseReference 上调用addListenerForSingleValueEvent() 并在步骤2 中生成的Task 上调用setResult()

      ref.addListenerForSingleValueEvent(new ValueEventListener() {
         @Override
         public void onDataChange(DataSnapshot dataSnapshot) {
              YourModelClass result = dataSnapshot.getValue(YourModelClass.class);
              if(result != null){
                  tcs.setResult(result);
              }
         }
      
         @Override
         public void onCancelled(DatabaseError databaseError){
              //handle error
         }
      });
      
    4. 调用Tasks.await() 阻塞当前线程,直到您感兴趣的Task 完成。当我们像在第 3 步中所做的那样调用 setResult() 时,Task 将被认为是完整的,并将返回结果。

      try {
          mModelClassObject = Tasks.await(tcsTask);
      }catch(ExecutionException e){
          //handle exception 
      }catch (InterruptedException e){
          //handle exception
      }
      

    如上所述,您可以使用Tasks.await() 方法和超时值来防止当前线程被无限期阻塞。

    提醒一下,我发现 Firebase 不会终止用于其操作的后台线程。这意味着 GAE 实例永远不会空闲。查看此线程以获取更多信息:

    Firebase, Background Threads, and App Engine

    【讨论】:

    • 谢谢凯文,我打算这个周末试一试。感谢帮助。如果解决了,我会在 24 小时内给你最佳答案。
    • 没问题,有机会我会添加更多关于使用任务的信息,因为这可能有点棘手。
    • 我了解所涉及的多线程,但我很难理解如何实现 Tasks API。就像您在上面所说的,当您有机会分享这些信息时,我将不胜感激。
    • 哇!感谢更新。今晚我会试一试。非常深入。
    • @KevinO'Neil 很棒的答案。非常全面。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-02
    • 2010-10-16
    • 2011-06-17
    • 1970-01-01
    • 2018-01-14
    • 2015-07-29
    相关资源
    最近更新 更多