【发布时间】:2021-07-29 18:30:02
【问题描述】:
背景: 我正在尝试开发一个与 3 个不同的 Firebase 项目连接的颤振应用程序。我按照 FlutterFire 上的说明进行操作,特别是在 FlutterFire Overview 和 Core | 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");
}
);
}
}
产生错误的步骤:
- 启动模拟器并按绿色箭头按钮运行应用程序
- 应用启动并运行后,按红色按钮停止执行。
- 按绿色箭头按钮重新启动应用程序
其他信息:
- 每个 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
-
当我第一次启动应用程序时,我绝对没有错误,所有三个项目都可以访问它们各自的 Cloud Firestores 和 Firebase Auth。
-
另外,我正在 Android Studio 提供的 Pixel 2 (API 29) 模拟器上测试我的应用。
-
我一直在网上搜索解决方案,但大多数人在使用启用持久性的 Firebase 时都会遇到此错误(例如:Firebase Database crash SQLiteDatabaseLockedException)——我没有使用。
-
我已尝试禁用共享首选项,但无济于事。执行重新启动时,应用程序仍然崩溃。
我的假设:
我有一种预感,当我重新运行该应用程序时,它会重新构造 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