Readest FireOS 导入失效问题(1217)源码剖析:Android Activity 重建与文件选择器结果丢失的根因与修复
Readest FireOS 导入失效问题#1217源码剖析Android Activity 重建与文件选择器结果丢失的根因与修复【免费下载链接】readestReadest is a modern, feature-rich ebook reader designed for avid readers offering seamless cross-platform access, powerful tools, and an intuitive interface to elevate your reading experience.项目地址: https://gitcode.com/gh_mirrors/re/readest导读本文基于 Readest 仓库中的调试记忆文档fireos-import-activity-recreation-1217.md完整还原了Import Books 点了没反应#1217这一典型 Android 疑难问题的根因链、复现路径与最终修复方案。读完本文你将理解低内存设备上系统文件选择器前台运行时 Activity/进程被销毁的机制、TauriActivityResultLauncher绑定已销毁 Activity 导致选择结果被静默丢弃的原理以及 Readest 通过自研原生文件选择器 待发事件重放队列彻底解决该问题的源码级实现并掌握一套可迁移到其他 Tauri/Android 应用的通用排查方法论。背景Tauri 应用中文件选择器的两条路径Readest 的导入入口位于图书库页面点击 Import Books 会调用handleImportBooksFromFiles。在此之前Android 平台上文件选择走的是tauri-plugin-dialog插件调用链为page.tsx handleImportBooksFromFiles → useFileSelector.ts selectFileTauri → nativeAppService.selectFiles → openDialog()tauri-plugin-dialogACTION_GET_CONTENT对应实现分别在 useFileSelector.ts、nativeAppService.ts。这条路径的本质是打开系统选择器 → 等待一个 JS Promise 被 resolve。桌面端、常规手机端没有问题但在低内存设备上这个跨 Activity 往返的 Promise恰恰是致命弱点。根因链FireOS 上导入没反应的三个环节环节一低内存导致 MainActivity 在选择器前台时被销毁Fire 7 等低内存设备logcat 证据来自 tauri-apps/plugins-workspace#2745在系统文件选择器位于前台时会销毁后台的 MainActivity。当用户完成选择返回时Android在同一进程内重建MainActivity。环节二Tauri 重新初始化 Builder 触发 SIGABRTActivity 重建后Tauri 会重新执行tauri::Builder初始化此时日志系统已被初始化过再次注册会触发 panicPluginInitialization(log, attempted to set a logger after the logging system was already initialized)随即 SIGABRT应用静默重启已选中的 URI 全部丢失——这正好对应报告者观察到的 DevTools 断连与WM_RESTART_ACTIVITYlogcat 记录。这一半的问题在上游 tao PR #1148 中已被部分修复tao 0.35.02025 年 10 月。Readest 在 commit58d4661b72026-07-13将 tao 从 0.34.8 升级到 0.35.3首个携带该修复的版本是 v0.11.18所有 #1217 的 issue 报告都早于该版本。环节三修复后仍然失效——结果被静默丢弃即使没有崩溃修复后的 Tauri 仍然存在另一半 bug重建后挂在 WebView 上的待决 JS Promise 随 WebView 一起死亡Tauri 的PluginManager.onActivityCreate会提前返回if (::activity.isInitialized) return因此其ActivityResultLauncher仍绑定在已销毁的 Activity 上选择器返回时结果没有任何回调可投递被直接丢弃——表现为不崩溃、但导入静默无效no-op。无 FireOS 硬件时的复现方法论方案 A开发者选项 Dont keep activities在开发者选项开启Dont keep activitiesalways_finish_activities再点 Import Books 即可触发 Activity 销毁路径。方案 B小米/澎湃 OS 上的验证与陷阱实测关键结论文档明确指出该方案在小米 13v0.11.202026-08-06上不生效原因是双重的MIUI 的 GET_CONTENT 选择器com.android.fileexplorer/.picker.PickMainNavigatorActivity是一个半透明 sheet调用方 MainActivity 只进入 PAUSED 而不会 STOPPED因此 Dont keep activities 永远不会触发销毁HyperOS 直接忽略always_finish_activities按 Home 后 Activity 停留在 STOPPED 而非 DESTROYEDdumpsys 中也没有mAlwaysFinishActivities。文档给出了一个在小米上实测可复现结果丢失的流程打开文件选择器按 Home 回到桌面执行adb shell am kill com.bilingify.readest杀掉进程通过最近任务recents恢复任务——半透明选择器会迫使 MainActivity 在后台冷重建v0.11.20 上不会崩溃选择文件并点 OK返回图书馆页面书静默地没有被导入同样的文件在无干扰的往返中可正常导入。这验证了 bug 中结果丢失的一半发生在进程死亡场景而进程内 Activity 重建那一半仍需真正会销毁 Activity 的设备如 FireOS来验证。复现时的关键提醒当用 adb 驱动选择器时务必先确认界面上的 (1/100) 计数再点 OK——一次漏掉的选中点击其表现和这个 bug 一模一样。最终修复方案PR #5531自研选择器 待发事件重放设计思想放弃 Promise改用事件 队列原修复方向文档中记录的设计思路是像已有的文件夹选择器select_directoryrequest code 1002一样在tauri-plugin-native-bridge中自建文件选择器使用ACTION_OPEN_DOCUMENTEXTRA_ALLOW_MULTIPLE在MainActivity.onActivityResult→handleActivityResult中处理takePersistableUriPermission获取持久权限并通过已有的emitOrQueue待发事件队列本为冷启动 VIEW/SEND 场景设计天然能跨越 WebView 重载存活投递结果。PR #55312026-08-06squash commit5b3f3e888最终落地方案如下层关键变更Kotlin 原生show_file_picker命令ACTION_OPEN_DOCUMENTrequest code1003companion 对象中的deliverActivityResult存根在load()时排空事件file-picker-result事件通过emitOrQueue投递JSuseAndroidPickedBooks在图书馆页面常驻监听把事件转成importBooks调用Kotlin 侧show_file_picker 命令核心实现在 NativeBridgePlugin.ktCommand fun show_file_picker(invoke: Invoke) { try { val intent Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) // Book extensions have no reliable MIME mapping in SAF; the // JS side re-applies the extension whitelist on the result. type */* putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) } activity.startActivityForResult(intent, FILE_PICKER_REQUEST_CODE) invoke.resolve() // 立即 resolve不持有跨选择器的 Promise } catch (e: Exception) { invoke.reject(Failed to open file picker: ${e.message}) } }关键设计invoke.resolve()立即返回不把 invoke 挂起等待选择结果结果通过事件异步投递。选择结果解析在emitFilePickerResult中先从clipData收集多个 URI空则回退到data.data随后对每个 URI 调用tryTakePersistableReadPermissionACTION_OPEN_DOCUMENT授予的权限可持久化保证进程重启后仍能读取并复制进书库最后emitOrQueue(file-picker-result, payload)。Kotlin 侧跨 Activity 重建的存根与排空FILE_PICKER_REQUEST_CODE 1003定义于 companion 对象NativeBridgePlugin.kt// A file-picker result can be delivered to a MainActivity that was // recreated after the process died behind the system picker (#1217). // onActivityResult then fires before Tauri has instantiated this // plugin, so the raw Intent is stashed here and drained in load(). private var pendingFilePickerData: Intent? null fun deliverActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { val plugin instance if (plugin ! null) { plugin.handleActivityResult(requestCode, resultCode, data) } else if (requestCode FILE_PICKER_REQUEST_CODE resultCode Activity.RESULT_OK data ! null) { pendingFilePickerData data } }当结果到达时插件实例尚未创建正是进程死亡后 Activity 重建场景原始 Intent 被暂存在load(webView)中NativeBridgePlugin.kt排空并重放pendingFilePickerData?.let { data - pendingFilePickerData null emitFilePickerResult(data) }MainActivity.onActivityResult统一转发到 companion 的deliverActivityResultMainActivity.kt注释明确说明了结果早于插件实例化 → 存根 → load() 重放的用途。Kotlin 侧emitOrQueue 待发事件队列emitOrQueueNativeBridgePlugin.kt复用自冷启动 VIEW/SEND 场景当事件名尚无监听者时入队registerListener重写中L496-L528在监听器注册后批量重放。这就是即使 WebView 重载、JS 监听器晚注册结果也不丢失的底层保证。Rust 侧命令桥接Rust 侧命令在 commands.rs 注册#[command] pub(crate) async fn show_file_pickerR: Runtime(app: AppHandleR) - Result() { app.native_bridge().show_file_picker() }移动端实现 通过run_mobile_plugin(show_file_picker, ())调用 Kotlin 侧命令注释同样标注了 #1217 的动机。JS 侧useAndroidPickedBooks 常驻监听JS 侧核心是 useAndroidFilePicker.ts 中的useAndroidPickedBookshook仅在appService.isAndroidApp时注册addPluginListener(native-bridge, file-picker-result, ...)收到uris后先用resolveTauriFileName解析真实显示名应对 SAF 不透明 document id见 #4489 注释再按FILE_SELECTION_PRESETS.books.extensions白名单过滤被白名单过滤掉的文件会弹 toast 指名道姓地提示对应 issue #5959避免全被过滤时看起来像按钮失灵用useRef保持最新回调而不反复重注册监听器防止 unregister/register 之间丢失队列事件。在图书馆页面中Android 分支改为 fire-and-forget 调用showFilePicker()bridge.ts 中invoke(plugin:native-bridge|show_file_picker)结果由常驻的useAndroidPickedBooks(appService, files importBooks(files, getImportTargetGroupId()))消费。测试覆盖JS 半边的行为有完整测试useAndroidFilePicker.test.tsx。测试断言Android 上注册file-picker-result监听器、非 Android 不注册、仅转发扩展名匹配的书籍文件、basename()抛错时不会炸掉整批处理回退到 raw-URI 解析。修复验证与已知坑小米 13 设备实测通过kill-behind-picker 场景0.11.20 上会丢结果现在能正常导入正常往返同样验证通过。HyperOS 会把ACTION_OPEN_DOCUMENT改写成hyper.intent.action.OPEN_DOCUMENT仍是 fileexplorer 选择器属于厂商自定义动作行为需按设备实测。FireOS 硬件验证仍待完成进程内 Activity 重建那一半变体只有真正会销毁 Activity 的设备才能覆盖。adb 驱动选择器验证时务必以 (1/100) 计数确认已选中再点 OK。关联但独立的问题文档附带发现排查过程中还发现三个独立问题值得单独跟进Samsung 选 100 MOBI 只导入 3 本2025 年 11 月同一线程useFileSelector.ts在basename()解析后按扩展名二次过滤解析失败的路径回退到 raw-URI 解析后被静默丢弃且对每个文件发起一个无界的Promise.allbasename IPC。代码中的过滤逻辑在 useFileSelector.ts含 #5959 的 toast 补偿。eink.rs 的EINK_MANUFACTURERS包含 amazon导致所有 Fire LCD 平板被当作墨水屏对比度主题、无过渡动画、EPD 刷新路径。这是外观性问题但判断错误值得单独修复。NativeBridgePlugin.handleActivityResult只处理FOLDER_PICKER_REQUEST_CODE1002REQUEST_MANAGE_STORAGE1001的结果被丢弃。相关代码在 NativeBridgePlugin.kt。方法论总结从 #1217 可以提炼出 Tauri/Android 混合应用系统选择器类功能的三条通用原则绝不让 JS Promise 跨越系统选择器的生命周期低内存设备上 Activity/进程随时可能被回收跨 Activity 的异步结果必须走事件 可重放队列而不是调用方持有的 Promise原生结果先落存根、后按需排空onActivityResult可能早于插件实例化到达companion 静态存根 load()排空是可靠的桥接模式复现必须贴合设备真实行为Dont keep activities 在 MIUI/HyperOS 上不生效因为厂商选择器形态半透明 sheet与系统策略忽略always_finish_activities都会改变触发条件——没有目标硬件时用adb shell am kill模拟进程死亡是验证结果丢失半边最有效的手段。【免费下载链接】readestReadest is a modern, feature-rich ebook reader designed for avid readers offering seamless cross-platform access, powerful tools, and an intuitive interface to elevate your reading experience.项目地址: https://gitcode.com/gh_mirrors/re/readest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Python知识点背诵手册:从入门到面试的完整知识框架

