【问题标题】:Integrating multiple Firebase projects in one Flutter app causes app to crash在一个 Flutter 应用中集成多个 Firebase 项目会导致应用崩溃
【发布时间】:2021-07-29 18:30:02
【问题描述】:

背景: 我正在尝试开发一个与 3 个不同的 Firebase 项目连接的颤振应用程序。我按照 FlutterFire 上的说明进行操作,特别是在 FlutterFire OverviewCore | FlutterFire 上,并在 App 中初始化了 3 个 firebase 项目应用程序。

注意:出于合法原因,我使用了 3 个不同的 Firebase 项目,但我不能使用各种风格。此外,该应用使用共享首选项在本地存储数据。

问题: 当我启动模拟器并运行应用程序时,一切似乎都正常。该应用程序启动,连接到所有三个 firebase 项目并执行我想要的所有操作。但是,现在如果我尝试重新运行应用程序(即停止应用程序,然后按绿色箭头重新启动它),应用程序会崩溃并显示错误消息:

D/AndroidRuntime(16200): Shutting down VM
E/AndroidRuntime(16200): FATAL EXCEPTION: main
E/AndroidRuntime(16200): Process: com.example.flutter_app, PID: 16200
E/AndroidRuntime(16200): java.lang.RuntimeException: Internal error in Cloud Firestore (22.0.1).
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue.lambda$panic$3(AsyncQueue.java:534)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$$Lambda$3.run(Unknown Source:2)
E/AndroidRuntime(16200): at android.os.Handler.handleCallback(Handler.java:883)
E/AndroidRuntime(16200): at android.os.Handler.dispatchMessage(Handler.java:100)
E/AndroidRuntime(16200): at android.os.Looper.loop(Looper.java:214)
E/AndroidRuntime(16200): at android.app.ActivityThread.main(ActivityThread.java:7356)
E/AndroidRuntime(16200): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(16200): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
E/AndroidRuntime(16200): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
E/AndroidRuntime(16200): Caused by: java.lang.RuntimeException: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor.lambda$executeAndReportResult$1(AsyncQueue.java:325)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$$Lambda$2.run(Unknown Source:4)
E/AndroidRuntime(16200): at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462)
E/AndroidRuntime(16200): at java.util.concurrent.FutureTask.run(FutureTask.java:266)
E/AndroidRuntime(16200): at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
E/AndroidRuntime(16200): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
E/AndroidRuntime(16200): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:229)
E/AndroidRuntime(16200): at java.lang.Thread.run(Thread.java:919)
E/AndroidRuntime(16200): Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:648)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteSession.beginTransactionUnchecked(SQLiteSession.java:325)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteSession.beginTransaction(SQLiteSession.java:300)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:568)
E/AndroidRuntime(16200): at android.database.sqlite.SQLiteDatabase.beginTransactionWithListener(SQLiteDatabase.java:531)
E/AndroidRuntime(16200): at com.google.firebase.firestore.local.SQLitePersistence.runTransaction(SQLitePersistence.java:190)
E/AndroidRuntime(16200): at com.google.firebase.firestore.local.LocalStore.startMutationQueue(LocalStore.java:159)
E/AndroidRuntime(16200): at com.google.firebase.firestore.local.LocalStore.start(LocalStore.java:155)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.ComponentProvider.initialize(ComponentProvider.java:138)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.FirestoreClient.initialize(FirestoreClient.java:249)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.FirestoreClient.lambda$new$0(FirestoreClient.java:96)
E/AndroidRuntime(16200): at com.google.firebase.firestore.core.FirestoreClient$$Lambda$1.run(Unknown Source:8)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue.lambda$enqueue$2(AsyncQueue.java:436)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$$Lambda$2.call(Unknown Source:2)
E/AndroidRuntime(16200): at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor.lambda$executeAndReportResult$1(AsyncQueue.java:322)
E/AndroidRuntime(16200): ... 8 more
I/Process (16200): Sending signal. PID: 16200 SIG: 9
Lost connection to device.

