Android的SQLiteOpenHelper类 笔记241027

SQLiteOpenHelper

SQLiteOpenHelper是Android开发中用于管理SQLite数据库的一个非常重要的工具类。以下是对SQLiteOpenHelper的详细介绍:

一、基本概念

SQLiteOpenHelper是一个抽象类,它主要用于管理数据库的创建和版本管理。通过继承这个类,开发者可以重写一些方法以实现数据库的创建、升级和降级等功能。

二、主要方法

  1. 构造方法 :用于创建SQLiteOpenHelper对象,需要传入数据库名称、版本号和一个可选的CursorFactory对象。
    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version)
    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version, @Nullable DatabaseErrorHandler errorHandler)
    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version, @NonNull SQLiteDatabase.OpenParams openParams)
  2. onCreate(SQLiteDatabase db):在数据库第一次创建时调用,用于执行创建表和初始化数据等操作。
  3. onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本升级时调用,用于执行表结构的修改、数据迁移等操作。
  4. onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本降级时调用,但这个方法不是必须的,因为在实际开发中降级操作并不常见。
  5. getReadableDatabase():获取一个可读的数据库对象。如果数据库不存在,则会先调用onCreate()方法创建数据库。
  6. getWritableDatabase():获取一个可写的数据库对象。如果数据库不存在,也会先调用onCreate()方法创建数据库。

SQLiteOpenHelper的主要方法

方法名 作用 备注
SQLiteOpenHelper( 构造方法三个 创建SQLiteOpenHelper实例 一般用 new SQLiteOpenHelper(Context context, String databaseName, CursorFactory factory, int version) 参数1可填MianActivity的实例,如this或MainActivity.this 参数2是数据库名称,如果不存在就会调用onCreate()方法 参数3指定CursorFactory , 可以为null(使用默认的CursorFactory) 参数4是版本号,如果变动就会执行onUpgrade()方法
抽象方法 onCreate() 创建数据库时做什么, 可以写入创建表结构的sql语句 没有对应的数据库时才调用 (构造函数的参数中的数据库名对应的数据库不存在时才调用) 构造获得实例,实例调用getReadableDatabase()getWritableDatabase()时,发现构造时指定的数据库不存在,就会调用该方法
抽象方法 onUpgrade() 升级数据库版本时做什么 构造方法参数的版本号上升时才调用
非抽象,可选 onDowngrade() 降级数据库版本时做什么 构造方法参数的版本号下降时才调用
非抽象,可选 onOpen() 打开数据库时做什么
close() 关闭所有打开的数据库对象
getWritableDatabase() 创建或打开可以读/写的数据库 通过返回的SQLiteDatabase对象对数据库进行操作
getReadableDatabase() 创建或打开可读的数据库 同上

SQLiteDatabase的主要方法

方法名 作用 备注
execSQL() 可进行增删改操作, 不能进行查询操作
query()、rawQuery() 查询数据库
insert() 插入数据
delete() 删除数据
SQLiteOpenHelper的构造方法

SQLiteOpenHelper 的构造方法通常看起来像这样(基于 Android SDK 的源代码):

java 复制代码
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
    // 验证版本号是否有效
    if (version < 1) {
        throw new IllegalArgumentException("Version must be >= 1, was " + version);
    }
    // 保存传入的参数
    mContext = context;
    mName = name;
    mFactory = factory;
    mNewVersion = version;
    mErrorHandler = errorHandler;
}

// 还有一个更简单的构造方法,它不接受 DatabaseErrorHandler
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
    this(context, name, factory, version, null);
}

在上面的构造方法中:

  • context 是一个 Context 对象,它允许访问应用的资源和类,以及调用应用级操作,如启动活动、广播和接收意图等。
  • name 是数据库文件的名称。如果不包括路径,则数据库文件将被存储在应用的私有文件目录中。
  • factory 是一个用于创建游标对象的 CursorFactory。如果传入 null,则使用默认的游标工厂。
  • version 是数据库的版本号。这是一个整数,用于跟踪数据库的结构变化。当版本号增加时,onUpgrade 方法将被调用。
  • errorHandler 是一个 DatabaseErrorHandler 对象,它允许在数据库遇到错误时执行自定义的错误处理逻辑。如果传入 null,则使用默认的错误处理器。

在创建 SQLiteOpenHelper 的子类时,我们需要调用其中一个构造方法来初始化父类。然后,我们可以实现 onCreateonUpgrade 等方法来定义数据库的结构和升级逻辑。

例如:

java 复制代码
public class MyDatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "mydatabase.db";
    private static final int DATABASE_VERSION = 1;

    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 创建表的 SQL 语句
        String CREATE_TABLE = "CREATE TABLE mytable (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "name TEXT" +
                ");";
        db.execSQL(CREATE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 版本升级时的逻辑
        db.execSQL("DROP TABLE IF EXISTS mytable");
        onCreate(db);
    }
}

在这个例子中,我们创建了一个名为 MyDatabaseHelper 的类,它继承自 SQLiteOpenHelper。我们在构造方法中调用了父类的构造方法,并传入了数据库名称、版本号和上下文对象。然后,我们实现了 onCreateonUpgrade 方法来定义数据库的结构和升级逻辑。

三、 SQLiteOpenHelper源代码

以下是一个简化的 SQLiteOpenHelper 源代码示例,并附有关键部分的解释:

java 复制代码
package android.database.sqlite;

import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.util.Log;

public abstract class SQLiteOpenHelper {
    // 调试标志,用于确定是否严格只读
    private static final boolean DEBUG_STRICT_READONLY = false;

    // 上下文对象,用于访问应用的资源和类
    private final Context mContext;

    // 数据库文件名
    private final String mName;

    // 用于创建游标对象的工厂,如果为 null,则使用默认工厂
    private final CursorFactory mFactory;

    // 数据库版本号
    private final int mNewVersion;

    // 数据库错误处理器,如果为 null,则使用默认处理器
    private final DatabaseErrorHandler mErrorHandler;

    // 数据库对象,可能为 null
    private SQLiteDatabase mDatabase;

