在 Android 開發中,預先準備好資料庫(如字典、離線地圖、預設設定)並隨 APK 發布,是常見需求。本文詳細說明如何將 SQLite 資料庫檔案放入 assets 目錄,並在首次啟動時自動複製到應用專屬資料目錄,附帶完整程式碼與進階方案(支援 SD 卡備份)。
src/main/assets/ 下建立資料庫檔案,例如 test.db。public class DbTestActivity extends Activity {
private static final String DB_NAME = "test.db";
private static final String DB_PATH = "/data/data/com.test.db/databases/"; // 依包名調整
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
copyDatabaseIfNeeded();
testDatabase();
}
private void copyDatabaseIfNeeded() {
File dbFile = new File(DB_PATH + DB_NAME);
if (!dbFile.exists()) {
// 確保目錄存在
new File(DB_PATH).mkdirs();
try (InputStream is = getAssets().open(DB_NAME);
OutputStream os = new FileOutputStream(dbFile)) {
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void testDatabase() {
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH + DB_NAME, null);
Cursor cursor = db.rawQuery("SELECT * FROM test", null);
if (cursor.moveToFirst()) {
byte[] blob = cursor.getBlob(0);
String text = new String(blob, StandardCharsets.UTF_8).trim();
Log.d("DB_TEST", "資料讀取成功: " + text);
}
cursor.close();
db.close();
}
}
/data/data/包名/databases/ 為 Android 預設資料庫目錄,應用解除安裝時自動清理。StandardCharsets.UTF_8 防止亂碼。適用於資料庫較大、或需讓使用者手動備份/匯出的場景。
public class ReleaseDatabaseHelper {
private final Context context;
private final String dbName;
private final File dbDir; // 例如 Environment.getExternalStorageDirectory() + "/myapp/db/"
public ReleaseDatabaseHelper(Context context, String dbName, File dbDir) {
this.context = context;
this.dbName = dbName;
this.dbDir = dbDir;
}
public SQLiteDatabase openOrCopyDatabase() {
if (!dbDir.exists()) dbDir.mkdirs();
File target = new File(dbDir, dbName);
if (!target.exists()) {
copyFromAssetsOrRaw(target);
}
return SQLiteDatabase.openOrCreateDatabase(target, null);
}
private void copyFromAssetsOrRaw(File target) {
try (InputStream is = context.getAssets().open(dbName); // 或 getResources().openRawResource(R.raw.db_name)
FileOutputStream fos = new FileOutputStream(target)) {
byte[] buffer = new byte[8192];
int count;
while ((count = is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
} catch (IOException e) {
Log.e("DB_HELPER", "複製資料庫失敗", e);
}
}
}
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 資料庫複製後讀取報 "no such table" | assets 中的 .db 檔案其實是空的或結構錯誤 | 用 DB Browser for SQLite 開啟確認結構與資料 |
| 檔案過大 (>1MB) 導致 assets 打包失敗 | 舊版 Android Gradle Plugin 對單一 asset 檔有限制 | 升級 AGP、或改用 split APK / raw 資源 / 網路下載 |
| 中文亂碼 | 資料庫建立時編碼非 UTF-8,或讀取時未指定字集 | 建表時指定 CHARSET=utf8;讀取時用 new String(bytes, "UTF-8") |
| Android 10+ Scoped Storage 導致無法寫入外部儲存 | 權限模型變更 | 優先用內部儲存;外部儲存需請求 MANAGE_EXTERNAL_STORAGE 或用 MediaStore |
assets → 內部儲存,最簡單、相容性最好。createFromAsset)。SQLiteOpenHelper.onUpgrade() 或 Room Migration,避免覆蓋使用者資料。掌握這套流程,即可輕鬆實現「預置資料庫隨 APK 發布、首次自動部署、升級平滑遷移」的完整生命週期管理。