You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题根源

  1. 线程模型冲突:Firebase Admin SDK的网络操作依赖自身线程池,但Forge主线程是单线程阻塞模型,直接在主线程执行Firebase异步操作会导致任务无法调度,甚至锁死主线程。
  2. 依赖与Shade配置问题:Firebase 9.2.0部分依赖与Java 8兼容性不佳,且当前Jar任务排除了所有META-INF文件,导致Firebase需要的服务配置无法加载。
  3. 硬编码路径与阻塞等待:硬编码凭证文件路径在打包后会失效,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 16:17:03