    // 标记数据库是否正在初始化
    private boolean mIsInitializing;

    // SQLiteOpenHelper 的构造函数
    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
        mContext = context;
        mName = name;
        mFactory = factory;
        mNewVersion = version;
        mErrorHandler = errorHandler;
    }

    // 简化构造函数,不指定错误处理器
    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

    // 创建或打开一个数据库,用于读写。如果数据库磁盘空间已满,则尝试以只读方式打开
    public synchronized SQLiteDatabase getWritableDatabase() {
        // ...(实现细节省略)
    }

    // 创建或打开一个数据库,用于读取。如果数据库磁盘空间已满,则只能以只读方式打开
    public synchronized SQLiteDatabase getReadableDatabase() {
        // ...(实现细节省略)
    }

    // 当数据库第一次创建时调用此方法
    public abstract void onCreate(SQLiteDatabase db);

    // 当数据库版本升级时调用此方法
    public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

    // 当数据库版本降级时调用此方法(可选实现)
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        throw new SQLiteException("Can't downgrade database from version " + oldVersion + " to " + newVersion);
    }

    // 当数据库打开时调用此方法(可选实现)
    public void onOpen(SQLiteDatabase db) {
    }

    // ...(其他方法和内部类省略)
}
关键部分解释:
  1. 构造函数SQLiteOpenHelper 提供了两个构造函数,允许开发者指定数据库名称、版本号、游标工厂和错误处理器。版本号必须大于等于 1。
  2. getWritableDatabase():此方法用于创建或打开一个数据库,用于读写操作。如果数据库磁盘空间已满,则尝试以只读方式打开,但会抛出异常。
  3. getReadableDatabase():此方法用于创建或打开一个数据库,用于读取操作。如果数据库磁盘空间已满,则只能以只读方式打开。
  4. onCreate(SQLiteDatabase db):这是一个抽象方法,当数据库第一次创建时调用。开发者应在此方法中编写创建表和初始化数据的 SQL 语句。
  5. onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):这也是一个抽象方法,当数据库版本升级时调用。开发者应在此方法中编写升级数据库的 SQL 语句,如添加新列、修改表结构等。
  6. onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion):这是一个可选实现的方法,当数据库版本降级时调用。默认情况下,此方法会抛出异常,因为降级操作通常不被推荐。
  7. onOpen(SQLiteDatabase db):这是一个可选实现的方法,当数据库打开时调用。开发者可以在此方法中执行一些初始化操作。

请注意,上述代码是一个简化的示例,并省略了部分实现细节和内部类。在实际开发中,SQLiteOpenHelper 的实现可能会更加复杂,具体取决于应用的需求和数据库的结构。

用法实例

一个用 匿名内部类实例化SQLiteOpenHelper的Activity

java 复制代码
package com.example.emptyviewsactivity2410261826;

import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity extends AppCompatActivity {

    final String DbName = "note1", TbName = "tableA1" , Col0="id" , Col1="content";
    final int RowQuantity = 666;
    LinearLayout linearLayout = null;



    TableLayout tableLayout = null;
    final TableRow[] TableRows = new TableRow[RowQuantity];
    final TextView[] TvAr = new TextView[RowQuantity];
    final EditText[] EtAr = new EditText[RowQuantity];
//    final AppCompatEditText EtAr[] = new AppCompatEditText[RowQuantity];
    final Button[] CopyBtnAr = new Button[RowQuantity];
    final Button[] PasteBtnAr = new Button[RowQuantity];
    final Button[] CutBtnAr = new Button[RowQuantity];
    final Button[] DelBtnAr = new Button[RowQuantity];



    SQLiteOpenHelper sqliteOpenHelper;

    SQLiteDatabase sqliteDatabase;





    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); Log.i("onCreate()","onCreate()");
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        /// 👆上面部分是新建项目自动生成的



        linearLayout = findViewById(R.id.LinearLayoutA1);
        tableLayout = findViewById(R.id.tableLayoutA1);


//        EditText editText = new EditText(this);  linearLayout.addView(editText);
//
//        AppCompatEditText appCompatEditText = new AppCompatEditText(MainActivity.this); linearLayout.addView(appCompatEditText);
//
//        AutoCompleteTextView autoCompleteTextView = new AutoCompleteTextView(this); linearLayout.addView(autoCompleteTextView);
//
//        MultiAutoCompleteTextView multiAutoCompleteTextView = new MultiAutoCompleteTextView(MainActivity.this); linearLayout.addView(multiAutoCompleteTextView);



         /*
        删除数据库
         deleteDatabase(DbName)删除数据库, Activity,AppCompatActivity都自带删除Sqlite数据库的方法, 这是实现自最顶层 public abstract class Context 的抽象方法  public abstract boolean deleteDatabase(String name);
         AppCompatActivity extends FragmentActivity extends ComponentActivity extends androidx.core.app.ComponentActivity extends Activity extends ContextThemeWrapper extends ContextWrapper extends Context
         */