我只有在初始化多个 firebase 项目并重新启动应用程序(不重新启动模拟器)时才会收到此错误。

您知道导致此错误的原因以及如何解决吗?

代码:

Future<void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    runApp(App());
}

class App extends StatefulWidget {
    @override
    _AppState createState() => _AppState();
}

class _AppState extends State<App> {
    @override
    void initState() {
        print("Constructing app");
        super.initState();
    }

    final Future<FirebaseApp> firebaseApp = Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key-1",
            appId: "app-id-1",
            projectId: "project-id-1",
            messagingSenderId: "sender-id-1"));
    final Future<FirebaseApp> firebaseAppDiscussions = Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key-2",
            appId: "app-id-2",
            projectId: "project-id-2",
            messagingSenderId: "sender-id-2"),
        name: "app2");
    final Future<FirebaseApp> firebaseAppComments = Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key-3",
            appId: "app-id-3",
            projectId: "project-id-3",
            messagingSenderId: "sender-id-3"),
        name: "app3");
    // My app also uses Shared Preferences, but I am not sure if it is playing a part in causing this error.
    final Future<SharedPreferences> sharedPrefStore = SharedPreferences.getInstance();
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'My App',
            home: FutureBuilder(
                future: Future.wait([
                    firebaseApp,
                    sharedPrefStore,
                    firebaseAppDiscussions,
                    firebaseAppComments
                ]),
                builder: (context, AsyncSnapshot<List<dynamic>> snapshot) { 
                    auth = FirebaseAuth.instance;
                    authDiscussion = FirebaseAuth.instanceFor(app: snapshot.data![2]);
                    authComment = FirebaseAuth.instanceFor(app: snapshot.data![3]);
                    firestoreInstance = FirebaseFirestore.instance;
                    firestoreInstanceDiscussion = FirebaseFirestore.instanceFor(app: snapshot.data![2]);
                    firestoreInstanceComment = FirebaseFirestore.instanceFor(app: snapshot.data![3]);
                    prefs = snapshot.data![1]; // sharedPrefStore
                    // If I comment out the if statement below, the app will work. 
                    // However, with the if statement, a crash is certain.
                    if (auth.currentUser?.uid == null) {
                        print("No user logged in");
                    }
                    return Text("App has loaded");
                }
        ); 
    }
}

产生错误的步骤:

  1. 启动模拟器并按绿色箭头按钮运行应用程序
  2. 应用启动并运行后,按红色按钮停止执行。
  3. 按绿色箭头按钮重新启动应用程序

其他信息:

  1. 每个 Firebase 项目都使用 Cloud Firestore 和 Firebase Auth,我已在未来的构建器中通过以下方式对其进行了初始化:
auth = FirebaseAuth.instance;
authDiscussion = FirebaseAuth.instanceFor(app: snapshot.data![2]);
authComment = FirebaseAuth.instanceFor(app: snapshot.data![3]);
firestoreInstance = FirebaseFirestore.instance;
firestoreInstanceDiscussion = FirebaseFirestore.instanceFor(app: snapshot.data![2]);
firestoreInstanceComment = FirebaseFirestore.instanceFor(app: snapshot.data![3]);
prefs = snapshot.data![1]; // sharedPrefStore  
  1. 当我第一次启动应用程序时,我绝对没有错误,所有三个项目都可以访问它们各自的 Cloud Firestores 和 Firebase Auth。

  2. 另外,我正在 Android Studio 提供的 Pixel 2 (API 29) 模拟器上测试我的应用。

  3. 我一直在网上搜索解决方案,但大多数人在使用启用持久性的 Firebase 时都会遇到此错误(例如:Firebase Database crash SQLiteDatabaseLockedException)——我没有使用。

  4. 我已尝试禁用共享首选项,但无济于事。执行重新启动时,应用程序仍然崩溃。

我的假设: 我有一种预感,当我重新运行该应用程序时,它会重新构造 App,从而重新初始化我的三个 firebase 项目,从而导致错误。我试图通过在App 中的initializeState() 方法中放置一个打印来验证这个理论,以跟踪App 的重建时间。果然,只要initializeState() 被多次调用,我的应用就会崩溃。