Python知识点背诵手册:从入门到面试的完整知识框架

简介:Python知识点背诵手册是一份面向初学者的分章节知识梳理文档,聚焦Python核心语法与常用编程范式。内容覆盖基础数据类型(整型、浮点型、字符串、列表、字典等)、缩进与注释、条件与循环、函数与高阶函数、模块和包、异常处理…

2026/9/24 18:54:08 阅读更多 →
连续式混合机结构参数优化:基于响应面法的混合均匀度提升实践

连续式混合机结构参数优化:基于响应面法的混合均匀度提升实践

简介:这份《连续式混合机结构参数的响应面分析优化》是一篇技术文档,适合机械设计、离散元仿真及结构优化方向的工程师与研究者阅读。内容针对卧式强制混合机结构笨重、材料冗余的问题,采用ANSYS-DesignModeler建立参数化模型,结合…

2026/9/24 13:38:49 阅读更多 →
GPS静动态滤波卡尔曼滤波实验:Q/R整定与新息门限实践

GPS静动态滤波卡尔曼滤波实验:Q/R整定与新息门限实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/22 23:24:30 阅读更多 →

最新新闻

ST-GCN骨骼动作识别项目实战:图卷积网络原理与工程实现详解

ST-GCN骨骼动作识别项目实战:图卷积网络原理与工程实现详解

