繁体   English   中英

在一个 Flutter 应用中集成多个 Firebase 项目会导致应用崩溃

[英]Integrating multiple Firebase projects in one Flutter app causes app to crash

背景:我正在尝试开发一个连接 3 个不同 Firebase 项目的 Flutter 应用程序。 我遵循 FlutterFire 上的说明,特别是FlutterFire 概述核心 | 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 数据库崩溃 SQLiteDatabaseLockedException ) - 我没有使用。

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

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

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

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

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

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

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

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

这是我完整的,带注释的工作代码。

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(),
                );
              }
            }));
  }
}

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

谢谢

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM