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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:45:54