//        deleteDatabase(DbName);

        /*
        通过匿名内部类实现SQLiteOpenHelper, 也可用继承类实现.
        实例化SQLiteOpenHelper时虽然指定了数据库名称和版本,但还不会创建或打开数据库,直到实例执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时,才会打开 或 创建再打开 数据库
         */
       sqliteOpenHelper = new SQLiteOpenHelper(MainActivity.this, DbName, null, 1) {


            //必须
            //onCreate(SQLiteDatabase sqLiteDatabase数据库实例)
            ///在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 如果数据库名称对应的数据库不存在,就会调用该方法,该方法为abstract抽象方法,必须实现.
            @Override
            public void onCreate(SQLiteDatabase sqLiteDatabase) {
                System.out.println("SQLiteOpenHelper 的 onCreate(SQLiteDatabase sqLiteDatabase)被调用    //在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 如果数据库名称对应的数据库不存在,就会调用该方法,该方法为abstract抽象方法,必须实现");

                final String CreateTableSql = "CREATE TABLE "+TbName+" ( id INTEGER PRIMARY KEY , content TEXT )";

                sqLiteDatabase.execSQL(CreateTableSql);

                for(int r=0; r<RowQuantity; r++){
                    ContentValues cvs = new ContentValues(2);
                    cvs.put(Col0, r);
                    cvs.put(Col1, "");
                    sqLiteDatabase.insert(TbName, null, cvs);
                }

            }

            //必须
            //onUpgrade(SQLiteDatabase sqLiteDatabase数据库实例, int oldVersion旧版本号, int newVersion新版本号)
            ///在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 如果数据库版本号升高,就会调用该方法,该方法为abstract抽象方法,必须实现.    在创建数据库时不会调用该方法
            @Override
            public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
                System.out.println("SQLiteOpenHelper 的 onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion)被调用    //在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 如果数据库版本号升高,就会调用该方法,该方法为abstract抽象方法,必须实现.    在创建数据库时不会调用该方法");

            }

            //非抽象,可选
            //onDowngrade(SQLiteDatabase sqLiteDatabase数据库实例, int oldVersion旧版本号, int newVersion新版本号)
            ///在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 如果数据库版本号发生变动,就会调用该方法,该方法非抽象,可选.    在创建数据库时不会调用该方法
            @Override
            public void onDowngrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
                System.out.println("SQLiteOpenHelper 的 onDowngrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion)被调用    //在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 如果数据库版本号降低,就会调用该方法,该方法非抽象,可选.    在创建数据库时不会调用该方法");

            }

            //非抽象,可选
            //onOpen(SQLiteDatabase sqLiteDatabase数据库实例)
            //在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 被调用
            @Override
            public void onOpen(SQLiteDatabase sqLiteDatabase) {
                Log.i("onOpen(SQLiteDatabase sqLiteDatabase)","SQLiteOpenHelper 的 onOpen(SQLiteDatabase sqLiteDatabase)被调用    //在执行 getReadableDatabase() 或 getWritableDatabase() 获取数据库时时, 会调用onOpen(SQLiteDatabase db)方法");
                @SuppressLint("Recycle") Cursor cursor = sqLiteDatabase.rawQuery("SELECT COUNT(*) FROM "+TbName, null);
                cursor.moveToFirst(); if(cursor.getInt(0) != RowQuantity){ 
                    Log.i("库表均已存在,但行数不对应","库表均已存在,但行数不对应, 将 DELETE TABLE FROM table-name 然后重新 INSERT INTO");
                    sqLiteDatabase.delete(TbName,null,null);
                    for(int r=0; r<RowQuantity; r++){
                       sqLiteDatabase.execSQL("INSERT INTO " + TbName + " VALUES (?,?) " , new Object[]{r,""});
                    }
                }
            }

        };

       sqliteDatabase = sqliteOpenHelper.getWritableDatabase();

        @SuppressLint("Recycle") Cursor cursor = sqliteDatabase.rawQuery("SELECT * FROM "+TbName, null);


        for(int r=0; cursor.moveToNext(); r++){
            TableRow row = TableRows[r] = new TableRow(this); tableLayout.addView(row);
            TextView tv = TvAr[r] = new TextView(this); row.addView(tv); tv.setText(cursor.getString(0));
            EditText et = EtAr[r] = new EditText(this); row.addView(et); et.setText(cursor.getString(1));
            Button copyBtn = CopyBtnAr[r] = new Button(this); row.addView(copyBtn, 100, 100); copyBtn.setText("复");
                copyBtn.setOnClickListener((view)->{
                    ClipboardManager clipboardManager = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
                    ClipData clipData = ClipData.newPlainText("text", et.getEditableText().toString());
                    clipboardManager.setPrimaryClip(clipData);
                });
            Button pBtn = PasteBtnAr[r] = new Button(this); row.addView(pBtn, 100, 100); pBtn.setText("粘"); pBtn.setOnClickListener((view)->{ et.setText(((ClipboardManager)getSystemService(CLIPBOARD_SERVICE)).getPrimaryClip().getItemAt(0).getText()); });
            Button cutBtn = CutBtnAr[r] = new Button(this); row.addView(cutBtn, 100, 100); cutBtn.setText("剪"); cutBtn.setOnClickListener((view)->{
                copyBtn.callOnClick(); et.setText("");
            });
            Button dBtn = DelBtnAr[r] = new Button(this); row.addView(dBtn, 100, 100); dBtn.setText("删"); dBtn.setOnClickListener((view)->{ et.setText(""); });
        }



//       for(int r=0; r<RowQuantity; r++){
//           TableRow row = tableRows[r] = new TableRow(this); tableLayout.addView(row);
//           EditText et = etAr[r] = new EditText(this); row.addView(et);
//
//       }


        /*
        测试多次调用  getReadableDatabase() 和 getWritableDatabase() 获取SQLiteDatabase数据库实例
        多次调用  getReadableDatabase() 和 getWritableDatabase() 返回同一个SQLiteDatabase实例
         */
        SQLiteDatabase dbw , dbr;
        dbr = sqliteOpenHelper.getReadableDatabase();
        Log.i("dbw.isReadOnly()",""+dbr.isReadOnly());
        dbw = sqliteOpenHelper.getWritableDatabase();
        Log.i("dbw.isReadOnly()",""+dbw.isReadOnly());
//        dbr = sqliteOpenHelper.getReadableDatabase();
        System.out.println("dbw==dbr 结果 "+(dbw==dbr));
        Log.i("dbw.isReadOnly()",""+dbw.isReadOnly());


        /*
        删除数据库
         deleteDatabase(DbName)删除数据库, Activity,AppCompatActivity都自带删除Sqlite数据库的方法, 这是实现自最顶层 public abstract class Context 的抽象方法  public abstract boolean deleteDatabase(String name);
         AppCompatActivity extends FragmentActivity extends ComponentActivity extends androidx.core.app.ComponentActivity extends Activity extends ContextThemeWrapper extends ContextWrapper extends Context
         */
