Bootstrap

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 的源代码):

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 等方法来定义数据库的结构和升级逻辑。

例如:

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 源代码示例,并附有关键部分的解释:

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

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();
    }

}










一些用法收集参考

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;
    }
}

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. 创建数据库帮助类
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. 使用数据库帮助类进行增删改查操作
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)

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(创建、读取、更新、删除)操作。

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 的源码

/*
 * 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) {}
}













;