但我不知道如何“告诉”应用不要重新初始化项目。

编辑#1: 我了解该错误与 SQLite 数据库被锁定有关。但是,(我不认为)我正在使用任何这样的数据库。我不使用 sqflite;相反,我的应用使用共享首选项在本地保存数据(因为它只保存少量数据),仅此而已。

编辑#2: 我已经更新了上面的代码 sn-p 以包含我正在测试的整个代码。如您所见,我没有在任何地方显式初始化 SQL 数据库。因此,Firebase.initializeApp 很可能在内部使用/锁定 SQL 数据库。而且由于我对 Firebase.initializeApp 进行了多次调用——所有这些都将访问同一个 SQL 数据库——这些调用相互阻塞,导致错误“SQL 数据库被锁定”。

如果确实如此,如果我等待每个 Firebase.initializeApp 调用完成,然后再为下一个 Firebase 项目再次调用它,错误应该会消失。

这是我目前的理论,我正在修改我的代码,一个接一个地初始化我的 firebase 项目。

解决方案: 我的理论(在“编辑#2”中提到)是有效的。我如上所述修改了我的代码,所有错误都消失了。

这是我使用 cmets 完成的工作代码。

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

/// Here is my updated code that initializes Firebase projects one after another to ensure they don't clash on the SQL database.
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    print("Constructing my app");
    super.initState();
  }

  final Future<SharedPreferences> sharedPrefStore =
      SharedPreferences.getInstance(); // This is used by my app later on and is not really relevant to the error.

  // Functions to initialize all 3 apps. I use these functions only help make the code look cleaner.
  Future<FirebaseApp> initFirebase3() {
    return Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key3",
            appId: "app-id3",
            projectId: "project-id3",
            messagingSenderId: "sender-id3"),
        name: "app3");
  }

  Future<FirebaseApp> initFirebase2() {
    return Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key2",
            appId: "app-id2",
            projectId: "project-id2",
            messagingSenderId: "sender-id2"),
        name: "app2");
  }

  Future<FirebaseApp> initFirebaseDefault() {
    return Firebase.initializeApp(
        options: FirebaseOptions(
            apiKey: "api-key1",
            appId: "app-id1",
            projectId: "project-id1",
            messagingSenderId: "sender-id1"));
  }

  @override
  Widget build(BuildContext context) {
    print("Building App.");
    return MaterialApp(
        title: 'My App',
        home: FutureBuilder(
            // sharedPrefStore and Firebase.initializeApp can run in parallel since they are independent of each other.
            future: Future.wait([initFirebaseDefault(), sharedPrefStore]),
            builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
              if (snapshot.hasError) {
                print("You have an error! ${snapshot.error.toString()}");
                return Text('Oops! Something went wrong. Please try again');
              } else if (snapshot.hasData) {
                // Initialize firestore and auth (global variables) using the completed future
                auth = FirebaseAuth.instance;

                firestoreInstance = FirebaseFirestore.instance;

                prefs = snapshot.data![1]; // sharedPrefStore

                // Now on to app2. Before we initialize app2, we must first check if it already exists. 
                // If it already exists and we try to initialize it again, we will get the error "app2 already exists"
                late FirebaseApp? app2;
                try {
                  app2 = Firebase.app("app2");
                } catch (e) {
                  print("app2 not initialized");
                  app2 = null;
                }
                if (app2 != null) {
                  auth2 = FirebaseAuth.instanceFor(app: app2);

                  firestoreInstance2 = FirebaseFirestore.instanceFor(app: app2);

                  // Same as above. Before we initialize app3, we must first check if it already exists. 
                  // If it already exists and we try to initialize it again, we will get the error "app3 already exists"
                  late FirebaseApp? app3;
                  try {
                    app3 = Firebase.app("app3");
                  } catch (e) {
                    print("app3 not initialized");
                    app3 = null;
                  }
                  if (app3 == null) {
                    return FutureBuilder(
                        future: initFirebase3(),
                        builder:
                            (context, AsyncSnapshot<FirebaseApp> snapshot) {
                          if (snapshot.hasError) {
                            print(
                                "You have an error! ${snapshot.error.toString()}");
                            return Text(
                                'Oops! Something went wrong. Please try again');
                          } else if (snapshot.hasData) {
                            auth3 = FirebaseAuth.instanceFor(app: snapshot.data!);

                            firestoreInstance3 = FirebaseFirestore.instanceFor(
                                app: snapshot.data!);
                            
                            return Scaffold(
                              body: Text("app has loaded"),
                            );
                          } else {
                            return Center(
                              child: CircularProgressIndicator(),
                            );
                          }
                        });
                  } else {
                    auth3 = FirebaseAuth.instanceFor(app: app3);

                    firestoreInstance3 = FirebaseFirestore.instanceFor(app: app3);
                    
                    return Scaffold(
                      body: Text("app has loaded"),
                    );
                  }
                }
                // app2 was null so we came here; now we must initialize app2
                return FutureBuilder(
                    future: initFirebase2(),
                    builder: (context, AsyncSnapshot<FirebaseApp> snapshot) {
                      if (snapshot.hasError) {
                        print(
                            "You have an error! ${snapshot.error.toString()}");
                        return Text(
                            'Oops! Something went wrong. Please try again');
                      } else if (snapshot.hasData) {
                        auth2 = FirebaseAuth.instanceFor(app: snapshot.data!);

                        firestoreInstance2 =
                            FirebaseFirestore.instanceFor(app: snapshot.data!);

                        // I assume that if app2 was not initialized neither was app3, hence I call initFirebase3().
                        return FutureBuilder(
                            future: initFirebase3(),
                            builder:
                                (context, AsyncSnapshot<FirebaseApp> snapshot) {
                              if (snapshot.hasError) {
                                print(
                                    "You have an error! ${snapshot.error.toString()}");
                                return Text(
                                    'Oops! Something went wrong. Please try again');
                              } else if (snapshot.hasData) {
                                auth3 = FirebaseAuth.instanceFor(
                                    app: snapshot.data!);

                                firestoreInstance3 =
                                    FirebaseFirestore.instanceFor(
                                        app: snapshot.data!);
                                
                                return Scaffold(
                                    body: Text("app has loaded"),
                                );
                              } else {
                                return Center(
                                    child: CircularProgressIndicator(),
                                );
                              }
                            });
                      } else {
                        return Center(
                          child: CircularProgressIndicator(),
                        );
                      }
                    });
              } else {
                return Center(
                  child: CircularProgressIndicator(),
                );
              }
            }));
  }
}

