Android端SQLiteDatabaseLockedException(code 5 SQLITE_BUSY)崩溃问题求助
排查SQLiteDatabaseLockedException崩溃问题
部分设备出现崩溃,触发Fatal Exception: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY),具体堆栈如下:
Fatal Exception: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5 SQLITE_BUSY) at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java) at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:715) at android.database.sqlite.SQLiteConnection.setLocaleFromConfiguration(SQLiteConnection.java:468) at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:269) at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:211) at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:505) at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:206) at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:198) at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:919) at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:899) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:763) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:752) at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:373) at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316) at com.zingoy.app.getlink.GetLinkStoreDBHandler.addC2CStore(GetLinkStoreDBHandler.java:94) at com.zingoy.app.home.HomeActivity.lambda$setAllStores$8(HomeActivity.java:624) at java.lang.Thread.run(Thread.java:1012)
已查阅多种解决方案但未定位代码问题,以下是相关代码:
HomeActivity中数据库调用逻辑
public void setAllStores(final AllStoreFetchListener listener) { if (DateUtil.isTimeMoreThanHours(mPreference.getAllStoresFetchedTime(), 24)) { new CommonApi().withAllStoreListener(response -> { try { if (response == null) return; if (CollectionUtils.isEmpty(response.stores)) return; mPreference.setAllStoresFetchedTime(); Handler handler = new Handler(Looper.getMainLooper()); new Thread(() -> { new GetLinkStoreDBHandler(HomeActivity.this).addStore(response.stores); this.runOnUiThread(() -> { if (listener != null) { listener.onAllStoreFetched(); } }); }).start(); } catch (Exception e) { FirebaseCrashlytics.getInstance().log(e.getMessage()); e.printStackTrace(); } }).getStoreData(); } if (DateUtil.isTimeMoreThanHours(mPreference.getC2CStoresFetchedTime(), 6)) { new SellerApi().withSellerStoreListener(response -> { try { if (response == null) return; if (CollectionUtils.isEmpty(response.stores)) return; mPreference.setC2CStoresFetchedTime(); Handler handler = new Handler(Looper.getMainLooper()); new Thread(() -> { new GetLinkStoreDBHandler(HomeActivity.this).addC2CStore(response.stores); this.runOnUiThread(() -> { if (listener != null) { listener.onC2CStoreFetched(); } }); }).start(); } catch (Exception e) { FirebaseCrashlytics.getInstance().log(e.getMessage()); e.printStackTrace(); } }).getStoreData(); } }
GetLinkStoreDBHandler实现
public class GetLinkStoreDBHandler extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 3; private static final String DATABASE_NAME = "GetLinkStores"; private static final String TABLE_STORE = "Store"; private static final String TABLE_C2C_STORE = "SellerStore"; // StoreDbRows Table Columns names private static final String KEY_STORE_ID = "storeId"; private static final String KEY_STORE_SLUG = "storeSlug"; private static final String KEY_STORE_NAME = "storeName"; private static final String KEY_STORE_LINK = "storeLink"; private static final String KEY_STORE_IMAGE_URL = "storeImageUrl"; public GetLinkStoreDBHandler(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } // Creating Tables @Override public void onCreate(SQLiteDatabase db) { String CREATE_StoreDbRowS_TABLE = "CREATE TABLE " + TABLE_STORE + "(" + KEY_STORE_ID + " INTEGER PRIMARY KEY," + KEY_STORE_NAME + " TEXT," + KEY_STORE_LINK + " TEXT," + KEY_STORE_IMAGE_URL + " TEXT" + ")"; db.execSQL(CREATE_StoreDbRowS_TABLE); String CREATE_SELLER_TABLE = "CREATE TABLE " + TABLE_C2C_STORE + "(" + KEY_STORE_SLUG + " TEXT," + KEY_STORE_NAME + " TEXT," + KEY_STORE_IMAGE_URL + " TEXT" + ")"; db.execSQL(CREATE_SELLER_TABLE); } // Upgrading database @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Drop older table if existed try { db.execSQL("DROP TABLE IF EXISTS " + TABLE_STORE); db.execSQL("DROP TABLE IF EXISTS " + TABLE_C2C_STORE); } catch (Exception e) { e.printStackTrace(); FirebaseCrashlytics.getInstance().log(e.getMessage()); } finally { // Create tables again onCreate(db); } } // Adding new Store row public void addStore(List<Store> getLinkStore) { SQLiteDatabase db = this.getWritableDatabase(); try { for (Store stores : getLinkStore) { ContentValues values = new ContentValues(); values.put(KEY_STORE_ID, stores.id); values.put(KEY_STORE_NAME, stores.name); values.put(KEY_STORE_LINK, stores.url); values.put(KEY_STORE_IMAGE_URL, stores.photoUrl); if (!isStoreAvailable(stores.id)) { // Inserting Row db.insert(TABLE_STORE, null, values); } } } catch (Exception e) { if (e.getMessage() == null) return; FirebaseCrashlytics.getInstance().log(e.getMessage()); } finally { if (db.isOpen()) db.close(); } }
问题定位
核心原因是多实例并发访问SQLite数据库:
setAllStores方法中,两个异步请求的回调会同时触发,各自在新线程中创建GetLinkStoreDBHandler实例,调用addStore和addC2CStore方法。- SQLite是单写锁机制,当一个连接持有写锁时,另一个连接请求写锁会直接抛出
SQLiteDatabaseLockedException。 GetLinkStoreDBHandler未实现单例,每次操作都新建实例,无法复用数据库连接,加剧了锁冲突概率。
解决方案
1. 将DBHandler改为单例模式
确保应用中只有一个DBHandler实例,避免多实例竞争数据库锁:
public class GetLinkStoreDBHandler extends SQLiteOpenHelper { private static GetLinkStoreDBHandler sInstance; public static synchronized GetLinkStoreDBHandler getInstance(Context context) { if (sInstance == null) { // 使用Application Context避免内存泄漏 sInstance = new GetLinkStoreDBHandler(context.getApplicationContext()); } return sInstance; } private GetLinkStoreDBHandler(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } // 原有方法保持不变 }
调用时替换为:
GetLinkStoreDBHandler.getInstance(HomeActivity.this).addStore(response.stores);
2. 串行化数据库写操作
使用单线程执行器处理所有数据库写操作,确保同一时间只有一个写任务执行:
// 在HomeActivity中初始化单线程执行器 private ExecutorService mDbExecutor = Executors.newSingleThreadExecutor(); // 替换原线程创建代码 mDbExecutor.execute(() -> { GetLinkStoreDBHandler.getInstance(HomeActivity.this).addStore(response.stores); runOnUiThread(() -> { if (listener != null) { listener.onAllStoreFetched(); } }); });
3. 优化批量插入逻辑
使用事务减少锁持有时间,提升插入性能:
public void addStore(List<Store> getLinkStore) { SQLiteDatabase db = this.getWritableDatabase(); try { db.beginTransaction(); // 开启事务 for (Store stores : getLinkStore) { ContentValues values = new ContentValues(); values.put(KEY_STORE_ID, stores.id); values.put(KEY_STORE_NAME, stores.name); values.put(KEY_STORE_LINK, stores.url); values.put(KEY_STORE_IMAGE_URL, stores.photoUrl); if (!isStoreAvailable(stores.id)) { db.insert(TABLE_STORE, null, values); } } db.setTransactionSuccessful(); // 标记事务成功 } catch (Exception e) { if (e.getMessage() != null) { FirebaseCrashlytics.getInstance().log(e.getMessage()); } } finally { db.endTransaction(); // 结束事务 if (db.isOpen()) { db.close(); } } }
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

