SQLiteOpenHelper
SQLiteOpenHelper是Android开发中用于管理SQLite数据库的一个非常重要的工具类。以下是对SQLiteOpenHelper的详细介绍:
一、基本概念
SQLiteOpenHelper是一个抽象类,它主要用于管理数据库的创建和版本管理。通过继承这个类,开发者可以重写一些方法以实现数据库的创建、升级和降级等功能。
二、主要方法
- 构造方法:用于创建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)
- onCreate(SQLiteDatabase db):在数据库第一次创建时调用,用于执行创建表和初始化数据等操作。
- onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本升级时调用,用于执行表结构的修改、数据迁移等操作。
- onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本降级时调用,但这个方法不是必须的,因为在实际开发中降级操作并不常见。
- getReadableDatabase():获取一个可读的数据库对象。如果数据库不存在,则会先调用onCreate()方法创建数据库。
- 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() 方法 |
抽象方法 |
创建数据库时做什么, |
没有对应的数据库时才调用 |
抽象方法 |
升级数据库版本时做什么 |
构造方法参数的版本号上升时才调用 |
非抽象,可选 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
的子类时,我们需要调用其中一个构造方法来初始化父类。然后,我们可以实现 onCreate
、onUpgrade
等方法来定义数据库的结构和升级逻辑。
例如:
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
。我们在构造方法中调用了父类的构造方法,并传入了数据库名称、版本号和上下文对象。然后,我们实现了 onCreate
和 onUpgrade
方法来定义数据库的结构和升级逻辑。
三、 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) {
}
// ...(其他方法和内部类省略)
}
关键部分解释:
- 构造函数:
SQLiteOpenHelper
提供了两个构造函数,允许开发者指定数据库名称、版本号、游标工厂和错误处理器。版本号必须大于等于 1。 - getWritableDatabase():此方法用于创建或打开一个数据库,用于读写操作。如果数据库磁盘空间已满,则尝试以只读方式打开,但会抛出异常。
- getReadableDatabase():此方法用于创建或打开一个数据库,用于读取操作。如果数据库磁盘空间已满,则只能以只读方式打开。
- onCreate(SQLiteDatabase db):这是一个抽象方法,当数据库第一次创建时调用。开发者应在此方法中编写创建表和初始化数据的 SQL 语句。
- onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):这也是一个抽象方法,当数据库版本升级时调用。开发者应在此方法中编写升级数据库的 SQL 语句,如添加新列、修改表结构等。
- onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion):这是一个可选实现的方法,当数据库版本降级时调用。默认情况下,此方法会抛出异常,因为降级操作通常不被推荐。
- 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是一个抽象类,它主要用于管理数据库的创建和版本管理。通过继承这个类,开发者可以重写一些方法以实现数据库的创建、升级和降级等功能。
二、主要方法
- 构造方法:用于创建SQLiteOpenHelper对象,需要传入数据库名称、版本号和一个可选的CursorFactory对象。
- onCreate(SQLiteDatabase db):在数据库第一次创建时调用,用于执行创建表和初始化数据等操作。
- onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本升级时调用,用于执行表结构的修改、数据迁移等操作。
- onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本降级时调用,但这个方法不是必须的,因为在实际开发中降级操作并不常见。
- getReadableDatabase():获取一个可读的数据库对象。如果数据库不存在,则会先调用onCreate()方法创建数据库。
- getWritableDatabase():获取一个可写的数据库对象。如果数据库不存在,也会先调用onCreate()方法创建数据库。
三、使用示例
以下是一个使用SQLiteOpenHelper创建数据库、表以及进行增删改查操作的简单示例:
- 创建数据库帮助类:
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);
}
}
- 使用数据库帮助类进行增删改查操作:
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();
四、注意事项
- 数据库版本管理:在开发过程中,随着应用程序的更新,数据库结构可能会发生变化。因此,合理使用onUpgrade()方法来进行数据库结构的升级是非常重要的。
- 异常处理:在进行数据库操作时,可能会遇到各种异常情况,如SQL语法错误、数据库文件损坏等。因此,在实际开发中,应该添加适当的异常处理代码来确保程序的健壮性。
- 性能优化:对于大规模的数据库操作,如批量插入、更新或删除数据,应该考虑使用事务(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) {}
}