如果需要更多信息,请告诉我。任何帮助将不胜感激。

谢谢

【问题讨论】:

    标签: android ios firebase flutter


    【解决方案1】:

    这不是 firebase 问题,而是 SQFlite 问题。错误说:

    Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY)
    E/AndroidRuntime(16200)
    

    这与初始化应用程序后代码中发生的情况有关。如果您正在获取大量数据,导致对 SQFlite 数据库的大量写入,它会导致它锁定。

    【讨论】:

    • 我实际上并没有获取大量数据。我只从一个 firebase 项目中读取数据,而该项目在 Cloud Firestore 中也只有一个小文档(只有 5 个字段)。我没有写任何东西。另外,如果它与获取大量数据有关,那么应用程序不会每次都崩溃吗?为什么只有重启的时候才会崩溃?
    • 错误明显指向sql,分享初始化相关的代码。也许它被困在一个重建它的循环中。要验证这一点,请注释掉触发写入 SQLite 的代码并查看它是否崩溃。
    • 我在帖子中更新了我的代码 sn-p 以显示我的整个测试代码。如您所见,我没有在任何地方初始化/调用 SQL 数据库。因此,我认为链接多个 firebase 项目是问题所在(请参阅帖子中的“编辑#2”部分)。
    猜你喜欢
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-26
    • 2019-04-15
    • 2019-05-22
    • 2019-05-02
    相关资源
    最近更新 更多