Minecraft 1.8.9 Forge模组中Firebase Admin SDK setValue无响应问题
Minecraft 1.8.9 Forge模组集成Firebase实时数据库无响应问题解决
问题背景
开发Minecraft 1.8.9 Forge模组时,集成Firebase实时数据库遇到以下问题:
- 模组正常加载,控制台打印
Test Mod is running!和[DEFAULT],说明Firebase初始化成功 - 所有
setValue调用无响应,数据库无数据更新,无错误日志 - 调用带
CountDownLatch的update方法时,游戏无限挂起,数据库仍未收到请求
环境:Windows x64、Java 1.8 JDK、Gradle 4.0(适配Forge 1.8.9)
现有代码
build.gradle
buildscript { repositories { google() mavenCentral() maven { name "forge" url "https://files.minecraftforge.net/maven" } maven { name "google" url "https://maven.google.com" } } dependencies { classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT' } } apply plugin: 'net.minecraftforge.gradle.forge' allprojects { repositories { google() mavenCentral() } } version = "1.0a" group= "com.oshotz.testmod" archivesBaseName = "testmod" minecraft { version = "1.8.9-11.15.1.2318-1.8.9" runDir = "run" mappings = "stable_20" } configurations { shade implementation.extendsFrom shade } dependencies { shade 'com.google.firebase:firebase-admin:9.2.0' shade 'com.google.firebase:firebase-database:9.2.0' } jar { configurations.shade.each { dep -> from(project.zipTree(dep)){ exclude 'META-INF', 'META-INF/**' } } } processResources { inputs.property "version", project.version inputs.property "mcversion", project.minecraft.version from(sourceSets.main.resources.srcDirs) { include 'mcmod.info' expand 'version':project.version, 'mcversion':project.minecraft.version } from(sourceSets.main.resources.srcDirs) { exclude 'mcmod.info' } }
TestMod.java
package com.oshotz.testmod; import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.CountDownLatch; import com.google.auth.oauth2.GoogleCredentials; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventHandler; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; @Mod(modid = Reference.MOD_ID, name = Reference.MOD_NAME, version = Reference.VERSION) public class TestMod { private FirebaseDatabase firebaseDatabase; @EventHandler public void preInit(FMLPreInitializationEvent event) { } @EventHandler public void init(FMLInitializationEvent event) throws IOException { System.out.println("Test Mod is running!"); FileInputStream serviceAccount = new FileInputStream("C:\\Users\\Brandon\\credentials.json"); FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .setDatabaseUrl("https://<redacted>.firebaseio.com") .build(); FirebaseApp defaultApp = FirebaseApp.initializeApp(options); firebaseDatabase = FirebaseDatabase.getInstance("https://<redacted>.firebaseio.com"); System.out.println(defaultApp.getName()); DatabaseReference ref = firebaseDatabase.getReference("message"); ref.setValue("Hello, World!", null); ref.setValue("testing!!", new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError de, DatabaseReference dr) { System.out.println("finish"); } }); MinecraftForge.EVENT_BUS.register(new ChatEventHandler()); MinecraftForge.EVENT_BUS.register(new ClientTickEventHandler()); MinecraftForge.EVENT_BUS.register(new ChestEventHandler()); // update("hello world"); } @EventHandler public void postInit(FMLPostInitializationEvent event) { } public void update(String value) { update(value, "message"); } public void update(String value, String key) { try { DatabaseReference ref = firebaseDatabase.getReference(key); final CountDownLatch latch = new CountDownLatch(1); ref.setValue(value, (databaseError, databaseReference) -> { if (databaseError != null) { System.out.println("Data could not be saved " + databaseError.getMessage()); } else { System.out.println("Data saved successfully."); } latch.countDown(); }); latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } }
问题根源
- 线程模型冲突:Firebase Admin SDK的网络操作依赖自身线程池,但Forge主线程是单线程阻塞模型,直接在主线程执行Firebase异步操作会导致任务无法调度,甚至锁死主线程。
- 依赖与Shade配置问题:Firebase 9.2.0部分依赖与Java 8兼容性不佳,且当前Jar任务排除了所有
META-INF文件,导致Firebase需要的服务配置无法加载。 - 硬编码路径与阻塞等待:硬编码凭证文件路径在打包后会失效,
CountDownLatch.await()直接阻塞主线程,导致游戏挂起。
解决方案
1. 调整Firebase依赖与Shade配置
修改build.gradle中的依赖和Jar打包规则:
dependencies { // 使用兼容Java 8的Firebase版本 shade 'com.google.firebase:firebase-admin:8.1.0' shade 'com.google.firebase:firebase-database:8.1.0' // 补充Firebase依赖的Netty库 shade 'io.netty:netty-all:4.1.90.Final' } jar { configurations.shade.each { dep -> from(project.zipTree(dep)){ // 仅排除签名文件,保留其他META-INF配置 exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA' } } }
2. 异步执行Firebase操作
绝对不要在Forge主线程中执行Firebase操作或阻塞等待,改用独立线程或Forge异步任务:
修改init方法中的Firebase初始化与写操作:
@EventHandler public void init(FMLInitializationEvent event) throws IOException { System.out.println("Test Mod is running!"); // 从资源目录读取凭证文件,避免硬编码路径 InputStream serviceAccount = getClass().getResourceAsStream("/credentials.json"); if (serviceAccount == null) { System.err.println("Error: credentials.json not found in resources!"); return; } FirebaseOptions options = new FirebaseOptions.Builder() .setCredentials(GoogleCredentials.fromStream(serviceAccount)) .setDatabaseUrl("https://<redacted>.firebaseio.com") .build(); // 在独立线程中初始化Firebase并执行操作 new Thread(() -> { FirebaseApp defaultApp = FirebaseApp.initializeApp(options); firebaseDatabase = FirebaseDatabase.getInstance(defaultApp); System.out.println("Firebase initialized: " + defaultApp.getName()); DatabaseReference ref = firebaseDatabase.getReference("message"); ref.setValue("testing!!", (de, dr) -> { if (de != null) { System.err.println("Firebase write failed: " + de.getMessage()); } else { System.out.println("Firebase write succeeded!"); } }); }).start(); // 注册事件处理器... }
修改update方法,移除阻塞逻辑:
public void update(String value, String key) { if (firebaseDatabase == null) { System.err.println("Firebase not initialized!"); return; } // 异步执行写操作 new Thread(() -> { DatabaseReference ref = firebaseDatabase.getReference(key); ref.setValue(value, (databaseError, databaseReference) -> { if (databaseError != null) { System.err.println("Save failed: " + databaseError.getMessage()); } else { System.out.println("Save succeeded."); } }); }).start(); }
3. 验证数据库规则
临时放开数据库读写权限(测试用,上线后需收紧):
{ "rules": { ".read": true, ".write": true } }
4. 主线程调度注意事项
如果需要在Firebase回调中修改游戏状态(如发送聊天消息),必须调度回Forge主线程:
// 在Firebase回调中调用 Minecraft.getMinecraft().addScheduledTask(() -> { // 执行主线程操作,比如发送聊天消息 Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Firebase update done!")); });
内容的提问来源于stack exchange,提问作者simplexshotz
相关产品推荐
相关产品推荐

