
LeakCanary Shark Explorer 变更日志解读从shark://深链到跨会话笔记的桌面端内存分析新特性【免费下载链接】leakcanaryA memory leak detection library for Android.项目地址: https://gitcode.com/gh_mirrors/le/leakcanaryShark Explorer 是 LeakCanary 仓库中独立于库版本线发布的桌面应用用于打开 Android 堆转储heap dump并可视化展示是什么在持有内存。本文基于 shark-explorer-changelog.md 的 Unreleased 条目逐一剖析其首批发布特性——shark://深链、位置笔记与导航历史下拉——并结合 shark-explorer.md 用户指南与仓库内shark/shark-explorer/模块的源码实现讲清每个能力的设计动机、工作原理与使用方式。读完你将理解 Shark Explorer 为什么给每个位置生成链接、笔记如何做到跨运行持久化以及这些设计如何在源码层落地。为什么 Shark Explorer 需要自己的变更日志Shark Explorer 是一个桌面应用而不是库。它在自己的版本线上独立发布因此拥有独立的变更日志LeakCanary 与 Shark 库的变更统一记录在 changelog.md该文件从不提及Shark ExplorerShark Explorer 的变更记录在 shark-explorer-changelog.md其中从不出现库的新特性一次版本如何被打包发布详见 releasing-shark-explorer.md。两条发布线共用同一个仓库但面向的读者不同读库变更日志的人关心 API、依赖与 Android 集成读应用变更日志的人关心桌面端的功能行为。变更标记符号体系Shark Explorer 的变更日志沿用了 LeakCanary 变更日志的标记符号唯一删掉的是新识别库泄漏——桌面应用没有库泄漏识别的概念标记含义⚠️破坏性变更你依赖的某些行为变了或没了。行为变更应用现在做了不同的事情。崩溃修复之前会崩溃现在不会了。缺陷修复应用做了错误的事但没有崩溃。✨新增之前不存在的功能。改进原本就能工作的东西现在工作得更好。LeakCanary 主 changelog.md 中该体系描述为⚠️ 升级可能破坏构建、 新识别库或厂商 ROM 中的泄漏本日志在引用时保持了同样的语义但去掉了 。Unreleased首批发布的三个新能力截至当前仓库Shark Explorer 的Unreleased一节包含四个条目全部是 ✨ 新增初始发布Initial releaseshark://深链右键任何窗口可达的位置即可复制指向它的链接笔记Notes每个位置可以保存一条 Markdown 笔记跨运行保留导航历史下拉右键 ← 或 → 箭头直接跳转到该方向上的任意一步。下面结合 shark-explorer.md 与源码分别展开。右击即得的shark://深链把看这里变成一行 URL变更日志说了什么右键点击窗口能带你去到的任何东西——一个标签页、一个矩形、一行、一个字段——即可复制一个指向它的shark://链接也可以在新标签页中打开它。点击链接会把应用带到前台并在新标签页中打开那个位置一个对象、一个过滤后的对象列表、展开了相同分组的泄漏列表。在用户指南中的用法shark-explorer.md 的 Link to a tab 一节给出了完整语义右键并选择 Copy link得到当前位置的shark://URL粘贴到任何可点击链接的地方聊天消息、issue、给自己的笔记点击后 Shark Explorer 会来到前台并在新标签页打开该位置。它在所有提供在新标签页打开的地方旁侧提供标签页、顶部按钮、地图上的矩形、对象列表或泄漏列表的行、引用链的一步、详情面板的字段、星标对象。文档给出了三个示例链接shark://vugs93jp/object?id0x7f2a4b18 shark://vugs93jp/objects?queryBitmapexacttrue shark://vugs93jp/leaks任何标签页所在的位置都是一个链接一个对象、带着已填搜索与过滤条件的对象列表、展开了相同分组的泄漏、星标对象。因此看一下这个是一段 URL 而非一段操作说明——这也是一个已经读过你堆转储的工具或 Agent 能直接把你指到它发现的东西的方式。两个关键设计点链接指向的是窗口而不是堆转储。同一份 dump 打开两次是两个窗口链接只会指向它被复制时所在的那个窗口。因此链接只在那个窗口开着时有效窗口关闭或应用重启后失效——此时跟随链接会打开一个空窗口并说明情况。链接永远不会替换你正在读的内容它总是打开自己的标签页。链接只能到达已安装构建中的应用是安装包告诉操作系统shark://属于这个应用。从源码运行的副本之间仍可互相链接但操作系统不会因为链接而启动它。源码级实现DeepLink.kt链接的解析与拼装完全在非 UI 模块实现以便单测而非靠手点验证。核心类 DeepLink.kt 的文档注释定义了这一格式shark://window/place?what that place needs一个窗口中的一处位置。值得展开的实现细节Scheme 与 window idscheme 固定为shark因为它是自有的、且短到值得手打和粘贴。window id 是 8 个小写字符取自一个不含l、1、o、0的字母表——因为链接经常要从屏幕上读出来再敲回去这保证了读到的就是原链接。id 是随机生成而非顺序编号的顺序编号会让昨天复制的链接今天打开某个窗口本次运行的第二个窗口而悄悄变错随机 id 要么指向它命名的窗口要么什么都不是——后者是一个明确的错误信息。位置路径placePlace的每种形态都有对应路径——object、smaller-objects、objects、leaks、starred见 Place.kt 中的五种位置类型。链接参数携带完整的位置状态过滤后的对象列表到达时仍是过滤状态泄漏页面到达时展开的是同一组。节点 id 的完整保留链接中的对象 id 写作0x加全部 64 位无符号十六进制。这与界面上标签所用的hexObjectId掩码到低 32 位刻意不同——树的节点跑满整个Long范围32 位 dump 中 2 GB 以上的对象在 shark 中是负数标签写法会丢失信息。注释明确链接必须能回到它被创建时的那个 id。解析错误信息parse()对非法链接抛出带说明的IllegalArgumentException如A link starts with shark://…因为每个链接都来自应用外部命令行、另一次运行、或操作系统递来的浏览器 URL。DeepLinkTest.kt 验证了这些保证链接写出后能读回同一对象Long.MAX_VALUE、-1L、Int.MIN_VALUE.toLong()等极端 id 都能完整往返负数 id 不会按标签的 32 位写法写否则两个不同节点会变成同一个链接。位置笔记Notes跨运行的 Markdown 便签变更日志说了什么Notes每个位置都可以保存一条 Markdown 笔记跨运行保留标签栏会标记那些位置有笔记的标签。笔记属于位置而不是标签页所以两个标签页指向同一位置时共享同一条笔记。笔记中写到的类名、地址和shark://链接会变成回到窗口中的链接按散文风格缩短显示GitHub URL 则按 GitHub 自己的缩短方式缩短。在用户指南中的用法shark-explorer.md 的 Take notes 一节完整描述了操作流在显示标签页位置标题下方的✎ Add Note开始写输入框内输入按Save保存笔记就画在输入框原来的位置Cancel丢弃输入。下次再到这个位置笔记还在标签栏会给有笔记的位置的标签打上 ✎ 标记。关键语义笔记属于位置而非标签页同一位置的两个标签页共享一条笔记同一份 dump 的两个窗口亦然。所以两次到达同一对象时写下的内容会追加到已有的笔记而不是在旁边另起一条。删除方式是打开笔记、删光文字再Save空笔记等于没有笔记✎ 标记随之消失。存储位置笔记存放在~/.shark-explorer/notes每个堆转储一个目录每个位置一个.md文件。因此笔记可以用编辑器打开、粘贴进 issue、或让 Agent 直接读取无需经过应用。可识别的写法保存后笔记中任何这个堆转储能识别的片段都会变成回到窗口的链接你写的它读成点击后com.example.MyApp$CacheMyApp$Cache在新标签页打开该类0x7f2a4b18Cache instance (0x7f2a4b18)在新标签页打开该对象shark://vugs93jp/leaksLeaks像在任何地方点击一样跟随链接https://github.com/square/leakcanary/issues/2841square/leakcanary#2841在浏览器中打开名字或地址若 dump 里没有对应物就原样保留为文本没听说过的类就是你写到的类而不是一个坏链接。这也让笔记在应用之外保持可读——磁盘上的内容从不被改写只有屏幕上的显示会变。支持标题、列表、引用、code、加粗、斜体、围栏代码块和[链接](https://example.com)一行就是一行两行之间不需要空行如同 GitHub 评论框读 Markdown 的方式。围栏代码块内的内容不会被链接化或缩短。位置是你在哪里而不是你如何排列所以在对象列表中搜索、展开泄漏或调整窗口大小都会停留在同一条笔记上而不是新开一条。由于shark://链接命名的是窗口写进笔记的链接在该窗口关闭后即失效——所以在写着它的时候为你想回来的标签页复制链接只要那个窗口还开着它就能带你回去。源码级实现NoteFile.kt 与 NoteMarkdown.kt笔记存储的落盘逻辑在 NoteFile.kt。设计决策都有明确的源码注释不放在堆转储旁边dump 可能来自设备拉取的目录、临时文件、只读挂载或本仓库的 checkout写进那些地方会弄脏其中一些并在其余地方失败。应用自己的目录root之下始终可写且总能再次找到。一个位置一个文件而非一个文件按章节分区保存只触及正在输入的那条笔记不必从一份还含有人家自己标题的文档中解析回内容目录列表本身就是索引——这正是keysWithNotes()所做的事也是标签栏标记的依据。目录命名dump文件名-父目录哈希如large-dump.hprof-1f3a9c0b。名字在前是因为这些目录会被人类按名字浏览哈希在后是因为两次运行的large-dump.hprof是两次调查。路径用absoluteFile.normalize()规范化使./heap.hprof与heap.hprof指向同一组笔记。写入的原子性先写*.partial文件再重命名保证保存中途被杀掉时留下的是上一次的完整笔记而不是半个空内容则删除文件而非留下空文件否则下一次运行会把空文件当作有笔记并给标签打标记。读写都抛IOException而非吞掉——读不出来的笔记绝不会被悄悄用空内容覆盖。Markdown 的解析在 NoteMarkdown.kt它是刻意实现的子集一行是一个块——不会把某人写成的两行重排成段落列表上方不需要空行这正是 GitHub 评论框读 Markdown 的方式。解析器本身不读堆转储只把类名/地址留成NoteMention等打字停顿后再由Note.resolvedWith一次性询问 dump。围栏代码块内的内容保持原样。GitHub URL 的缩短按 GitHub 自己的规则…/issues/2841显示为square/leakcanary#2841commit 短 SHA 取 7 位评论片段追加(comment)。笔记键note key的归属规则在 Place.noteKey() 中同样明确每个对象一条、每个对象堆一条、对象列表无论怎么过滤一条、泄漏无论怎么展开一条、星标对象一条整个堆转储本身也是一个对象HeapDominatorTreemap.ROOT_OBJECT_ID是窗口首开标签页所在的位置也是写关于整个 dump 的笔记的地方。前进/后退历史一次点击走四步变更日志说了什么右键点击 ← 或 →会列出该箭头能带你去到的所有地方于是后退四步是一次点击而不是四次。源码级实现NavigationHistory.ktNavigationHistory.kt 实现每个标签页而非每个窗口一份的导航历史。其设计文档说得很清楚Shark Explorer 的一切操作都是横向移动——点击支配者或路径的一步会转而显示那个对象打开对象列表或星标列表则完全离开地图——因此没有向上可走需要记住的正是这些移动本身。实现要点不可变数据结构entriesindex可安全地作为 UI 状态持有并保持在核心模块中以供单测backEntries/forwardEntries按箭头访问的顺序排列——最接近当前的一步在前所以列表第一项就是一次后退点击第二项是两次goBack(steps)/goForward(steps)钳制而非校验越界因为点击来源的列表与它来自的历史在重组之间是同一个值goTo(entry)记录当前位置并丢弃前进方向的记录行为同浏览器对当前条目是 no-op避免反复点击同一矩形塞满历史replacingCurrent(entry)不记录移动地替换当前位置——用于路径回来后比请求的短以及对象列表里敲入的过滤这类不应让后退箭头返回的情况。NavigationHistoryTest.kt 覆盖了这些语义。深入理解为什么窗口链接与位置笔记如此设计把两个新特性放回 shark-explorer.md 的语境其设计哲学是一致的一切命名的对象都是到达它的方式。地图上每个矩形、引用链每一行、列表每一行都可点击点击前往、中键/⌘/Ctrl 点击后台新标签、右键菜单同时提供两者与链接标签页可全部关闭——关到最后一个仍保留堆转储打开与按钮就绪。在这种处处可去、处处可链的界面里shark://链接让协作与自动化成为可能——把发现贴进 issue、或让已读取 dump 的工具/Agent 直接把人带到现场位置笔记让调查过程本身持久化——堆转储文件会过期、窗口会关闭但写下来的调查记录以普通 Markdown 的形式留在~/.shark-explorer/notes比应用活得久导航历史下拉则是日常浏览的减负深树形结构里后退四步是一次点击而非四次。三者共同支撑的定位是Shark Explorer 回答 LeakCanary 旁边的那个问题——这个应用占了 200 MB全都是什么是什么在留着它见 shark-explorer.md 开头。它运行在 Shark 之上应用无需添加任何东西任何可调试应用的.hprof都可以直接打开。如何验证与运行按 shark-explorer.md 的说明从源码运行需要 JDK 17git clone gitgithub.com:square/leakcanary.git cd leakcanary ./gradlew :shark:shark-explorer:shark-explorer-app:run --argspath/to/dump.hprof路径参数可省略没有它时窗口只显示Open heap dump…按钮。注意shark://链接只对已安装构建生效安装包负责注册 scheme从源码运行时链接仍可在副本之间互传但操作系统不会因链接而启动应用。若要报告问题每次运行都会向~/.shark-explorer/logs写入日志保留最近 20 份附上出问题那次运行的日志即可。小结Shark Explorer 使用与 LeakCanary 相同的标记体系⚠️ ✨ 维护独立变更日志首发版本全部是 ✨ 新增初始发布、shark://深链、位置笔记、导航历史下拉shark://链接在 DeepLink.kt 中实现为窗口 位置两级寻址随机 8 字符 window id 保证链接要么准确要么明确失效参数携带完整界面状态笔记存储于~/.shark-explorer/notes的纯 Markdown 文件NoteFile.kt按位置而非标签页归属支持类名/地址/链接的屏幕内回链NoteMarkdown.kt导航历史NavigationHistory.kt以不可变条目列表 索引为每个标签页维护右键箭头可一次跳跃多步。对于想跟进 Shark Explorer 桌面端开发的读者docs/shark-explorer-changelog.md 是当前功能的权威来源深入功能语义可读 docs/shark-explorer.md了解版本如何被切割、macOS 签名如何完成可读 docs/releasing-shark-explorer.md。【免费下载链接】leakcanaryA memory leak detection library for Android.项目地址: https://gitcode.com/gh_mirrors/le/leakcanary创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考