//        deleteDatabase(DbName);




    }

    void save(){
        for(int r=0; r<RowQuantity; r++){
            ContentValues cvs = new ContentValues();
            cvs.put(Col1, EtAr[r].getText().toString());
            sqliteDatabase.update(TbName, cvs, "id=?", new String[]{""+r});
        }
    }

    @Override
    protected void onStart() {
        super.onStart(); Log.i("onStart()","onStart()");
    }

    @Override
    protected void onPause() {
        super.onPause(); Log.i(" onPause()"," onPause()");
        save();
    }


    @Override
    protected void onStop() {
        super.onStop(); Log.i("onStop()","onStop()");
        save();
    }

    @Override
    protected void onRestart() {
        super.onRestart(); Log.i("onRestart()","onRestart()");
    }

    @Override
    protected void onPostResume() {
        super.onPostResume(); Log.i("onPostResume()","onPostResume()");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy(); Log.i("onDestroy()","onDestroy()");
        save();
    }

}

一些用法收集参考

java 复制代码
public class SQLiteHelper extends SQLiteOpenHelper {
    private SQLiteDatabase sqLiteDatabase;

    //调用父类 SQLiteOpenHelper 的构造函数
    public SQLiteHelper(Context context) {
        //context上下文环境(例如,一个 Activity),数据库名字,一个可选的游标工厂(通常是 Null),一个代表你正在使用的数据库模型版本的整数。
        super(context, DBUtils.DATABASE_NAME, null, DBUtils.DATABASE_VERSION);
        sqLiteDatabase = this.getWritableDatabase();
    }

    //创建数据库 只在没有数据库时执行
    @Override
    public void onCreate(SQLiteDatabase db) {
        //execSQL() 方法适用于所有不返回结果的 SQL 语句
        db.execSQL("CREATE TABLE " + DBUtils.DATABASE_TABLE + "(" + DBUtils.NOTE_ID +
                " INTEGER PRIMARY KEY AUTOINCREMENT," + DBUtils.NOTE_CONTENT +
                " TEXT," + DBUtils.NOTE_TIME + " TEXT)");
    }

    //把一个数据库从旧的模型转变到新的模型。
    //它需要三个参数,一个 SQLiteDatabase 对象,一个旧的版本号和一个新的版本号
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}

    //添加数据
    public boolean insertData(String userContent, String userTime) {
    	//ContentValues 储存数据,只能存储基本类型的数据,不能存储对象
        ContentValues values = new ContentValues();
        values.put(DBUtils.NOTE_CONTENT, userContent);
        values.put(DBUtils.NOTE_TIME, userTime);
        //插入数据
        //第一个参数是表的名称
        //第二个参数为空值字段,就是如果第三个参数为空(null)的时候就会用到第二个参数的值。用第二个参数代替第三个参数组拼成SQL语句
        //比如:insert into person(name) values(null)   这里的person字段使用了第二个参数的name
        //第三个参数不为空就不会用到第二个参数
        return sqLiteDatabase.insert(DBUtils.DATABASE_TABLE, null, values) > 0;
    }

    //删除数据
    public boolean deleteData(String id) {
        String sql = DBUtils.NOTE_ID + "=?";
        String[] contentValuesArrary = new String[]{String.valueOf(id)};
        //1表名、2字段名、3占位符的数据
        return sqLiteDatabase.delete(DBUtils.DATABASE_TABLE, sql, contentValuesArrary) > 0;
    }

    //修改数据
    public boolean updateData(String id, String content, String userYear) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(DBUtils.NOTE_CONTENT, content);
        contentValues.put(DBUtils.NOTE_TIME, userYear);
        String sql = DBUtils.NOTE_ID + "=?";
        String[] strings = new String[]{id};
        //1表名、2需要更新值、3以什么条件字段更新、4条件字段的数据值(占位符的值)
        return sqLiteDatabase.update(DBUtils.DATABASE_TABLE, contentValues, sql, strings) > 0;
    }

    //查询数据
    public List<NotepadBean> qurry() {
        List<NotepadBean> list = new ArrayList<NotepadBean>();
        //1 表名、   2 需要查询的字段列表,用字符串数组形式传入,null为所有的字段、   3 以什么条件字段查询、   4 条件字段的数据值(占位符的值)、
        // 5 groupBy相当于select语句的groupby后面的部分、   6 having相当于select语句的having后面的部分、  7 order是我们想要的排序方式。
        Cursor cursor = sqLiteDatabase.query(DBUtils.DATABASE_TABLE, null, null, null,
                null, null, DBUtils.NOTE_ID + " desc");
        if (cursor != null) {
            while (cursor.moveToNext()) {
                NotepadBean noteInfo = new NotepadBean();
                String id = String.valueOf(cursor.getInt
                        (cursor.getColumnIndex(DBUtils.NOTE_ID)));
                String content = cursor.getString(cursor.getColumnIndex
                        (DBUtils.NOTE_CONTENT));
                String time = cursor.getString(cursor.getColumnIndex(DBUtils.NOTE_TIME));
                noteInfo.setId(id);
                noteInfo.setNotepadContent(content);
                noteInfo.setNotepadTime(time);
                list.add(noteInfo);
            }
            cursor.close();
        }
        return list;
    }
}
java 复制代码
package com.example.dbproject;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;
public class DbContect extends SQLiteOpenHelper {
    private static final int VERSION=1;
    private static final String DBNAME="Users.db";   //  创建数据库名叫 Users
    private Context mContext;

    public DbContect(Context context){
        super(context,DBNAME,null,VERSION);
        mContext = context;
    }
    //创建数据库 只在没有数据库时执行
    public void onCreate(SQLiteDatabase db){
        //创建密码表  pwd_tb
        db.execSQL("create table pwd_tb (pwd varchar(20) primary key)");
        //创建收入表    user_tb
        db.execSQL("create table user_tb(_id integer primary key autoincrement, money decimal," +
                " time varchar(10),type varchar(10),handler varchar(100),mark varchar(200))");
    }
    //数据库版本更新时执行
    public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)
    {
        db.execSQL("drop table if exists pwd_tb");
        db.execSQL("drop table if exists user_tb");
        onCreate(db);
    }


}

SQLiteOpenHelper是Android开发中用于管理SQLite数据库的一个非常重要的工具类。以下是对SQLiteOpenHelper的详细介绍:

一、基本概念

SQLiteOpenHelper是一个抽象类,它主要用于管理数据库的创建和版本管理。通过继承这个类,开发者可以重写一些方法以实现数据库的创建、升级和降级等功能。

二、主要方法

  1. 构造方法:用于创建SQLiteOpenHelper对象,需要传入数据库名称、版本号和一个可选的CursorFactory对象。
  2. onCreate(SQLiteDatabase db):在数据库第一次创建时调用,用于执行创建表和初始化数据等操作。
  3. onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本升级时调用,用于执行表结构的修改、数据迁移等操作。
  4. onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本降级时调用,但这个方法不是必须的,因为在实际开发中降级操作并不常见。
  5. getReadableDatabase():获取一个可读的数据库对象。如果数据库不存在,则会先调用onCreate()方法创建数据库。
  6. getWritableDatabase():获取一个可写的数据库对象。如果数据库不存在,也会先调用onCreate()方法创建数据库。

三、使用示例

以下是一个使用SQLiteOpenHelper创建数据库、表以及进行增删改查操作的简单示例:

  1. 创建数据库帮助类
java 复制代码
public class MyDatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "mydatabase.db";
    private static final int DATABASE_VERSION = 1;

    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTable = "CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)";
        db.execSQL(createTable);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS users");
        onCreate(db);
    }
}
  1. 使用数据库帮助类进行增删改查操作
java 复制代码
MyDatabaseHelper dbHelper = new MyDatabaseHelper(context);
SQLiteDatabase db = dbHelper.getWritableDatabase();

// 插入数据
ContentValues values = new ContentValues();
values.put("name", "John Doe");
long id = db.insert("users", null, values);

// 查询数据
Cursor cursor = db.query("users", null, null, null, null, null, null);
while (cursor.moveToNext()) {
    int userId = cursor.getInt(cursor.getColumnIndex("id"));
    String userName = cursor.getString(cursor.getColumnIndex("name"));
    // 处理查询到的数据
}
cursor.close();

// 更新数据
ContentValues updateValues = new ContentValues();
updateValues.put("name", "Jane Smith");
db.update("users", updateValues, "id=?", new String[]{String.valueOf(id)});

// 删除数据
db.delete("users", "id=?", new String[]{String.valueOf(id)});

// 关闭数据库
db.close();

四、注意事项

  1. 数据库版本管理:在开发过程中,随着应用程序的更新,数据库结构可能会发生变化。因此,合理使用onUpgrade()方法来进行数据库结构的升级是非常重要的。
  2. 异常处理:在进行数据库操作时,可能会遇到各种异常情况,如SQL语法错误、数据库文件损坏等。因此,在实际开发中,应该添加适当的异常处理代码来确保程序的健壮性。
  3. 性能优化:对于大规模的数据库操作,如批量插入、更新或删除数据,应该考虑使用事务(Transaction)来提高性能并减少数据库锁定的时间。

综上所述,SQLiteOpenHelper是Android开发中管理SQLite数据库的重要工具类。通过合理使用该类,开发者可以实现高效的数据管理,从而提升应用程序的性能和用户体验。

如何使用SQLiteOpenHelper

SQLiteOpenHelper 是 Android 平台上用于管理 SQLite 数据库的一个辅助类。它简化了数据库的创建和版本管理。以下是使用 SQLiteOpenHelper 的基本步骤:

1. 创建一个继承自 SQLiteOpenHelper 的类

首先,你需要创建一个类,这个类继承自 SQLiteOpenHelper 并实现其两个抽象方法:onCreate(SQLiteDatabase db)onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)

java 复制代码
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {

    // 数据库名称
    private static final String DATABASE_NAME = "my_database.db";

    // 数据库版本
    private static final int DATABASE_VERSION = 1;

    // 创建一个表
    private static final String TABLE_CREATE =
            "CREATE TABLE my_table (" +
            "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
            "name TEXT NOT NULL, " +
            "age INTEGER NOT NULL);";

    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(TABLE_CREATE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 如果表已经存在,删除表
        db.execSQL("DROP TABLE IF EXISTS my_table");
        // 重新创建表
        onCreate(db);
    }
}

2. 在你的 Activity 或其他组件中使用 SQLiteOpenHelper

在你的 Activity 或其他组件中,你可以通过实例化 MyDatabaseHelper 类来获取 SQLiteDatabase 对象,然后执行 CRUD(创建、读取、更新、删除)操作。

java 复制代码
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dbHelper = new MyDatabaseHelper(this);

        // 插入数据
        insertData();

        // 查询数据
        queryData();
    }

    private void insertData() {
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put("name", "Alice");
        values.put("age", 30);

        long newRowId = db.insert("my_table", null, values);
        Toast.makeText(this, "Inserted Row ID: " + newRowId, Toast.LENGTH_SHORT).show();
    }

    private void queryData() {
        SQLiteDatabase db = dbHelper.getReadableDatabase();

        String[] projection = {
                "id",
                "name",
                "age"
        };

        Cursor cursor = db.query(
                "my_table",   // 表名
                projection,   // 要返回的列
                null,         // WHERE 子句的选择条件
                null,         // WHERE 子句的选择参数
                null,         // GROUP BY 子句
                null,         // HAVING 子句
                null          // ORDER BY 子句
        );

        while (cursor.moveToNext()) {
            int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
            String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
            int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));

            // 处理查询结果
            Toast.makeText(this, "ID: " + id + ", Name: " + name + ", Age: " + age, Toast.LENGTH_SHORT).show();
        }

        cursor.close();
    }
}

3. 注意事项

  • 线程安全 :SQLite 数据库不是线程安全的,因此你应该确保所有的数据库操作都在一个单独的线程中执行,或者使用 AsyncTask 或其他异步方法。
  • 关闭游标 :每次使用完 Cursor 后,都应该调用 cursor.close() 方法来释放资源。
  • 错误处理 :在实际应用中,你应该添加适当的错误处理逻辑,比如捕获 SQLException

通过以上步骤,你就可以在 Android 应用中使用 SQLiteOpenHelper 来管理 SQLite 数据库了。

Android API34 的 SQLiteOpenHelper 的源码

java 复制代码
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.database.sqlite;

import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.os.FileUtils;
import android.util.Log;

import java.io.File;
import java.util.Objects;

/**
 * A helper class to manage database creation and version management.
 *
 * <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
 * optionally {@link #onOpen}, and this class takes care of opening the database
 * if it exists, creating it if it does not, and upgrading it as necessary.
 * Transactions are used to make sure the database is always in a sensible state.
 *
 * <p>This class makes it easy for {@link android.content.ContentProvider}
 * implementations to defer opening and upgrading the database until first use,
 * to avoid blocking application startup with long-running database upgrades.
 *
 * <p>For an example, see the NotePadProvider class in the NotePad sample application,
 * in the <em>samples/</em> directory of the SDK.</p>
 *
 * <p class="note"><strong>Note:</strong> this class assumes
 * monotonically increasing version numbers for upgrades.</p>
 *
 * <p class="note"><strong>Note:</strong> the {@link AutoCloseable} interface was
 * first added in the {@link android.os.Build.VERSION_CODES#Q} release.</p>
 */
public abstract class SQLiteOpenHelper implements AutoCloseable {
    private static final String TAG = SQLiteOpenHelper.class.getSimpleName();

    private final Context mContext;
    @UnsupportedAppUsage
    private final String mName;
    private final int mNewVersion;
    private final int mMinimumSupportedVersion;

    private SQLiteDatabase mDatabase;
    private boolean mIsInitializing;
    private SQLiteDatabase.OpenParams.Builder mOpenParamsBuilder;

    /**
     * Create a helper object to create, open, and/or manage a database.
     * This method always returns very quickly.  The database is not actually
     * created or opened until one of {@link #getWritableDatabase} or
     * {@link #getReadableDatabase} is called.
     *
     * @param context to use for locating paths to the the database
     * @param name of the database file, or null for an in-memory database
     * @param factory to use for creating cursor objects, or null for the default
     * @param version number of the database (starting at 1); if the database is older,
     *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     *     newer, {@link #onDowngrade} will be used to downgrade the database
     */
    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
            @Nullable CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

    /**
     * Create a helper object to create, open, and/or manage a database.
     * The database is not actually created or opened until one of
     * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
     *
     * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
     * used to handle corruption when sqlite reports database corruption.</p>
     *
     * @param context to use for locating paths to the the database
     * @param name of the database file, or null for an in-memory database
     * @param factory to use for creating cursor objects, or null for the default
     * @param version number of the database (starting at 1); if the database is older,
     *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     *     newer, {@link #onDowngrade} will be used to downgrade the database
     * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
     * corruption, or null to use the default error handler.
     */
    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
            @Nullable CursorFactory factory, int version,
            @Nullable DatabaseErrorHandler errorHandler) {
        this(context, name, factory, version, 0, errorHandler);
    }

    /**
     * Create a helper object to create, open, and/or manage a database.
     * This method always returns very quickly.  The database is not actually
     * created or opened until one of {@link #getWritableDatabase} or
     * {@link #getReadableDatabase} is called.
     *
     * @param context to use for locating paths to the the database
     * @param name of the database file, or null for an in-memory database
     * @param version number of the database (starting at 1); if the database is older,
     *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     *     newer, {@link #onDowngrade} will be used to downgrade the database
     * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}.
     *        Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be
     *        set when the helper opens the database
     */
    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
            @NonNull SQLiteDatabase.OpenParams openParams) {
        this(context, name, version, 0, openParams.toBuilder());
    }

    /**
     * Same as {@link #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)}
     * but also accepts an integer minimumSupportedVersion as a convenience for upgrading very old
     * versions of this database that are no longer supported. If a database with older version that
     * minimumSupportedVersion is found, it is simply deleted and a new database is created with the
     * given name and version
     *
     * @param context to use for locating paths to the the database
     * @param name the name of the database file, null for a temporary in-memory database
     * @param factory to use for creating cursor objects, null for default
     * @param version the required version of the database
     * @param minimumSupportedVersion the minimum version that is supported to be upgraded to
     *            {@code version} via {@link #onUpgrade}. If the current database version is lower
     *            than this, database is simply deleted and recreated with the version passed in
     *            {@code version}. {@link #onBeforeDelete} is called before deleting the database
     *            when this happens. This is 0 by default.
     * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
     *            corruption, or null to use the default error handler.
     * @see #onBeforeDelete(SQLiteDatabase)
     * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)
     * @see #onUpgrade(SQLiteDatabase, int, int)
     * @hide
     */
    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
            @Nullable CursorFactory factory, int version,
            int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler) {
        this(context, name, version, minimumSupportedVersion,
                new SQLiteDatabase.OpenParams.Builder());
        mOpenParamsBuilder.setCursorFactory(factory);
        mOpenParamsBuilder.setErrorHandler(errorHandler);
    }

    private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
            int minimumSupportedVersion,
            @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
        Objects.requireNonNull(openParamsBuilder);
        if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);

        mContext = context;
        mName = name;
        mNewVersion = version;
        mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
        setOpenParamsBuilder(openParamsBuilder);
    }

    /**
     * Return the name of the SQLite database being opened, as given to
     * the constructor.
     */
    public String getDatabaseName() {
        return mName;
    }

    /**
     * Enables or disables the use of write-ahead logging for the database.
     *
     * Write-ahead logging cannot be used with read-only databases so the value of
     * this flag is ignored if the database is opened read-only.
     *
     * @param enabled True if write-ahead logging should be enabled, false if it
     * should be disabled.
     *
     * @see SQLiteDatabase#enableWriteAheadLogging()
     */
    public void setWriteAheadLoggingEnabled(boolean enabled) {
        synchronized (this) {
            if (mOpenParamsBuilder.isWriteAheadLoggingEnabled() != enabled) {
                if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
                    if (enabled) {
                        mDatabase.enableWriteAheadLogging();
                    } else {
                        mDatabase.disableWriteAheadLogging();
                    }
                }
                mOpenParamsBuilder.setWriteAheadLoggingEnabled(enabled);
            }

            // Compatibility WAL is disabled if an app disables or enables WAL
            mOpenParamsBuilder.removeOpenFlags(SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL);
        }
    }

    /**
     * Configures <a href="https://sqlite.org/malloc.html#lookaside">lookaside memory allocator</a>
     *
     * <p>This method should be called from the constructor of the subclass,
     * before opening the database, since lookaside memory configuration can only be changed
     * when no connection is using it
     *
     * <p>SQLite default settings will be used, if this method isn't called.
     * Use {@code setLookasideConfig(0,0)} to disable lookaside
     *
     * <p><strong>Note:</strong> Provided slotSize/slotCount configuration is just a recommendation.
     * The system may choose different values depending on a device, e.g. lookaside allocations
     * can be disabled on low-RAM devices
     *
     * @param slotSize The size in bytes of each lookaside slot.
     * @param slotCount The total number of lookaside memory slots per database connection.
     */
    public void setLookasideConfig(@IntRange(from = 0) final int slotSize,
            @IntRange(from = 0) final int slotCount) {
        synchronized (this) {
            if (mDatabase != null && mDatabase.isOpen()) {
                throw new IllegalStateException(
                        "Lookaside memory config cannot be changed after opening the database");
            }
            mOpenParamsBuilder.setLookasideConfig(slotSize, slotCount);
        }
    }

    /**
     * Sets configuration parameters that are used for opening {@link SQLiteDatabase}.
     * <p>Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be set when
     * opening the database
     *
     * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}.
     * @throws IllegalStateException if the database is already open
     */
    public void setOpenParams(@NonNull SQLiteDatabase.OpenParams openParams) {
        Objects.requireNonNull(openParams);
        synchronized (this) {
            if (mDatabase != null && mDatabase.isOpen()) {
                throw new IllegalStateException(
                        "OpenParams cannot be set after opening the database");
            }
            setOpenParamsBuilder(new SQLiteDatabase.OpenParams.Builder(openParams));
        }
    }

    private void setOpenParamsBuilder(SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
        mOpenParamsBuilder = openParamsBuilder;
        mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
    }

    /**
     * Sets the maximum number of milliseconds that SQLite connection is allowed to be idle
     * before it is closed and removed from the pool.
     *
     * <p>This method should be called from the constructor of the subclass,
     * before opening the database
     *
     * <p><b>DO NOT USE</b> this method.
     * This feature has negative side effects that are very hard to foresee.
     * See the javadoc of
     * {@link SQLiteDatabase.OpenParams.Builder#setIdleConnectionTimeout(long)}
     * for the details.
     *
     * @param idleConnectionTimeoutMs timeout in milliseconds. Use {@link Long#MAX_VALUE} value
     * to allow unlimited idle connections.
     *
     * @see SQLiteDatabase.OpenParams.Builder#setIdleConnectionTimeout(long)
     *
     * @deprecated DO NOT USE this method. See the javadoc of
     * {@link SQLiteDatabase.OpenParams.Builder#setIdleConnectionTimeout(long)}
     * for the details.
     */
    @Deprecated
    public void setIdleConnectionTimeout(@IntRange(from = 0) final long idleConnectionTimeoutMs) {
        synchronized (this) {
            if (mDatabase != null && mDatabase.isOpen()) {
                throw new IllegalStateException(
                        "Connection timeout setting cannot be changed after opening the database");
            }
            mOpenParamsBuilder.setIdleConnectionTimeout(idleConnectionTimeoutMs);
        }
    }

    /**
     * Create and/or open a database that will be used for reading and writing.
     * The first time this is called, the database will be opened and
     * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
     * called.
     *
     * <p>Once opened successfully, the database is cached, so you can
     * call this method every time you need to write to the database.
     * (Make sure to call {@link #close} when you no longer need the database.)
     * Errors such as bad permissions or a full disk may cause this method
     * to fail, but future attempts may succeed if the problem is fixed.</p>
     *
     * <p class="caution">Database upgrade may take a long time, you
     * should not call this method from the application main thread, including
     * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
     *
     * @throws SQLiteException if the database cannot be opened for writing
     * @return a read/write database object valid until {@link #close} is called
     */
    public SQLiteDatabase getWritableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(true);
        }
    }

    /**
     * Create and/or open a database.  This will be the same object returned by
     * {@link #getWritableDatabase} unless some problem, such as a full disk,
     * requires the database to be opened read-only.  In that case, a read-only
     * database object will be returned.  If the problem is fixed, a future call
     * to {@link #getWritableDatabase} may succeed, in which case the read-only
     * database object will be closed and the read/write object will be returned
     * in the future.
     *
     * <p class="caution">Like {@link #getWritableDatabase}, this method may
     * take a long time to return, so you should not call it from the
     * application main thread, including from
     * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
     *
     * @throws SQLiteException if the database cannot be opened
     * @return a database object valid until {@link #getWritableDatabase}
     *     or {@link #close} is called.
     */
    public SQLiteDatabase getReadableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(false);
        }
    }

    private SQLiteDatabase getDatabaseLocked(boolean writable) {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // Darn!  The user closed the database by calling mDatabase.close().
                mDatabase = null;
            } else if (!writable || !mDatabase.isReadOnly()) {
                // The database is already open for business.
                return mDatabase;
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException("getDatabase called recursively");
        }

        SQLiteDatabase db = mDatabase;
        try {
            mIsInitializing = true;

            if (db != null) {
                if (writable && db.isReadOnly()) {
                    db.reopenReadWrite();
                }
            } else if (mName == null) {
                db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
            } else {
                final File filePath = mContext.getDatabasePath(mName);
                SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
                try {
                    db = SQLiteDatabase.openDatabase(filePath, params);
                    // Keep pre-O-MR1 behavior by resetting file permissions to 660
                    setFilePermissionsForDb(filePath.getPath());
                } catch (SQLException ex) {
                    if (writable) {
                        throw ex;
                    }
                    Log.e(TAG, "Couldn't open " + mName
                            + " for writing (will try read-only):", ex);
                    params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
                    db = SQLiteDatabase.openDatabase(filePath, params);
                }
            }

            onConfigure(db);

            final int version = db.getVersion();
            if (version != mNewVersion) {
                if (db.isReadOnly()) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + mName);
                }

                if (version > 0 && version < mMinimumSupportedVersion) {
                    File databaseFile = new File(db.getPath());
                    onBeforeDelete(db);
                    db.close();
                    if (SQLiteDatabase.deleteDatabase(databaseFile)) {
                        mIsInitializing = false;
                        return getDatabaseLocked(writable);
                    } else {
                        throw new IllegalStateException("Unable to delete obsolete database "
                                + mName + " with version " + version);
                    }
                } else {
                    db.beginTransaction();
                    try {
                        if (version == 0) {
                            onCreate(db);
                        } else {
                            if (version > mNewVersion) {
                                onDowngrade(db, version, mNewVersion);
                            } else {
                                onUpgrade(db, version, mNewVersion);
                            }
                        }
                        db.setVersion(mNewVersion);
                        db.setTransactionSuccessful();
                    } finally {
                        db.endTransaction();
                    }
                }
            }

            onOpen(db);

            if (db.isReadOnly()) {
                Log.w(TAG, "Opened " + mName + " in read-only mode");
            }

            mDatabase = db;
            return db;
        } finally {
            mIsInitializing = false;
            if (db != null && db != mDatabase) {
                db.close();
            }
        }
    }

    private static void setFilePermissionsForDb(String dbPath) {
        int perms = FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP;
        FileUtils.setPermissions(dbPath, perms, -1, -1);
    }

    /**
     * Close any open database object.
     */
    public synchronized void close() {
        if (mIsInitializing) throw new IllegalStateException("Closed during initialization");

        if (mDatabase != null && mDatabase.isOpen()) {
            mDatabase.close();
            mDatabase = null;
        }
    }

    /**
     * Called when the database connection is being configured, to enable features such as
     * write-ahead logging or foreign key support.
     * <p>
     * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade}, or
     * {@link #onOpen} are called. It should not modify the database except to configure the
     * database connection as required.
     * </p>
     * <p>
     * This method should only call methods that configure the parameters of the database
     * connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
     * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, {@link SQLiteDatabase#setLocale},
     * {@link SQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
     * </p>
     *
     * @param db The database.
     */
    public void onConfigure(SQLiteDatabase db) {}

    /**
     * Called before the database is deleted when the version returned by
     * {@link SQLiteDatabase#getVersion()} is lower than the minimum supported version passed (if at
     * all) while creating this helper. After the database is deleted, a fresh database with the
     * given version is created. This will be followed by {@link #onConfigure(SQLiteDatabase)} and
     * {@link #onCreate(SQLiteDatabase)} being called with a new SQLiteDatabase object
     *
     * @param db the database opened with this helper
     * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, int, DatabaseErrorHandler)
     * @hide
     */
    public void onBeforeDelete(SQLiteDatabase db) {
    }

    /**
     * Called when the database is created for the first time. This is where the
     * creation of tables and the initial population of the tables should happen.
     *
     * @param db The database.
     */
    public abstract void onCreate(SQLiteDatabase db);

    /**
     * Called when the database needs to be upgraded. The implementation
     * should use this method to drop tables, add tables, or do anything else it
     * needs to upgrade to the new schema version.
     *
     * <p>
     * The SQLite ALTER TABLE documentation can be found
     * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
     * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
     * you can use ALTER TABLE to rename the old table, then create the new table and then
     * populate the new table with the contents of the old table.
     * </p><p>
     * This method executes within a transaction.  If an exception is thrown, all changes
     * will automatically be rolled back.
     * </p>
     * <p>
     * <em>Important:</em> You should NOT modify an existing migration step from version X to X+1
     * once a build has been released containing that migration step.  If a migration step has an
     * error and it runs on a device, the step will NOT re-run itself in the future if a fix is made
     * to the migration step.</p>
     * <p>For example, suppose a migration step renames a database column from {@code foo} to
     * {@code bar} when the name should have been {@code baz}.  If that migration step is released
     * in a build and runs on a user's device, the column will be renamed to {@code bar}.  If the
     * developer subsequently edits this same migration step to change the name to {@code baz} as
     * intended, the user devices which have already run this step will still have the name
     * {@code bar}.  Instead, a NEW migration step should be created to correct the error and rename
     * {@code bar} to {@code baz}, ensuring the error is corrected on devices which have already run
     * the migration step with the error.</p>
     *
     * @param db The database.
     * @param oldVersion The old database version.
     * @param newVersion The new database version.
     */
    public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

    /**
     * Called when the database needs to be downgraded. This is strictly similar to
     * {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
     * However, this method is not abstract, so it is not mandatory for a customer to
     * implement it. If not overridden, default implementation will reject downgrade and
     * throws SQLiteException
     *
     * <p>
     * This method executes within a transaction.  If an exception is thrown, all changes
     * will automatically be rolled back.
     * </p>
     *
     * @param db The database.
     * @param oldVersion The old database version.
     * @param newVersion The new database version.
     */
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        throw new SQLiteException("Can't downgrade database from version " +
                oldVersion + " to " + newVersion);
    }

    /**
     * Called when the database has been opened.  The implementation
     * should check {@link SQLiteDatabase#isReadOnly} before updating the
     * database.
     * <p>
     * This method is called after the database connection has been configured
     * and after the database schema has been created, upgraded or downgraded as necessary.
     * If the database connection must be configured in some way before the schema
     * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
     * </p>
     *
     * @param db The database.
     */
    public void onOpen(SQLiteDatabase db) {}
}
相关推荐
二流小码农2 小时前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少2 小时前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker2 小时前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋3 小时前
Android 协程时代,Handler 应该退休了吗?
android
tingshuo291711 小时前
S001 【模板】从前缀函数到KMP应用 字符串匹配 字符串周期
笔记
火柴就是我16 小时前
让我们实现一个更好看的内部阴影按钮
android·flutter
砖厂小工1 天前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心1 天前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心1 天前
Android 17 来了!新特性介绍与适配建议
android·前端
Kapaseker1 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin