/* * 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; /** * 笔记数据库帮助类 * * 功能概述: * 1. 继承SQLiteOpenHelper,管理SQLite数据库的创建和升级 * 2. 定义note表和data表的SQL创建语句 * 3. 定义数据库触发器,实现自动化数据维护 * 4. 管理数据库版本升级逻辑 * * 数据库设计: * - note表:存储笔记基本信息(ID、类型、时间、摘要等) * - data表:存储笔记具体内容(文本、通话记录等) * - 触发器:自动维护文件夹计数、笔记摘要、级联删除等 * * 单例模式: * - 使用getInstance()获取唯一实例 * - 确保整个应用中只有一个数据库连接 */ public class NotesDatabaseHelper extends SQLiteOpenHelper { // 数据库文件名 private static final String DB_NAME = "note.db"; // 数据库版本号,当前为版本5 // 版本历史: // V1: 初始版本 // V2: 重建表结构 // V3: 添加Google Task支持和回收站 // V4: 添加版本号字段 // V5: 当前版本 private static final int DB_VERSION = 5; /** * 表名常量接口 * 定义数据库中的表名 */ public interface TABLE { // 笔记表,存储笔记基本信息 public static final String NOTE = "note"; // 数据表,存储笔记具体内容 public static final String DATA = "data"; } private static final String TAG = "NotesDatabaseHelper"; // 单例实例,确保全局只有一个数据库连接 private static NotesDatabaseHelper mInstance; /** * 创建笔记表的SQL语句 * * note表结构说明: * - ID: 主键,自增 * - PARENT_ID: 父文件夹ID,0=根文件夹,负数=系统文件夹 * - ALERTED_DATE: 提醒时间,0=无提醒 * - BG_COLOR_ID: 背景颜色ID * - CREATED_DATE: 创建时间,默认当前时间 * - HAS_ATTACHMENT: 是否有附件,0=无,1=有 * - MODIFIED_DATE: 修改时间,默认当前时间 * - NOTES_COUNT: 笔记数量(仅文件夹有效) * - SNIPPET: 内容摘要 * - TYPE: 类型,0=笔记,1=文件夹,2=系统文件夹 * - WIDGET_ID: 桌面Widget ID * - WIDGET_TYPE: Widget类型,-1=无效,0=2x2,1=4x4 * - SYNC_ID: Google同步ID * - LOCAL_MODIFIED: 本地修改标记 * - ORIGIN_PARENT_ID: 原始父文件夹ID * - GTASK_ID: Google Task ID * - VERSION: 版本号 */ 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" + ")"; /** * 创建数据表的SQL语句 * * data表结构说明: * - ID: 主键,自增 * - MIME_TYPE: MIME类型,区分数据类型(text_note、call_note) * - NOTE_ID: 所属笔记ID,外键关联note表 * - CREATED_DATE: 创建时间 * - MODIFIED_DATE: 修改时间 * - CONTENT: 内容文本 * - DATA1-DATA5: 扩展字段,不同类型有不同用途 * * 采用EAV模型设计: * - 一条笔记可以有多个data记录 * - 通过MIME_TYPE区分数据类型 * - 通过NOTE_ID关联到note表 */ 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 ''" + ")"; /** * 创建数据表索引的SQL语句 * * 索引说明: * - 在data表的NOTE_ID字段上创建索引 * - 加速根据笔记ID查询数据记录的操作 * - 提高note-data关联查询的性能 */ private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX IF NOT EXISTS note_id_index ON " + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** * 触发器:更新笔记父文件夹时,增加目标文件夹的笔记计数 * * 触发时机:当note表的PARENT_ID字段被更新后 * 触发条件:无(每次PARENT_ID更新都触发) * 执行动作:将新父文件夹的NOTES_COUNT加1 * * 使用场景: * - 将笔记从一个文件夹移动到另一个文件夹 * - 自动维护文件夹中的笔记数量 */ 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"; /** * 触发器:更新笔记父文件夹时,减少原文件夹的笔记计数 * * 触发时机:当note表的PARENT_ID字段被更新后 * 触发条件:无(每次PARENT_ID更新都触发) * 执行动作:将原父文件夹的NOTES_COUNT减1(如果大于0) * * 注意事项: * - 使用old.PARENT_ID获取原父文件夹ID * - 检查NOTES_COUNT>0防止出现负数 */ 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"; /** * 触发器:插入新笔记时,增加父文件夹的笔记计数 * * 触发时机:向note表插入新记录后 * 触发条件:无(每次插入都触发) * 执行动作:将父文件夹的NOTES_COUNT加1 * * 使用场景: * - 创建新笔记 * - 自动维护文件夹中的笔记数量 */ 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"; /** * 触发器:删除笔记时,减少父文件夹的笔记计数 * * 触发时机:从note表删除记录后 * 触发条件:无(每次删除都触发) * 执行动作:将父文件夹的NOTES_COUNT减1(如果大于0) * * 注意事项: * - 使用old.PARENT_ID获取父文件夹ID * - 检查NOTES_COUNT>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"; /** * 触发器:插入数据时,更新笔记的内容摘要 * * 触发时机:向data表插入新记录后 * 触发条件:新记录的MIME_TYPE为普通笔记(DataConstants.NOTE) * 执行动作:将note表的SNIPPET字段更新为新记录的内容 * * 使用场景: * - 创建新笔记内容 * - 自动更新笔记列表显示的摘要 * * 注意事项: * - 只处理普通文本笔记,不处理通话记录 * - 摘要用于在笔记列表中快速预览内容 */ 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"; /** * 触发器:更新数据时,更新笔记的内容摘要 * * 触发时机:更新data表记录后 * 触发条件:旧记录的MIME_TYPE为普通笔记(DataConstants.NOTE) * 执行动作:将note表的SNIPPET字段更新为新记录的内容 * * 使用场景: * - 编辑笔记内容 * - 自动更新笔记列表显示的摘要 * * 注意事项: * - 检查旧记录的MIME_TYPE,确保是普通笔记 * - 保持摘要与内容同步 */ 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"; /** * 触发器:删除数据时,清空笔记的内容摘要 * * 触发时机:从data表删除记录后 * 触发条件:旧记录的MIME_TYPE为普通笔记(DataConstants.NOTE) * 执行动作:将note表的SNIPPET字段设置为空字符串 * * 使用场景: * - 删除笔记内容 * - 清空笔记列表显示的摘要 * * 注意事项: * - 摘要设置为空字符串而非NULL * - 保持数据库一致性 */ 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"; /** * 触发器:删除笔记时,级联删除关联的数据记录 * * 触发时机:从note表删除记录后 * 触发条件:无(每次删除都触发) * 执行动作:删除data表中所有NOTE_ID等于被删除笔记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"; /** * 触发器:删除文件夹时,级联删除文件夹内的所有笔记 * * 触发时机:从note表删除记录后 * 触发条件:无(每次删除都触发) * 执行动作:删除note表中所有PARENT_ID等于被删除文件夹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"; /** * 触发器:移动笔记到回收站时,级联移动子笔记 * * 触发时机:更新note表记录后 * 触发条件:新记录的PARENT_ID等于回收站文件夹ID(Notes.ID_TRASH_FOLER) * 执行动作:将所有PARENT_ID等于被移动笔记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的构造方法: * - context: 上下文环境 * - DB_NAME: 数据库文件名(note.db) * - factory: Cursor工厂(null表示使用默认工厂) * - DB_VERSION: 数据库版本号(5) * * @param context 上下文环境,用于访问应用文件和数据库 */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } /** * 创建笔记表 * * 执行步骤: * 1. 执行CREATE_NOTE_TABLE_SQL创建note表 * 2. 重新创建note表相关触发器 * 3. 创建系统文件夹(根文件夹、临时文件夹等) * * 该方法在数据库首次创建时调用(onCreate方法中) * * @param db SQLiteDatabase对象,用于执行SQL语句 */ public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); createSystemFolder(db); Log.d(TAG, "note table has been created"); } /** * 重新创建笔记表触发器 * * 执行步骤: * 1. 删除所有已存在的note表触发器(如果存在) * 2. 重新创建所有note表触发器 * * 触发器列表: * - increase_folder_count_on_update: 更新文件夹时增加计数 * - decrease_folder_count_on_update: 更新文件夹时减少计数 * - decrease_folder_count_on_delete: 删除笔记时减少计数 * - delete_data_on_delete: 删除笔记时级联删除数据 * - increase_folder_count_on_insert: 插入笔记时增加计数 * - folder_delete_notes_on_delete: 删除文件夹时级联删除笔记 * - folder_move_notes_on_trash: 移动到回收站时级联移动 * * 使用场景: * - 数据库升级后重新创建触发器 * - 确保触发器定义与当前代码一致 * * @param db SQLiteDatabase对象,用于执行SQL语句 */ private void reCreateNoteTableTriggers(SQLiteDatabase db) { // 删除所有旧触发器 db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); // 重新创建所有触发器 db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } /** * 创建系统文件夹 * * 创建的系统文件夹: * 1. ID_CALL_RECORD_FOLDER(-2): 通话记录文件夹 * 2. ID_ROOT_FOLDER(0): 根文件夹 * 3. ID_TEMPARAY_FOLDER(-1): 临时文件夹 * 4. ID_TRASH_FOLER(-3): 回收站文件夹 * * 所有系统文件夹的TYPE字段都设置为TYPE_SYSTEM(2) * * 使用场景: * - 数据库首次创建时调用 * - 确保系统文件夹存在 * * @param db SQLiteDatabase对象,用于执行SQL语句 */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); // 创建通话记录文件夹 values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); // 创建根文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); // 创建临时文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); // 创建回收站文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } /** * 创建数据表 * * 执行步骤: * 1. 执行CREATE_DATA_TABLE_SQL创建data表 * 2. 重新创建data表相关触发器 * 3. 创建data表索引(note_id_index) * * 该方法在数据库首次创建时调用(onCreate方法中) * * @param db SQLiteDatabase对象,用于执行SQL语句 */ 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"); } /** * 重新创建数据表触发器 * * 执行步骤: * 1. 删除所有已存在的data表触发器(如果存在) * 2. 重新创建所有data表触发器 * * 触发器列表: * - update_note_content_on_insert: 插入数据时更新摘要 * - update_note_content_on_update: 更新数据时更新摘要 * - update_note_content_on_delete: 删除数据时清空摘要 * * 使用场景: * - 数据库升级后重新创建触发器 * - 确保触发器定义与当前代码一致 * * @param db SQLiteDatabase对象,用于执行SQL语句 */ private void reCreateDataTableTriggers(SQLiteDatabase db) { // 删除所有旧触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); // 重新创建所有触发器 db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } /** * 获取单例实例 * * 使用双重检查锁定模式实现线程安全的单例: * 1. 检查mInstance是否为null * 2. 如果为null,创建新实例 * 3. 返回唯一实例 * * 优点: * - 确保全局只有一个数据库连接 * - 节省系统资源 * - 避免多线程并发创建多个实例 * * 使用场景: * - 整个应用中获取数据库帮助类实例 * - NotesProvider中初始化数据库连接 * * @param context 上下文环境 * @return NotesDatabaseHelper的唯一实例 */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); } return mInstance; } /** * 数据库首次创建时调用 * * 执行步骤: * 1. 创建note表 * 2. 创建data表 * * 该方法由SQLiteOpenHelper自动调用: * - 当数据库文件不存在时 * - 当数据库版本号与代码中定义的版本号一致时 * * @param db SQLiteDatabase对象,表示新创建的数据库 */ @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } /** * 数据库升级处理 * * 升级策略: * - 根据旧版本号逐步升级到新版本 * - 每个版本都有对应的升级方法 * - 升级过程中可能需要重建触发器 * * 版本升级流程: * V1 -> V2: 重建表结构(upgradeToV2) * V2 -> V3: 添加Google Task支持和回收站(upgradeToV3) * V3 -> V4: 添加版本号字段(upgradeToV4) * * 注意事项: * - 升级过程中不删除用户数据 * - 升级失败会抛出IllegalStateException * - 某些版本升级后需要重建触发器 * * @param db SQLiteDatabase对象 * @param oldVersion 旧版本号 * @param newVersion 新版本号 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; boolean skipV2 = false; // 从版本1升级到版本2 if (oldVersion == 1) { upgradeToV2(db); skipV2 = true; // V2升级已在V1升级中完成,跳过V2 oldVersion++; } // 从版本2升级到版本3 if (oldVersion == 2 && !skipV2) { upgradeToV3(db); reCreateTriggers = true; // V3升级需要重建触发器 oldVersion++; } // 从版本3升级到版本4 if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } // 如果需要重建触发器 if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } // 检查升级是否成功 if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } /** * 升级到版本2 - 重建表结构 * * 升级内容: * 1. 删除旧的note表和data表 * 2. 重新创建note表和data表 * 3. 重新创建所有触发器 * 4. 重新创建系统文件夹 * * 注意事项: * - 这是破坏性升级,会丢失所有数据 * - 仅在版本1时使用(初始版本可能存在设计缺陷) * - 后续版本升级不会执行此方法 * * @param db SQLiteDatabase对象 */ 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); } /** * 升级到版本3 - 添加Google Task支持和回收站 * * 升级内容: * 1. 删除旧的修改日期触发器(已废弃) * 2. 在note表中添加GTASK_ID字段 * 3. 创建回收站文件夹(ID_TRASH_FOLER) * * 新功能: * - Google Task同步:通过GTASK_ID字段与Google Tasks同步 * - 回收站功能:删除的笔记先放入回收站,而非直接删除 * * 注意事项: * - 升级后需要重建触发器(reCreateTriggers=true) * - GTASK_ID字段默认值为空字符串 * * @param db SQLiteDatabase对象 */ private void upgradeToV3(SQLiteDatabase db) { // 删除旧的触发器(已废弃) db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); // 添加Google Task ID字段 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); // 创建回收站文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } /** * 升级到版本4 - 添加版本号字段 * * 升级内容: * 1. 在note表中添加VERSION字段 * * 新功能: * - 版本控制:通过VERSION字段跟踪笔记的修改版本 * - 冲突检测:在同步时通过版本号检测冲突 * - 并发控制:防止多人同时修改同一笔记 * * 注意事项: * - VERSION字段默认值为0 * - 每次修改笔记时,VERSION字段会自动加1(通过increaseNoteVersion方法) * * @param db SQLiteDatabase对象 */ private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } }