
跨设备阅读历史记录系统:基于HarmonyOS的阅读进度同步方案 原创
跨设备阅读历史记录系统:基于HarmonyOS的阅读进度同步方案
一、项目概述
本文实现一个基于HarmonyOS的阅读历史记录系统,该系统能够记录用户阅读进度并实现本地持久化存储,同时借鉴《鸿蒙跨端U同步》中的状态同步机制,实现阅读进度在多设备间的实时同步。该系统适用于电子书阅读器、新闻客户端、文档查看器等需要跨设备同步阅读进度的场景。
二、架构设计
±--------------------+ ±--------------------+
阅读界面 <-----> 阅读同步服务
(ReadingActivity) (ReadingSyncService)
±---------±---------+ ±---------±---------+
±---------v----------+ ±---------v----------+
本地存储管理 分布式数据管理
(LocalStorage) (DistributedData)
±---------±---------+ ±---------±---------+
±---------v-----------------------------v----------+
HarmonyOS基础服务 |
±--------------------------------------------------+
三、核心代码实现
阅读记录模型类
public class ReadingRecord {
private String bookId; // 书籍唯一标识
private String bookTitle; // 书籍标题
private int currentPage; // 当前阅读页码
private int totalPages; // 总页数
private long timestamp; // 最后阅读时间
private float progress; // 阅读进度(0-1)
// 构造方法
public ReadingRecord(String bookId, String bookTitle, int currentPage, int totalPages) {
this.bookId = bookId;
this.bookTitle = bookTitle;
this.currentPage = currentPage;
this.totalPages = totalPages;
this.timestamp = System.currentTimeMillis();
this.progress = totalPages > 0 ? (float) currentPage / totalPages : 0;
// JSON序列化
public JSONObject toJson() throws JSONException {
JSONObject json = new JSONObject();
json.put("bookId", bookId);
json.put("bookTitle", bookTitle);
json.put("currentPage", currentPage);
json.put("totalPages", totalPages);
json.put("timestamp", timestamp);
json.put("progress", progress);
return json;
// JSON反序列化
public static ReadingRecord fromJson(JSONObject json) throws JSONException {
ReadingRecord record = new ReadingRecord(
json.getString("bookId"),
json.getString("bookTitle"),
json.getInt("currentPage"),
json.getInt("totalPages")
);
record.timestamp = json.getLong("timestamp");
return record;
// getter方法…
本地存储管理实现
public class ReadingLocalStorage {
private static final String TAG = “ReadingLocalStorage”;
private static final String STORAGE_FILE = “reading_history.json”;
private static ReadingLocalStorage instance;
private Context context;
private Map<String, ReadingRecord> records = new HashMap<>();
private ReadingLocalStorage(Context context) {
this.context = context;
loadFromFile();
public static synchronized ReadingLocalStorage getInstance(Context context) {
if (instance == null) {
instance = new ReadingLocalStorage(context);
return instance;
// 添加或更新阅读记录
public void updateRecord(ReadingRecord record) {
records.put(record.getBookId(), record);
saveToFile();
// 获取所有阅读记录
public List<ReadingRecord> getAllRecords() {
return new ArrayList<>(records.values());
// 获取特定书籍的阅读记录
public ReadingRecord getRecord(String bookId) {
return records.get(bookId);
// 从文件加载数据
private void loadFromFile() {
try {
File file = new File(context.getDataDir(), STORAGE_FILE);
if (!file.exists()) return;
String jsonStr = Files.readString(file.toPath());
JSONObject json = new JSONObject(jsonStr);
JSONArray recordsArray = json.getJSONArray("records");
for (int i = 0; i < recordsArray.length(); i++) {
ReadingRecord record = ReadingRecord.fromJson(recordsArray.getJSONObject(i));
records.put(record.getBookId(), record);
} catch (Exception e) {
HiLog.error(TAG, "Failed to load reading history: " + e.getMessage());
}
// 保存数据到文件
private void saveToFile() {
try {
JSONObject json = new JSONObject();
JSONArray recordsArray = new JSONArray();
for (ReadingRecord record : records.values()) {
recordsArray.put(record.toJson());
json.put(“records”, recordsArray);
File file = new File(context.getDataDir(), STORAGE_FILE);
Files.writeString(file.toPath(), json.toString());
catch (Exception e) {
HiLog.error(TAG, "Failed to save reading history: " + e.getMessage());
}
阅读同步服务实现
public class ReadingSyncService {
private static final String TAG = “ReadingSyncService”;
private static final String SYNC_CHANNEL = “reading_progress_sync”;
private static ReadingSyncService instance;
private DistributedDataManager dataManager;
private Map<String, ReadingSyncListener> listeners = new HashMap<>();
private ReadingSyncService(Context context) {
this.dataManager = DistributedDataManagerFactory.getInstance()
.createDistributedDataManager(context);
initDataListener();
public static synchronized ReadingSyncService getInstance(Context context) {
if (instance == null) {
instance = new ReadingSyncService(context);
return instance;
private void initDataListener() {
dataManager.registerDataChangeListener(SYNC_CHANNEL, new DataChangeListener() {
@Override
public void onDataChanged(String deviceId, String key, String value) {
try {
JSONObject progressJson = new JSONObject(value);
String bookId = progressJson.getString("bookId");
int page = progressJson.getInt("page");
long timestamp = progressJson.getLong("timestamp");
String deviceName = progressJson.getString("deviceName");
// 忽略本地设备发送的更新
if (deviceId.equals(DistributedDeviceInfo.getLocalDeviceId())) {
return;
ReadingSyncListener listener = listeners.get(bookId);
if (listener != null) {
listener.onReadingProgressChanged(bookId, page, deviceName, timestamp);
} catch (JSONException e) {
HiLog.error(TAG, "Failed to parse reading progress data");
}
});
// 同步阅读进度
public void syncReadingProgress(String bookId, int page, String bookTitle) {
JSONObject progressJson = new JSONObject();
try {
progressJson.put("bookId", bookId);
progressJson.put("bookTitle", bookTitle);
progressJson.put("page", page);
progressJson.put("timestamp", System.currentTimeMillis());
progressJson.put("deviceName", DistributedDeviceInfo.getLocalDeviceName());
progressJson.put("version", getNextVersion());
DistributedOptions options = new DistributedOptions();
options.setPriority(DistributedOptions.Priority.HIGH);
dataManager.putString(SYNC_CHANNEL,
progressJson.toString(),
DistributedDataManager.PUT_MODE_RELIABLE,
options);
catch (JSONException e) {
HiLog.error(TAG, "Failed to serialize reading progress data");
}
// 注册阅读进度监听器
public void registerListener(String bookId, ReadingSyncListener listener) {
listeners.put(bookId, listener);
// 取消注册监听器
public void unregisterListener(String bookId) {
listeners.remove(bookId);
public interface ReadingSyncListener {
void onReadingProgressChanged(String bookId, int page, String fromDevice, long timestamp);
private int versionCounter = 0;
private synchronized int getNextVersion() {
return ++versionCounter;
}
阅读界面实现
public class ReadingActivity extends AbilitySlice {
private static final String TAG = “ReadingActivity”;
private String bookId;
private String bookTitle;
private int totalPages;
private int currentPage = 0;
private Text pageDisplay;
private Text syncStatus;
private ReadingLocalStorage localStorage;
private ReadingSyncService syncService;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
setUIContent(ResourceTable.Layout_reading_layout);
// 初始化书籍信息
bookId = intent.getStringParam("bookId");
bookTitle = intent.getStringParam("title");
totalPages = intent.getIntParam("totalPages", 100);
// 初始化服务
localStorage = ReadingLocalStorage.getInstance(this);
syncService = ReadingSyncService.getInstance(this);
// 获取UI组件
pageDisplay = (Text) findComponentById(ResourceTable.Id_page_display);
syncStatus = (Text) findComponentById(ResourceTable.Id_sync_status);
Button prevBtn = (Button) findComponentById(ResourceTable.Id_prev_btn);
Button nextBtn = (Button) findComponentById(ResourceTable.Id_next_btn);
// 设置按钮事件
prevBtn.setClickedListener(component -> gotoPrevPage());
nextBtn.setClickedListener(component -> gotoNextPage());
// 加载阅读进度
loadReadingProgress();
// 注册同步监听器
syncService.registerListener(bookId, new ReadingSyncService.ReadingSyncListener() {
@Override
public void onReadingProgressChanged(String bookId, int page, String fromDevice, long timestamp) {
getUITaskDispatcher().asyncDispatch(() -> {
if (bookId.equals(ReadingActivity.this.bookId)) {
currentPage = page;
updatePageDisplay();
syncStatus.setText("已从" + fromDevice + "同步: 第" + page + "页");
// 更新本地存储
saveReadingProgress();
});
});
private void loadReadingProgress() {
// 1. 尝试从本地存储加载
ReadingRecord record = localStorage.getRecord(bookId);
if (record != null) {
currentPage = record.getCurrentPage();
updatePageDisplay();
syncStatus.setText("已从本地恢复阅读进度");
return;
// 2. 默认从第一页开始
currentPage = 0;
updatePageDisplay();
private void saveReadingProgress() {
ReadingRecord record = new ReadingRecord(bookId, bookTitle, currentPage, totalPages);
localStorage.updateRecord(record);
// 同步到其他设备
syncService.syncReadingProgress(bookId, currentPage, bookTitle);
private void gotoPrevPage() {
if (currentPage > 0) {
currentPage--;
updatePageDisplay();
saveReadingProgress();
}
private void gotoNextPage() {
if (currentPage < totalPages - 1) {
currentPage++;
updatePageDisplay();
saveReadingProgress();
}
private void updatePageDisplay() {
pageDisplay.setText("第 " + (currentPage + 1) + " 页 / 共 " + totalPages + " 页");
@Override
protected void onStop() {
super.onStop();
// 保存当前进度
saveReadingProgress();
// 取消注册监听器
syncService.unregisterListener(bookId);
}
阅读历史界面实现
public class ReadingHistoryActivity extends AbilitySlice {
private static final String TAG = “ReadingHistoryActivity”;
private ListContainer historyList;
private ReadingLocalStorage localStorage;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
setUIContent(ResourceTable.Layout_reading_history_layout);
localStorage = ReadingLocalStorage.getInstance(this);
historyList = (ListContainer) findComponentById(ResourceTable.Id_history_list);
refreshHistoryList();
private void refreshHistoryList() {
List<ReadingRecord> records = localStorage.getAllRecords();
HistoryListAdapter adapter = new HistoryListAdapter(records, this);
historyList.setItemProvider(adapter);
private static class HistoryListAdapter extends BaseItemProvider {
private List<ReadingRecord> records;
private AbilitySlice slice;
public HistoryListAdapter(List<ReadingRecord> records, AbilitySlice slice) {
this.records = records;
this.slice = slice;
@Override
public int getCount() {
return records.size();
@Override
public Object getItem(int position) {
return records.get(position);
@Override
public long getItemId(int position) {
return position;
@Override
public Component getComponent(int position, Component convertComponent, ComponentContainer parent) {
final DirectionalLayout itemLayout = (DirectionalLayout) LayoutScatter.getInstance(slice)
.parse(ResourceTable.Layout_history_item_layout, null, false);
ReadingRecord record = records.get(position);
Text titleText = (Text) itemLayout.findComponentById(ResourceTable.Id_book_title);
Text progressText = (Text) itemLayout.findComponentById(ResourceTable.Id_book_progress);
Text timeText = (Text) itemLayout.findComponentById(ResourceTable.Id_last_time);
titleText.setText(record.getBookTitle());
progressText.setText(String.format(Locale.getDefault(),
"进度: %d/%d (%.1f%%)",
record.getCurrentPage() + 1,
record.getTotalPages(),
record.getProgress() * 100));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
timeText.setText("最后阅读: " + sdf.format(new Date(record.getTimestamp())));
// 设置点击事件
itemLayout.setClickedListener(component -> {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName(slice.getBundleName())
.withAbilityName("ReadingActivity")
.build();
intent.setOperation(operation);
intent.setParam("bookId", record.getBookId());
intent.setParam("title", record.getBookTitle());
intent.setParam("totalPages", record.getTotalPages());
slice.startAbility(intent);
});
return itemLayout;
}
四、与《鸿蒙跨端U同步》的技术关联
本项目借鉴了游戏多设备同步的以下关键技术:
状态同步模型:类似游戏中玩家状态的实时同步,阅读进度通过JSON格式广播
设备标识:使用设备名称区分不同来源的阅读进度更新
版本控制:引入版本号解决潜在的冲突问题
可靠传输:使用高优先级的数据传输确保同步成功率
本地持久化:类似游戏中的本地存档,实现阅读进度的本地存储
增强的同步逻辑(借鉴游戏同步机制):
// 增强的阅读进度同步方法
public void syncReadingProgress(String bookId, int page, String bookTitle) {
JSONObject progressJson = new JSONObject();
try {
progressJson.put(“bookId”, bookId);
progressJson.put(“bookTitle”, bookTitle);
progressJson.put(“page”, page);
progressJson.put(“timestamp”, System.currentTimeMillis());
progressJson.put(“deviceName”, DistributedDeviceInfo.getLocalDeviceName());
progressJson.put(“version”, getNextVersion());
// 增加校验码
progressJson.put("checksum", calculateChecksum(bookId, page));
// 设置传输选项
DistributedOptions options = new DistributedOptions();
options.setPriority(DistributedOptions.Priority.HIGH);
options.setTimeToLive(30000); // 30秒有效期
options.setRetryCount(5); // 重试5次
// 使用可靠传输
int result = dataManager.putString(SYNC_CHANNEL,
progressJson.toString(),
DistributedDataManager.PUT_MODE_RELIABLE,
options);
if (result != 0) {
HiLog.warn(TAG, "Reading progress sync failed with code: " + result);
// 可以在这里实现重试逻辑
} catch (JSONException e) {
HiLog.error(TAG, "Failed to serialize reading progress data");
}
// 校验阅读进度数据完整性
private boolean validateReadingData(JSONObject progressJson) {
try {
String bookId = progressJson.getString(“bookId”);
int page = progressJson.getInt(“page”);
int checksum = progressJson.getInt(“checksum”);
return checksum == calculateChecksum(bookId, page);
catch (JSONException e) {
return false;
}
// 简单的校验码计算
private int calculateChecksum(String bookId, int page) {
return (bookId.hashCode() ^ page) & 0x7FFFFFFF;
五、项目特色与创新点
双重存储机制:本地持久化+分布式同步,确保数据可靠性
高效同步:仅同步必要数据,减少网络开销
冲突解决:基于时间戳的最近更新策略
完整历史记录:保留所有阅读记录,支持快速跳转
响应式UI:实时更新阅读进度和同步状态
六、应用场景
电子书阅读器:跨设备同步阅读进度
新闻客户端:多设备间同步已读状态
文档查看器:团队协作时的进度同步
教育应用:学生阅读进度的多端同步
七、总结
本阅读历史记录系统实现了以下功能:
本地持久化存储阅读进度
多设备间实时同步阅读进度
完整的阅读历史记录查看
可靠的同步传输机制
直观的进度展示和同步状态反馈
通过借鉴游戏中的状态同步技术,我们构建了一个可靠的跨设备阅读进度同步系统。未来可扩展功能包括:
章节标记:支持按章节记录进度
批注同步:跨设备同步阅读笔记和划重点
阅读统计:分析阅读习惯和时长
云端备份:额外的云端存储保障
这个系统展示了如何将游戏中的同步技术应用于阅读类应用开发,为用户提供无缝的跨设备阅读体验。
