如何在Android平台的LibGDX应用中使用SQLite数据库
Hey there! Since you're building an Android game with LibGDX and need SQLite for user authentication, game stats, and timestamp storage—here's a practical, step-by-step solution that aligns with your relational database design:
LibGDX doesn't include built-in SQLite support, but since you're targeting Android specifically, we can leverage Android's native SQLiteOpenHelper to handle database creation, upgrades, and operations. We'll wrap this in a clean, reusable layer that integrates smoothly with your LibGDX game logic.
1. Create a SQLite Database Helper Class
This class manages database setup and versioning. It will create your user and game stats tables based on your relational design:
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class GameDatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "GameDB.db"; private static final int DATABASE_VERSION = 1; // User Table Schema private static final String TABLE_USERS = "users"; private static final String COL_USER_ID = "user_id"; private static final String COL_USERNAME = "username"; private static final String COL_PASSWORD = "password"; // *Always encrypt this!* // Game Stats Table Schema private static final String TABLE_STATS = "game_stats"; private static final String COL_STAT_ID = "stat_id"; private static final String COL_USER_FK = "user_id"; private static final String COL_TIMESTAMP = "timestamp"; private static final String COL_SCORE = "score"; public GameDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // Create Users Table String createUsersTable = String.format( "CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s TEXT UNIQUE NOT NULL, %s TEXT NOT NULL)", TABLE_USERS, COL_USER_ID, COL_USERNAME, COL_PASSWORD ); db.execSQL(createUsersTable); // Create Game Stats Table (with foreign key to users) String createStatsTable = String.format( "CREATE TABLE %s (%s INTEGER PRIMARY KEY AUTOINCREMENT, %s INTEGER NOT NULL, %s INTEGER NOT NULL, %s INTEGER NOT NULL, FOREIGN KEY(%s) REFERENCES %s(%s))", TABLE_STATS, COL_STAT_ID, COL_USER_FK, COL_TIMESTAMP, COL_SCORE, COL_USER_FK, TABLE_USERS, COL_USER_ID ); db.execSQL(createStatsTable); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // *Important: In production, add data migration logic instead of dropping tables!* db.execSQL("DROP TABLE IF EXISTS " + TABLE_STATS); db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS); onCreate(db); } }
Critical Note: Never store plain-text passwords. Use a hashing algorithm like SHA-256 (or stronger) to encrypt passwords before saving them.
2. Integrate the Database with LibGDX
LibGDX's core logic is platform-agnostic, so we'll pass the database helper from your Android launcher to your game's main class:
// AndroidLauncher.java (Android-specific entry point) import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; public class AndroidLauncher extends AndroidApplication { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); // Initialize the database helper GameDatabaseHelper dbHelper = new GameDatabaseHelper(this); // Pass it to your LibGDX game instance initialize(new MyGame(dbHelper), config); } }
Then in your main game class:
// MyGame.java (LibGDX core game class) import com.badlogic.gdx.Game; public class MyGame extends Game { private GameDataManager dataManager; public MyGame(GameDatabaseHelper dbHelper) { dataManager = new GameDataManager(dbHelper); } @Override public void create() { // Use dataManager for all database operations in your game screens } @Override public void dispose() { super.dispose(); dataManager.close(); // Clean up database resources } }
3. Build a Data Access Layer (DAO)
Wrap all database operations in a GameDataManager class to keep your game logic clean and decoupled:
import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteConstraintException; import android.content.ContentValues; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; public class GameDataManager { private SQLiteDatabase db; private GameDatabaseHelper dbHelper; public GameDataManager(GameDatabaseHelper helper) { dbHelper = helper; db = helper.getWritableDatabase(); } // Register a new user public boolean registerUser(String username, String password) { String encryptedPassword = encryptPassword(password); ContentValues values = new ContentValues(); values.put(GameDatabaseHelper.COL_USERNAME, username); values.put(GameDatabaseHelper.COL_PASSWORD, encryptedPassword); try { long insertId = db.insert(GameDatabaseHelper.TABLE_USERS, null, values); return insertId != -1; // Return true if insertion succeeded } catch (SQLiteConstraintException e) { return false; // Username already exists } } // Validate user login public boolean loginUser(String username, String password) { String encryptedPassword = encryptPassword(password); String[] projection = {GameDatabaseHelper.COL_USER_ID}; String selection = String.format("%s = ? AND %s = ?", GameDatabaseHelper.COL_USERNAME, GameDatabaseHelper.COL_PASSWORD); String[] selectionArgs = {username, encryptedPassword}; Cursor cursor = db.query( GameDatabaseHelper.TABLE_USERS, projection, selection, selectionArgs, null, null, null ); boolean isValid = cursor.getCount() > 0; cursor.close(); return isValid; } // Save game statistics for a user public long saveGameStat(int userId, long timestamp, int score) { ContentValues values = new ContentValues(); values.put(GameDatabaseHelper.COL_USER_FK, userId); values.put(GameDatabaseHelper.COL_TIMESTAMP, timestamp); values.put(GameDatabaseHelper.COL_SCORE, score); return db.insert(GameDatabaseHelper.TABLE_STATS, null, values); } // Fetch all game stats for a user public List<GameStat> getUserStats(int userId) { List<GameStat> stats = new ArrayList<>(); String selection = GameDatabaseHelper.COL_USER_FK + " = ?"; String[] selectionArgs = {String.valueOf(userId)}; String sortOrder = GameDatabaseHelper.COL_TIMESTAMP + " DESC"; Cursor cursor = db.query( GameDatabaseHelper.TABLE_STATS, null, selection, selectionArgs, null, null, sortOrder ); if (cursor.moveToFirst()) { do { int statId = cursor.getInt(cursor.getColumnIndexOrThrow(GameDatabaseHelper.COL_STAT_ID)); long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(GameDatabaseHelper.COL_TIMESTAMP)); int score = cursor.getInt(cursor.getColumnIndexOrThrow(GameDatabaseHelper.COL_SCORE)); stats.add(new GameStat(statId, userId, timestamp, score)); } while (cursor.moveToNext()); } cursor.close(); return stats; } // Password encryption helper (use a stronger method like Argon2 in production) private String encryptPassword(String password) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8)); StringBuilder hexBuilder = new StringBuilder(); for (byte b : hash) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) hexBuilder.append('0'); hexBuilder.append(hex); } return hexBuilder.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Password encryption failed", e); } } // Clean up database resources public void close() { dbHelper.close(); } } // Game Stat data model class GameStat { private int statId; private int userId; private long timestamp; private int score; public GameStat(int statId, int userId, long timestamp, int score) { this.statId = statId; this.userId = userId; this.timestamp = timestamp; this.score = score; } // Add getters as needed for your game logic }
- Thread Safety: Never run database operations on the UI thread—use
AsyncTask, Kotlin Coroutines, or a background thread pool to avoid ANRs (Application Not Responding) errors. - Data Migration: For future database updates, replace the table-dropping logic in
onUpgradewith proper data migration (e.g., altering tables instead of recreating them) to preserve user data. - Database Encryption: For sensitive data, consider using SQLCipher to encrypt the entire database file, preventing unauthorized access if the device is rooted.
- Resource Management: Always close
CursorandSQLiteDatabaseinstances when done to avoid memory leaks.
内容的提问来源于stack exchange,提问作者Shahzeb Manjlai