简介:一套基于时空图卷积网络ST-GCN的骨骼动作识别Python毕业设计,涵盖源代码、训练好的模型与全套项目文档,面向计算机视觉、深度学习方向的毕设选题与课程作业。项目来自课程设计,代码均测试通过,实现了从骨骼关键点…

2026/9/24 22:45:40 阅读更多 →
Java工程师转型Agent开发的实战路径

Java工程师转型Agent开发的实战路径

1. 为什么Java工程师转Agent开发不是“换赛道”,而是“升级武器库”我带过三届校招Java后端团队,也参与过五个AI原生应用的从0到1落地。去年底有个典型场景:一位在支付系统写了七年Spring Boot的老同事,突然开始研究LangChain4j的…

2026/9/24 22:45:40 阅读更多 →
如何去除AI味?从95%到0%的AIGC降重改写指南

如何去除AI味?从95%到0%的AIGC降重改写指南

先把一个真实场景摆出来:你在某个AI对话框里让它写一篇产品推广文案,复制粘贴进在线AIGC检测工具,屏幕上跳出一行刺眼的数字——疑似AI生成比例95%。再把同一段文字发给一个做编辑的朋友看,对方扫了两眼就摇头:“这味儿…

2026/9/24 22:45:40 阅读更多 →
DeepSeek Harness Desktop:基于Electron的本地大模型评测工具

DeepSeek Harness Desktop:基于Electron的本地大模型评测工具

1. 项目概述:这不是一个“突然出现”的桌面应用,而是一次有迹可循的技术演进最近在 GitHub 上刷到 DeepSeek 官方仓库时,我下意识点开 Releases 页面,结果一眼就看到了deepseek-harness-desktop这个新包——不是 PR、不是草稿、不…

2026/9/24 22:45:40 阅读更多 →
从94%到0%:用Skill彻底去除AI写作痕迹的实操指南

从94%到0%:用Skill彻底去除AI写作痕迹的实操指南

1. 先认清"AI味"是什么:不是玄学,是统计学特征我拿自己前两天的一篇文章来开头。文章是让AI帮忙起草的,内容讲一个效率工具的使用心得。我自认为已经加了不少人情味的表述,结果发给一个朋友,他三秒就回了三个…

2026/9/24 22:45:40 阅读更多 →
Java数据类型与运算符避坑指南:从基本类型到Integer缓存

Java数据类型与运算符避坑指南:从基本类型到Integer缓存

最近带了个新人,他问我:Java 里到底有几种数据类型?我说 8 种基本类型。他又问:那 String 呢?我说 String 是引用类型。他接着问:那为什么有人总说 Java 的运算符优先级比数据类型还难记?遇到长…

2026/9/24 22:44:39 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →