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