From a6f16c4b8c6c31d3911662c26231492c5ec1b80c Mon Sep 17 00:00:00 2001 From: zgx <2821644377@qq.com> Date: Tue, 20 May 2025 11:24:15 +0800 Subject: [PATCH] 111 --- src/net/micode/notes/data/Contact.java | 41 ++- src/net/micode/notes/data/Notes.java | 331 ++++-------------- .../notes/data/NotesDatabaseHelper.java | 212 ++++++----- src/net/micode/notes/data/NotesProvider.java | 162 ++++++--- 4 files changed, 338 insertions(+), 408 deletions(-) diff --git a/src/net/micode/notes/data/Contact.java b/src/net/micode/notes/data/Contact.java index 805f6cd..346591e 100644 --- a/src/net/micode/notes/data/Contact.java +++ b/src/net/micode/notes/data/Contact.java @@ -16,7 +16,7 @@ package net.micode.notes.data;//导入包 -import android.content.Context; +import android.content.Context;// import android.database.Cursor; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Data; @@ -24,19 +24,32 @@ import android.telephony.PhoneNumberUtils; import android.util.Log; import java.util.HashMap; - +/** + * 联系人工具类,用于根据电话号码查询联系人名称,并带有缓存机制 + */ public class Contact { + // 静态缓存,用于存储电话号码与对应联系人名称的映射关系,避免重复查询数据库 private static HashMap sContactCache; + // 日志标签 private static final String TAG = "Contact"; - + // 查询条件模板: + // 1. 使用PHONE_NUMBERS_EQUAL比较电话号码(考虑格式差异) + // 2. 数据类型为电话条目 + // 3. 限制raw_contact_id必须在phone_lookup表中匹配指定最小匹配长度的记录 private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + " AND " + Data.RAW_CONTACT_ID + " IN " + "(SELECT raw_contact_id " + " FROM phone_lookup" + " WHERE min_match = '+')"; - + /** + * 根据电话号码查询联系人名称 + * @param context 上下文对象,用于访问内容解析器 + * @param phoneNumber 要查询的电话号码 + * @return 联系人名称,如果未找到或出错则返回null + */ public static String getContact(Context context, String phoneNumber) { + // 初始化缓存 if(sContactCache == null) { sContactCache = new HashMap(); } @@ -44,26 +57,30 @@ public class Contact { if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } - + // 优先从缓存中获取 String selection = CALLER_ID_SELECTION.replace("+", + // 构造完整查询条件:替换最小匹配长度参数 PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 通过内容解析器查询联系人数据库 Cursor cursor = context.getContentResolver().query( - Data.CONTENT_URI, - new String [] { Phone.DISPLAY_NAME }, - selection, - new String[] { phoneNumber }, - null); - + Data.CONTENT_URI,// 访问组合联系人数据 + new String [] { Phone.DISPLAY_NAME },// 查询显示名字段 + selection,// 动态生成的查询条件 + new String[] { phoneNumber },// 查询参数(原始电话号码) + null);// 排序方式(无) + // 处理查询结果 if (cursor != null && cursor.moveToFirst()) { try { + // 获取第一条记录的显示名称 String name = cursor.getString(0); + // 更新缓存 sContactCache.put(phoneNumber, name); return name; } catch (IndexOutOfBoundsException e) { Log.e(TAG, " Cursor get string error " + e.toString()); return null; } finally { - cursor.close(); + cursor.close();// 确保关闭Cursor释放资源 } } else { Log.d(TAG, "No contact matched with number:" + phoneNumber); diff --git a/src/net/micode/notes/data/Notes.java b/src/net/micode/notes/data/Notes.java index bb19d5d..346591e 100644 --- a/src/net/micode/notes/data/Notes.java +++ b/src/net/micode/notes/data/Notes.java @@ -14,266 +14,77 @@ * limitations under the License. */ -package net.micode.notes.data; - -import android.net.Uri; -public class Notes {//定义类 - public static final String AUTHORITY = "micode_notes"; - public static final String TAG = "Notes"; - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; - - /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records - */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; - - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; - - public static class DataConstants { - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; - } - - /** - * Uri to query all notes and folders - */ - public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); - - /** - * Uri to query data +package net.micode.notes.data;//导入包 + +import android.content.Context;// +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; +/** + * 联系人工具类,用于根据电话号码查询联系人名称,并带有缓存机制 + */ +public class Contact { + // 静态缓存,用于存储电话号码与对应联系人名称的映射关系,避免重复查询数据库 + private static HashMap sContactCache; + // 日志标签 + private static final String TAG = "Contact"; + // 查询条件模板: + // 1. 使用PHONE_NUMBERS_EQUAL比较电话号码(考虑格式差异) + // 2. 数据类型为电话条目 + // 3. 限制raw_contact_id必须在phone_lookup表中匹配指定最小匹配长度的记录 + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + /** + * 根据电话号码查询联系人名称 + * @param context 上下文对象,用于访问内容解析器 + * @param phoneNumber 要查询的电话号码 + * @return 联系人名称,如果未找到或出错则返回null */ - public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); - - public interface NoteColumns { - /** - * The unique ID for a row - *

Type: INTEGER (long)

- */ - public static final String ID = "_id"; - - /** - * The parent's id for note or folder - *

Type: INTEGER (long)

- */ - public static final String PARENT_ID = "parent_id"; - - /** - * Created data for note or folder - *

Type: INTEGER (long)

- */ - public static final String CREATED_DATE = "created_date"; - - /** - * Latest modified date - *

Type: INTEGER (long)

- */ - public static final String MODIFIED_DATE = "modified_date"; - - - /** - * Alert date - *

Type: INTEGER (long)

- */ - public static final String ALERTED_DATE = "alert_date"; - - /** - * Folder's name or text content of note - *

Type: TEXT

- */ - public static final String SNIPPET = "snippet"; - - /** - * Note's widget id - *

Type: INTEGER (long)

- */ - public static final String WIDGET_ID = "widget_id"; - - /** - * Note's widget type - *

Type: INTEGER (long)

- */ - public static final String WIDGET_TYPE = "widget_type"; - - /** - * Note's background color's id - *

Type: INTEGER (long)

- */ - public static final String BG_COLOR_ID = "bg_color_id"; - - /** - * For text note, it doesn't has attachment, for multi-media - * note, it has at least one attachment - *

Type: INTEGER

- */ - public static final String HAS_ATTACHMENT = "has_attachment"; - - /** - * Folder's count of notes - *

Type: INTEGER (long)

- */ - public static final String NOTES_COUNT = "notes_count"; - - /** - * The file type: folder or note - *

Type: INTEGER

- */ - public static final String TYPE = "type"; - - /** - * The last sync id - *

Type: INTEGER (long)

- */ - public static final String SYNC_ID = "sync_id"; - - /** - * Sign to indicate local modified or not - *

Type: INTEGER

- */ - public static final String LOCAL_MODIFIED = "local_modified"; - - /** - * Original parent id before moving into temporary folder - *

Type : INTEGER

- */ - public static final String ORIGIN_PARENT_ID = "origin_parent_id"; - - /** - * The gtask id - *

Type : TEXT

- */ - public static final String GTASK_ID = "gtask_id"; - - /** - * The version code - *

Type : INTEGER (long)

- */ - public static final String VERSION = "version"; - } - - public interface DataColumns { - /** - * The unique ID for a row - *

Type: INTEGER (long)

- */ - public static final String ID = "_id"; - - /** - * The MIME type of the item represented by this row. - *

Type: Text

- */ - public static final String MIME_TYPE = "mime_type"; - - /** - * The reference id to note that this data belongs to - *

Type: INTEGER (long)

- */ - public static final String NOTE_ID = "note_id"; - - /** - * Created data for note or folder - *

Type: INTEGER (long)

- */ - public static final String CREATED_DATE = "created_date"; - - /** - * Latest modified date - *

Type: INTEGER (long)

- */ - public static final String MODIFIED_DATE = "modified_date"; - - /** - * Data's content - *

Type: TEXT

- */ - public static final String CONTENT = "content"; - - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

- */ - public static final String DATA1 = "data1"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

- */ - public static final String DATA2 = "data2"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

- */ - public static final String DATA3 = "data3"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

- */ - public static final String DATA4 = "data4"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

- */ - public static final String DATA5 = "data5"; - } - - public static final class TextNote implements DataColumns { - /** - * Mode to indicate the text in check list mode or not - *

Type: Integer 1:check list mode 0: normal mode

- */ - public static final String MODE = DATA1; - - public static final int MODE_CHECK_LIST = 1; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); - } - - public static final class CallNote implements DataColumns { - /** - * Call date for this record - *

Type: INTEGER (long)

- */ - public static final String CALL_DATE = DATA1; - - /** - * Phone number for this record - *

Type: TEXT

- */ - public static final String PHONE_NUMBER = DATA3; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); + public static String getContact(Context context, String phoneNumber) { + // 初始化缓存 + if(sContactCache == null) { + sContactCache = new HashMap(); + } + + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + // 优先从缓存中获取 + String selection = CALLER_ID_SELECTION.replace("+", + // 构造完整查询条件:替换最小匹配长度参数 + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 通过内容解析器查询联系人数据库 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI,// 访问组合联系人数据 + new String [] { Phone.DISPLAY_NAME },// 查询显示名字段 + selection,// 动态生成的查询条件 + new String[] { phoneNumber },// 查询参数(原始电话号码) + null);// 排序方式(无) + // 处理查询结果 + if (cursor != null && cursor.moveToFirst()) { + try { + // 获取第一条记录的显示名称 + String name = cursor.getString(0); + // 更新缓存 + sContactCache.put(phoneNumber, name); + return name; + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + cursor.close();// 确保关闭Cursor释放资源 + } + } else { + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } } } diff --git a/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/net/micode/notes/data/NotesDatabaseHelper.java index 93afb00..9ad6333 100644 --- a/src/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/net/micode/notes/data/NotesDatabaseHelper.java @@ -25,63 +25,69 @@ 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; - +/** + * 笔记数据库帮助类,负责数据库的创建、升级以及表结构管理 + * 采用单例模式确保全局唯一数据库实例 + */ public class NotesDatabaseHelper extends SQLiteOpenHelper { - private static final String DB_NAME = "note.db"; - - private static final int DB_VERSION = 4; + // 数据库元数据配置 + private static final String DB_NAME = "note.db";// 数据库文件名 + private static final int DB_VERSION = 4;// 当前数据库版本 + // 表名常量接口 public interface TABLE { - public static final String NOTE = "note"; + public static final String NOTE = "note";// 主表:存储笔记元信息(标题、创建时间、父目录等) - public static final String DATA = "data"; + public static final String DATA = "data";// 数据表:存储笔记具体内容及扩展数据 } - private static final String TAG = "NotesDatabaseHelper"; - - private static NotesDatabaseHelper mInstance; + private static final String TAG = "NotesDatabaseHelper";// 日志标签 + private static NotesDatabaseHelper mInstance; // 单例实例 + // Note表建表语句 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.ID + " INTEGER PRIMARY KEY," +// 笔记唯一标识 + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +// 所属文件夹ID(树形结构) + 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" + + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +// 是否有附件(0/1布尔值) + 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," +// 关联的小部件ID + 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 ''," +// Google Task关联ID + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 数据版本号(乐观锁) ")"; - +// Data表建表语句 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 ''" + + DataColumns.ID + " INTEGER PRIMARY KEY," +// 数据项唯一标识 + DataColumns.MIME_TYPE + " TEXT NOT NULL," +// MIME类型(文本、清单、媒体等) + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID + 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," +// 扩展字段1(类型相关数据) + DataColumns.DATA2 + " INTEGER," +// 扩展字段2(如清单项状态) + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +// 扩展字段3(如地理位置) + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +// 扩展字段4(如附件路径) + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +// 扩展字段5(保留字段) ")"; - +// 数据表索引:加速按笔记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 + ");"; + // 触发器:当更新笔记的父目录时,增加新文件夹的计数 + // 场景示例:把笔记A从文件夹X移动到文件夹Y时,Y的计数+1 /** * Increase folder's note count when move note to the folder */ @@ -93,7 +99,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; - +// 触发器:当更新笔记的父目录时,减少原文件夹的计数 + // 场景示例:同上移动操作时,X的计数-1(需>0时生效) /** * Decrease folder's note count when move note from folder */ @@ -106,7 +113,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " 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 */ @@ -118,7 +125,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " 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 */ @@ -131,33 +138,42 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + " AND " + NoteColumns.NOTES_COUNT + ">0;" + " END"; - +/** + * 插入NOTE类型数据时自动更新笔记摘要 + * 场景:当在data表插入类型为普通笔记的数据时,自动更新note表的snippet字段 + */ /** * Update note's content when insert data with type {@link DataConstants#NOTE} */ 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 + "'" + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +// 仅处理普通笔记类型 " BEGIN" + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +// 使用新插入的内容更新摘要 + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +// 通过NOTE_ID关联 " END"; - + /** + * 更新NOTE类型数据时同步更新笔记摘要 + * 场景:修改已有笔记内容时(如编辑文本),自动更新对应笔记摘要 + */ /** * Update note's content when data with {@link DataConstants#NOTE} type has changed */ 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 + "'" + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +// 仅处理原类型为笔记的记录 " BEGIN" + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +// 同步最新内容到摘要 " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; - +/** + * 删除NOTE类型数据时清空笔记摘要 + * 场景:当删除笔记的正文数据时,自动清空对应笔记的摘要 + */ /** * Update note's content when data with {@link DataConstants#NOTE} type has deleted */ @@ -167,10 +183,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + " BEGIN" + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=''" + + " SET " + NoteColumns.SNIPPET + "=''" +// 清空摘要内容 " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " END"; - +/** + * 笔记级联删除触发器 + * 场景:当删除note表的记录时,自动删除关联的data表数据 + * 作用:防止产生孤儿数据(没有对应笔记的数据项) + */ /** * Delete datas belong to note which has been deleted */ @@ -179,9 +199,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " AFTER DELETE ON " + TABLE.NOTE + " BEGIN" + " DELETE FROM " + TABLE.DATA + - " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +// 根据被删除笔记ID清理数据 " END"; - + /** + * 文件夹级联删除触发器 + * 场景:当删除文件夹时,自动删除其下所有笔记 + * 注意:会触发delete_data_on_delete触发器进行二次清理 + */ /** * Delete notes belong to folder which has been deleted */ @@ -190,22 +214,33 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " AFTER DELETE ON " + TABLE.NOTE + " BEGIN" + " DELETE FROM " + TABLE.NOTE + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +// 删除所有父目录为被删文件夹的笔记 " END"; - + /** + * 文件夹移动至回收站操作触发器 + * 场景:当文件夹被移动到回收站时,自动将其子项也移动到回收站 + * 设计目的:保持文件夹结构的完整性 + */ /** * Move notes belong to folder which has been moved to trash folder */ 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 + + " 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 + ";" + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +// 批量更新子项父目录为回收站 + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 选择原父目录下的所有笔记 " END"; - + /** + * 初始化系统文件夹(首次创建数据库时调用) + * 插入预定义的系统文件夹: + * 1. 通话记录文件夹(ID_CALL_RECORD_FOLDER) + * 2. 根文件夹(ID_ROOT_FOLDER) + * 3. 临时文件夹(ID_TEMPARAY_FOLDER) + * 4. 回收站(ID_TRASH_FOLER) + */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @@ -237,14 +272,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - + // 插入通话记录文件夹 /** * call record foler for call notes */ values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);// 标记为系统文件夹 db.insert(TABLE.NOTE, null, values); - + // 插入根文件夹(默认笔记存储位置) /** * root folder which is default folder */ @@ -252,7 +287,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { 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 */ @@ -260,7 +295,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - + // 插入回收站(软删除数据存储位置) /** * create trash folder */ @@ -293,7 +328,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } return mInstance; } - + /** + * 数据库版本升级控制中心 + * 升级策略: + * V1 → V2:完全重建表结构(破坏性升级) + * V2 → V3:添加Google Task支持 + 创建回收站 + * V3 → V4:添加版本控制字段 + */ @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); @@ -304,57 +345,72 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; boolean skipV2 = false; - +// V1到V2升级策略:完全重建表结构 if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 + skipV2 = true; // this upgrade including the upgrade from v2 to v3// 包含V2到V3的升级逻辑 oldVersion++; } - + // V2到V3升级策略:非破坏性升级 if (oldVersion == 2 && !skipV2) { upgradeToV3(db); - reCreateTriggers = true; + reCreateTriggers = true;// 需要重建触发器 oldVersion++; } - + // V3到V4升级策略:添加版本字段 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"); } } - +/** + * 升级到V2版本(破坏性升级) + * 操作说明: + * 1. 删除旧表 + * 2. 重建表结构 + * 3. 初始化系统文件夹 + */ 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); } - + /** + * 升级到V3版本(功能增强) + * 主要变更: + * 1. 移除过期的修改时间触发器 + * 2. 新增GTASK_ID字段(用于Google Tasks同步) + * 3. 初始化回收站文件夹 + */ private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers + // drop unused triggers// 移除旧版的时间更新触发器 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"); - // add a column for gtask id + // add a column for gtask id// 添加Google Task关联字段 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder + // add a trash system folder// 插入回收站系统文件夹 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); } - +/** + * 升级到V4版本(数据版本控制) + * 新增字段:VERSION(用于实现乐观锁机制) + */ private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); diff --git a/src/net/micode/notes/data/NotesProvider.java b/src/net/micode/notes/data/NotesProvider.java index edb0a60..c27894a 100644 --- a/src/net/micode/notes/data/NotesProvider.java +++ b/src/net/micode/notes/data/NotesProvider.java @@ -34,22 +34,26 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; - +/** + * 笔记内容提供者,提供标准ContentProvider接口管理笔记数据 + * 功能包括:基础CRUD操作、搜索建议支持、数据变更通知 + */ public class NotesProvider extends ContentProvider { + // URI匹配器,用于路由不同数据请求 private static final UriMatcher mMatcher; - private NotesDatabaseHelper mHelper; - - private static final String TAG = "NotesProvider"; + private NotesDatabaseHelper mHelper;// 数据库帮助类实例 - private static final int URI_NOTE = 1; - private static final int URI_NOTE_ITEM = 2; - private static final int URI_DATA = 3; - private static final int URI_DATA_ITEM = 4; + private static final String TAG = "NotesProvider";// 日志标签 - private static final int URI_SEARCH = 5; - private static final int URI_SEARCH_SUGGEST = 6; + private static final int URI_NOTE = 1;// 笔记集合操作 + private static final int URI_NOTE_ITEM = 2; // 单个笔记操作 + private static final int URI_DATA = 3;// 数据项集合操作 + private static final int URI_DATA_ITEM = 4;// 单个数据项操作 + private static final int URI_SEARCH = 5; // 全文搜索 + private static final int URI_SEARCH_SUGGEST = 6;// 搜索建议 +// 初始化URI匹配规则 static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); @@ -57,30 +61,37 @@ public class NotesProvider extends ContentProvider { mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + // 系统搜索建议标准URI mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); } - + /** + * 搜索建议投影映射: + * - 将数据库字段映射到系统搜索建议需要的字段 + * - x'0A'代表SQLite中的换行符,去除换行和空格以优化显示 + */ /** * x'0A' represents the '\n' character in sqlite. For title and content in the search result, * we will trim '\n' and white space in order to show more information. */ - private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," - + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," - + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," - + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," - + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; - + private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","// 原始ID + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","// 建议项携带数据 + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","// 主文本 + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 副文本 + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","// 建议图标 + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","// 点击动作 + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; // 数据类型 +// 笔记片段搜索SQL查询(排除回收站且类型为普通笔记) private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?"// 模糊查询 + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER// 过滤回收站 + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 仅普通笔记类型 + @Override public boolean onCreate() { + // 初始化数据库帮助类(单例模式) mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } @@ -91,47 +102,53 @@ public class NotesProvider extends ContentProvider { Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; + // 根据URI类型路由处理 switch (mMatcher.match(uri)) { - case URI_NOTE: + case URI_NOTE: // 查询所有笔记 c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); break; - case URI_NOTE_ITEM: + case URI_NOTE_ITEM:// 查询单个笔记 id = uri.getPathSegments().get(1); - c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id// 从URI路径获取ID + parseSelection(selection), selectionArgs, null, null, sortOrder); break; - case URI_DATA: + case URI_DATA:// 查询所有数据项 c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); break; - case URI_DATA_ITEM: + case URI_DATA_ITEM:// 查询单个数据项 id = uri.getPathSegments().get(1); c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; - case URI_SEARCH: - case URI_SEARCH_SUGGEST: + case URI_SEARCH:// 处理搜索请求 + case URI_SEARCH_SUGGEST: // 处理搜索建议请求 + // 验证参数完整性 if (sortOrder != null || projection != null) { throw new IllegalArgumentException( "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); } - + // 解析搜索关键词 String searchString = null; if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + // 从建议URI路径获取:content://auth/search_suggest_query/关键词 if (uri.getPathSegments().size() > 1) { searchString = uri.getPathSegments().get(1); } } else { + // 从查询参数获取:content://auth/search?pattern=关键词 searchString = uri.getQueryParameter("pattern"); } if (TextUtils.isEmpty(searchString)) { - return null; + return null; // 空搜索直接返回 } try { + // 构造模糊查询参数(前后加%) searchString = String.format("%%%s%%", searchString); + // 执行原始SQL查询(参数化防止注入) c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); } catch (IllegalStateException ex) { @@ -141,21 +158,27 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + // 设置数据变更通知URI(重要:使CursorLoader能自动刷新) if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } - +/** + * 插入新数据项 + * @param uri 目标URI(note或data表) + * @param values 要插入的数据值 + * @return 新创建项的URI + */ @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = mHelper.getWritableDatabase(); long dataId = 0, noteId = 0, insertedId = 0; switch (mMatcher.match(uri)) { - case URI_NOTE: + case URI_NOTE: // 插入新笔记 insertedId = noteId = db.insert(TABLE.NOTE, null, values); break; - case URI_DATA: + case URI_DATA: // 插入笔记关联数据 if (values.containsKey(DataColumns.NOTE_ID)) { noteId = values.getAsLong(DataColumns.NOTE_ID); } else { @@ -166,50 +189,55 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri - if (noteId > 0) { + // Notify the note uri// 发送数据变更通知 + if (noteId > 0) {// 通知笔记URI变更(触发CursorLoader刷新) getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - // Notify the data uri + // Notify the data uri// 通知数据URI变更 if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } - +// 返回新创建项的完整URI(如content://auth/note/123) return ContentUris.withAppendedId(uri, insertedId); } - +/** + * 删除数据项 + * @param uri 目标URI(可指定单个项或集合) + * @return 删除的行数 + */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean deleteData = false; + boolean deleteData = false;// 标记是否删除的是data表数据 switch (mMatcher.match(uri)) { - case URI_NOTE: + case URI_NOTE:// 删除多个笔记 + // 防止删除系统文件夹(ID>0的条件) selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); break; - case URI_NOTE_ITEM: + case URI_NOTE_ITEM:// 删除单个笔记 id = uri.getPathSegments().get(1); /** * ID that smaller than 0 is system folder which is not allowed to * trash */ - long noteId = Long.valueOf(id); + long noteId = Long.valueOf(id); // 系统文件夹(ID<=0)不允许删除 if (noteId <= 0) { break; } count = db.delete(TABLE.NOTE, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; - case URI_DATA: + case URI_DATA:// 删除多个数据项 count = db.delete(TABLE.DATA, selection, selectionArgs); deleteData = true; break; - case URI_DATA_ITEM: + case URI_DATA_ITEM:// 删除单个数据项 id = uri.getPathSegments().get(1); count = db.delete(TABLE.DATA, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); @@ -218,37 +246,44 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + // 发送通知(数据变更可能影响笔记显示) if (count > 0) { if (deleteData) { + // 数据变更需要通知笔记列表更新 getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } + } // 通知当前URI对应的数据变更 getContext().getContentResolver().notifyChange(uri, null); } return count; } - +/** + * 更新数据项 + * @param uri 目标URI + * @param values 新数据值 + * @return 更新的行数 + */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean updateData = false; + boolean updateData = false;// 标记是否更新data表 switch (mMatcher.match(uri)) { - case URI_NOTE: + case URI_NOTE: // 批量更新笔记 increaseNoteVersion(-1, selection, selectionArgs); count = db.update(TABLE.NOTE, values, selection, selectionArgs); break; - case URI_NOTE_ITEM: + case URI_NOTE_ITEM: // 更新单个笔记 id = uri.getPathSegments().get(1); increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; - case URI_DATA: + case URI_DATA: // 更新多个数据项 count = db.update(TABLE.DATA, values, selection, selectionArgs); updateData = true; break; - case URI_DATA_ITEM: + case URI_DATA_ITEM:// 更新单个数据项 id = uri.getPathSegments().get(1); count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); @@ -257,20 +292,31 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - +// 发送数据变更通知 if (count > 0) { if (updateData) { + // 数据变更需要更新关联笔记 getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } getContext().getContentResolver().notifyChange(uri, null); } return count; } - +/** + * 合并基本选择条件 + * @param selection 原始选择条件 + * @return 格式化后的条件语句 + */ private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } + /** + * 增加笔记版本号(实现乐观锁机制) + * @param id 笔记ID(-1表示批量更新) + * @param selection 选择条件 + * @param selectionArgs 条件参数 + */ private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); @@ -278,13 +324,13 @@ public class NotesProvider extends ContentProvider { sql.append(" SET "); sql.append(NoteColumns.VERSION); sql.append("=" + NoteColumns.VERSION + "+1 "); - + // 构造WHERE条件 if (id > 0 || !TextUtils.isEmpty(selection)) { sql.append(" WHERE "); } if (id > 0) { sql.append(NoteColumns.ID + "=" + String.valueOf(id)); - } + } // 处理选择条件(手动替换参数防止注入) if (!TextUtils.isEmpty(selection)) { String selectString = id > 0 ? parseSelection(selection) : selection; for (String args : selectionArgs) { @@ -292,13 +338,13 @@ public class NotesProvider extends ContentProvider { } sql.append(selectString); } - + // 执行原生SQL更新(绕过常规更新流程) mHelper.getWritableDatabase().execSQL(sql.toString()); } @Override public String getType(Uri uri) { - // TODO Auto-generated method stub + // TODO Auto-generated method stub// 根据URI返回MIME类型(当前未实现) return null; }