Android集成SQLCipher遇SQLiteDatabaseLockedException数据库锁定问题求助
Android SQLCipher 数据库锁定问题排查与解决
问题描述
集成SQLCipher时触发数据库锁定错误,核心日志如下:
E/testmsg: onCreate activity called E/testmsg: getAllEmail called E/testmsg: onCreate db called E/testmsg: getDataBase called I/SQLiteConnection: Database keying operation returned:0 E/SQLiteLog: (5) database is locked in "SELECT COUNT(*) FROM sqlite_schema;" E/SQLiteDatabase: Failed to open database '/data/user/0/com.example.newsqlcipher/databases/EDMT.db'. android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: SELECT COUNT(*) FROM sqlite_schema;
项目核心文件如下:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.NewSQLCipher" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
DBHelper.java
package com.example.newsqlcipher; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.util.Log; import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteOpenHelper; import java.io.File; import java.util.ArrayList; import java.util.List; public class DBHelper extends SQLiteOpenHelper { private static DBHelper instance; Context c; private static final int DATABASE_VER=1; public static final String DATABASE_NAME="EDMT.db"; public static final String TABLE_NAME="CONTACTS"; public static final String COLUMN_EMAIL="EMAIL"; private static final String SQL_CREATE_TABLE_QUERY="CREATE TABLE "+TABLE_NAME+" ("+COLUMN_EMAIL+" TEXT PRIMARY KEY)"; private static final String SQL_DELETE_TABLE_QUERY="DROP TABLE IF EXISTS "+TABLE_NAME; public String dbpath; SQLiteDatabase myDB; public static final String PASS_PHARSE="!@#ABC"; public DBHelper(Context context) { super(context,DATABASE_NAME,null,DATABASE_VER); c=context; } static public synchronized DBHelper getInstance(Context context) { if (instance == null) { instance = new DBHelper(context); } return instance; } public SQLiteDatabase getDataBase() { Log.e("testmsg","getDataBase called"); if(myDB == null) { try{ System.loadLibrary("sqlcipher"); File databaseFile = c.getDatabasePath(DATABASE_NAME); databaseFile.mkdirs(); myDB = SQLiteDatabase.openOrCreateDatabase(databaseFile,PASS_PHARSE,null,null,null); myDB.execSQL(SQL_CREATE_TABLE_QUERY); } catch (Exception e) { Log.e("err:"+c.toString(),e.getMessage()); if(myDB != null) { myDB.close(); } } } return myDB; } @Override public void onCreate(SQLiteDatabase db) { Log.e("testmsg","onCreate db called"); getDataBase(); dbpath = String.valueOf(c.getDatabasePath(DATABASE_NAME)); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { sqLiteDatabase.execSQL(SQL_DELETE_TABLE_QUERY); onCreate(sqLiteDatabase); } public void insertNewEmail(String email) { SQLiteDatabase db = instance.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(COLUMN_EMAIL,email); db.insert(TABLE_NAME,null,values); db.close(); } public List<String> getAllEmail() { Log.e("testmsg","getAllEmail called"); SQLiteDatabase db = instance.getWritableDatabase(); Cursor cursor = db.rawQuery(String.format("SELECT * FROM '%s';",TABLE_NAME),null); List<String> emails = new ArrayList<>(); if (cursor.moveToFirst()) { while(!cursor.isAfterLast()) { String email = cursor.getString(0); emails.add(email); cursor.moveToNext(); } } cursor.close(); db.close(); return emails; } }
MainActivity.java
package com.example.newsqlcipher; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; import net.zetetic.database.sqlcipher.SQLiteDatabase; public class MainActivity extends AppCompatActivity { Button btnAdd,btnUpdate,btnDelete; EditText edtEmail; ListView lstEmails; String saveEmail=""; DBHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); System.loadLibrary("sqlcipher"); //SQLiteDatabase.loadLibs(this); dbHelper = new DBHelper(this); lstEmails = (ListView)findViewById(R.id.lstEmails); lstEmails.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String item = (String)lstEmails.getItemAtPosition(position); edtEmail.setText(item); saveEmail = item; } }); edtEmail = (EditText)findViewById(R.id.edtEmail); btnAdd = (Button)findViewById(R.id.btnAdd); btnAdd.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { DBHelper.getInstance(MainActivity.this).insertNewEmail(edtEmail.getText().toString()); reloadEmails(); Toast.makeText(MainActivity.this, ""+dbHelper.dbpath, Toast.LENGTH_LONG).show(); } }); Log.e("testmsg","onCreate activity called"); reloadEmails(); } private void reloadEmails() { ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,android.R.id.text1, DBHelper.getInstance(this).getAllEmail()); lstEmails.setAdapter(adapter); } }
错误原因
- 重复打开数据库:
onCreate回调中手动调用getDataBase()打开连接,同时getWritableDatabase()又会触发一次打开操作,两个连接争抢同一数据库导致锁定。 - 表创建逻辑冲突:跳过
SQLiteOpenHelper的生命周期,在自定义方法中执行CREATE TABLE,与系统回调的初始化逻辑冲突。 - 资源管理混乱:每次操作都打开并关闭数据库,频繁的连接切换增加了冲突概率,且
rawQuery中给表名加单引号会导致SQL语法错误。
解决方案
修改后的DBHelper.java
package com.example.newsqlcipher; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.util.Log; import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteOpenHelper; import java.util.ArrayList; import java.util.List; public class DBHelper extends SQLiteOpenHelper { private static DBHelper instance; private static final int DATABASE_VER = 1; public static final String DATABASE_NAME = "EDMT.db"; public static final String TABLE_NAME = "CONTACTS"; public static final String COLUMN_EMAIL = "EMAIL"; private static final String SQL_CREATE_TABLE_QUERY = "CREATE TABLE " + TABLE_NAME + " (" + COLUMN_EMAIL + " TEXT PRIMARY KEY)"; private static final String SQL_DELETE_TABLE_QUERY = "DROP TABLE IF EXISTS " + TABLE_NAME; public String dbpath; public static final String PASS_PHRASE = "!@#ABC"; static { System.loadLibrary("sqlcipher"); } private DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VER, new SQLiteDatabase.OpenParams.Builder() .setKey(PASS_PHRASE) .build()); dbpath = String.valueOf(context.getDatabasePath(DATABASE_NAME)); } public static synchronized DBHelper getInstance(Context context) { if (instance == null) { instance = new DBHelper(context.getApplicationContext()); } return instance; } @Override public void onCreate(SQLiteDatabase db) { Log.e("testmsg", "onCreate db called"); db.execSQL(SQL_CREATE_TABLE_QUERY); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { sqLiteDatabase.execSQL(SQL_DELETE_TABLE_QUERY); onCreate(sqLiteDatabase); } public void insertNewEmail(String email) { SQLiteDatabase db = getWritableDatabase(); try { ContentValues values = new ContentValues(); values.put(COLUMN_EMAIL, email); db.insert(TABLE_NAME, null, values); } finally { db.close(); } } public List<String> getAllEmail() { Log.e("testmsg", "getAllEmail called"); SQLiteDatabase db = getReadableDatabase(); Cursor cursor = null; List<String> emails = new ArrayList<>(); try { cursor = db.rawQuery("SELECT * FROM " + TABLE_NAME, null); if (cursor.moveToFirst()) { do { String email = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_EMAIL)); emails.add(email); } while (cursor.moveToNext()); } } finally { if (cursor != null) { cursor.close(); } db.close(); } return emails; } }
修改后的MainActivity.java
package com.example.newsqlcipher; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { Button btnAdd, btnUpdate, btnDelete; EditText edtEmail; ListView lstEmails; String saveEmail = ""; DBHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = DBHelper.getInstance(this); lstEmails = findViewById(R.id.lstEmails); lstEmails.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String item = (String) lstEmails.getItemAtPosition(position); edtEmail.setText(item); saveEmail = item; } }); edtEmail = findViewById(R.id.edtEmail); btnAdd = findViewById(R.id.btnAdd); btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dbHelper.insertNewEmail(edtEmail.getText().toString()); reloadEmails(); Toast.makeText(MainActivity.this, "" + dbHelper.dbpath, Toast.LENGTH_LONG).show(); } }); Log.e("testmsg", "onCreate activity called"); reloadEmails(); } private void reloadEmails() { ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, dbHelper.getAllEmail()); lstEmails.setAdapter(adapter); } }
关键修改说明
- 统一连接管理:移除自定义
getDataBase()方法,完全依赖SQLiteOpenHelper的原生方法获取连接,避免重复打开。 - 正确初始化加密库:通过
OpenParams在构造方法中传入密码,替代手动调用openOrCreateDatabase。 - 修复SQL语法:移除表名的单引号,避免被解析为字符串常量。
- 优化资源释放:用
try-finally确保Cursor和数据库连接被正确关闭,避免泄漏。 - 避免内存泄漏:获取实例时使用
ApplicationContext而非Activity上下文。
内容的提问来源于stack exchange,提问作者SHRI Y.N.TANK
相关产品推荐
相关产品推荐

