/* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * * 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 net.micode.notes.data; import android.content.ContentValues; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; /** * `NotesDatabaseHelper`类继承自`SQLiteOpenHelper`,是用于管理和操作应用中笔记相关数据库的帮助类。 * 它负责数据库的创建、升级以及创建各种数据表、索引、触发器等操作,以支持笔记应用的数据存储和相关业务逻辑。 */ public class NotesDatabaseHelper extends SQLiteOpenHelper { // 定义数据库的名称,这里是“note.db”,表示应用中存储笔记数据的数据库文件名 private static final String DB_NAME = "note.db"; // 定义数据库的版本号,用于数据库升级等操作的版本控制,这里初始版本号为4,每次数据库结构有变更时可能会相应更新该版本号 private static final int DB_VERSION = 4; /** * 定义内部接口`TABLE`,用于存放数据库中表的名称常量,方便在代码中统一引用,提高代码的可读性和可维护性。 */ public interface TABLE { // 定义笔记表的名称常量,用于在创建表、查询等操作中明确操作的是哪个表 public static final String NOTE = "note"; // 定义数据(可能是笔记附属的数据,如附件等相关数据)表的名称常量,同样用于相关数据库操作中对该表的引用 public static final String DATA = "data"; } // 用于在日志输出中标识当前类的简单名称,方便在查看日志时快速定位到该类相关的操作记录,便于调试和问题排查 private static final String TAG = "NotesDatabaseHelper"; // 单例模式下的唯一实例对象,通过静态方法`getInstance`来获取该实例,保证整个应用只有一个该类实例来管理数据库操作,避免多次创建造成资源浪费或数据不一致等问题 private static NotesDatabaseHelper mInstance; // 创建笔记表(`TABLE.NOTE`)的SQL语句,定义了表的各个列名、数据类型以及一些列的默认值等信息,用于构建笔记表的结构 private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + ")"; // 创建数据(`TABLE.DATA`)表的SQL语句,规定了该表包含的列名、数据类型以及默认值等,用于存储与笔记相关的数据信息,比如可能是附件数据等 private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" + DataColumns.ID + " INTEGER PRIMARY KEY," + DataColumns.MIME_TYPE + " TEXT NOT NULL," + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + DataColumns.DATA1 + " INTEGER," + DataColumns.DATA2 + " INTEGER," + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + ")"; // 创建数据(`TABLE.DATA`)表中基于`NOTE_ID`字段的索引的SQL语句,通过创建索引可以提高根据笔记ID查询相关数据的效率 private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX IF NOT EXISTS note_id_index ON " + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** * Increase folder's note count when move note to the folder */ /** * 定义触发器的SQL语句,当笔记移动到某个文件夹(更新`NoteColumns.PARENT_ID`字段)时,增加该文件夹的笔记数量。 * 此触发器在`TABLE.NOTE`表的`NoteColumns.PARENT_ID`字段更新操作之后触发,通过更新`NoteColumns.NOTES_COUNT`字段来实现数量增加。 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update "+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; /** * Decrease folder's note count when move note from folder */ /** * 定义触发器的SQL语句,当笔记从某个文件夹移出(更新`NoteColumns.PARENT_ID`字段)时,减少该文件夹的笔记数量。 * 在`TABLE.NOTE`表的`NoteColumns.PARENT_ID`字段更新操作之后触发,并且只有当当前笔记数量大于0时才进行减少操作,通过更新`NoteColumns.NOTES_COUNT`字段实现数量减少。 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + " END"; /** * Increase folder's note count when insert new note to the folder */ /** * 定义触发器的SQL语句,当向某个文件夹插入新笔记(在`TABLE.NOTE`表插入操作之后)时,增加该文件夹的笔记数量。 * 通过更新`NoteColumns.NOTES_COUNT`字段来实现数量增加,增加的条件是根据新插入笔记的`Parent_ID`来确定对应的文件夹。 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = "CREATE TRIGGER increase_folder_count_on_insert " + " AFTER INSERT ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; /** * Decrease folder's note count when delete note from the folder */ /** * 定义触发器的SQL语句,当从某个文件夹删除笔记(在`TABLE.NOTE`表删除操作之后)时,减少该文件夹的笔记数量。 * 通过更新`NoteColumns.NOTES_COUNT`字段来实现数量减少,减少的条件是根据被删除笔记的原`Parent_ID`来确定对应的文件夹,并且只有当前笔记数量大于0时才进行减少操作。 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + " AND " + NoteColumns.NOTES_COUNT + ">0;" + " END"; /** * Update note's content when insert data with type {@link DataConstants#NOTE} */ /** * 定义触发器的SQL语句,当向`TABLE.DATA`表插入数据(插入操作之后),且插入的数据类型为`DataConstants.NOTE`(可能表示笔记主体内容相关数据)时, * 更新对应的笔记(`TABLE.NOTE`表中)的内容(`NoteColumns.SNIPPET`字段),将其设置为新插入数据的`CONTENT`字段内容,通过关联`NOTE_ID`字段来确定对应的笔记。 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert " + " AFTER INSERT ON " + TABLE.DATA + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + " BEGIN" + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; /** * Update note's content when data with {@link DataConstants#NOTE} type has changed */ /** * 定义触发器的SQL语句,当`TABLE.DATA`表中类型为`DataConstants.NOTE`的数据发生更新(更新操作之后)时, * 更新对应的笔记(`TABLE.NOTE`表中)的内容(`NoteColumns.SNIPPET`字段),将其设置为更新后的数据的`CONTENT`字段内容,同样通过`NOTE_ID`字段关联对应的笔记。 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = "CREATE TRIGGER update_note_content_on_update " + " AFTER UPDATE ON " + TABLE.DATA + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + " BEGIN" + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; /** * Update note's content when data with {@link DataConstants#NOTE} type has deleted */ /** * 定义触发器的SQL语句,当`TABLE.DATA`表中类型为`DataConstants.NOTE`的数据被删除(删除操作之后)时, * 将对应的笔记(`TABLE.NOTE`表中)的内容(`NoteColumns.SNIPPET`字段)清空,通过`NOTE_ID`字段确定对应的笔记进行操作。 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = "CREATE TRIGGER update_note_content_on_delete " + " AFTER delete ON " + TABLE.DATA + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + " BEGIN" + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.SNIPPET + "=''" + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " END"; /** * Delete datas belong to note which has been deleted */ /** * 定义触发器的SQL语句,当`TABLE.NOTE`表中的笔记被删除(删除操作之后),删除`TABLE.DATA`表中属于该被删除笔记的所有相关数据, * 通过关联`DataColumns.NOTE_ID`和`NoteColumns.ID`字段来确定要删除的数据记录。 */ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = "CREATE TRIGGER delete_data_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + " BEGIN" + " DELETE FROM " + TABLE.DATA + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + " END"; /** * Delete notes belong to folder which has been deleted */ /** * 定义触发器的SQL语句,当`TABLE.NOTE`表中的文件夹被删除(删除操作之后),删除`TABLE.NOTE`表中属于该被删除文件夹的所有笔记, * 通过关联`NoteColumns.PARENT_ID`和`NoteColumns.ID`字段来确定要删除的笔记记录,实现级联删除的效果。 */ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = "CREATE TRIGGER folder_delete_notes_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + " BEGIN" + " DELETE FROM " + TABLE.NOTE + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; /** * Move notes belong to folder which has been moved to trash folder */ /** * 定义触发器的SQL语句,当`TABLE.NOTE`表中的文件夹被移动到回收站文件夹(通过更新`NoteColumns.PARENT_ID`字段来表示移动操作)时, * 将属于该文件夹的所有笔记也移动到回收站文件夹,同样通过关联`NoteColumns.PARENT_ID`字段来确定要移动的笔记记录。 */ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = "CREATE TRIGGER folder_move_notes_on_trash " + " AFTER UPDATE ON " + TABLE.NOTE + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + " BEGIN" + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; /** * 构造函数,调用父类(`SQLiteOpenHelper`)的构造函数来初始化数据库帮助类,传入应用上下文、数据库名称、游标工厂(这里为`null`)以及数据库版本号等参数。 * * @param context 应用的上下文环境,用于获取应用相关的资源以及进行一些与系统交互的操作,是数据库操作所依赖的上下文基础。 */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } /** * 创建笔记表(`TABLE.NOTE`)的方法,在给定的`SQLiteDatabase`对象上执行创建表的SQL语句, * 然后重新创建与笔记表相关的所有触发器(通过调用`reCreateNoteTableTriggers`方法), * 最后创建系统文件夹(通过调用`createSystemFolder`方法),并在日志中记录笔记表已创建的信息。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,通常是通过`getWritableDatabase`或`getReadableDatabase`方法获取的可读写或只读的数据库实例。 */ public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); createSystemFolder(db); Log.d(TAG, "note table has been created"); } /** * 重新创建笔记表(`TABLE.NOTE`)相关触发器的私有方法。 * 此方法的目的是先删除可能已存在的与笔记表相关的各个触发器(使用`DROP TRIGGER IF EXISTS`语句确保删除操作的安全性,即只有存在对应触发器时才删除), * 然后再按照预先定义好的触发器创建语句重新创建所有需要的触发器,以保证数据库中的触发器配置与当前代码定义的业务逻辑一致。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,通过该对象执行删除和创建触发器的SQL语句,对数据库中的触发器进行操作。 */ private void reCreateNoteTableTriggers(SQLiteDatabase db) { // 删除名为“increase_folder_count_on_update”的触发器(如果存在的话),该触发器用于在笔记移动到文件夹时增加文件夹的笔记数量 db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); // 删除名为“decrease_folder_count_on_update”的触发器(若存在),此触发器在笔记从文件夹移出时减少文件夹的笔记数量 db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); // 删除“decrease_folder_count_on_delete”触发器(如果有),用于在从文件夹删除笔记时减少文件夹笔记数量 db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); // 删除“delete_data_on_delete”触发器(若存在),其作用是当笔记被删除时,删除与之关联的数据表中的相关数据 db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); // 删除“increase_folder_count_on_insert”触发器(如果存在),该触发器在向文件夹插入新笔记时增加文件夹笔记数量 db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); // 删除“folder_delete_notes_on_delete”触发器(若存在),用于在文件夹被删除时,删除该文件夹下的所有笔记 db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); // 删除“folder_move_notes_on_trash”触发器(如果有),此触发器在文件夹被移动到回收站时,将该文件夹下的笔记也移动到回收站 db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); // 重新创建“increase_folder_count_on_update”触发器,用于实现笔记移动到文件夹时文件夹笔记数量的增加逻辑 db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); // 重新创建“decrease_folder_count_on_update”触发器,以处理笔记从文件夹移出时文件夹笔记数量减少的情况 db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); // 重新创建“decrease_folder_count_on_delete”触发器,用于在从文件夹删除笔记时正确减少文件夹的笔记数量 db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); // 重新创建“delete_data_on_delete”触发器,确保在笔记被删除时能正确删除与之关联的数据 db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); // 重新创建“increase_folder_count_on_insert”触发器,实现向文件夹插入新笔记时增加文件夹笔记数量的功能 db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); // 重新创建“folder_delete_notes_on_delete”触发器,使得文件夹被删除时能相应删除其下的所有笔 db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); // 重新创建“folder_move_notes_on_trash”触发器,保证文件夹移动到回收站时其下笔记也能正确移动到回收站 db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } /** * 私有方法,用于在数据库中创建系统文件夹相关记录。 * 通过向`TABLE.NOTE`表插入不同类型的系统文件夹记录,每个文件夹记录使用`ContentValues`对象来设置对应列的值, * 然后调用`db.insert`方法将记录插入到数据库的笔记表中,以此创建不同用途的系统文件夹,如通话记录文件夹、根文件夹、临时文件夹和回收站文件夹等。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,借助该对象向数据库中插入系统文件夹的记录信息。 */ private void createSystemFolder(SQLiteDatabase db) { // 创建一个用于存储表列值的ContentValues对象,用于后续设置要插入的文件夹记录的各列数据 ContentValues values = new ContentValues(); /** * call record foler for call notes */ /** * 创建通话记录文件夹的记录插入到笔记表(`TABLE.NOTE`)中。 * 设置该文件夹记录的ID为`Notes.ID_CALL_RECORD_FOLDER`(这里`Notes.ID_CALL_RECORD_FOLDER`应该是在其他地方定义的表示通话记录文件夹的唯一标识符常量), * 类型设置为`Notes.TYPE_SYSTEM`(表示这是系统文件夹类型),然后将该记录插入到笔记表中。 */ values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); /** * root folder which is default folder */ /** * 创建根文件夹记录并插入到笔记表中,根文件夹是默认文件夹,设置其ID为`Notes.ID_ROOT_FOLDER`(对应的唯一标识符常量), * 类型同样为`Notes.TYPE_SYSTEM`,然后执行插入操作将根文件夹记录添加到数据库。 */ values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); /** * temporary folder which is used for moving note */ /** * 创建临时文件夹记录插入到笔记表,临时文件夹用于笔记移动等操作场景,设置其ID为`Notes.ID_TEMPARAY_FOLDER`(对应的标识符常量), * 类型为`Notes.TYPE_SYSTEM`,接着插入该记录到数据库中。 */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); /** * create trash folder */ /** * 创建回收站文件夹记录并插入到笔记表,设置其ID为`Notes.ID_TRASH_FOLER`(对应的标识符常量), * 类型为`Notes.TYPE_SYSTEM`,最后将回收站文件夹记录添加到数据库里。 */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } /** * 创建数据(`TABLE.DATA`)表的方法,在给定的`SQLiteDatabase`对象上执行创建数据表的SQL语句来创建数据表格结构, * 接着调用`reCreateDataTableTriggers`方法重新创建与该数据表相关的触发器,然后创建基于`NOTE_ID`字段的索引(通过执行相应的索引创建SQL语句), * 最后在日志中记录数据表格已创建的信息,方便调试和查看数据库操作情况。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,在该对象对应的数据库中执行创建表、触发器和索引等相关操作。 */ public void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_TABLE_SQL); reCreateDataTableTriggers(db); db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); Log.d(TAG, "data table has been created"); } /** * 重新创建数据(`TABLE.DATA`)表相关触发器的私有方法。 * 先删除可能已存在的与数据表格相关的特定触发器(使用`DROP TRIGGER IF EXISTS`语句确保删除的安全性,只有存在对应触发器时才执行删除), * 然后再按照预定义的触发器创建语句重新创建这些触发器,以保证数据表格相关的业务逻辑通过触发器正确实现,例如在数据插入、更新、删除时对关联笔记内容的相应更新操作等。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,通过该对象执行删除和创建触发器的SQL语句,对数据库中的数据表格相关触发器进行操作。 */ private void reCreateDataTableTriggers(SQLiteDatabase db) { // 删除名为“update_note_content_on_insert”的触发器(如果存在的话),该触发器用于在向数据表格插入特定类型数据时更新关联笔记的内容 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); // 删除“update_note_content_on_update”触发器(若存在),此触发器在数据表格中的数据更新时,相应更新关联笔记的内容 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); // 删除“update_note_content_on_delete”触发器(如果有),用于在数据表格中的数据被删除时,对关联笔记内容进行相应处理 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); // 重新创建“update_note_content_on_insert”触发器,以实现向数据表格插入特定类型数据时更新关联笔记内容的功能 db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); // 重新创建“update_note_content_on_insert”触发器,以实现向数据表格插入特定类型数据时更新关联笔记内容的功能 db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); // 重新创建“update_note_content_on_delete”触发器,使得在数据表格中的数据被删除时能按逻辑处理关联笔记的内容 db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } /** * 获取`NotesDatabaseHelper`类单例实例的静态同步方法,采用单例模式保证整个应用中只有一个该类的实例来管理数据库操作, * 避免多次创建实例可能导致的数据库连接混乱、资源浪费以及数据不一致等问题。 * 如果实例尚未创建(`mInstance`为`null`),则通过传入的应用上下文创建一个新的`NotesDatabaseHelper`实例,然后返回该实例供其他地方使用。 * * @param context 应用的上下文环境,用于初始化`NotesDatabaseHelper`实例,提供数据库操作所需的上下文相关信息,比如资源访问、系统服务调用等。 * @return `NotesDatabaseHelper`类的单例实例,可用于后续的数据库创建、升级以及各种数据操作相关方法的调用。 */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); } return mInstance; } /** * `SQLiteOpenHelper`类的抽象方法重写,在数据库首次创建时被调用。 * 此方法负责创建数据库中的笔记表(通过调用`createNoteTable`方法)和数据表(调用`createDataTable`方法), * 完成数据库初始结构的搭建,使得应用后续能够在这些表中存储和查询笔记及相关数据信息。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,通过该对象执行创建表的SQL语句,在新建的数据库中创建相应的数据表结构。 */ @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } /** * `SQLiteOpenHelper`类的抽象方法重写,用于处理数据库升级的逻辑。 * 根据数据库的旧版本号(`oldVersion`)来执行不同的升级操作,比如删除旧表重新创建(从版本1升级时)、添加新列(如版本3升级到版本4时添加`NoteColumns.VERSION`列)、 * 创建或重新创建相关触发器(根据`reCreateTriggers`标志来判断)等,最后会检查升级后的版本号是否与期望的新版本号(`newVersion`)一致, * 如果不一致则抛出异常,表示数据库升级失败。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,在该数据库上执行各种升级相关的SQL语句,如修改表结构、添加列、创建触发器等操作。 * @param oldVersion 数据库当前的旧版本号,用于判断需要执行哪些具体的升级步骤,不同的旧版本对应不同的升级逻辑。 * @param newVersion 期望将数据库升级到的新版本号,用于最后验证升级是否成功完成,即升级后数据库的版本号应与该新的版本号一致。 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; boolean skipV2 = false; // 如果数据库的旧版本号为1,表示是从版本1进行升级,执行`upgradeToV2`方法进行升级操作,同时标记`skipV2`为`true`, // 意味着这次升级包含了从版本2到版本3的升级逻辑(可能后续有相关判断依赖此标记),然后将旧版本号加1,表示已经处理了版本1的升级情况。 if (oldVersion == 1) { upgradeToV2(db); skipV2 = true; // this upgrade including the upgrade from v2 to v3 oldVersion++; } // 如果旧版本号为2且`skipV2`为`false`(即不是从版本1直接升级过来包含了后续版本升级逻辑的情况),则执行`upgradeToV3`方法进行版本3的升级操作, // 并将`reCreateTriggers`标记为`true`,表示需要重新创建相关触发器,之后将旧版本号再加1。 if (oldVersion == 2 && !skipV2) { upgradeToV3(db); reCreateTriggers = true; oldVersion++; } // 如果旧版本号为3,则执行`upgradeToV4`方法将数据库升级到版本4,然后将旧版本号加1 if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } // 如果`reCreateTriggers`为`true`,即需要重新创建触发器的情况,分别调用`reCreateNoteTableTriggers`和`reCreateDataTableTriggers`方法 // 来重新创建笔记表和数据表相关的触发器,确保数据库升级后触发器的配置符合新的业务逻辑要求。 if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } // 最后检查升级后的数据库版本号是否与期望的新版本号一致,如果不一致则抛出`IllegalStateException`异常,提示数据库升级失败, // 并给出期望升级到的版本号信息,方便排查升级过程中出现的问题。 if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } /** * 私有方法,用于将数据库从版本1升级到版本2的具体操作逻辑。 * 先删除已存在的笔记表(`TABLE.NOTE`)和数据表(`TABLE.DATA`)(如果存在的话,使用`DROP TABLE IF EXISTS`语句确保删除操作的安全性), * 然后重新创建这两个表(通过分别调用`createNoteTable`和`createDataTable`方法),以此完成从版本1到版本2的数据库结构更新操作。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,通过该对象执行删除表和重新创建表的SQL语句,实现数据库结构的变更。 */ private void upgradeToV2(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); createNoteTable(db); createDataTable(db); } /** * 私有方法,用于将数据库从版本2升级到版本3的具体操作逻辑。 * 首先删除一些不再使用的触发器(通过执行`DROP TRIGGER IF EXISTS`语句来删除对应的触发器), * 然后向笔记表(`TABLE.NOTE`)中添加一个新列`NoteColumns.GTASK_ID`(用于存储可能与Google Tasks相关的标识符等信息,数据类型为文本且默认值为空字符串), * 最后向笔记表中插入一个新的系统文件夹记录,即回收站文件夹记录(通过创建`ContentValues`对象设置对应列值并调用`db.insert`方法插入记录)。 * * @param db 用于执行数据库操作的`SQLiteDatabase`对象,通过该对象执行删除触发器、修改表结构(添加列)以及插入记录等操作,完成数据库从版本2到版本3的升级相关变更。 */ private void upgradeToV3(SQLiteDatabase db) { // drop unused triggers // 删除名为“update_note_modified_date_on_insert”的触发器(如果存在的话),可能该触发器在新版本中不再需要使用 db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); // 删除名为“update_note_modified_date_on_delete”的触发器(若存在)。 // 这个触发器或许是在删除笔记记录时对笔记修改日期进行相关处理的,由于业务逻辑变更等原因,在版本3中不再使用,因此将其删除 db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); // 删除名为“update_note_modified_date_on_update”的触发器(如果有)。 // 同理,该触发器可能是在更新笔记记录时涉及笔记修改日期操作的,在新的版本里此功能不再需要,所以要删除掉这个触发器。 db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); // add a column for gtask id // 向笔记表(`TABLE.NOTE`)添加一个新的列,名为`NoteColumns.GTASK_ID`,用于存储与Google Tasks相关的标识符等信息。 // 数据类型定义为 `TEXT`(文本类型),并且设置该列的默认值为空字符串 `''`。 // 通过 `ALTER TABLE` 语句实现对表结构的修改,添加新列以满足新的业务需求,比如可能用于后续与Google Tasks服务交互时标记笔记对应的任务等功能。 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); // add a trash system folder // 创建一个 `ContentValues` 对象,用于存储要插入到数据库表中的数据(以键值对的形式)。 // 这里准备向笔记表(`TABLE.NOTE`)中插入一条新的系统文件夹记录,即回收站文件夹的记录信息。 ContentValues values = new ContentValues(); // 将回收站文件夹的唯一标识符(`Notes.ID_TRASH_FOLER`,应该是在别处定义好的常量)设置到 `NoteColumns.ID` 列中, // 以此确定这个记录对应的文件夹的唯一标识,方便后续对该文件夹进行操作和识别 values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); // 设置该文件夹的类型为 `Notes.TYPE_SYSTEM`,表明这是一个系统文件夹,用于和普通用户创建的文件夹进行区分, // 系统文件夹通常具有特定的功能和管理方式,例如回收站文件夹有其特殊的处理逻辑(如还原、彻底删除等操作) values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 使用 `db.insert` 方法将包含上述设置好的列值的记录插入到笔记表(`TABLE.NOTE`)中。 // 第一个参数指定要插入数据的表名(这里就是 `TABLE.NOTE`),第二个参数通常用于指定如果插入的数据为空值时对应的处理策略(这里传 `null` 表示按默认方式处理), // 第三个参数就是包含了要插入的具体数据的 `ContentValues` 对象。通过这个插入操作,就在数据库中创建了回收站文件夹的记录信息。 db.insert(TABLE.NOTE, null, values); } private void upgradeToV4(SQLiteDatabase db) { // 使用 `ALTER TABLE` 语句来修改笔记表(`TABLE.NOTE`)的结构。 // 具体操作是添加一个新的列,列名为 `NoteColumns.VERSION`,这个列名通常在相关的列定义常量类(如 `Notes` 类中应该有对应定义)中有具体的定义和含义。 // 该列的数据类型被定义为 `INTEGER`(整数类型),并且设置其默认值为 `0`。添加这个版本列可能用于记录笔记数据本身的版本情况, // 例如,随着应用功能迭代,笔记数据结构可能有变化,通过这个版本号可以方便地进行数据兼容性处理、区分不同阶段的笔记格式等操作,有助于维护数据的一致性和正确处理。 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } }