You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
TEST1231/BackupUtils.java

823 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 包名,表示这个类属于哪个应用或库的一部分
package net.micode.notes.tool;
import android.content.Context; // 引入Context类它是Android应用的全局信息的接口
import android.database.Cursor; // 引入Cursor类用于遍历数据库查询结果
import android.os.Environment; // 引入Environment类提供有关Android设备环境的访问
import android.text.TextUtils; // 引入TextUtils类包含一些静态方法用于处理字符串
import android.text.format.DateFormat; // 引入DateFormat类用于格式化日期和时间
import android.util.Log; // 引入Log类用于记录日志信息
import net.micode.notes.R; // 引入R类它是一个包含应用资源引用的类
import net.micode.notes.data.Notes; // 引入Notes类它可能是一个包含数据库访问逻辑的类
import net.micode.notes.data.Notes.DataColumns; // 引入DataColumns接口定义了数据表列名
import net.micode.notes.data.Notes.DataConstants; // 引入DataConstants类可能包含一些数据相关的常量
import net.micode.notes.data.Notes.NoteColumns; // 引入NoteColumns接口定义了笔记表列名
import java.io.File; // 引入File类表示文件和目录路径名的抽象表示形式
import java.io.FileNotFoundException; // 引入FileNotFoundException类当文件未找到时抛出
import java.io.FileOutputStream; // 引入FileOutputStream类用于写入文件数据
import java.io.IOException; // 引入IOException类是输入输出异常的超类
import java.io.PrintStream; // 引入PrintStream类用于表示打印流的抽象类
// BackupUtils类用于实现备份工具的功能
public class BackupUtils {
private static final String TAG = "BackupUtils"; // 定义日志标签,用于日志输出
// Singleton stuff单例模式相关代码
private static BackupUtils sInstance; // 定义单例实例变量
// 获取BackupUtils类的实例使用单例模式确保全局只有一个实例
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
// 定义一些常量来表示备份或恢复的状态
public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在
public static final int STATE_DATA_DESTROIED = 2; // 数据被破坏
public static final int STATE_SYSTEM_ERROR = 3; // 系统错误
public static final int STATE_SUCCESS = 4; // 成功
private TextExport mTextExport; // TextExport类的实例变量用于导出文本
// 私有构造方法,防止外部直接创建实例,配合单例模式使用
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
// 检查外部存储是否可用的方法
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 导出数据到文本文件的方法
public int exportToText() {
return mTextExport.exportToText();
}
// 获取导出的文本文件名的方法
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
// 获取导出的文本文件目录的方法
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
// TextExport内部类用于实现将数据导出到文本文件的功能
private static class TextExport {
// 定义从Note表中查询数据的列名数组
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
// 定义Note表中各列在NOTE_PROJECTION数组中的索引
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
// 定义从Data表中查询数据的列名数组
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
// 定义Data表中各列在DATA_PROJECTION数组中的索引
// 注意这里有一个错误应该是DATA_COLUMN_DATA3而不是DATA_COLUMN_CALL_DATE
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2; // 错误应为DATA_COLUMN_DATA1或其他正确的索引
private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 错误索引与DATA_PROJECTION不匹配
// 从资源文件中获取文本格式的数组
private final String [] TEXT_FORMAT;
// 定义TEXT_FORMAT数组中各个格式的索引
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
// Context实例变量用于访问应用的资源和类
private Context mContext;
// 导出的文本文件名
private String mFileName;
// 导出的文本文件目录
private String mFileDirectory;
// TextExport类的构造方法
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
// 根据索引获取TEXT_FORMAT数组中对应格式的方法
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// 使用内容解析器查询属于指定文件夹的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
// 检查返回的Cursor是否为null
if (notesCursor != null) {
// 移动Cursor到第一行如果Cursor不为空且至少有一行数据
if (notesCursor.moveToFirst()) {
do {
// 打印笔记的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 获取当前笔记的ID
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
// 调用方法导出指定ID的笔记到文本
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext()); // 循环直到所有笔记都被处理
}
// 关闭Cursor
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
// 使用内容解析器查询指定笔记的数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
// 检查返回的Cursor是否为null
if (dataCursor != null) {
// 移动Cursor到第一行如果Cursor不为空且至少有一行数据
if (dataCursor.moveToFirst()) {
do {
// 获取当前数据的MIME类型
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
// 如果MIME类型表示这是一个电话笔记
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// 打印电话号码
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
// 如果电话号码不为空,则打印电话号码
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// 打印通话日期
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// 如果位置信息不为空,则打印位置信息
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
}
// 如果MIME类型表示这是一个普通笔记
else if (DataConstants.NOTE.equals(mimeType)) {
// 获取笔记内容
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
// 如果内容不为空,则打印内容
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext()); // 循环直到所有笔记数据都被处理
}
// 关闭Cursor
dataCursor.close();
}
// 在每个笔记之后打印一个行分隔符
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, // 实际上这里应该是只有行分隔符,后面这个字符可能是误加
// Character.LETTER_NUMBER // 这行应该是错误的,通常我们只需要行分隔符
});
} catch (IOException e) {
// 捕获并打印异常
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {
// 检查外部存储是否可用
if (!externalStorageAvailable()) {
// 如果外部存储不可用,记录日志并返回存储卡未挂载的状态
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
// 获取用于导出文本的输出流
PrintStream ps = getExportToTextPrintStream();
// 如果无法获取输出流,记录错误日志并返回系统错误状态
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 首先导出文件夹及其笔记
// 使用ContentResolver查询数据库中的文件夹数据
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 查询的URI
NOTE_PROJECTION, // 查询的列
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " // 文件夹类型
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " // 非垃圾文件夹
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, // 或通话记录文件夹
null, null); // 无排序和选择参数
// 如果查询结果不为空
if (folderCursor != null) {
// 移动到查询结果的第一条记录
if (folderCursor.moveToFirst()) {
do {
// 打印文件夹的名称
String folderName = "";
// 如果是通话记录文件夹,使用特定的字符串
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
// 否则,使用文件夹的摘要(名称)
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
// 如果文件夹名称不为空,则格式化并打印
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
// 获取文件夹ID并导出该文件夹下的所有笔记
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext()); // 移动到下一条记录
}
// 关闭Cursor以释放资源
folderCursor.close();
}
// 导出根文件夹中的笔记
// 使用ContentResolver查询数据库中的笔记数据
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 查询的URI
NOTE_PROJECTION, // 查询的列
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID // 笔记类型且父ID为0根文件夹
+ "=0", null, null); // 无排序和选择参数
// 如果查询结果不为空
if (noteCursor != null) {
// 移动到查询结果的第一条记录
if (noteCursor.moveToFirst()) {
do {
// 打印笔记的修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm), // 日期格式
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); // 笔记的修改日期
// 获取笔记ID并导出该笔记的详细数据
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext()); // 移动到下一条记录
}
// 关闭Cursor以释放资源
noteCursor.close();
}
// 关闭输出流以释放资源
ps.close();
// 返回成功状态
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file defined by generateExportedTextFile.
* This method is private and returns a PrintStream object or null if an error occurs.
*/
private PrintStream getExportToTextPrintStream() {
// Call a method to generate a file on the SD card.
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
// If the file generation failed (i.e., file is null), log an error and return null.
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
// Store the file name and directory for later use.
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
// Initialize a PrintStream object to null.
PrintStream ps = null;
// Try to create a FileOutputStream for the file and wrap it in a PrintStream.
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
// If the file was not found, log the error and return null.
e.printStackTrace();
return null;
} catch (NullPointerException e) {
// If a NullPointerException occurs (e.g., due to a null file object), log the error and return null.
e.printStackTrace();
return null;
}
// Return the PrintStream object if no errors occurred.
return ps;
}
/**
* Generate the text file to store imported data on the SD card.
* This method is private and static, returning a File object or null if an error occurs.
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
// Create a StringBuilder to build the file path.
StringBuilder sb = new StringBuilder();
// Append the external storage directory path.
sb.append(Environment.getExternalStorageDirectory());
// Append the directory path from resources.
sb.append(context.getString(filePathResId));
// Create a File object for the directory.
File filedir = new File(sb.toString());
// Append the formatted file name to the StringBuilder.
// The file name includes a date format obtained from resources.
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
// Create a File object for the full file path.
File file = new File(sb.toString());
try {
// If the directory does not exist, create it.
if (!filedir.exists()) {
filedir.mkdir();
}
// If the file does not exist, create it.
if (!file.exists()) {
file.createNewFile();
}
// Return the File object if no errors occurred.
return file;
} catch (SecurityException e) {
// If there is a security exception (e.g., no permission to write to external storage), log the error.
e.printStackTrace();
} catch (IOException e) {
// If an I/O error occurs, log the error.
e.printStackTrace();
}
// Return null if an error occurred.
return null;
}