diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx index 743021a..f8be71f 100644 Binary files a/doc/小米便签开源代码的泛读报告.docx and b/doc/小米便签开源代码的泛读报告.docx differ diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java index d97ac5d..9b125c6 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java @@ -26,17 +26,25 @@ import android.util.Log; import java.util.HashMap; public class Contact { + // 用于处理联系人信息 + // 实现了从联系人数据库中获取指定电话号码对应的联系人姓名的功能 + + // sContactCache:用于缓存电话号码和对应的联系人姓名 + // TAG:用于日志输出的标识 + private static HashMap sContactCache; private static final String TAG = "Contact"; - + //SQL查询条件( WHERE 后面的语句),用于从联系人数据库中筛选出与给定电话号码匹配的联系人。 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 " + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + "(SELECT raw_contact_id " + " FROM phone_lookup" + " WHERE min_match = '+')"; - + //功能简介:用于从Android设备的联系人数据库中获取与给定电话号码对应的联系人姓名。 + //参数:Context对象:用于访问系统服务和应用资源 phoneNumber:需要查询的联系人电话号码 public static String getContact(Context context, String phoneNumber) { + // 没映射表就建表,有就查缓存中有没有这个联系人 if(sContactCache == null) { sContactCache = new HashMap(); } @@ -44,7 +52,9 @@ public class Contact { if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } - + //缓存没有,就查询数据库 + //构造一个SQL查询条件:CALLER_ID_SELECTION中的"+"被替换为电话号码的最小匹配值 + //然后执行查询语句 String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); Cursor cursor = context.getContentResolver().query( @@ -53,6 +63,13 @@ public class Contact { selection, new String[] { phoneNumber }, null); + //判断查询结果: + //查询结果不为空,且能够移动到第一条记录: + // 那么就尝试从Cursor中获取联系人姓名,并将其存入缓存sContactCache。然后返回联系人姓名。 + // 异常情况:如果在获取字符串时发生数组越界异常,则记录一个错误日志并返回null。 + // 最后都要确保关闭Cursor对象,以避免内存泄漏。 + //如果查询结果为空或者没有记录可以移动到(即没有找到匹配的联系人): + // 则记录一条调试日志并返回null if (cursor != null && cursor.moveToFirst()) { try { diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java index f240604..59e0e2c 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java @@ -18,8 +18,10 @@ 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"; + // 这边定义了note表中,类型行的3种取值: public static final int TYPE_NOTE = 0; public static final int TYPE_FOLDER = 1; public static final int TYPE_SYSTEM = 2; @@ -30,11 +32,16 @@ public class Notes { * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records */ + // ID_ROOT_FOLDER:默认文件夹 + // ID_TEMPARAY_FOLDER:不属于文件夹的笔记 + // ID_CALL_RECORD_FOLDER:用于存储通话记录,以便返回 + // ID_TRASH_FOLER:垃圾回收站 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; + // 个人理解为就是定义一些布局的ID,这部分就是用于设置UI界面的一些布局或小组件的id。 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"; @@ -46,6 +53,7 @@ public class Notes { 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; @@ -54,12 +62,16 @@ public class Notes { /** * Uri to query all notes and folders */ + // Android开发中常见的用于定义内容提供者(Content Provider)URI,内容提供者是一种Android组件,它允许应用程序共享和存储数据。这里定义了一个URI来查询数据 public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); /** * Uri to query data */ public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + //这个接口定义了一系列静态的、最终的字符串常量,这些常量代表数据库表中的列名。 + // + //里面定义的属性有:ID、父级ID、创建日期、修改日期、提醒日期、文件(标签)名(摘要?)、小部件ID、小部件类型、背景颜色ID、附件、文件中的标签数量、 文件(标签)类型、最后一个同步ID、本地修改标签、移动前的ID、谷歌任务ID、代码版本信息 public interface NoteColumns { /** @@ -179,30 +191,35 @@ public class Notes { *

Type: Text

*/ public static final String MIME_TYPE = "mime_type"; + //MIME类型是一种标准,用于标识文档、文件或字节流的性质和格式。在数据库中,这个字段可以用来识别不同类型的数据,例如文本、图片、音频或视频等。 /** * The reference id to note that this data belongs to *

Type: INTEGER (long)

*/ public static final String NOTE_ID = "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"; + //数据内容 /** @@ -210,6 +227,10 @@ public class Notes { * integer data type *

Type: INTEGER

*/ + + // 以下5个是通用数据列,它们的具体意义取决于MIME类型(由MIME_TYPE字段指定)。 + // 不同的MIME类型可能需要存储不同类型的数据,这5个字段提供了灵活性,允许根据MIME类型来存储相应的数据。 + // 读后面的代码感觉这部分是在表示内容的不同状态? public static final String DATA1 = "data1"; /** @@ -241,39 +262,43 @@ public class Notes { 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

*/ + //模式?这个被存在DATA1列中 public static final String MODE = DATA1; - + //所处检查列表模式? public static final int MODE_CHECK_LIST = 1; - + // 定义了MIME类型,用于标识文本标签的目录 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - + // 定义了MIME类型,用于标识文本标签的单个项 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - + //文本标签内容提供者(Content Provider)的URI,用于访问文本标签数据 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

*/ + //意味着在数据库表中,这个电话号码信息将被存储在DATA3列中 public static final String PHONE_NUMBER = DATA3; - + // 同样定义了MIME类型,是用于标识通话记录的目录。 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - + // 同样定义了MIME类型,是用于标识通话记录的单个项。 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - + //定义了通话记录内容提供者的URI,用于访问通话记录数据。 public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } } diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..30d9287 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -28,194 +28,264 @@ import net.micode.notes.data.Notes.NoteColumns; public class NotesDatabaseHelper extends SQLiteOpenHelper { + // 数据库帮助类,用于管理名为 note.db 的 SQLite 数据库。 + // 它继承自 SQLiteOpenHelper 类,这是 Android提供的一个方便的工具类,用于管理数据库的创建和版本更新. + // 数据库的基本信息;数据库名称和版本信息(在创建实例对象时会用到) private static final String DB_NAME = "note.db"; private static final int DB_VERSION = 4; - + //内部接口:个人理解为两个表名,一个note,一个data 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表的语句,这里的NoteColumns就是我们刚刚在Notes中定义的一个接口,里面定义了一系列静态的数据库表中的列名 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" + - ")"; - + "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" + + ")"; + //同上,创建data表的语句,这里的DataColumns就是我们刚刚在Notes中定义的一个接口,里面定义了一系列静态的数据库表中的列名 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 ''" + - ")"; + "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 ''" + + ")"; + // 功能简介: + // 创建一个以note的ID为索引 + // 解读: + // 用于在TABLE.DATA表上创建一个名为note_id_index的索引。 + // 这个索引是基于DataColumns.NOTE_ID列的。IF NOT EXISTS确保了如果索引已经存在,那么就不会尝试重新创建它,避免了可能的错误。 + // 索引通常用于提高查询性能,特别是在对某个字段进行频繁查询时。 private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = - "CREATE INDEX IF NOT EXISTS note_id_index ON " + - TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; + + /* 以下是一些对便签增删改定义的触发器 */ + /* 总结 + * 这些触发器都是用来维护NOTE表和与之相关联的DATA表之间数据一致性的。 + * 当在NOTE表中发生删除或更新操作时,这些触发器会自动执行相应的数据清理或更新操作,确保数据库中的数据保持正确和一致。 + * 特别是在处理文件夹和回收站等逻辑时,这些触发器起到了非常重要的作用,可以自动管理数据的移动和删除。*/ /** * Increase folder's note count when move note to the folder */ + // 功能简介: + // 添加触发器:增加文件夹的便签个数记录(因为我们会移动便签进入文件夹,这时候文件夹的计数要进行更新) + // 解读: + // 定义了一个SQL触发器increase_folder_count_on_update。 + // 触发器是一种特殊的存储过程,它会在指定表上的指定事件(如INSERT、UPDATE、DELETE)发生时自动执行。 + // 这个触发器会在TABLE.NOTE表的NoteColumns.PARENT_ID字段更新后执行。 + // 触发器的逻辑是:当某个笔记的PARENT_ID(即父文件夹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"; + "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 */ + // 功能简介:(触发器和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了) + // 添加触发器:减少文件夹的便签个数记录(因为我们会移动便签移出文件夹,这时候文件夹的计数要进行更新) + 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"; + "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 */ + // 功能简介:(触发器原理和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了) + // 添加触发器:当我们在文件夹插入便签时,增加文件夹的便签个数记录 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"; + "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 */ + // 功能简介:(触发器原理和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了) + // 添加触发器:当我们在文件夹删除便签时,减少文件夹的便签个数记录 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"; + "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} */ + // 功能简介: + // 添加触发器:当向DATA表中插入类型为NOTE(便签)的数据时,更新note表对应的笔记内容。 + // 解读: + // 在DATA表上进行INSERT操作后,如果新插入的数据的MIME_TYPE为NOTE,则触发此操作。 + // 它会更新NOTE表,将与新插入数据相关联的标签的SNIPPET(摘要)字段设置为新插入数据的CONTENT字段的值 + 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"; + "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 */ + // 功能简介: + // 添加触发器:当DATA表中,类型为NOTE(便签)的数据更改时,更新note表对应的笔记内容。 + // 解读: + // 在DATA表上进行UPDATE操作后,如果更新前的数据的MIME_TYPE为NOTE,则触发此操作。 + // 它会更新NOTE表,将与更新后的数据相关联的笔记的SNIPPET字段设置为新数据的CONTENT字段的值 + 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"; + "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 */ + // 功能简介: + // 添加触发器:当DATA表中,类型为NOTE(便签)的数据删除时,更新note表对应的笔记内容(置空)。 + // 解读: + // 在DATA表上进行DELETE操作后,如果删除的数据的MIME_TYPE为NOTE,则触发此操作。 + // 它会更新NOTE表,将与删除的数据相关联的笔记的SNIPPET字段设置为空字符串。 + 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"; + "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 */ + // 功能简介: + // 添加触发器:当从NOTE表中删除笔记时,删除与该笔记相关联的数据(就是删除data表中为该note的数据) + // 解读: + // 在NOTE表上进行DELETE操作后,此触发器被激活。 + // 它会从DATA表中删除所有与已删除的笔记(由old.ID表示)相关联的数据行(通过比较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"; + "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 */ + // 功能简介: + // 添加触发器:当从NOTE表中删除一个文件夹时,删除该文件夹下的所有笔记。 + // 解读: + // 在NOTE表上进行DELETE操作后,如果删除的是一个文件夹(由old.ID表示) + // 触发器会删除所有以该文件夹为父级(PARENT_ID)的笔记(通过比较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"; + "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 */ - 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"; + // 功能简介: + // 添加触发器:当某个文件夹被移动到回收站时,移动该文件夹下的所有笔记到回收站 + // 解读: + // 在NOTE表上进行UPDATE操作后,如果某个文件夹的新PARENT_ID字段值等于回收站的ID(Notes.ID_TRASH_FOLER) + // 触发器会更新所有以该文件夹为父级(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"; + // 构造器 public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } - + // 创建note(标签)表 public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); createSystemFolder(db); Log.d(TAG, "note table has been created"); } + // 重新创建或更新与笔记表相关的触发器。 + // 首先,使用DROP TRIGGER IF EXISTS语句删除已存在的触发器。确保在重新创建触发器之前,不存在同名的触发器。 + // 然后,使用db.execSQL()方法执行预定义的SQL语句,这些语句用于创建新的触发器。 private void reCreateNoteTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); @@ -234,6 +304,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } + /* 以下部分是操作SQLite数据库部分 */ + // 功能简介: + // 创建通话记录文件夹、默认文件夹、临时文件夹和回收站,并插入相关数据 + // 具体解读: + // ContentValues是一个用于存储键值对的类,常用于SQLite数据库的插入操作 + // values.put方法可以向ContentValues对象中添加数据。 + // NoteColumns.ID是存储文件夹ID的列名,Notes.ID_CALL_RECORD_FOLDER是通话记录文件夹的ID。 + // NoteColumns.TYPE是存储文件夹类型的列名,Notes.TYPE_SYSTEM表示这是一个系统文件夹。 + // 使用db.insert方法将values中的数据插入到TABLE.NOTE(即标签表)中。 + // 每次插入新数据前,都使用values.clear()方法清除ContentValues对象中的旧数据,确保不会重复插入旧数据。 + // 然后分别创建默认文件夹、临时文件夹和回收站,并以同样的方法插入数据。 private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); @@ -248,6 +329,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * root folder which is default folder */ + // 创建默认文件夹:重复上述步骤,但这次是为根文件夹插入数据。 values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); @@ -256,6 +338,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * temporary folder which is used for moving note */ + // 创建“临时”文件夹:同样地,为临时文件夹插入数据。 + values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); @@ -264,11 +348,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * create trash folder */ + // 创建“回收站”文件夹:最后,为回收站文件夹插入数据。 + values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + //功能简介: + //创建data(数据)表 + //解读: + //这个方法用于创建数据表,以及与之相关的触发器。 + //创建数据表:使用db.execSQL方法执行预定义的SQL语句CREATE_DATA_TABLE_SQL,用于创建数据表。 + //重新创建数据表触发器:调用reCreateDataTableTriggers方法,用于删除并重新创建与数据表相关的触发器。 + //创建索引:使用db.execSQL方法执行CREATE_DATA_NOTE_ID_INDEX_SQL语句,为数据表创建索引。 + //记录日志:使用Log.d方法记录一条调试级别的日志,表示数据表已经创建。 public void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_TABLE_SQL); @@ -276,6 +370,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); Log.d(TAG, "data table has been created"); } + //和上面的note表的reCreate...同理 + //重新创建或更新与笔记表相关的触发器。 + //首先,使用DROP TRIGGER IF EXISTS语句删除已存在的触发器。确保在重新创建触发器之前,不存在同名的触发器。 + //然后,使用db.execSQL()方法执行预定义的SQL语句,这些语句用于创建新的触发器。 + private void reCreateDataTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); @@ -286,6 +385,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } + //解读: + //synchronized关键字确保在多线程环境下,只有一个线程能够进入这个方法,防止了同时创建多个实例的情况 + //getInstance(Context context)方法使用了单例模式来确保整个应用程序中只有一个NotesDatabaseHelper实例。 + //它首先检查mInstance(类的静态成员变量,没有在代码片段中显示)是否为null。 + //如果是null,则创建一个新的NotesDatabaseHelper实例,并将其赋值给mInstance。最后返回mInstance。 static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { @@ -293,13 +397,19 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } return mInstance; } - + //功能简介: + //当数据库首次创建时,onCreate方法会被调用。 + //这里重写onCreate方法,它调用了上述createNoteTable(db)和createDataTable(db)两个方法 + //这样首次创建数据库时就多出了两张表。 @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } + //功能简介: + //当数据库需要升级时(即数据库的版本号改变),onUpgrade方法会被调用。 + //该方法会根据当前的oldVersion和新的newVersion来执行相应的升级操作 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; @@ -332,6 +442,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { + "fails"); } } + //功能简介: + // 将数据库从版本1升级到版本2。 + //解读: + // 首先,它删除了已经存在的NOTE和DATA表(如果存在的话)。DROP TABLE IF EXISTS语句确保了即使这些表不存在,也不会抛出错误。 + // 然后,它调用了createNoteTable(db)和createDataTable(db)方法来重新创建这两个表。这意味着在升级到版本2时,这两个表的内容会被完全清除,并重新创建新的空表。 private void upgradeToV2(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); @@ -339,6 +454,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { createNoteTable(db); createDataTable(db); } + //功能简介: + // 将数据库从版本2(或可能是跳过版本2的某个状态)升级到版本3。 + //解读: + // 首先,删除了三个不再使用的触发器(如果存在的话)。触发器是数据库中的一种对象,可以在插入、更新或删除记录时自动执行某些操作。 + // 然后,使用ALTER TABLE语句修改表结构,向NOTE表中添加了一个名为GTASK_ID的新列,并设置默认值为空字符串。 + // 最后,向NOTE表中插入了一条新的系统文件夹记录,表示一个名为“trash folder”的系统文件夹。这可能是用于存储已删除笔记的回收站功能。 private void upgradeToV3(SQLiteDatabase db) { // drop unused triggers @@ -354,7 +475,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } - + //功能简介: + // 这个方法负责将数据库从版本3升级到版本4。 + //解读: + // 它向NOTE表中添加了一个名为VERSION的新列,并设置了默认值为0。这个新列用于记录标签版本信息。 private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java index edb0a60..80d05a1 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java @@ -34,14 +34,14 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; - +//uri匹配器、NotesDatabaseHelper实类与日志标记的定义。 public class NotesProvider extends ContentProvider { private static final UriMatcher mMatcher; private NotesDatabaseHelper mHelper; private static final String TAG = "NotesProvider"; - +//6个URI的匹配码,用于区分不同的URI类型 private static final int URI_NOTE = 1; private static final int URI_NOTE_ITEM = 2; private static final int URI_DATA = 3; @@ -49,14 +49,32 @@ public class NotesProvider extends ContentProvider { private static final int URI_SEARCH = 5; private static final int URI_SEARCH_SUGGEST = 6; + //实例化一个mMatcher对象,并进一步定义uri的匹配规则。 + // + //这种写法是Android开发中常见的初始化静态成员变量的方式,特别是当涉及到内容提供者(Content Providers)时。 + // + //静态初始化块确保了在类加载时mMatcher就被初始化,并且所有的URI匹配规则也一并设置好了。这样做使得代码组织清晰,所有与URI匹配相关的设置都集中在一个地方,方便管理和维护 + //———————————————— + // + + ////功能概述: + ////初始化了一个UriMatcher对象mMatcher,并添加了一系列的URI匹配规则。 + ////解读: + static { + //创建了一个UriMatcher实例,并设置默认匹配码为NO_MATCH,表示如果没有任何URI匹配,则返回这个码。 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + //添加规则,当URI的authority为Notes.AUTHORITY,路径为note时,返回匹配码URI_NOTE mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + //添加规则,当URI的authority为Notes.AUTHORITY,路径为note/后跟一个数字(#代表数字)时,返回匹配码URI_NOTE_ITEM。 mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + //和上面两句同理,但用于匹配数据相关的URI mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + //用于匹配搜索相关的URI 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); } @@ -65,7 +83,15 @@ public class NotesProvider extends ContentProvider { * 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 + "," + //解读:(每行对应) +//返回笔记的 ID。 +//笔记的 ID 也被重命名为 SUGGEST_COLUMN_INTENT_EXTRA_DATA,这通常用于 Android 的搜索建议中,作为传递给相关 Intent 的额外数据。 +//对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n)替换为空字符串,然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_1 +//对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n)替换为空字符串,然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_2 +//返回一个用于搜索建议图标的资源 ID,并命名为 SUGGEST_COLUMN_ICON_1。 +//返回一个固定的 Intent 动作 ACTION_VIEW,并命名为 SUGGEST_COLUMN_INTENT_ACTION。 +//返回一个内容类型,并命名为 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 + "," @@ -73,12 +99,23 @@ public class NotesProvider extends ContentProvider { + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; +//解读: +// 使用上面定义的投影来选择数据。 +// 并指定从哪个表中选择数据。 +//WHERE子句包含三个条件: +// ①搜索 SNIPPET 列中包含特定模式的行(? 是一个占位符,实际查询时会用具体的值替换)。 +// ②父ID不为回收站的ID:排除那些父 ID 为回收站的行。 +// ③只选择类型为note(标签)的行。 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; +//重写onCreate方法: +//getContext() 方法被调用以获取当前组件的上下文(Context),以便 NotesDatabaseHelper 能够访问应用程序的资源和其他功能 +//mHelper用于存储从 NotesDatabaseHelper.getInstance 方法返回的实例。这样,该实例就可以在整个组件的其他方法中被访问和使用。 + @Override public boolean onCreate() { mHelper = NotesDatabaseHelper.getInstance(getContext()); @@ -88,10 +125,20 @@ public class NotesProvider extends ContentProvider { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + //初始化变量: + //Cursor对象 c,用来存储查询结果 + //使用 NotesDatabaseHelper 的实例 mHelper来获取一个可读的数据库实例 + //定义一个字符串id,用来存储从URI中解析出的ID Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; + //根据匹配不同的URI来进行不同的查询 switch (mMatcher.match(uri)) { + // URI_NOTE:查询整个 NOTE 表。 + // URI_NOTE_ITEM:查询 NOTE 表中的特定项。ID 从 URI 的路径段中获取,并添加到查询条件中。 + // URI_DATA:查询整个 DATA 表。 + // URI_DATA_ITEM:查询 DATA 表中的特定项。ID 的获取和处理方式与 URI_NOTE_ITEM 相同。 + case URI_NOTE: c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); @@ -109,7 +156,12 @@ public class NotesProvider extends ContentProvider { id = uri.getPathSegments().get(1); c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); - break; + break;//URI_SEARCH 和 URI_SEARCH_SUGGEST:处理搜索查询。 + // 代码首先检查是否提供了不应与搜索查询一起使用的参数(如 sortOrder, selection, selectionArgs, 或 projection)。 + // 如果提供了这些参数,则抛出一个 IllegalArgumentException。 + // 根据 URI 类型,从 URI 的路径段或查询参数中获取搜索字符串 searchString。 + // 如果 searchString 为空或无效,则返回 null,表示没有搜索结果。 + case URI_SEARCH: case URI_SEARCH_SUGGEST: if (sortOrder != null || projection != null) { @@ -129,7 +181,8 @@ public class NotesProvider extends ContentProvider { if (TextUtils.isEmpty(searchString)) { return null; } - + //字符串格式化:格式化后的字符串就会是 "%s%",即包含s是任何文本 + //然后执行原始SQL查询 try { searchString = String.format("%%%s%%", searchString); c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, @@ -141,16 +194,24 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + //如果查询结果不为空(即 Cursor 对象 c 不是 null),则为其设置一个通知 URI。 + //这意味着当与这个 URI 关联的数据发生变化时,任何注册了监听这个 URI 的 ContentObserver 都会被通知。 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } + //参数:Uri 用来标识要插入数据的表,ContentValues对象包含要插入的键值对 @Override public Uri insert(Uri uri, ContentValues values) { + +//参数:Uri 用来标识要插入数据的表,ContentValues对象包含要插入的键值对 SQLiteDatabase db = mHelper.getWritableDatabase(); long dataId = 0, noteId = 0, insertedId = 0; + //对于 URI_NOTE,将values插入到 TABLE.NOTE 表中,并返回插入行的 ID。 + //对于 URI_DATA,首先检查values是否包含 DataColumns.NOTE_ID,如果包含,则获取其值。如果不包含,记录一条日志信息。然后,将 values 插入到 TABLE.DATA 表中,并返回插入行的 ID。 + //如果 uri 不是已知的 URI 类型,则抛出一个 IllegalArgumentException。 switch (mMatcher.match(uri)) { case URI_NOTE: insertedId = noteId = db.insert(TABLE.NOTE, null, values); @@ -167,6 +228,11 @@ public class NotesProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URI " + uri); } // Notify the note uri + //功能:通知变化 + //如果noteId 或 dataId 大于 0(即成功插入了数据),则使用 ContentResolver 的 notifyChange 方法通知监听这些 URI 的观察者,告知数据已经改变。 + //ContentUris.withAppendedId 方法用于在基本 URI 后面追加一个 ID,形成完整的 URI。 + // Notify the note uri + if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); @@ -177,17 +243,25 @@ public class NotesProvider extends ContentProvider { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } - +//返回包含新插入数据项ID 的 Uri。允许调用者知道新插入的数据项的位置 return ContentUris.withAppendedId(uri, insertedId); } - + //参数:uri:标识要删除数据的表或数据项。 selection:一个可选的 WHERE 子句,用于指定删除条件。 selectionArgs:一个可选的字符串数组,用于替换 selection 中的占位符 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { + //count:记录被删除的行数。 + //id:用于存储从 URI 中解析出的数据项 ID。 + //db:可写的数据库对象,用于执行删除操作。 + //deleteData:一个布尔值,用于标记是否删除了 DATA 表中的数据。 int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; switch (mMatcher.match(uri)) { + //URI_NOTE: 修改 selection 语句:确保只删除 ID 大于 0 的笔记。然后执行删除操作并返回被删除的行数。 + //URI_NOTE_ITEM: 从 URI 中解析出 ID。检查 ID 是否小于等于 0,如果是,则不执行删除操作;否则执行删除操作并返回被删除的行数 + //URI_DATA: 执行删除操作并返回被删除的行数。设置 deleteData 为 true,表示删除了 DATA 表中的数据。 + //URI_DATA_ITEM: 先从 URI 中解析出 ID,然后执行删除操作并返回被删除的行数,并设置 deleteData 为 true,表示删除了 DATA 表中的数据。 case URI_NOTE: selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); @@ -199,6 +273,9 @@ public class NotesProvider extends ContentProvider { * trash */ long noteId = Long.valueOf(id); + //如果 count 大于 0,说明有数据被删除。 + //如果 deleteData 为 true,则通知监听 Notes.CONTENT_NOTE_URI 的观察者,数据已改变。 + //通知监听传入 uri 的观察者数据已改变。 if (noteId <= 0) { break; } @@ -226,14 +303,25 @@ public class NotesProvider extends ContentProvider { } return count; } +//参数:uri:标识要更新数据的表或数据项。 values:一个包含新值的键值对集合。 +// selection:一个可选的 WHERE 子句,用于指定更新条件。 selectionArgs:一个可选的字符串数组,用于替换 selection 中的占位符。 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + //count:记录被更新的行数。 + //id:用于存储从 URI 中解析出的数据项 ID。 + //db:可写的 SQLite 数据库对象,用于执行更新操作。 + //updateData:用于标记是否更新了 data 表中的数据。 int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; switch (mMatcher.match(uri)) { + //URI_NOTE:调用 increaseNoteVersion 方法(用于增加便签版本),然后在note表执行更新操作并返回被更新的行数。 + //URI_NOTE_ITEM:从 URI 中解析出 ID,并调用 increaseNoteVersion 方法,传入解析出的 ID,最后在note表执行更新操作并返回被更新的行数。 + //URI_DATA:在data表执行更新操作并返回被更新的行数。设置 updateData 为 true,表示更新了 DATA 表中的数据。 + //URI_DATA_ITEM:从 URI 中解析出 ID。执行更新操作并返回被更新的行数。置 updateData 为 true,表示更新了 DATA 表中的数据。 + case URI_NOTE: increaseNoteVersion(-1, selection, selectionArgs); count = db.update(TABLE.NOTE, values, selection, selectionArgs); @@ -259,6 +347,9 @@ public class NotesProvider extends ContentProvider { } if (count > 0) { + //如果 count 大于 0,说明有数据被更新。 + //如果 updateData 为 true,则通知监听 Notes.CONTENT_NOTE_URI 的观察者数据已改变。 + //通知监听传入 uri 的观察者数据已改变。 if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } @@ -267,10 +358,12 @@ public class NotesProvider extends ContentProvider { return count; } + //解析传入的条件语句:一个 SQL WHERE 子句的一部分 private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } + //更新note表的version列,将其值增加 1。 private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java index 3a2050b..9b5da24 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java @@ -26,29 +26,45 @@ import org.json.JSONObject; public class MetaData extends Task { + /* + * 功能描述:得到类的简写名称存入字符串TAG中 + * 实现过程:调用getSimpleName ()函数 + */ private final static String TAG = MetaData.class.getSimpleName(); private String mRelatedGid = null; - + /* + * 功能描述:设置数据,即生成元数据库 + * 实现过程:调用JSONObject库函数put (),Task类中的setNotes ()和setName ()函数 + * 参数注解: + */ public void setMeta(String gid, JSONObject metaInfo) { + //对函数块进行注释 try { metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); + // 将这对键值放入metaInfo这个jsonobject对象中 } catch (JSONException e) { + //输出错误信息 Log.e(TAG, "failed to put related gid"); } setNotes(metaInfo.toString()); setName(GTaskStringUtils.META_NOTE_NAME); } - + //功能描述:获取相关联的Gid public String getRelatedGid() { return mRelatedGid; } + //功能描述:判断当前数据是否为空,若为空则返回真即值得保存 @Override public boolean isWorthSaving() { return getNotes() != null; } - + /* + * 功能描述:使用远程json数据对象设置元数据内容 + * 实现过程:调用父类Task中的setContentByRemoteJSON ()函数,并 + * 参数注解: + */ @Override public void setContentByRemoteJSON(JSONObject js) { super.setContentByRemoteJSON(js); @@ -58,22 +74,27 @@ public class MetaData extends Task { mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { Log.w(TAG, "failed to get related gid"); + + //输出警告信息 + mRelatedGid = null; } } } - + //功能描述:使用本地json数据对象设置元数据内容,一般不会用到,若用到,则抛出异常 @Override public void setContentByLocalJSON(JSONObject js) { // this function should not be called throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); + //传递非法参数异常 } - + //功能描述:从元数据内容中获取本地json对象,一般不会用到,若用到,则抛出异常 @Override public JSONObject getLocalJSONFromContent() { throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); + //传递非法参数异常 } - + //功能描述:获取同步动作状态,一般不会用到,若用到,则抛出异常 @Override public int getSyncAction(Cursor c) { throw new IllegalAccessError("MetaData:getSyncAction should not be called"); diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java index 63950e0..90c6534 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java @@ -19,34 +19,34 @@ package net.micode.notes.gtask.data; import android.database.Cursor; import org.json.JSONObject; - +//应该是同步操作的基础数据类型,定义了相关指示同步操作的常量 public abstract class Node { public static final int SYNC_ACTION_NONE = 0; - + // 本地和云端都无可更新内容(即本地和云端内容一致) public static final int SYNC_ACTION_ADD_REMOTE = 1; - + // 需要在远程云端增加内容 public static final int SYNC_ACTION_ADD_LOCAL = 2; - + // 需要在本地增加内容 public static final int SYNC_ACTION_DEL_REMOTE = 3; - + // 需要在远程云端删除内容 public static final int SYNC_ACTION_DEL_LOCAL = 4; - + // 需要在本地删除内容 public static final int SYNC_ACTION_UPDATE_REMOTE = 5; - + // 需要将本地内容更新到远程云端 public static final int SYNC_ACTION_UPDATE_LOCAL = 6; - + // 需要将远程云端内容更新到本地 public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; - + // 同步出现冲突 public static final int SYNC_ACTION_ERROR = 8; - + // 同步出现错误 private String mGid; private String mName; private long mLastModified; - + //记录最后一次修改时间 private boolean mDeleted; - + //表征是否被删除 public Node() { mGid = null; mName = ""; diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java index d3ec3be..cda030d 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java @@ -14,6 +14,8 @@ * limitations under the License. */ +// Description:用于支持小米便签最底层的数据库相关操作,和sqlnote的关系上是子集关系,即data是note的子集(节点)。 +// SqlData其实就是也就是所谓数据中的数据 package net.micode.notes.gtask.data; import android.content.ContentResolver; @@ -35,16 +37,23 @@ import org.json.JSONException; import org.json.JSONObject; + public class SqlData { + /* + * 功能描述:得到类的简写名称存入字符串TAG中 + * 实现过程:调用getSimpleName ()函数 + */ private static final String TAG = SqlData.class.getSimpleName(); private static final int INVALID_ID = -99999; - + // 为mDataId置初始值-99999 + // 来自Notes类中定义的DataColumn中的一些常量 + // 集合了interface DataColumns中所有SF常量 public static final String[] PROJECTION_DATA = new String[] { DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 }; - + //以下五个变量作为sql表中5列的编号 public static final int DATA_ID_COLUMN = 0; public static final int DATA_MIME_TYPE_COLUMN = 1; @@ -56,7 +65,7 @@ public class SqlData { public static final int DATA_CONTENT_DATA_3_COLUMN = 4; private ContentResolver mContentResolver; - + //判断是否直接用Content生成,是为true,否则为false private boolean mIsCreate; private long mDataId; @@ -70,6 +79,12 @@ public class SqlData { private String mDataContentData3; private ContentValues mDiffDataValues; + /* + * 功能描述:构造函数,用于初始化数据 + * 参数注解:mContentResolver用于获取ContentProvider提供的数据 + * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) + * 参数注解: + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); @@ -81,6 +96,12 @@ public class SqlData { mDataContentData3 = ""; mDiffDataValues = new ContentValues(); } + /* + * 功能描述:构造函数,初始化数据 + * 参数注解:mContentResolver用于获取ContentProvider提供的数据 + * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) + * 参数注解: + */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); @@ -88,7 +109,10 @@ public class SqlData { loadFromCursor(c); mDiffDataValues = new ContentValues(); } - + /* + * 功能描述:从光标处加载数据 + * 从当前的光标处将五列的数据加载到该类的对象 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -96,8 +120,12 @@ public class SqlData { mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } - + /* + * 功能描述:设置用于共享的数据,并提供异常抛出与处理机制 + * 参数注解: + */ public void setContent(JSONObject js) throws JSONException { + //如果传入的JSONObject对象中有DataColumns.ID这一项,则设置,否则设为INVALID_ID long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { mDiffDataValues.put(DataColumns.ID, dataId); @@ -130,11 +158,14 @@ public class SqlData { mDataContentData3 = dataContentData3; } + //功能描述:获取共享的数据内容,并提供异常抛出与处理机制 + public JSONObject getContent() throws JSONException { if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); return null; } + //创建JSONObject对象。并将相关数据放入其中,并返回。 JSONObject js = new JSONObject(); js.put(DataColumns.ID, mDataId); js.put(DataColumns.MIME_TYPE, mDataMimeType); @@ -143,7 +174,7 @@ public class SqlData { js.put(DataColumns.DATA3, mDataContentData3); return js; } - + //功能描述:commit函数用于把当前造作所做的修改保存到数据库 public void commit(long noteId, boolean validateVersion, long version) { if (mIsCreate) { @@ -167,7 +198,7 @@ public class SqlData { Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); } else { result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { String.valueOf(noteId), String.valueOf(version) @@ -182,7 +213,7 @@ public class SqlData { mDiffDataValues.clear(); mIsCreate = false; } - + //功能描述:获取当前id public long getId() { return mDataId; } diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java index 79a4095..cd8453e 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +/* + * Description:用于支持小米便签最底层的数据库相关操作,和sqldata的关系上是父集关系,即note是data的子父集。 + * 和SqlData相比,SqlNote算是真正意义上的数据了。 + */ package net.micode.notes.gtask.data; import android.appwidget.AppWidgetManager; @@ -39,10 +42,14 @@ import java.util.ArrayList; public class SqlNote { + /* + * 功能描述:得到类的简写名称存入字符串TAG中 + * 实现过程:调用getSimpleName ()函数 + */ private static final String TAG = SqlNote.class.getSimpleName(); private static final int INVALID_ID = -99999; - + // 集合了interface NoteColumns中所有SF常量(17个) public static final String[] PROJECTION_NOTE = new String[] { NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, @@ -51,7 +58,7 @@ public class SqlNote { NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, NoteColumns.VERSION }; - + //以下设置17个列的编号 public static final int ID_COLUMN = 0; public static final int ALERTED_DATE_COLUMN = 1; @@ -85,7 +92,7 @@ public class SqlNote { public static final int GTASK_ID_COLUMN = 15; public static final int VERSION_COLUMN = 16; - + //一下定义了17个内部的变量,其中12个可以由content中获得,5个需要初始化为0或者new private Context mContext; private ContentResolver mContentResolver; @@ -121,7 +128,11 @@ public class SqlNote { private ContentValues mDiffNoteValues; private ArrayList mDataList; - + /* + * 功能描述:构造函数 + * 参数注解: mIsCreate用于标示构造方式 + */ + //构造函数只有context,对所有的变量进行初始化 public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); @@ -129,9 +140,9 @@ public class SqlNote { mId = INVALID_ID; mAlertDate = 0; mBgColorId = ResourceParser.getDefaultBgId(context); - mCreatedDate = System.currentTimeMillis(); + mCreatedDate = System.currentTimeMillis();//调用系统函数获得创建时间 mHasAttachment = 0; - mModifiedDate = System.currentTimeMillis(); + mModifiedDate = System.currentTimeMillis();//最后一次修改时间初始化为创建时间 mParentId = 0; mSnippet = ""; mType = Notes.TYPE_NOTE; @@ -142,7 +153,11 @@ public class SqlNote { mDiffNoteValues = new ContentValues(); mDataList = new ArrayList(); } - + /* + * 功能描述:构造函数 + * 参数注解: mIsCreate用于标示构造方式 + */ + //构造函数有context和一个数据库的cursor,多数变量通过cursor指向的一条记录直接进行初始化 public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); @@ -153,7 +168,10 @@ public class SqlNote { loadDataContent(); mDiffNoteValues = new ContentValues(); } - + /* + * 功能描述:构造函数 + * 参数注解: mIsCreate用于标示构造方式 + */ public SqlNote(Context context, long id) { mContext = context; mContentResolver = context.getContentResolver(); @@ -165,17 +183,18 @@ public class SqlNote { mDiffNoteValues = new ContentValues(); } - + //功能描述:通过id从光标处加载数据 private void loadFromCursor(long id) { Cursor c = null; try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", new String[] { - String.valueOf(id) - }, null); + String.valueOf(id) + }, null);//通过id获得对应的ContentResolver中的cursor if (c != null) { c.moveToNext(); - loadFromCursor(c); + loadFromCursor(c);//然后加载数据进行初始化,这样函数 + //SqlNote(Context context, long id)与SqlNote(Context context, long id)的实现方式基本相同 } else { Log.w(TAG, "loadFromCursor: cursor = null"); } @@ -184,8 +203,9 @@ public class SqlNote { c.close(); } } - + //功能描述:通过游标从光标处加载数据 private void loadFromCursor(Cursor c) { + //直接从一条记录中的获得以下变量的初始值 mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); @@ -199,14 +219,14 @@ public class SqlNote { mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); mVersion = c.getLong(VERSION_COLUMN); } - + //功能描述:通过content机制获取共享数据并加载到数据库当前游标处 private void loadDataContent() { Cursor c = null; mDataList.clear(); try { c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, "(note_id=?)", new String[] { - String.valueOf(mId) + String.valueOf(mId) }, null); if (c != null) { if (c.getCount() == 0) { @@ -225,7 +245,7 @@ public class SqlNote { c.close(); } } - + //功能描述:设置通过content机制用于共享的数据信息 public boolean setContent(JSONObject js) { try { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); @@ -358,7 +378,7 @@ public class SqlNote { } return true; } - + //获取content机制提供的数据并加载到note中 public JSONObject getContent() { try { JSONObject js = new JSONObject(); @@ -406,40 +426,40 @@ public class SqlNote { } return null; } - + //功能描述:给当前id设置父id public void setParentId(long id) { mParentId = id; mDiffNoteValues.put(NoteColumns.PARENT_ID, id); } - + //功能描述:给当前id设置Gtaskid public void setGtaskId(String gid) { mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); } - + //功能描述:给当前id设置同步id public void setSyncId(long syncId) { mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); } - + //初始化本地修改,即撤销所有当前修改 public void resetLocalModified() { mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); } - + //获得当前id public long getId() { return mId; } - + //获得当前id的父id public long getParentId() { return mParentId; } - + //获取小片段即用于显示的部分便签内容 public String getSnippet() { return mSnippet; } - + //判断是否为便签类型 public boolean isNoteType() { return mType == Notes.TYPE_NOTE; } - + //commit函数用于把当前造作所做的修改保存到数据库 public void commit(boolean validateVersion) { if (mIsCreate) { if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { @@ -458,7 +478,7 @@ public class SqlNote { } if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) { + for (SqlData sqlData : mDataList) {//直接使用sqldata中的实现 sqlData.commit(mId, false, -1); } } @@ -470,14 +490,14 @@ public class SqlNote { if (mDiffNoteValues.size() > 0) { mVersion ++; int result = 0; - if (!validateVersion) { + if (!validateVersion) {//构造字符串 result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) + String.valueOf(mId) }); } else { result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", new String[] { String.valueOf(mId), String.valueOf(mVersion) }); @@ -503,3 +523,4 @@ public class SqlNote { mIsCreate = false; } } + diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java index 6a19454..87d7325 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java @@ -35,22 +35,22 @@ import org.json.JSONObject; public class Task extends Node { private static final String TAG = Task.class.getSimpleName(); - private boolean mCompleted; + private boolean mCompleted;//是否完成 private String mNotes; - private JSONObject mMetaInfo; + private JSONObject mMetaInfo;//将在实例中存储数据的类型 - private Task mPriorSibling; + private Task mPriorSibling;//对应的优先兄弟Task的指针(待完善) - private TaskList mParent; + private TaskList mParent;//所在的任务列表的指针 public Task() { super(); mCompleted = false; mNotes = null; - mPriorSibling = null; - mParent = null; + mPriorSibling = null;//TaskList中当前Task前面的Task的指针 + mParent = null;//当前Task所在的TaskList mMetaInfo = null; } @@ -349,3 +349,4 @@ public class Task extends Node { } } + diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..a1f821e 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java @@ -31,18 +31,18 @@ import java.util.ArrayList; public class TaskList extends Node { - private static final String TAG = TaskList.class.getSimpleName(); + private static final String TAG = TaskList.class.getSimpleName();//tag标记 - private int mIndex; + private int mIndex;//当前TaskList的指针 - private ArrayList mChildren; + private ArrayList mChildren;//类中主要的保存数据的单元,用来实现一个以Task为元素的ArrayList public TaskList() { super(); mChildren = new ArrayList(); mIndex = 1; } - + //生成并返回一个包含了一定数据的JSONObject实体 public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -58,7 +58,7 @@ public class TaskList extends Node { js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); // entity_delta - JSONObject entity = new JSONObject(); + JSONObject entity = new JSONObject();//entity实体 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, @@ -73,7 +73,7 @@ public class TaskList extends Node { return js; } - + //生成并返回一个包含了一定数据的JSONObject实体 public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -215,11 +215,15 @@ public class TaskList extends Node { return SYNC_ACTION_ERROR; } - + //获得TaskList的大小,即mChildren的大小 public int getChildTaskCount() { return mChildren.size(); } - + /* + * @param task + * @return 返回值为是否成功添加任务。 + * 功能:在当前任务表末尾添加新的任务。 + */ public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { @@ -229,11 +233,18 @@ public class TaskList extends Node { task.setPriorSibling(mChildren.isEmpty() ? null : mChildren .get(mChildren.size() - 1)); task.setParent(this); + //注意:每一次ArrayList的变化都要紧跟相关Task中PriorSibling的更改 + //,接下来几个函数都有相关操作 } } return ret; } - + /* + * @param task + * @param index + * @return + * 功能:在当前任务表的指定位置添加新的任务。 + */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { Log.e(TAG, "add child task: invalid index"); @@ -259,7 +270,11 @@ public class TaskList extends Node { return true; } - + /* + * @param task + * @return 返回删除是否成功 + * 功能:删除TaskList中的一个Task + */ public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); @@ -280,7 +295,12 @@ public class TaskList extends Node { } return ret; } - + /* + * @param task + * @param index + * @return + * 功能:将当前TaskList中含有的某个Task移到index位置 + */ public boolean moveChildTask(Task task, int index) { if (index < 0 || index >= mChildren.size()) { @@ -297,8 +317,13 @@ public class TaskList extends Node { if (pos == index) return true; return (removeChildTask(task) && addChildTask(task, index)); + //利用已实现好的功能完成当下功能; } - + /* + * @param gid + * @return返回寻找结果 + * 功能:按gid寻找Task + */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); @@ -308,11 +333,11 @@ public class TaskList extends Node { } return null; } - + //返回指定Task的index public int getChildTaskIndex(Task task) { return mChildren.indexOf(task); } - + //返回指定index的Task public Task getChildTaskByIndex(int index) { if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "getTaskByIndex: invalid index"); @@ -320,7 +345,7 @@ public class TaskList extends Node { } return mChildren.get(index); } - + //返回指定gid的Task public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) @@ -341,3 +366,5 @@ public class TaskList extends Node { return this.mIndex; } } + + diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java index 15504be..6db2779 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java @@ -13,16 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +/* + * Description:支持小米便签运行过程中的运行异常处理。 + */ package net.micode.notes.gtask.exception; public class ActionFailureException extends RuntimeException { private static final long serialVersionUID = 4425249765923293627L; + /* + * serialVersionUID相当于java类的身份证。主要用于版本控制。 + * serialVersionUID作用是序列化时保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 + * Made By Cuican13 + */ public ActionFailureException() { super(); } - + /* + * 在JAVA类中使用super来引用父类的成分,用this来引用当前对象. + * 如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象。 + * 怎么去引用里面的父类对象呢?使用super来引用 + * 也就是说,此处super()以及super (paramString)可认为是Exception ()和Exception (paramString) + * Made By Cuican24 + */ public ActionFailureException(String paramString) { super(paramString); } diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java index b08cfb1..6c012b7 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -14,15 +14,31 @@ * limitations under the License. */ +/* + * Description:支持小米便签运行过程中的网络异常处理。 + */ + package net.micode.notes.gtask.exception; public class NetworkFailureException extends Exception { private static final long serialVersionUID = 2107610287180234136L; + /* + * serialVersionUID相当于java类的身份证。主要用于版本控制。 + * serialVersionUID作用是序列化时保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 + * Made By Cuican13 + */ public NetworkFailureException() { + super(); } - + /* + * 在JAVA类中使用super来引用父类的成分,用this来引用当前对象. + * 如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象。 + * 怎么去引用里面的父类对象呢?使用super来引用 + * 也就是说,此处super()以及super (paramString)可认为是Exception ()和Exception (paramString) + * Made By Cuican + */ public NetworkFailureException(String paramString) { super(paramString); } diff --git a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..57d353e 100644 --- a/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java @@ -15,7 +15,10 @@ */ package net.micode.notes.gtask.remote; - +/* + * 主要功能:实现GTASK的登录操作,进行GTASK任务的创建,创建任务列表,从网络上获取任务和任务列表的内容 + * 主要使用类或技术:accountManager JSONObject HttpParams authToken Gid + */ import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerFuture; @@ -100,7 +103,10 @@ public class GTaskClient { mActionId = 1; mAccount = null; mUpdateArray = null; - } + }/*用来获取的实例化对象 + * 使用 getInstance() + * 返回mInstance这个实例化对象 + */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { @@ -108,7 +114,11 @@ public class GTaskClient { } return mInstance; } - + /*用来实现登录操作的函数,传入的参数是一个Activity + * 设置登录操作限制时间,如果超时则需要重新登录 + * 有两种登录方式,使用用户自己的URL登录或者使用谷歌官方的URL登录 + * 返回true或者false,即最后是否登陆成功 + */ public boolean login(Activity activity) { // we suppose that the cookie would expire after 5 minutes // then we need to re-login @@ -117,41 +127,44 @@ public class GTaskClient { mLoggedin = false; } - // need to re-login after account switch + // need to re-login after account switch 重新登录操作 if (mLoggedin && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity .getSyncAccountName(activity))) { mLoggedin = false; } + //如果没超过时间,则不需要重新登录 if (mLoggedin) { Log.d(TAG, "already logged in"); return true; } - mLastLoginTime = System.currentTimeMillis(); - String authToken = loginGoogleAccount(activity, false); + mLastLoginTime = System.currentTimeMillis();//更新最后登录时间,改为系统当前的时间 + String authToken = loginGoogleAccount(activity, false);//判断是否登录到谷歌账户 if (authToken == null) { Log.e(TAG, "login google account failed"); return false; } // login with custom domain if necessary + // login with custom domain if necessary + //尝试使用用户自己的域名登录 if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); int index = mAccount.name.indexOf('@') + 1; String suffix = mAccount.name.substring(index); url.append(suffix + "/"); - mGetUrl = url.toString() + "ig"; - mPostUrl = url.toString() + "r/ig"; - + mGetUrl = url.toString() + "ig";//设置用户对应的getUrl + mPostUrl = url.toString() + "r/ig";//设置用户对应的postUrl if (tryToLoginGtask(activity, authToken)) { mLoggedin = true; } } // try to login with google official url + //如果用户账户无法登录,则使用谷歌官方的URI进行登录 if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -165,9 +178,9 @@ public class GTaskClient { } private String loginGoogleAccount(Activity activity, boolean invalidateToken) { - String authToken; - AccountManager accountManager = AccountManager.get(activity); - Account[] accounts = accountManager.getAccountsByType("com.google"); + String authToken; //令牌,是登录操作保证安全性的一个方法 + AccountManager accountManager = AccountManager.get(activity);//AccountManager这个类给用户提供了集中注册账号的接口 + Account[] accounts = accountManager.getAccountsByType("com.google");//获取全部以com.google结尾的account if (accounts.length == 0) { Log.e(TAG, "there is no available google account"); @@ -176,6 +189,7 @@ public class GTaskClient { String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; + //遍历获得的accounts信息,寻找已经记录过的账户信息 for (Account a : accounts) { if (a.name.equals(accountName)) { account = a; @@ -190,11 +204,13 @@ public class GTaskClient { } // get the token now + //获取选中账号的令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { Bundle authTokenBundle = accountManagerFuture.getResult(); authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); + //如果是invalidateToken,那么需要调用invalidateAuthToken(String, String)方法废除这个无效token if (invalidateToken) { accountManager.invalidateAuthToken("com.google", authToken); loginGoogleAccount(activity, false); @@ -206,11 +222,12 @@ public class GTaskClient { return authToken; } - + //尝试登陆Gtask,这只是一个预先判断令牌是否是有效以及是否能登上GTask的方法,而不是具体实现登陆的方法 private boolean tryToLoginGtask(Activity activity, String authToken) { if (!loginGtask(authToken)) { - // maybe the auth token is out of date, now let's invalidate the + // maybe the auth token is out of authTokedate, now let's invalidate the // token and try again + //删除过一个无效的authToken,申请一个新的后再次尝试登陆 authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); @@ -225,25 +242,28 @@ public class GTaskClient { return true; } + //实现登录GTask的具体操作 private boolean loginGtask(String authToken) { int timeoutConnection = 10000; - int timeoutSocket = 15000; - HttpParams httpParameters = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); - HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); + int timeoutSocket = 15000;//socket是一种通信连接实现数据的交换的端口 + HttpParams httpParameters = new BasicHttpParams();//实例化一个新的HTTP参数类 + HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);//设置连接超时时间 + HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);//设置设置端口超时时间 mHttpClient = new DefaultHttpClient(httpParameters); - BasicCookieStore localBasicCookieStore = new BasicCookieStore(); + BasicCookieStore localBasicCookieStore = new BasicCookieStore();//设置本地cookie mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // login gtask try { - String loginUrl = mGetUrl + "?auth=" + authToken; - HttpGet httpGet = new HttpGet(loginUrl); + String loginUrl = mGetUrl + "?auth=" + authToken;//设置登录的url + HttpGet httpGet = new HttpGet(loginUrl);//通过登录的uri实例化网页上资源的查找 + HttpResponse response = null; response = mHttpClient.execute(httpGet); // get the cookie now + //获取CookieStore里存放的cookie,看如果存有“GTL(不知道什么意思)”,则说明有验证成功的有效的cookie List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { @@ -256,6 +276,7 @@ public class GTaskClient { } // get the client version + //获取client的内容,具体操作是在返回的Content中截取从_setup(开始到)}中间的字符串内容,也就是gtask_url的内容 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -283,14 +304,20 @@ public class GTaskClient { private int getActionId() { return mActionId++; } - + /*实例化创建一个用于向网络传输数据的对象 + * 使用HttpPost类 + * 返回一个httpPost实例化对象,但里面还没有内容 + */ private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); httpPost.setHeader("AT", "1"); return httpPost; } - + /*通过URL获取响应后返回的数据,也就是网络上的数据和资源 + * 使用getContentEncoding()获取网络上的资源和数据 + * 返回值就是获取到的资源 + */ private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { @@ -322,21 +349,27 @@ public class GTaskClient { input.close(); } } - + /*通过JSON发送请求 + * 请求的具体内容在json的实例化对象js中然后传入 + * 利用UrlEncodedFormEntity entity和httpPost.setEntity(entity)方法把js中的内容放置到httpPost中 + * 执行请求后使用getResponseContent方法得到返回的数据和资源 + * 将资源再次放入json后返回 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); } - + //实例化一个httpPost的对象用来向服务器传输数据,在这里就是发送请求,而请求的内容在js里 HttpPost httpPost = createHttpPost(); try { LinkedList list = new LinkedList(); list.add(new BasicNameValuePair("r", js.toString())); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); +// execute the post + //执行这个请求 - // execute the post HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); return new JSONObject(jsString); @@ -359,7 +392,12 @@ public class GTaskClient { throw new ActionFailureException("error occurs when posting request"); } } - + /*创建单个任务 + * 传入参数是一个.gtask.data.Task包里Task类的对象 + * 利用json获取Task里的内容,并且创建相应的jsPost + * 利用postRequest得到任务的返回信息 + * 使用task.setGid设置task的new_ID + */ public void createTask(Task task) throws NetworkFailureException { commitUpdate(); try { @@ -385,7 +423,9 @@ public class GTaskClient { throw new ActionFailureException("create task: handing jsonobject failed"); } } - + /* + * 创建一个任务列表,与createTask几乎一样,区别就是最后设置的是tasklist的gid + */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); try { @@ -411,7 +451,11 @@ public class GTaskClient { throw new ActionFailureException("create tasklist: handing jsonobject failed"); } } - + /* + * 同步更新操作 + * 使用JSONObject进行数据存储,使用jsPost.put,Put的信息包括UpdateArray和ClientVersion + * 使用postRequest发送这个jspost,进行处理 + */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { @@ -432,7 +476,10 @@ public class GTaskClient { } } } - + /* + * 添加更新的事项 + * 调用commitUpdate()实现 + */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { // too many update items may result in an error @@ -446,7 +493,12 @@ public class GTaskClient { mUpdateArray.put(node.getUpdateAction(getActionId())); } } - + /* + * 移动task,比如讲task移动到不同的task列表中去 + * 通过getGid获取task所属列表的gid + * 通过JSONObject.put(String name, Object value)函数设置移动后的task的相关属性值,从而达到移动的目的 + * 最后还是通过postRequest进行更新后的发送 + */ public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { commitUpdate(); @@ -463,6 +515,7 @@ public class GTaskClient { if (preParent == curParent && task.getPriorSibling() != null) { // put prioring_sibing_id only if moving within the tasklist and // it is not the first one + //设置优先级ID,只有当移动是发生在文件中 action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); @@ -472,6 +525,7 @@ public class GTaskClient { action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } actionList.put(action); + //最后将ACTION_LIST加入到jsPost中 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // client_version @@ -483,7 +537,11 @@ public class GTaskClient { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("move task: handing jsonobject failed"); - } + }/* + * 删除操作节点 + * 还是利用JSON + * 删除过后使用postRequest发送删除后的结果 + */ } public void deleteNode(Node node) throws NetworkFailureException { @@ -494,7 +552,7 @@ public class GTaskClient { // action_list node.setDeleted(true); - actionList.put(node.getUpdateAction(getActionId())); + actionList.put(node.getUpdateAction(getActionId()));//这里会获取到删除操作的ID,加入到actionLiast中 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // client_version @@ -508,7 +566,11 @@ public class GTaskClient { throw new ActionFailureException("delete node: handing jsonobject failed"); } } - + /* + * 获取任务列表 + * 首先通过GetURI使用getResponseContent从网上获取数据 + * 然后筛选出"_setup("到)}的部分,并且从中获取GTASK_JSON_LISTS的内容返回 + */ public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -521,6 +583,7 @@ public class GTaskClient { response = mHttpClient.execute(httpGet); // get the task list + //筛选工作,把筛选出的字符串放入jsString String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -531,6 +594,7 @@ public class GTaskClient { jsString = resString.substring(begin + jsBegin.length(), end); } JSONObject js = new JSONObject(jsString); + //获取GTASK_JSON_LISTS return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); } catch (ClientProtocolException e) { Log.e(TAG, e.toString()); @@ -546,7 +610,9 @@ public class GTaskClient { throw new ActionFailureException("get task lists: handing jasonobject failed"); } } - + /* + * 通过传入的TASKList的gid,从网络上获取相应属于这个任务列表的任务 + */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { commitUpdate(); try { @@ -578,6 +644,7 @@ public class GTaskClient { public Account getSyncAccount() { return mAccount; } + //重置更新的内容 public void resetUpdateArray() { mUpdateArray = null;