<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Website of SauceWu</title><link>https://saucewu.github.io/posts/</link><description>Recent content in Posts on Website of SauceWu</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Fri, 24 Apr 2026 12:00:00 +0900</lastBuildDate><atom:link href="https://saucewu.github.io/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Mac Mini 作为 GitLab Runner：iOS 和 Flutter 打包节点配置指南</title><link>https://saucewu.github.io/posts/mac-mini-%E4%BD%9C%E4%B8%BA-gitlab-runnerios-%E5%92%8C-flutter-%E6%89%93%E5%8C%85%E8%8A%82%E7%82%B9%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97/</link><pubDate>Fri, 24 Apr 2026 12:00:00 +0900</pubDate><guid>https://saucewu.github.io/posts/mac-mini-%E4%BD%9C%E4%B8%BA-gitlab-runnerios-%E5%92%8C-flutter-%E6%89%93%E5%8C%85%E8%8A%82%E7%82%B9%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97/</guid><description>&lt;h1 id="mac-mini-作为-gitlab-runnerios-和-flutter-打包节点配置指南"&gt;Mac Mini 作为 GitLab Runner：iOS 和 Flutter 打包节点配置指南&lt;/h1&gt;
&lt;p&gt;iOS 和 Flutter 项目的 CI 有一个绕不开的限制：&lt;strong&gt;必须在 macOS 上构建&lt;/strong&gt;。云端 macOS 实例贵，自建 Mac Mini 作为 GitLab Runner 是最常见的低成本方案。&lt;/p&gt;
&lt;p&gt;这篇文章记录从零把一台 Mac Mini 配置成 GitLab CI 打包节点的完整过程，覆盖环境安装、Runner 注册、&lt;code&gt;.gitlab-ci.yml&lt;/code&gt; 配置、常见坑和维护建议。&lt;/p&gt;</description></item><item><title>子 Agent 并发的效率上限：什么时候加人反而更慢</title><link>https://saucewu.github.io/posts/%E5%AD%90-agent-%E5%B9%B6%E5%8F%91%E7%9A%84%E6%95%88%E7%8E%87%E4%B8%8A%E9%99%90%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E5%8A%A0%E4%BA%BA%E5%8F%8D%E8%80%8C%E6%9B%B4%E6%85%A2/</link><pubDate>Fri, 24 Apr 2026 10:00:00 +0900</pubDate><guid>https://saucewu.github.io/posts/%E5%AD%90-agent-%E5%B9%B6%E5%8F%91%E7%9A%84%E6%95%88%E7%8E%87%E4%B8%8A%E9%99%90%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E5%8A%A0%E4%BA%BA%E5%8F%8D%E8%80%8C%E6%9B%B4%E6%85%A2/</guid><description>&lt;h1 id="子-agent-并发的效率上限什么时候加人反而更慢"&gt;子 Agent 并发的效率上限：什么时候加人反而更慢&lt;/h1&gt;
&lt;p&gt;用过 Claude 多 Agent 框架之后，第一个直觉往往是：&lt;strong&gt;并发数越多越快。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;起 5 个 agent 写 5 个脚本，明显比串行快。那起 20 个呢？50 个？&lt;/p&gt;
&lt;p&gt;现实不是这样的。并发 agent 和线程池一样，存在一个效率拐点，超过之后加 agent 不仅不再加速，反而可能让整个任务变慢。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一并发加速比从线性到平台期"&gt;一、并发加速比：从线性到平台期&lt;/h2&gt;
&lt;p&gt;理论上，N 个 agent 并行跑 N 个独立任务，应该是 N 倍加速。但实际测下来大致是这张表：&lt;/p&gt;</description></item><item><title>flutter-patrol-pilot：让 AI Agent 自主跑通 Flutter iOS 测试</title><link>https://saucewu.github.io/posts/flutter-patrol-pilot%E8%AE%A9-ai-agent-%E8%87%AA%E4%B8%BB%E8%B7%91%E9%80%9A-flutter-ios-%E6%B5%8B%E8%AF%95/</link><pubDate>Thu, 23 Apr 2026 16:00:00 +0900</pubDate><guid>https://saucewu.github.io/posts/flutter-patrol-pilot%E8%AE%A9-ai-agent-%E8%87%AA%E4%B8%BB%E8%B7%91%E9%80%9A-flutter-ios-%E6%B5%8B%E8%AF%95/</guid><description>&lt;h1 id="flutter-patrol-pilot让-ai-agent-自主跑通-flutter-ios-测试"&gt;flutter-patrol-pilot：让 AI Agent 自主跑通 Flutter iOS 测试&lt;/h1&gt;
&lt;p&gt;Flutter iOS 的集成测试一直是个麻烦事：Xcode 环境脆、xcresult 日志难读、Patrol 框架有自己的坑、失败原因五花八门。让人类来修还好，让 AI Agent 来修——很容易陷入死循环。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/sauce-wu/flutter-patrol-pilot"&gt;flutter-patrol-pilot&lt;/a&gt;（简称 fpp）是一个专门解决这个问题的 Claude Code Agent Skill：它接管整个 Flutter iOS 测试生命周期，从编译到运行，从失败诊断到代码修复，带着硬性规则和停止条件，不进死循环。&lt;/p&gt;</description></item><item><title>Flutter 状态管理选型：Provider、Riverpod、GetIt 对比</title><link>https://saucewu.github.io/posts/flutter-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E9%80%89%E5%9E%8Bproviderriverpodgetit-%E5%AF%B9%E6%AF%94/</link><pubDate>Mon, 20 Apr 2026 13:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/flutter-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86%E9%80%89%E5%9E%8Bproviderriverpodgetit-%E5%AF%B9%E6%AF%94/</guid><description>&lt;h1 id="flutter-状态管理选型providerriverpodgetit-对比"&gt;Flutter 状态管理选型：Provider、Riverpod、GetIt 对比&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、先说结论：它们并不是同一类东西&lt;/li&gt;
&lt;li&gt;二、Provider：最贴近 Flutter 原生心智&lt;/li&gt;
&lt;li&gt;三、Riverpod：更现代的响应式状态管理&lt;/li&gt;
&lt;li&gt;四、GetIt：更像依赖注入容器，不是完整状态管理方案&lt;/li&gt;
&lt;li&gt;五、核心维度横向对比&lt;/li&gt;
&lt;li&gt;六、实际项目怎么选&lt;/li&gt;
&lt;li&gt;七、常见误区&lt;/li&gt;
&lt;li&gt;八、总结&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="一先说结论它们并不是同一类东西"&gt;一、先说结论：它们并不是同一类东西&lt;/h2&gt;
&lt;p&gt;很多人在 Flutter 里做技术选型时，会把 &lt;strong&gt;Provider / Riverpod / GetIt&lt;/strong&gt; 放在一起比较，但它们其实不完全是同一层的东西。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Provider&lt;/strong&gt;：基于 &lt;code&gt;InheritedWidget&lt;/code&gt; 的状态注入与订阅方案，和 Flutter widget tree 绑定很深&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Riverpod&lt;/strong&gt;：从 Provider 演化出来的独立响应式状态管理框架，不依赖 &lt;code&gt;BuildContext&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GetIt&lt;/strong&gt;：本质是 &lt;strong&gt;Service Locator / 依赖注入容器&lt;/strong&gt;，负责“拿对象”，不天然负责“驱动 UI 刷新”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以如果一句话概括：&lt;/p&gt;</description></item><item><title>Flutter 性能优化实战</title><link>https://saucewu.github.io/posts/flutter-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%AE%9E%E6%88%98/</link><pubDate>Mon, 20 Apr 2026 12:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/flutter-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E5%AE%9E%E6%88%98/</guid><description>&lt;h1 id="flutter-性能优化实战"&gt;Flutter 性能优化实战&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、性能问题的三个来源&lt;/li&gt;
&lt;li&gt;二、Build 阶段优化&lt;/li&gt;
&lt;li&gt;三、Layout / Paint 阶段优化&lt;/li&gt;
&lt;li&gt;四、内存与图片优化&lt;/li&gt;
&lt;li&gt;五、列表性能&lt;/li&gt;
&lt;li&gt;六、用 DevTools 定位问题&lt;/li&gt;
&lt;li&gt;七、速查清单&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="一性能问题的三个来源"&gt;一、性能问题的三个来源&lt;/h2&gt;
&lt;p&gt;Flutter 渲染一帧的流程是：&lt;strong&gt;Build → Layout → Paint → Composite → Rasterize&lt;/strong&gt;。性能问题几乎都出在前三个阶段：&lt;/p&gt;</description></item><item><title>Flutter 交易所架构实战：DDD + Clean 落地指南</title><link>https://saucewu.github.io/posts/flutter-%E4%BA%A4%E6%98%93%E6%89%80%E6%9E%B6%E6%9E%84%E5%AE%9E%E6%88%98ddd-+-clean-%E8%90%BD%E5%9C%B0%E6%8C%87%E5%8D%97/</link><pubDate>Mon, 20 Apr 2026 20:30:00 +0900</pubDate><guid>https://saucewu.github.io/posts/flutter-%E4%BA%A4%E6%98%93%E6%89%80%E6%9E%B6%E6%9E%84%E5%AE%9E%E6%88%98ddd-+-clean-%E8%90%BD%E5%9C%B0%E6%8C%87%E5%8D%97/</guid><description>&lt;h1 id="flutter-交易所架构实战ddd--clean-落地指南"&gt;Flutter 交易所架构实战：DDD + Clean 落地指南&lt;/h1&gt;
&lt;p&gt;交易所项目做到一定规模，就会开始还之前欠下的债——页面里写 API、规则到处复制、改一处牵十处。&lt;/p&gt;
&lt;p&gt;用 &lt;code&gt;DDD + Clean&lt;/code&gt; 不是为了架构好看，是因为不用它的话，这类项目很难不失控。&lt;/p&gt;
&lt;p&gt;这篇不讲大而空的概念，只讲三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为什么交易所需要 &lt;code&gt;DDD + Clean&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在 Flutter 里到底怎么分层&lt;/li&gt;
&lt;li&gt;一笔下单请求如何在系统里流转（含时序图）&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="一为什么交易所适合-ddd--clean"&gt;一、为什么交易所适合 DDD + Clean&lt;/h2&gt;
&lt;p&gt;交易所天然是“复杂业务系统”，不是普通内容 App。你会长期面对：&lt;/p&gt;</description></item><item><title>Flutter FFI 插件的 GitHub CI 实践：以 ceres-wallet-core 为例</title><link>https://saucewu.github.io/posts/flutter-ffi-%E6%8F%92%E4%BB%B6%E7%9A%84-github-ci-%E5%AE%9E%E8%B7%B5%E4%BB%A5-ceres-wallet-core-%E4%B8%BA%E4%BE%8B/</link><pubDate>Fri, 17 Apr 2026 18:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/flutter-ffi-%E6%8F%92%E4%BB%B6%E7%9A%84-github-ci-%E5%AE%9E%E8%B7%B5%E4%BB%A5-ceres-wallet-core-%E4%B8%BA%E4%BE%8B/</guid><description>&lt;h1 id="flutter-ffi-插件的-github-ci-实践以-ceres-wallet-core-为例"&gt;Flutter FFI 插件的 GitHub CI 实践：以 ceres-wallet-core 为例&lt;/h1&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;普通 Flutter 插件的 CI 很简单：跑一下 &lt;code&gt;flutter test&lt;/code&gt; 就够了。但 Flutter FFI 插件夹着 C++/Rust native 库，问题就复杂得多——&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;native 库需要在 macOS 上编译 iOS，在 Linux 上编译 Android，不能混&lt;/li&gt;
&lt;li&gt;编译一次要十几分钟，开发者 &lt;code&gt;pub get&lt;/code&gt; 的时候不能让他们自己编&lt;/li&gt;
&lt;li&gt;Trust Wallet Core 有几十万行 C++ 代码，子模块更新需要自动化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ceres-wallet-core 的解法是两条 workflow：&lt;/p&gt;</description></item><item><title>ceres-wallet-core：用 Flutter 封装 Trust Wallet Core</title><link>https://saucewu.github.io/posts/ceres-wallet-core%E7%94%A8-flutter-%E5%B0%81%E8%A3%85-trust-wallet-core/</link><pubDate>Thu, 16 Apr 2026 16:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/ceres-wallet-core%E7%94%A8-flutter-%E5%B0%81%E8%A3%85-trust-wallet-core/</guid><description>&lt;h1 id="ceres-wallet-core用-flutter-封装-trust-wallet-core"&gt;ceres-wallet-core：用 Flutter 封装 Trust Wallet Core&lt;/h1&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;在做 Flutter 钱包应用时，密钥管理和交易签名是绕不开的核心模块。社区里能用的方案要么只支持 EVM 单链、要么 Dart 原生实现存在安全隐患、要么直接调 Web3 RPC 把私钥暴露在网络层。&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/trustwallet/wallet-core"&gt;Trust Wallet Core&lt;/a&gt; 是目前业界覆盖链最广、经过生产验证的开源密钥库，用 C++ 实现，已经支持 60+ 条链。问题是它没有官方的 Flutter 绑定。&lt;/p&gt;</description></item><item><title>ceres-mpc：基于 DKLs23 的 Flutter 两方 MPC 钱包 SDK</title><link>https://saucewu.github.io/posts/ceres-mpc%E5%9F%BA%E4%BA%8E-dkls23-%E7%9A%84-flutter-%E4%B8%A4%E6%96%B9-mpc-%E9%92%B1%E5%8C%85-sdk/</link><pubDate>Sun, 12 Apr 2026 17:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/ceres-mpc%E5%9F%BA%E4%BA%8E-dkls23-%E7%9A%84-flutter-%E4%B8%A4%E6%96%B9-mpc-%E9%92%B1%E5%8C%85-sdk/</guid><description>&lt;h1 id="ceres-mpc基于-dkls23-的-flutter-两方-mpc-钱包-sdk"&gt;ceres-mpc：基于 DKLs23 的 Flutter 两方 MPC 钱包 SDK&lt;/h1&gt;
&lt;h2 id="为什么需要-mpc-钱包"&gt;为什么需要 MPC 钱包&lt;/h2&gt;
&lt;p&gt;传统 HD 钱包的私钥完整存储在用户设备上，一旦设备丢失或被攻击，资产就没了。助记词备份虽然能恢复，但本身也是攻击面。&lt;/p&gt;
&lt;p&gt;MPC（Multi-Party Computation）钱包的思路是：&lt;strong&gt;私钥从始至终都不存在于任何单一设备&lt;/strong&gt;，签名由多方协作计算完成。两方 ECDSA 的典型部署是客户端持有一份密钥分片，服务端持有另一份，任意一方单独都无法完成签名。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ceres-mpc&lt;/strong&gt; 基于 &lt;a href="https://eprint.iacr.org/2023/765"&gt;DKLs23 协议&lt;/a&gt; 实现了一套完整的 Flutter MPC 钱包 SDK，密码学核心用 Rust 编写，通过 flutter_rust_bridge 暴露给 Dart 调度层。&lt;/p&gt;</description></item><item><title>Flutter 手势系统与竞技场机制详解</title><link>https://saucewu.github.io/posts/flutter-%E6%89%8B%E5%8A%BF%E7%B3%BB%E7%BB%9F%E4%B8%8E%E7%AB%9E%E6%8A%80%E5%9C%BA%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/</link><pubDate>Fri, 10 Apr 2026 10:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/flutter-%E6%89%8B%E5%8A%BF%E7%B3%BB%E7%BB%9F%E4%B8%8E%E7%AB%9E%E6%8A%80%E5%9C%BA%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/</guid><description>&lt;h1 id="flutter-手势系统与竞技场机制详解"&gt;Flutter 手势系统与竞技场机制详解&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、Gesture Arena（手势竞技场）运行原理&lt;/li&gt;
&lt;li&gt;二、与 Android 触摸分发机制的核心区别&lt;/li&gt;
&lt;li&gt;三、OneSequenceGestureRecognizer 生命周期详解&lt;/li&gt;
&lt;li&gt;四、MultiTapGestureRecognizer 使用场景与区别&lt;/li&gt;
&lt;li&gt;五、GestureArenaTeam 实际应用场景&lt;/li&gt;
&lt;li&gt;六、总结&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1 id="一gesture-arena手势竞技场运行原理"&gt;一、Gesture Arena（手势竞技场）运行原理&lt;/h1&gt;
&lt;h2 id="1-两层触摸模型"&gt;1. 两层触摸模型&lt;/h2&gt;
&lt;p&gt;Flutter 的触摸处理分为两层：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;对应类&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pointer&lt;/td&gt;
&lt;td&gt;原始触摸事件（down / move / up）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PointerEvent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gesture&lt;/td&gt;
&lt;td&gt;语义手势（tap、drag、scale 等）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GestureRecognizer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Pointer 层不做任何裁决，&lt;strong&gt;所有命中节点都会收到完整的 PointerEvent 序列&lt;/strong&gt;。Gesture Arena 工作在 Gesture 层，负责在多个识别器之间做出唯一裁决。&lt;/p&gt;</description></item><item><title>MPC 钱包：私钥从未完整存在过</title><link>https://saucewu.github.io/posts/mpc-wallet%E7%AC%94%E8%AE%B0/</link><pubDate>Sun, 15 Mar 2026 10:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/mpc-wallet%E7%AC%94%E8%AE%B0/</guid><description>&lt;h1 id="mpc-钱包私钥从未完整存在过"&gt;MPC 钱包：私钥从未完整存在过&lt;/h1&gt;
&lt;h2 id="传统钱包的问题"&gt;传统钱包的问题&lt;/h2&gt;
&lt;p&gt;用过普通钱包（MetaMask、Trust Wallet）的人都知道，创建钱包时会生成一组助记词：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;witch collapse practice feed shame open despair creek road again ice least
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这 12 个单词本质上就是你的私钥。它的问题很简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;抄错一个字 → 永久丢失资产&lt;/li&gt;
&lt;li&gt;被人拍到 → 资产全部被盗&lt;/li&gt;
&lt;li&gt;手机丢了没备份 → 再见&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;私钥是&lt;strong&gt;单点故障&lt;/strong&gt;。整个安全体系建立在&amp;quot;这串数字只有你知道&amp;quot;的假设上，一旦泄露，没有任何补救手段。&lt;/p&gt;</description></item><item><title>AA 钱包：让智能合约成为你的钱包</title><link>https://saucewu.github.io/posts/aa-wallet%E7%AC%94%E8%AE%B0/</link><pubDate>Wed, 28 Jan 2026 10:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/aa-wallet%E7%AC%94%E8%AE%B0/</guid><description>&lt;h1 id="aa-钱包让智能合约成为你的钱包"&gt;AA 钱包：让智能合约成为你的钱包&lt;/h1&gt;
&lt;h2 id="传统钱包的体验困境"&gt;传统钱包的体验困境&lt;/h2&gt;
&lt;p&gt;用过 Web3 钱包的人几乎都遇到过这些问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一次用 DApp，账户里没有 ETH，但用 USDC 付 gas 不行&lt;/li&gt;
&lt;li&gt;想同时 approve + swap，得签两笔交易，付两次 gas&lt;/li&gt;
&lt;li&gt;私钥丢了，资产永久没了，没有任何找回手段&lt;/li&gt;
&lt;li&gt;每次操作都要手动签名确认，体验比 Web2 差太多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题的根源是以太坊原始账户设计的限制——&lt;strong&gt;EOA（外部账户）&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="两种账户类型"&gt;两种账户类型&lt;/h2&gt;
&lt;p&gt;以太坊上有两种账户：&lt;/p&gt;</description></item><item><title>Flutter 渲染流程与三棵树详解</title><link>https://saucewu.github.io/posts/flutter-%E6%B8%B2%E6%9F%93%E6%B5%81%E7%A8%8B%E4%B8%8E%E4%B8%89%E6%A3%B5%E6%A0%91%E8%AF%A6%E8%A7%A3/</link><pubDate>Tue, 20 Jan 2026 10:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/flutter-%E6%B8%B2%E6%9F%93%E6%B5%81%E7%A8%8B%E4%B8%8E%E4%B8%89%E6%A3%B5%E6%A0%91%E8%AF%A6%E8%A7%A3/</guid><description>&lt;h1 id="flutter-渲染流程与三棵树详解"&gt;Flutter 渲染流程与三棵树详解&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、Flutter 渲染架构全览&lt;/li&gt;
&lt;li&gt;二、三棵树是什么&lt;/li&gt;
&lt;li&gt;三、三棵树的协作流程&lt;/li&gt;
&lt;li&gt;四、各层职责与修改时机&lt;/li&gt;
&lt;li&gt;五、常见场景：该动哪一层？&lt;/li&gt;
&lt;li&gt;六、总结&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="一flutter-渲染架构全览"&gt;一、Flutter 渲染架构全览&lt;/h2&gt;
&lt;p&gt;Flutter 的渲染流程从 Dart 代码到屏幕像素，经过了多个层次的处理：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;你写的 Widget
↓
Widget Tree（配置描述）
↓
Element Tree（生命周期 + 状态管理）
↓
RenderObject Tree（布局 + 绘制）
↓
Layer Tree（合成）
↓
Skia / Impeller（光栅化）
↓
GPU → 屏幕
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这套架构的核心设计目标是&lt;strong&gt;把&amp;quot;描述&amp;quot;和&amp;quot;执行&amp;quot;分离&lt;/strong&gt;，让 Widget 可以廉价重建，而真正昂贵的布局和绘制操作尽量复用。&lt;/p&gt;</description></item><item><title>StatefulShellRoute 踩坑：为什么 Loading 只遮半屏，pop 还会误关页面</title><link>https://saucewu.github.io/posts/statefulshellroute-%E8%B8%A9%E5%9D%91%E4%B8%BA%E4%BB%80%E4%B9%88-loading-%E5%8F%AA%E9%81%AE%E5%8D%8A%E5%B1%8Fpop-%E8%BF%98%E4%BC%9A%E8%AF%AF%E5%85%B3%E9%A1%B5%E9%9D%A2/</link><pubDate>Sat, 20 Dec 2025 16:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/statefulshellroute-%E8%B8%A9%E5%9D%91%E4%B8%BA%E4%BB%80%E4%B9%88-loading-%E5%8F%AA%E9%81%AE%E5%8D%8A%E5%B1%8Fpop-%E8%BF%98%E4%BC%9A%E8%AF%AF%E5%85%B3%E9%A1%B5%E9%9D%A2/</guid><description>&lt;h1 id="statefulshellroute-踩坑为什么-loading-只遮半屏pop-还会误关页面"&gt;StatefulShellRoute 踩坑：为什么 Loading 只遮半屏，pop 还会误关页面&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、问题现象&lt;/li&gt;
&lt;li&gt;二、根因不是 Dialog，而是 Navigator 变多了&lt;/li&gt;
&lt;li&gt;三、为什么只遮住半屏&lt;/li&gt;
&lt;li&gt;四、为什么 pop 之后页面被关了&lt;/li&gt;
&lt;li&gt;五、正确做法：全局浮层挂到 root navigator&lt;/li&gt;
&lt;li&gt;六、一个更稳的封装思路&lt;/li&gt;
&lt;li&gt;七、总结&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="一问题现象"&gt;一、问题现象&lt;/h2&gt;
&lt;p&gt;我在用 &lt;code&gt;go_router + StatefulShellRoute&lt;/code&gt; 做多 Tab 导航时，遇到了一个很烦的坑：&lt;/p&gt;</description></item><item><title>Flutter 路由方案对比：原生 Router 与 go_router</title><link>https://saucewu.github.io/posts/flutter-%E8%B7%AF%E7%94%B1%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94%E5%8E%9F%E7%94%9F-router-%E4%B8%8E-go_router/</link><pubDate>Mon, 10 Nov 2025 14:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/flutter-%E8%B7%AF%E7%94%B1%E6%96%B9%E6%A1%88%E5%AF%B9%E6%AF%94%E5%8E%9F%E7%94%9F-router-%E4%B8%8E-go_router/</guid><description>&lt;h1 id="flutter-路由方案对比原生-router-与-go_router"&gt;Flutter 路由方案对比：原生 Router 与 go_router&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、为什么 Flutter 路由总让人觉得“有点拧巴”&lt;/li&gt;
&lt;li&gt;二、先区分三套东西：Navigator、原生 Router、go_router&lt;/li&gt;
&lt;li&gt;三、原生 Router 的价值到底是什么&lt;/li&gt;
&lt;li&gt;四、go_router 做了什么封装&lt;/li&gt;
&lt;li&gt;五、go_router 的独特优势&lt;/li&gt;
&lt;li&gt;六、什么时候该选原生 Router，什么时候该选 go_router&lt;/li&gt;
&lt;li&gt;七、常见误区&lt;/li&gt;
&lt;li&gt;八、总结&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="一为什么-flutter-路由总让人觉得有点拧巴"&gt;一、为什么 Flutter 路由总让人觉得“有点拧巴”&lt;/h2&gt;
&lt;p&gt;很多人第一次接触 Flutter 路由时，会觉得：&lt;/p&gt;</description></item><item><title>Flutter 键盘弹起不流畅的根因与优化方案</title><link>https://saucewu.github.io/posts/flutter-%E9%94%AE%E7%9B%98%E5%BC%B9%E8%B5%B7%E4%B8%8D%E6%B5%81%E7%95%85%E7%9A%84%E6%A0%B9%E5%9B%A0%E4%B8%8E%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88/</link><pubDate>Thu, 23 Oct 2025 19:20:00 +0000</pubDate><guid>https://saucewu.github.io/posts/flutter-%E9%94%AE%E7%9B%98%E5%BC%B9%E8%B5%B7%E4%B8%8D%E6%B5%81%E7%95%85%E7%9A%84%E6%A0%B9%E5%9B%A0%E4%B8%8E%E4%BC%98%E5%8C%96%E6%96%B9%E6%A1%88/</guid><description>&lt;h1 id="flutter-键盘弹起不流畅的根因与优化方案"&gt;Flutter 键盘弹起不流畅的根因与优化方案&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、现象：为什么总感觉“不顺”&lt;/li&gt;
&lt;li&gt;二、根因 1：Flutter 响应的是 Insets 变化，不是系统级联动动画&lt;/li&gt;
&lt;li&gt;三、根因 2：键盘一弹，往往整棵页面都在重排&lt;/li&gt;
&lt;li&gt;四、根因 3：默认方案优先保证“别挡住”，不保证“像原生一样丝滑”&lt;/li&gt;
&lt;li&gt;五、几个最常见的踩坑场景&lt;/li&gt;
&lt;li&gt;六、优化思路：不要让整页跟着键盘一起动&lt;/li&gt;
&lt;li&gt;七、几种常见实现方案&lt;/li&gt;
&lt;li&gt;八、一个更稳的实战建议&lt;/li&gt;
&lt;li&gt;九、总结&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="一现象为什么总感觉不顺"&gt;一、现象：为什么总感觉“不顺”&lt;/h2&gt;
&lt;p&gt;很多 Flutter 开发者都遇到过这个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入框获取焦点时，页面像被突然“顶”了一下&lt;/li&gt;
&lt;li&gt;底部输入栏和键盘不是一起上来的，而是分两拍&lt;/li&gt;
&lt;li&gt;列表先跳一下，输入框再动一下&lt;/li&gt;
&lt;li&gt;iOS 上尤其明显，总觉得没有原生页面跟键盘联动得自然&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;功能上看，它通常是对的：&lt;/p&gt;</description></item><item><title>Flutter K线系统拆解（五）：绘图工具系统设计与工程优化</title><link>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%BA%94%E7%BB%98%E5%9B%BE%E5%B7%A5%E5%85%B7%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%B7%A5%E7%A8%8B%E4%BC%98%E5%8C%96/</link><pubDate>Mon, 21 Jul 2025 13:35:00 +0900</pubDate><guid>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%BA%94%E7%BB%98%E5%9B%BE%E5%B7%A5%E5%85%B7%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%B7%A5%E7%A8%8B%E4%BC%98%E5%8C%96/</guid><description>&lt;h1 id="flutter-k线系统拆解五绘图工具系统设计与工程优化"&gt;Flutter K线系统拆解（五）：绘图工具系统设计与工程优化&lt;/h1&gt;
&lt;h2 id="为什么要单独讲这一篇"&gt;为什么要单独讲这一篇&lt;/h2&gt;
&lt;p&gt;K 线里的“画线工具”看起来是附属能力，但实现难度其实很高。&lt;br&gt;
它同时涉及：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;绘图对象数据建模&lt;/li&gt;
&lt;li&gt;点位命中与拖拽编辑&lt;/li&gt;
&lt;li&gt;坐标与价格/时间映射&lt;/li&gt;
&lt;li&gt;持久化与跨周期恢复&lt;/li&gt;
&lt;li&gt;手势冲突与性能控制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果这套系统没设计好，常见问题就是：点不中、拖不稳、误触多、数据丢、越画越卡。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一先看系统分层抽象视角"&gt;一、先看系统分层（抽象视角）&lt;/h2&gt;
&lt;p&gt;一个完整的绘图工具系统，建议拆成 4 层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;控制层：管理当前绘图对象集合、选中状态、缓存恢复、默认样式&lt;/li&gt;
&lt;li&gt;绘图对象层：定义线段/射线/矩形/圆/斐波那契等结构与完成条件&lt;/li&gt;
&lt;li&gt;渲染命中层：负责路径构建、绘制、命中测试、控制点渲染&lt;/li&gt;
&lt;li&gt;交互层：手势状态机（创建/编辑/移动/缩放）与工具面板联动&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这类分层在交易类图表里经过大量实践验证，长期维护成本也更低。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二绘图对象模型怎么设计更稳"&gt;二、绘图对象模型怎么设计更稳&lt;/h2&gt;
&lt;h3 id="1-基础绘图对象字段"&gt;1) 基础绘图对象字段&lt;/h3&gt;
&lt;p&gt;建议每个绘图对象至少包含：&lt;/p&gt;</description></item><item><title>Flutter K线系统拆解（四）：交互体系与性能工程</title><link>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E5%9B%9B%E4%BA%A4%E4%BA%92%E4%BD%93%E7%B3%BB%E4%B8%8E%E6%80%A7%E8%83%BD%E5%B7%A5%E7%A8%8B/</link><pubDate>Mon, 07 Apr 2025 13:15:00 +0900</pubDate><guid>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E5%9B%9B%E4%BA%A4%E4%BA%92%E4%BD%93%E7%B3%BB%E4%B8%8E%E6%80%A7%E8%83%BD%E5%B7%A5%E7%A8%8B/</guid><description>&lt;h1 id="flutter-k线系统拆解四交互体系与性能工程"&gt;Flutter K线系统拆解（四）：交互体系与性能工程&lt;/h1&gt;
&lt;h2 id="为什么交互比指标更容易暴露问题"&gt;为什么交互比指标更容易暴露问题&lt;/h2&gt;
&lt;p&gt;很多交易页在功能上都能“跑起来”，但只要用户开始频繁缩放、拖动、长按，体验差异会立刻被放大。&lt;br&gt;
体感里的“丝滑”和“卡顿”，通常不来自单一 bug，而是交互链路里多个小问题叠加：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手势事件触发过密&lt;/li&gt;
&lt;li&gt;状态更新范围过大&lt;/li&gt;
&lt;li&gt;动画和计算抢同一帧预算&lt;/li&gt;
&lt;li&gt;非关键绘制在高频交互里持续执行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这一篇的重点不是“某个技巧”，而是如何把交互链路做成一套稳定的工程系统。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一交互设计里的三个关键决定"&gt;一、交互设计里的三个关键决定&lt;/h2&gt;
&lt;h3 id="1-方向锁定先判断用户意图再决定谁接管手势"&gt;1) 方向锁定：先判断用户意图，再决定谁接管手势&lt;/h3&gt;
&lt;p&gt;在图表可编辑、页面又可滚动的场景里，方向锁是刚需。&lt;br&gt;
当手势轨迹偏垂直时，把事件交给外层滚动容器；偏水平时再由图表处理，这能明显减少“手势打架”。&lt;/p&gt;
&lt;p&gt;方向锁不是锦上添花，而是复杂页面里“能不能用”的分水岭。&lt;/p&gt;
&lt;h3 id="2-状态分离长按拖拽缩放不要混成一个开关"&gt;2) 状态分离：长按、拖拽、缩放不要混成一个开关&lt;/h3&gt;
&lt;p&gt;把 &lt;code&gt;isLongPress&lt;/code&gt;、&lt;code&gt;isDrag&lt;/code&gt;、&lt;code&gt;isScale&lt;/code&gt; 独立维护，收益很直接：&lt;br&gt;
状态更可控、回归更容易、边界问题更少。&lt;br&gt;
尤其在十字线与拖拽共存时，状态分离可以显著降低互相干扰。&lt;/p&gt;
&lt;h3 id="3-绘制隔离高频区域和低频区域分开"&gt;3) 绘制隔离：高频区域和低频区域分开&lt;/h3&gt;
&lt;p&gt;给图表主体加 &lt;code&gt;RepaintBoundary&lt;/code&gt;，本质是在缩小重绘传播范围。&lt;br&gt;
在包含导航栏、侧边信息、浮层工具条的复杂页面里，这个动作通常会带来稳定的帧率收益。&lt;/p&gt;</description></item><item><title>Flutter K线系统拆解（三）：渲染管线与绘制分层</title><link>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%B8%89%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF%E4%B8%8E%E7%BB%98%E5%88%B6%E5%88%86%E5%B1%82/</link><pubDate>Tue, 01 Apr 2025 13:00:00 +0900</pubDate><guid>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%B8%89%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF%E4%B8%8E%E7%BB%98%E5%88%B6%E5%88%86%E5%B1%82/</guid><description>&lt;h1 id="flutter-k线系统拆解三渲染管线与绘制分层"&gt;Flutter K线系统拆解（三）：渲染管线与绘制分层&lt;/h1&gt;
&lt;p&gt;这一篇聚焦渲染层最关键的问题：&lt;br&gt;
如何把复杂 K 线绘制从“巨型 paint 方法”重构成可维护、可扩展、可定位问题的阶段化系统。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一渲染底座先算清楚再开始画"&gt;一、渲染底座：先算清楚，再开始画&lt;/h2&gt;
&lt;p&gt;一帧绘制开始前，建议先完成 5 件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算绘制区域（主图、成交量、副图）&lt;/li&gt;
&lt;li&gt;根据缩放和滚动计算可视区索引范围&lt;/li&gt;
&lt;li&gt;在可视区内扫描 max/min（主图、vol、副图）&lt;/li&gt;
&lt;li&gt;初始化渲染器&lt;/li&gt;
&lt;li&gt;构建并执行渲染管线&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中最影响性能的是两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;像素坐标反推数据索引&lt;/li&gt;
&lt;li&gt;只绘制可见区，不遍历全量数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;滚动和缩放能否稳定流畅，基本就看这一步是否扎实。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二渲染器组装把画什么与怎么调度分开"&gt;二、渲染器组装：把“画什么”与“怎么调度”分开&lt;/h2&gt;
&lt;p&gt;渲染层建议把主图、成交量、副图拆成独立渲染器，由装配层按当前模式组合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主图：&lt;code&gt;CandleRenderer&lt;/code&gt; 或 &lt;code&gt;TimeLineRenderer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;成交量：&lt;code&gt;VolRenderer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;副图：&lt;code&gt;SecondaryRenderer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再通过策略层控制是否启用某些区域（比如成交量、副图）。&lt;/p&gt;</description></item><item><title>Flutter K线系统拆解（二）：指标引擎与增量计算</title><link>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%BA%8C%E6%8C%87%E6%A0%87%E5%BC%95%E6%93%8E%E4%B8%8E%E5%A2%9E%E9%87%8F%E8%AE%A1%E7%AE%97/</link><pubDate>Mon, 31 Mar 2025 12:45:00 +0900</pubDate><guid>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%BA%8C%E6%8C%87%E6%A0%87%E5%BC%95%E6%93%8E%E4%B8%8E%E5%A2%9E%E9%87%8F%E8%AE%A1%E7%AE%97/</guid><description>&lt;h1 id="flutter-k线系统拆解二指标引擎与增量计算"&gt;Flutter K线系统拆解（二）：指标引擎与增量计算&lt;/h1&gt;
&lt;h2 id="目标"&gt;目标&lt;/h2&gt;
&lt;p&gt;这篇只讲一件事：&lt;br&gt;
如何把 K 线指标计算做成&lt;strong&gt;高性能、可维护、可测试&lt;/strong&gt;的生产级引擎。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一最佳实践架构三层拆分"&gt;一、最佳实践架构：三层拆分&lt;/h2&gt;
&lt;p&gt;指标模块建议拆成三层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;IndicatorConfig&lt;/code&gt;（配置层）：维护 MA/BOLL/MACD 等参数与开关&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IndicatorEngine&lt;/code&gt;（计算层）：只负责输入数据、输出结果，不关心 UI&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ChartAdapter&lt;/code&gt;（消费层）：把结果喂给渲染，不做任何业务计算&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;核心原则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;渲染层只读结果，不参与计算&lt;/li&gt;
&lt;li&gt;计算层纯函数化，避免隐式全局状态&lt;/li&gt;
&lt;li&gt;配置变更和行情更新走不同刷新路径&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二指标计算顺序要固定"&gt;二、指标计算顺序要固定&lt;/h2&gt;
&lt;p&gt;生产上要明确“依赖顺序”，不能随意调整。&lt;/p&gt;
&lt;p&gt;推荐顺序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入 &lt;code&gt;List&amp;lt;KLineEntity&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;MA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;BOLL&lt;/code&gt;（依赖 MA）&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;Volume MA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;KDJ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;MACD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;RSI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;WR&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;SuperTrend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;结果回填到每个 &lt;code&gt;KLineEntity&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;渲染层直接读取实体字段&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;建议把顺序写进统一入口方法，避免外部随意单独调用。&lt;/p&gt;</description></item><item><title>Flutter K线系统拆解（一）：架构分层与领域模型</title><link>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%B8%80%E6%9E%B6%E6%9E%84%E5%88%86%E5%B1%82%E4%B8%8E%E9%A2%86%E5%9F%9F%E6%A8%A1%E5%9E%8B/</link><pubDate>Thu, 20 Mar 2025 12:30:00 +0900</pubDate><guid>https://saucewu.github.io/posts/flutter-k%E7%BA%BF%E7%B3%BB%E7%BB%9F%E6%8B%86%E8%A7%A3%E4%B8%80%E6%9E%B6%E6%9E%84%E5%88%86%E5%B1%82%E4%B8%8E%E9%A2%86%E5%9F%9F%E6%A8%A1%E5%9E%8B/</guid><description>&lt;h1 id="flutter-k线系统拆解一架构分层与领域模型"&gt;Flutter K线系统拆解（一）：架构分层与领域模型&lt;/h1&gt;
&lt;p&gt;K 线在 demo 阶段通常不难，难的是进入真实交易场景之后的长期维护。&lt;br&gt;
一旦业务进入高频迭代，常见问题很快会暴露出来：主题体系耦合、模式分支散落、改动范围不可控、扩展成本越来越高。&lt;/p&gt;
&lt;p&gt;这一篇只讲架构层面的核心问题：&lt;br&gt;
如何把 K 线从“能画出来的组件”做成“可长期演进的系统”。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一推荐的系统分层"&gt;一、推荐的系统分层&lt;/h2&gt;
&lt;p&gt;一套可维护的 K 线实现，通常至少要有这几层：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;入口与装配层：图表 widget 生命周期与运行时状态
SDK 抽象层：主题、国际化、事件、屏幕适配等外部能力
数据与指标层：行情实体、指标结果、计算引擎
渲染核心层：坐标、可视区、渲染器管理
渲染阶段层：背景/网格/主图/十字线/叠加层等阶段化执行
策略扩展层：现货、合约等模式差异封装
工具叠加层：订单标记、绘图工具、信息浮层
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;分层的价值不在“看起来规范”，而在于改动隔离：&lt;br&gt;
新增一个能力时，只需要改对应层，不会把整个系统拖下水。&lt;/p&gt;</description></item><item><title>Solana 交易手续费与常见坑</title><link>https://saucewu.github.io/posts/solana-%E4%BA%A4%E6%98%93%E6%89%8B%E7%BB%AD%E8%B4%B9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91/</link><pubDate>Fri, 21 Feb 2025 15:00:00 +0900</pubDate><guid>https://saucewu.github.io/posts/solana-%E4%BA%A4%E6%98%93%E6%89%8B%E7%BB%AD%E8%B4%B9%E4%B8%8E%E5%B8%B8%E8%A7%81%E5%9D%91/</guid><description>&lt;h1 id="solana-交易手续费与常见坑"&gt;Solana 交易手续费与常见坑&lt;/h1&gt;
&lt;p&gt;Solana 的手续费设计和 EVM 差别很大，从 EVM 过来的开发者很容易踩坑。这篇把几个最常见的问题整理一下。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="手续费结构"&gt;手续费结构&lt;/h2&gt;
&lt;p&gt;Solana 的交易费用分两部分：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;总费用 = 基础费用（Base Fee）+ 优先费用（Priority Fee，可选）
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="基础费用"&gt;基础费用&lt;/h3&gt;
&lt;p&gt;每个&lt;strong&gt;签名&lt;/strong&gt;收 &lt;code&gt;5000 lamports&lt;/code&gt;（0.000005 SOL）。一笔交易有几个签名就收几倍。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;基础费用 = 签名数 × 5000 lamports
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;普通转账只有 1 个签名，费用就是 5000 lamports。多签或者复杂交易有多个签名，费用叠加。&lt;/p&gt;</description></item><item><title>EVM 字节码与合约执行过程详解</title><link>https://saucewu.github.io/posts/evm-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E5%90%88%E7%BA%A6%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3/</link><pubDate>Thu, 20 Apr 2023 14:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/evm-%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8E%E5%90%88%E7%BA%A6%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3/</guid><description>&lt;h1 id="evm-字节码与合约执行过程详解"&gt;EVM 字节码与合约执行过程详解&lt;/h1&gt;
&lt;h2 id="目录"&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一、EVM 是什么&lt;/li&gt;
&lt;li&gt;二、Solidity 到字节码的编译过程&lt;/li&gt;
&lt;li&gt;三、EVM 的运行时结构&lt;/li&gt;
&lt;li&gt;四、核心 OPCODE 分类详解&lt;/li&gt;
&lt;li&gt;五、一个完整的合约调用流程&lt;/li&gt;
&lt;li&gt;六、Gas 的本质&lt;/li&gt;
&lt;li&gt;七、常见安全问题的字节码视角&lt;/li&gt;
&lt;li&gt;八、总结&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="一evm-是什么"&gt;一、EVM 是什么&lt;/h2&gt;
&lt;p&gt;EVM（Ethereum Virtual Machine）是以太坊的智能合约运行环境。它是一个&lt;strong&gt;基于栈的虚拟机&lt;/strong&gt;，与 JVM 的基于栈架构类似，但设计上做了很多针对区块链场景的取舍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;确定性&lt;/strong&gt;：相同输入永远产生相同输出，所有节点独立执行后结果必须一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔离性&lt;/strong&gt;：合约运行在沙箱里，无法访问网络、文件系统、随机数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计量性&lt;/strong&gt;：每条指令都有固定 Gas 费用，防止无限循环&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;EVM 使用 &lt;strong&gt;256 位字长&lt;/strong&gt;（32 字节），这是它最核心的设计决定——与 Keccak-256 哈希和椭圆曲线密钥长度对齐，避免频繁的位数转换。&lt;/p&gt;</description></item><item><title>EIP-1559 手续费机制详解</title><link>https://saucewu.github.io/posts/eip-1559-%E6%89%8B%E7%BB%AD%E8%B4%B9%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/</link><pubDate>Sat, 21 Jan 2023 14:00:00 +0900</pubDate><guid>https://saucewu.github.io/posts/eip-1559-%E6%89%8B%E7%BB%AD%E8%B4%B9%E6%9C%BA%E5%88%B6%E8%AF%A6%E8%A7%A3/</guid><description>&lt;h1 id="eip-1559-手续费机制详解"&gt;EIP-1559 手续费机制详解&lt;/h1&gt;
&lt;p&gt;EIP-1559 是 2021 年 8 月以太坊 London 升级引入的手续费改革，它彻底改变了 Gas 的定价方式。如果你在做钱包或交易所，发交易时不理解这套机制，要么手续费估高了用户体验差，要么估低了交易迟迟不上链。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="老机制的问题"&gt;老机制的问题&lt;/h2&gt;
&lt;p&gt;EIP-1559 之前，以太坊用的是&lt;strong&gt;一价拍卖（First Price Auction）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户自己出一个 &lt;code&gt;gasPrice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;矿工优先打包出价最高的交易&lt;/li&gt;
&lt;li&gt;网络拥堵时用户只能盲猜出多少合适&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果就是：拥堵时大家拼命加价，但实际上很多人出了远超必要的手续费，钱白白浪费。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="eip-1559-的核心变化"&gt;EIP-1559 的核心变化&lt;/h2&gt;
&lt;p&gt;EIP-1559 把手续费拆成两部分：&lt;/p&gt;</description></item><item><title>交易所 WebSocket 连接管理踩坑录</title><link>https://saucewu.github.io/posts/%E4%BA%A4%E6%98%93%E6%89%80-websocket-%E8%BF%9E%E6%8E%A5%E7%AE%A1%E7%90%86%E8%B8%A9%E5%9D%91%E5%BD%95/</link><pubDate>Sat, 21 May 2022 10:00:00 +0900</pubDate><guid>https://saucewu.github.io/posts/%E4%BA%A4%E6%98%93%E6%89%80-websocket-%E8%BF%9E%E6%8E%A5%E7%AE%A1%E7%90%86%E8%B8%A9%E5%9D%91%E5%BD%95/</guid><description>&lt;h1 id="交易所-websocket-连接管理踩坑录"&gt;交易所 WebSocket 连接管理踩坑录&lt;/h1&gt;
&lt;p&gt;交易所 App 对实时连接的依赖极高，行情、深度、订单状态全靠 WebSocket 推送，连接一断用户就处于盲区。从基础到深度整理踩过的坑。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一重连与指数退避"&gt;一、重连与指数退避&lt;/h2&gt;
&lt;p&gt;裸写的 &lt;code&gt;WebSocket.connect&lt;/code&gt; 断了就断了，没有自愈能力：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dart" data-lang="dart"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Future&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; connect(&lt;span style="color:#66d9ef"&gt;String&lt;/span&gt; url) &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;_disposed &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; _retryCount &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; _maxRetries) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _socket &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; WebSocket.connect(url);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _retryCount &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _socket&lt;span style="color:#f92672"&gt;!&lt;/span&gt;.listen(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; onData: _onMessage,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; onDone: () &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; _scheduleReconnect(url),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; onError: (_) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; _scheduleReconnect(url),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancelOnError: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ).asFuture();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (_) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _scheduleReconnect(url);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; _scheduleReconnect(&lt;span style="color:#66d9ef"&gt;String&lt;/span&gt; url) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (_disposed) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;final&lt;/span&gt; delay &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _baseDelay &lt;span style="color:#f92672"&gt;*&lt;/span&gt; (&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt; _retryCount.clamp(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;)); &lt;span style="color:#75715e"&gt;// 1s → 2s → 4s … → 64s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _retryCount&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Future.delayed(delay, () &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; connect(url));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;指数退避必须加。服务端重启时如果所有客户端同时重连，会瞬间打死服务器。&lt;/p&gt;</description></item><item><title>Clean Architecture 是什么：一篇讲清分层与依赖</title><link>https://saucewu.github.io/posts/clean-architecture-%E6%98%AF%E4%BB%80%E4%B9%88%E4%B8%80%E7%AF%87%E8%AE%B2%E6%B8%85%E5%88%86%E5%B1%82%E4%B8%8E%E4%BE%9D%E8%B5%96/</link><pubDate>Sat, 04 Sep 2021 21:20:00 +0900</pubDate><guid>https://saucewu.github.io/posts/clean-architecture-%E6%98%AF%E4%BB%80%E4%B9%88%E4%B8%80%E7%AF%87%E8%AE%B2%E6%B8%85%E5%88%86%E5%B1%82%E4%B8%8E%E4%BE%9D%E8%B5%96/</guid><description>&lt;h1 id="clean-architecture-是什么一篇讲清分层与依赖"&gt;Clean Architecture 是什么：一篇讲清分层与依赖&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;Clean Architecture&lt;/code&gt; 常被说成“分层架构”，但它最关键的不是分层本身，而是：&lt;br&gt;
&lt;strong&gt;依赖方向必须可控。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一clean-想解决什么问题"&gt;一、Clean 想解决什么问题&lt;/h2&gt;
&lt;p&gt;项目变大后，最容易失控的是依赖关系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;页面直接调 API&lt;/li&gt;
&lt;li&gt;业务规则写在网络回调里&lt;/li&gt;
&lt;li&gt;换个数据库或 SDK，核心逻辑跟着重写&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Clean 的核心目标是：&lt;br&gt;
&lt;strong&gt;让核心业务不依赖外部框架和实现细节。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二经典四层怎么理解"&gt;二、经典四层怎么理解&lt;/h2&gt;
&lt;h3 id="1-presentation表现层"&gt;1) Presentation（表现层）&lt;/h3&gt;
&lt;p&gt;页面、组件、状态管理。&lt;br&gt;
负责“怎么展示”和“怎么交互”。&lt;/p&gt;
&lt;h3 id="2-application应用层"&gt;2) Application（应用层）&lt;/h3&gt;
&lt;p&gt;UseCase（用例）编排流程。&lt;br&gt;
负责“做什么流程”，不负责“怎么请求网络”。&lt;/p&gt;</description></item><item><title>DDD 是什么：一篇讲清领域驱动设计</title><link>https://saucewu.github.io/posts/ddd-%E6%98%AF%E4%BB%80%E4%B9%88%E4%B8%80%E7%AF%87%E8%AE%B2%E6%B8%85%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1/</link><pubDate>Wed, 11 Aug 2021 21:10:00 +0900</pubDate><guid>https://saucewu.github.io/posts/ddd-%E6%98%AF%E4%BB%80%E4%B9%88%E4%B8%80%E7%AF%87%E8%AE%B2%E6%B8%85%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1/</guid><description>&lt;h1 id="ddd-是什么一篇讲清领域驱动设计"&gt;DDD 是什么：一篇讲清领域驱动设计&lt;/h1&gt;
&lt;p&gt;很多人第一次听到 &lt;code&gt;DDD&lt;/code&gt;（Domain-Driven Design，领域驱动设计）会觉得它很“玄学”。&lt;br&gt;
其实它解决的问题非常现实：&lt;strong&gt;业务太复杂，代码开始失真，团队开始说不清同一个概念。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一ddd-到底在解决什么问题"&gt;一、DDD 到底在解决什么问题&lt;/h2&gt;
&lt;p&gt;当系统进入复杂业务阶段（交易、支付、风控、订单流转），常见问题会出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同一个规则在多个地方重复实现&lt;/li&gt;
&lt;li&gt;接口字段就是业务模型，改字段就改全系统&lt;/li&gt;
&lt;li&gt;“下单”“成交”“结算”这些词，每个人理解都不一样&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DDD 的核心目标是：&lt;br&gt;
&lt;strong&gt;让代码结构贴近业务结构，让业务语言和代码语言一致。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二ddd-的核心概念只记最关键的"&gt;二、DDD 的核心概念（只记最关键的）&lt;/h2&gt;
&lt;h3 id="1-领域domain"&gt;1) 领域（Domain）&lt;/h3&gt;
&lt;p&gt;你的业务问题空间，比如“交易系统”就是一个领域。&lt;/p&gt;
&lt;h3 id="2-统一语言ubiquitous-language"&gt;2) 统一语言（Ubiquitous Language）&lt;/h3&gt;
&lt;p&gt;产品、业务、研发使用同一套术语。&lt;br&gt;
比如 &lt;code&gt;Order&lt;/code&gt;、&lt;code&gt;Fill&lt;/code&gt;、&lt;code&gt;Position&lt;/code&gt; 必须定义一致，避免口头理解和代码实现偏差。&lt;/p&gt;</description></item><item><title>JVM学习笔记（内存分区篇）</title><link>https://saucewu.github.io/posts/jvm%E5%86%85%E5%AD%98%E5%88%86%E5%8C%BA%E7%AC%94%E8%AE%B0/</link><pubDate>Fri, 15 May 2020 00:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/jvm%E5%86%85%E5%AD%98%E5%88%86%E5%8C%BA%E7%AC%94%E8%AE%B0/</guid><description>&lt;h1 id="jvm学习笔记内存分区篇"&gt;JVM学习笔记（内存分区篇）&lt;/h1&gt;
&lt;h1 id="概述"&gt;概述&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://s1.ax1x.com/2020/05/15/YsPiX6.jpg" alt="YsPiX6.jpg"&gt;&lt;/p&gt;
&lt;h2 id="线程私有区"&gt;线程私有区&lt;/h2&gt;
&lt;p&gt;这三个区是随着线程生命周期创建销毁的。&lt;/p&gt;
&lt;h3 id="程序计数器"&gt;程序计数器&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;与CPU中程序计数器作用相同 ，用于记录当前线程程序现在运行到哪里。&lt;/li&gt;
&lt;li&gt;基于CPU的时间片轮转运行机制，CPU并不会把一个线程中的任务做完之后才切换线程，而是在不同线程中不停切换以达到并发的效果，所以我们需要记录下当切程序运行到的指令地址，在下一次轮到该线程执行时可以继续运行。&lt;/li&gt;
&lt;li&gt;Java虚拟机中的程序计数器仅仅是虚拟机中的，存在于内存之上的“&lt;strong&gt;虚拟&lt;/strong&gt;”计数器。&lt;/li&gt;
&lt;li&gt;要注意的是在运行native方法, 使用的是cpu的程序计数器 JVM中的程序计数器会被定义为undefined。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="可能产生的异常"&gt;可能产生的异常&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;因为程序计数器只是存储一个定长的指令地址，所以不会有OutOfMemoryError出现&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="虚拟机栈-本地方法栈"&gt;虚拟机栈 /本地方法栈&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;把这两个区的作用大致是一样的，只是Java虚拟机栈运行的是Java方法，本地方法栈运行的是native方法。而且在很多虚拟机(比如HotSpot VM)中把这两个栈融合成所谓的“&lt;strong&gt;mixed stack&lt;/strong&gt;”两种栈帧都可以储存。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;栈区是由栈帧组成的。当调用方法时，Java虚拟机将新建一个帧；方法退出时，帧将自动消除。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;线程运行过程中，只有一个栈帧是处于活跃状态，称为“&lt;strong&gt;当前活跃栈帧&lt;/strong&gt;”，当前活动栈帧始终是虚拟机栈的栈顶元素。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="栈帧"&gt;栈帧&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://s1.ax1x.com/2020/05/15/YsDwzF.jpg" alt="YsDwzF.jpg"&gt;&lt;/p&gt;
&lt;h5 id="局部变量表"&gt;局部变量表&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;在编译程序代码的时候就可以确定栈帧中需要多大的局部变量表，具体大小可在编译后的 Class 文件中看到。&lt;/li&gt;
&lt;li&gt;局部变量表的容量以&lt;strong&gt;Variable Slot&lt;/strong&gt;（变量槽）为最小单位，每个变量槽都可以存储 32 位长度的内存空间。&lt;/li&gt;
&lt;li&gt;在方法执行时，虚拟机使用局部变量表完成参数值到参数变量列表的传递过程的，如果执行的是实例方法，那局部变量表中第 0 位索引的 Slot 默认是用于传递方法所属对象实例的引用（在方法中可以通过关键字 this 来访问到这个隐含的参数）。&lt;/li&gt;
&lt;li&gt;其余参数则按照参数表顺序排列，占用从 1 开始的局部变量 Slot。&lt;/li&gt;
&lt;li&gt;基本类型数据以及引用和 returnAddress（返回地址）占用一个变量槽，long 和 double 需要两个。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="操作数栈"&gt;操作数栈&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;同样也可以在编译期确定大小。&lt;/li&gt;
&lt;li&gt;Frame 被创建时，操作栈是空的。操作栈的每个项可以存放 JVM 的各种类型数据，其中 long 和 double 类型（64位数据）占用两个栈深。&lt;/li&gt;
&lt;li&gt;方法执行的过程中，会有各种字节码指令往操作数栈中写入和提取内容，也就是出栈和入栈操作（与 Java 栈中栈帧操作类似）。&lt;/li&gt;
&lt;li&gt;操作栈调用其它有返回结果的方法时，会把结果 push 到栈上（通过操作数栈来进行参数传递）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="动态链接"&gt;动态链接&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用，持有这个引用是为了支持方法调用过程中的动态链接。&lt;/li&gt;
&lt;li&gt;在类加载阶段中的解析阶段会将符号引用转为直接引用，这种转化也称为静态解析。另外的一部分将在运行时转化为直接引用，这部分称为动态链接。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="返回地址"&gt;返回地址&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;方法开始执行后，只有 2 种方式可以退出 ：方法返回指令，异常退出。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="可能产生的异常-1"&gt;可能产生的异常&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;线程请求分配的栈容量&amp;gt;Java虚拟机最大栈容量，则JVM会抛出StackOverFlowError异常。&lt;/p&gt;</description></item><item><title>从 setTint 引发的Drawable 的缓存问题</title><link>https://saucewu.github.io/posts/drawable%E7%9A%84%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6/</link><pubDate>Sun, 10 May 2020 15:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/drawable%E7%9A%84%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6/</guid><description>&lt;h1 id="从-settint-引发的drawable-的缓存问题"&gt;从 setTint 引发的Drawable 的缓存问题&lt;/h1&gt;
&lt;p&gt;这两天在做一个新需求是突然发现 DrawableCompat.setTint()更改drawable 渲染颜色的方法居然是全局生效的。变相说明getResource().getDrawable() 每次取出drawable的时候都是从一个缓存池里面出去的。&lt;/p&gt;
&lt;h3 id="避免缓存的解决方案"&gt;避免缓存的解决方案&lt;/h3&gt;
&lt;p&gt;第一反应是clone一个新的对象出来进行修改 但是貌似drawable并没有实现Cloneable的借口0那看来得想想别的办法了&lt;/p&gt;
&lt;p&gt;看了一会源码果然google在drawable类中已经给我们提供了一个 mutate方法 看注释看似乎这个方法可以返回一个新的drawable对象&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * Make this drawable mutable. This operation cannot be reversed. A mutable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * drawable is guaranteed to not share its state with any other drawable.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * This is especially useful when you need to modify properties of drawables
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * loaded from resources. By default, all drawables instances loaded from
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * the same resource share a common state; if you modify the state of one
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * instance, all the other instances will receive the same modification.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * Calling this method on a mutable Drawable will have no effect.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; *
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * @return This drawable.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * @see ConstantState
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * @see #getConstantState()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;@NonNull&lt;/span&gt; Drawable &lt;span style="color:#a6e22e"&gt;mutate&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最终的实现是根据不同的drawable类型有不同的实现方式 这里就用bitmapdrawable作为例子 不仅clone了的对象 还禁止了这个对象再次被复制 猜测是为了方式无限clone导致oom&lt;/p&gt;</description></item><item><title>VPS 被暴力尝试登录的几种解决方案</title><link>https://saucewu.github.io/posts/vps%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4/</link><pubDate>Thu, 02 Apr 2020 20:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/vps%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4/</guid><description>&lt;h1 id="vps-被暴力尝试登录的几种解决方案"&gt; VPS  被暴力尝试登录的几种解决方案&lt;/h1&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;最近登录自己blog的服务器时偶然发现&lt;/p&gt;
&lt;p&gt;&lt;code&gt; There were 34176 failed login attempts since the last successful login.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;被人扫到了端口 还被疯狂尝试登录 虽然这个破服务器没什么东西 但被破解出来被黑产拉去挖矿什么的也是有点难受的&lt;/p&gt;
&lt;p&gt;首先先看一下到底是谁在扫我的机器&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# lastb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="解决方案"&gt;解决方案&lt;/h2&gt;
&lt;h3 id="1denyhost"&gt;1.denyhost&lt;/h3&gt;
&lt;p&gt;一顿google之后 被安利了denyhost这个python脚本 原理就是通过它会分析sshd的日志文件（/var/log/secure），当发现重复的攻击时就会记录IP到/etc/hosts.deny文件，从而达到自动屏IP的功能&lt;/p&gt;</description></item><item><title>WebView实现离线缓存</title><link>https://saucewu.github.io/posts/android-webview%E5%AE%9E%E7%8E%B0%E7%A6%BB%E7%BA%BF%E7%BC%93%E5%AD%98/</link><pubDate>Tue, 10 Mar 2020 20:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/android-webview%E5%AE%9E%E7%8E%B0%E7%A6%BB%E7%BA%BF%E7%BC%93%E5%AD%98/</guid><description>&lt;h1 id="webview实现离线缓存"&gt;WebView实现离线缓存&lt;/h1&gt;
&lt;h3 id="场景"&gt;场景&lt;/h3&gt;
&lt;p&gt;在App在长期发展之中，对动态性要求很高的 活动页面 或是 一些带有简单功能的详情页面都可能会有大量Webview使用的情况。但是webview初始化时极有可能遇到网络波动的影响导致加载不出 或者 会重复下载一些公共资源造成性能问题。这时我们希望有一种缓存方案能够暂时解决这些初始化变慢的问题&lt;/p&gt;
&lt;h3 id="原理"&gt;原理&lt;/h3&gt;
&lt;p&gt;android WebViewClient提供了shouldInterceptRequest的接口供我们使用这个接口会拦截webview所有请求。如果错误缓存了资源，可能会出现web页面无法更新的情况。所以用的时候要谨慎只对我们需要使用缓存的部分进行拦截&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fun&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;shouldInterceptRequest&lt;/span&gt;(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (view &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; request &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//判断需要使用缓存的url
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;WebViewCacheUtils&lt;/span&gt;.needCache(request.url.toString())) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//从缓存池中获取缓存
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; cache = &lt;span style="color:#a6e22e"&gt;WebViewCacheUtils&lt;/span&gt;.getCache(request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (cache &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; cache
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//未找到缓存文件或者不需要缓存 还是正常走请求
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;.shouldInterceptRequest(view, request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fun&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getCache&lt;/span&gt;(webResourceRequest: WebResourceRequest): WebResourceResponse? {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; uri = webResourceRequest.url
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//获取加载资源类型
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; mimeType: String? = &lt;span style="color:#a6e22e"&gt;MimeTypeMapUtils&lt;/span&gt;.getMimeTypeFromUrl(uri.toString())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; type: String
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; header = HashMap&amp;lt;String, String&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//我们可能对多个域名进行缓存 先设置跨域
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; header[&lt;span style="color:#e6db74"&gt;&amp;#34;Access-Control-Allow-Origin&amp;#34;&lt;/span&gt;] = &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; header[&lt;span style="color:#e6db74"&gt;&amp;#34;Access-Control-Allow-Headers&amp;#34;&lt;/span&gt;] = &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (mimeType &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (uri.path&lt;span style="color:#f92672"&gt;!!&lt;/span&gt;.contains(&lt;span style="color:#e6db74"&gt;&amp;#34;js&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type = &lt;span style="color:#e6db74"&gt;&amp;#34;js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mimeType = &lt;span style="color:#e6db74"&gt;&amp;#34;application/javascript&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; header[&lt;span style="color:#e6db74"&gt;&amp;#34;content-type&amp;#34;&lt;/span&gt;] = &lt;span style="color:#e6db74"&gt;&amp;#34;application/javascript; charset=utf-8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mimeType = &lt;span style="color:#e6db74"&gt;&amp;#34;text/html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type = &lt;span style="color:#e6db74"&gt;&amp;#34;html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; header[&lt;span style="color:#e6db74"&gt;&amp;#34;content-type&amp;#34;&lt;/span&gt;] = &lt;span style="color:#e6db74"&gt;&amp;#34;text/html; charset=utf-8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (mimeType.contains(&lt;span style="color:#e6db74"&gt;&amp;#34;img&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;||&lt;/span&gt; mimeType.contains(&lt;span style="color:#e6db74"&gt;&amp;#34;image&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type = &lt;span style="color:#e6db74"&gt;&amp;#34;img&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//这里主要是css 格式是 text/css
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type = mimeType.split(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;.toRegex()).dropLastWhile { &lt;span style="color:#66d9ef"&gt;it&lt;/span&gt;.isEmpty() }.toTypedArray()[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//确定是否获取了资源类型
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!&lt;span style="color:#a6e22e"&gt;TextUtils&lt;/span&gt;.isEmpty(mimeType)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; name = (&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (type &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;html&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//由于多个url可能使用的是同一个html 这里需要判断下 返回的是 该html的md5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; needCacheHtml(uri.path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MD5Utils.encode(uri.path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//从缓存池中获取缓存
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; cacheSteam=getWebCache(name, type)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//构造响应体 并返回
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; WebResourceResponse(mimeType, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;, header, cacheSteam)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (e: FileNotFoundException) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; e.printStackTrace()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="注意事项"&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HTML的缓存一定要小心 最好是由前端同学出一份目录 的接口并且做好版本管理 防止误操作 否则线上可能会出严重问题&lt;/li&gt;
&lt;li&gt;存储的文件名都是用md5过的 防止有特殊字符影响持久化&lt;/li&gt;
&lt;li&gt;如果有大文件缓存 最好需要有文件完整性验证&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>关于 Androidx 的5.x版本 webview crash</title><link>https://saucewu.github.io/posts/androidx-webview%E5%B4%A9%E6%BA%83/</link><pubDate>Tue, 10 Mar 2020 15:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/androidx-webview%E5%B4%A9%E6%BA%83/</guid><description>&lt;h1 id="关于-androidx-的5x版本-webview-crash"&gt;关于 Androidx 的5.x版本 webview crash&lt;/h1&gt;
&lt;p&gt;这两更新了androidx compat 在5.x一下版本手机上遇到crash问题&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;android.content.res.Resources$NotFoundException: String resource ID #0x2040002
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;确定是androidx 1.1.0版本问题 &lt;a href="https://issuetracker.google.com/issues/141132133"&gt;google issue&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;而且是只有 1.1.0 正式包才会有问题。以后还是少升级这种基础包为好&lt;/p&gt;</description></item><item><title> WebView页面 软键盘弹出无法正确折叠页面的问题</title><link>https://saucewu.github.io/posts/%E6%B2%89%E6%B5%B8%E5%BC%8F%E7%8A%B6%E6%80%81%E6%A0%8F%E7%9A%84%E8%BD%AF%E9%94%AE%E7%9B%98%E5%BC%B9%E5%87%BA%E9%97%AE%E9%A2%98/</link><pubDate>Thu, 03 May 2018 18:30:00 +0000</pubDate><guid>https://saucewu.github.io/posts/%E6%B2%89%E6%B5%B8%E5%BC%8F%E7%8A%B6%E6%80%81%E6%A0%8F%E7%9A%84%E8%BD%AF%E9%94%AE%E7%9B%98%E5%BC%B9%E5%87%BA%E9%97%AE%E9%A2%98/</guid><description>&lt;h1 id="webview页面-软键盘弹出无法正确折叠页面的问题"&gt;WebView页面 软键盘弹出无法正确折叠页面的问题&lt;/h1&gt;
&lt;p&gt;问题出在webview页面 选中webview中edit控件弹出的软键盘 无法正确折叠页面 遮挡了输入框。一开始认为是webview的适配问题，想当然的加上了&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;activity&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;android:name=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;.WebViewActivity&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;android:windowSoftInputMode=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;adjustPan&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/activity&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;结果当然是毫！无！反！应！&lt;/p&gt;
&lt;p&gt;在一番调试之后 发现时隐藏了状态栏之后 屏幕尺寸计算出现问题的缘故 也是个老&lt;a href="https://code.google.com/p/android/issues/detail?id=5497"&gt;google issue&lt;/a&gt;了&lt;/p&gt;
&lt;p&gt;网上有很多解决办法 最通用的当然还是 原理很简单就是每当页面有变动的时候自行去计算页面高度- -&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AndroidBug5497Workaround&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;assistActivity&lt;/span&gt;(Activity activity) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AndroidBug5497Workaround(activity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; View mChildOfContent;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; usableHeightPrevious;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; FrameLayout.&lt;span style="color:#a6e22e"&gt;LayoutParams&lt;/span&gt; frameLayoutParams;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; contentHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;boolean&lt;/span&gt; isfirst &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; statusBarHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AndroidBug5497Workaround&lt;/span&gt;(Activity activity) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (activity &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//获取状态栏的高度&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; resourceId &lt;span style="color:#f92672"&gt;=&lt;/span&gt; activity.&lt;span style="color:#a6e22e"&gt;getResources&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;getIdentifier&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;status_bar_height&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;dimen&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;android&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; statusBarHeight &lt;span style="color:#f92672"&gt;=&lt;/span&gt; activity.&lt;span style="color:#a6e22e"&gt;getResources&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;getDimensionPixelSize&lt;/span&gt;(resourceId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FrameLayout content &lt;span style="color:#f92672"&gt;=&lt;/span&gt; activity.&lt;span style="color:#a6e22e"&gt;findViewById&lt;/span&gt;(android.&lt;span style="color:#a6e22e"&gt;R&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mChildOfContent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; content.&lt;span style="color:#a6e22e"&gt;getChildAt&lt;/span&gt;(0);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//界面出现变动都会调用这个监听事件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mChildOfContent.&lt;span style="color:#a6e22e"&gt;getViewTreeObserver&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;addOnGlobalLayoutListener&lt;/span&gt;(() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (isfirst) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; contentHeight &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mChildOfContent.&lt;span style="color:#a6e22e"&gt;getHeight&lt;/span&gt;();&lt;span style="color:#75715e"&gt;//兼容华为等机型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; isfirst &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; possiblyResizeChildOfContent();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frameLayoutParams &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (FrameLayout.&lt;span style="color:#a6e22e"&gt;LayoutParams&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mChildOfContent.&lt;span style="color:#a6e22e"&gt;getLayoutParams&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//重新调整跟布局的高度&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;possiblyResizeChildOfContent&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; usableHeightNow &lt;span style="color:#f92672"&gt;=&lt;/span&gt; computeUsableHeight();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//当前可见高度和上一次可见高度不一致 布局变动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (usableHeightNow &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; usableHeightPrevious) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//int usableHeightSansKeyboard2 = mChildOfContent.getHeight();//兼容华为等机型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; usableHeightSansKeyboard &lt;span style="color:#f92672"&gt;=&lt;/span&gt; mChildOfContent.&lt;span style="color:#a6e22e"&gt;getRootView&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;getHeight&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; heightDifference &lt;span style="color:#f92672"&gt;=&lt;/span&gt; usableHeightSansKeyboard &lt;span style="color:#f92672"&gt;-&lt;/span&gt; usableHeightNow;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (heightDifference &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; (usableHeightSansKeyboard &lt;span style="color:#f92672"&gt;/&lt;/span&gt; 4)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// keyboard probably just became visible&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frameLayoutParams.&lt;span style="color:#a6e22e"&gt;height&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; usableHeightSansKeyboard &lt;span style="color:#f92672"&gt;-&lt;/span&gt; heightDifference &lt;span style="color:#f92672"&gt;+&lt;/span&gt; statusBarHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frameLayoutParams.&lt;span style="color:#a6e22e"&gt;height&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; contentHeight;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mChildOfContent.&lt;span style="color:#a6e22e"&gt;requestLayout&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; usableHeightPrevious &lt;span style="color:#f92672"&gt;=&lt;/span&gt; usableHeightNow;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; * 计算mChildOfContent可见高度
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; ** @return 高度
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;computeUsableHeight&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Rect r &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Rect();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mChildOfContent.&lt;span style="color:#a6e22e"&gt;getWindowVisibleDisplayFrame&lt;/span&gt;(r);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; (r.&lt;span style="color:#a6e22e"&gt;bottom&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; r.&lt;span style="color:#a6e22e"&gt;top&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Android 编译期的黑科技（三）-字节码篇</title><link>https://saucewu.github.io/posts/android-%E7%BC%96%E8%AF%91%E6%9C%9F%E7%9A%84%E9%BB%91%E7%A7%91%E6%8A%80%E4%B8%89-%E5%AD%97%E8%8A%82%E7%A0%81%E7%AF%87/</link><pubDate>Thu, 05 Apr 2018 19:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/android-%E7%BC%96%E8%AF%91%E6%9C%9F%E7%9A%84%E9%BB%91%E7%A7%91%E6%8A%80%E4%B8%89-%E5%AD%97%E8%8A%82%E7%A0%81%E7%AF%87/</guid><description>&lt;h1 id="android-编译期的黑科技三--字节码篇"&gt;Android 编译期的黑科技（三）- 字节码篇&lt;/h1&gt;
&lt;h2 id="字节码织入"&gt;字节码织入&lt;/h2&gt;
&lt;p&gt;可以绕过编译，直接操作字节码，从而实现代码注入。所以使用 Javassist 的时机就是在构建工具 Gradle 将源 文件编译成 .class 文件之后，在将 .class 打包成 .dex 文件之前。也有两个相当成熟的字节码框架可供使用。使用起来大同小异这节主要介绍ASM&lt;/p&gt;
&lt;h3 id="asm"&gt;ASM&lt;/h3&gt;
&lt;p&gt;ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件，也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里，这些类文件拥有足够的元数据来解析类中的所有元素：类名称、方法、属性以及 Java 字节码（指令）。ASM 从类文件中读入信息后，能够改变类行为，分析类信息，甚至能够根据用户要求生成新类。&lt;/p&gt;</description></item><item><title>Android 编译期的黑科技（二）- AOP 篇</title><link>https://saucewu.github.io/posts/android-%E7%BC%96%E8%AF%91%E6%9C%9F%E7%9A%84%E9%BB%91%E7%A7%91%E6%8A%80%E4%BA%8C-aop%E7%AF%87/</link><pubDate>Sat, 17 Mar 2018 19:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/android-%E7%BC%96%E8%AF%91%E6%9C%9F%E7%9A%84%E9%BB%91%E7%A7%91%E6%8A%80%E4%BA%8C-aop%E7%AF%87/</guid><description>&lt;h1 id="android-编译期的黑科技二--aop-篇"&gt;Android 编译期的黑科技（二）- AOP 篇&lt;/h1&gt;
&lt;h2 id="aop-定义"&gt;AOP 定义&lt;/h2&gt;
&lt;p&gt;AOP 是 Aspect Oriented Programming 的缩写，即“面向切面编程”。使用 AOP，可以在编译期间对代码进行动态管理， 以达到统一维护的目的。AOP 是 OOP 编程的一种延续，也是 Spring 框架中的一个重要模块。利用 AOP 可以对业务逻辑 的各个模块进行隔离，从而使得业务逻辑各个部分之间的耦合度降低，提高程序的可重用性，同时提高开发的效率。利用 AOP，我们可以在无浸入的在宿主中插入一些代码逻辑，从而可以实现一些特殊的功能，比如日志埋点、性能监控、动态 权限控制、代码调试等。&lt;/p&gt;
&lt;h3 id="优点"&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;织入的代码都是Java代码没有过多的学习难度&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="缺点"&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;无法织入第三方的库&lt;/li&gt;
&lt;li&gt;由于定义的切点依赖编程语言，该方案无法兼容 Lambda 语法&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="使用"&gt;使用&lt;/h2&gt;
&lt;p&gt;AOP只是个概念的定义有很多库都可以实现。例如Javapoet/AspectJ。使用的方法都很相似这里只介绍Javapoet。
防不胜防来啦这部分会用&lt;a href="https://github.com/SauceWu/InjectExtra"&gt;InjectExtra&lt;/a&gt;为例解释&lt;/p&gt;</description></item><item><title>Android 编译期的黑科技（一）基础篇</title><link>https://saucewu.github.io/posts/android-%E7%BC%96%E8%AF%91%E6%9C%9F%E7%9A%84%E9%BB%91%E7%A7%91%E6%8A%80%E4%B8%80%E5%9F%BA%E7%A1%80%E7%AF%87/</link><pubDate>Sat, 10 Mar 2018 19:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/android-%E7%BC%96%E8%AF%91%E6%9C%9F%E7%9A%84%E9%BB%91%E7%A7%91%E6%8A%80%E4%B8%80%E5%9F%BA%E7%A1%80%E7%AF%87/</guid><description>&lt;h1 id="android-编译期的黑科技一基础篇"&gt;Android 编译期的黑科技（一）基础篇&lt;/h1&gt;
&lt;h2 id="序言"&gt;序言&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;为什么需要这些编译期的黑科技
使用这些编译期的小工具可以有效减少 重复代码和重复逻辑 在android中大量运用的ButterKnife Gilde Room和DataBind都大量的时候用编译期生成代码的技术&lt;/li&gt;
&lt;li&gt;哪里可以用到这些
应用场景很多 最经典的应用场景是无痕埋点技术和解决重复逻辑&lt;/li&gt;
&lt;li&gt;为什么需要这个基础篇
直接上工具当然也可以使用，但毕竟写代码 知其然还要知其所以然 不然除了各种问题无法解决就很尴尬了&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="什么是编译"&gt;什么是编译&lt;/h2&gt;
&lt;p&gt;它主要的目的是将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序，翻译为计算机能解读、运行的低阶机器语言的程序，也就是可执行文件。编译器将原始程序（source program）作为输入，翻译产生使用目标语言（target language）的等价程序。源代码一般为高级语言（High-level language），如Pascal、C、C++、C# 、Java等，而目标语言则是汇编语言或目标机器的目标代码（Object code），有时也称作机器代码（Machine code）。
java编译专指 .java &amp;mdash;&amp;gt;.class&lt;/p&gt;</description></item><item><title>Fragment 踩坑实录</title><link>https://saucewu.github.io/posts/fragment-%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/</link><pubDate>Sat, 10 Feb 2018 15:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/fragment-%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95/</guid><description>&lt;h1 id="fragment-踩坑实录"&gt;Fragment 踩坑实录&lt;/h1&gt;
&lt;h3 id="不要轻易使用commitnowallowingstateloss"&gt;不要轻易使用commitNowAllowingStateLoss&lt;/h3&gt;
&lt;p&gt;在很多情况下使用不正确使用Fragment 会导致 java.lang.IllegalStateException 网上很多推荐commitNowAllowingStateLoss 来治标
但使用commitNowAllowingStateLoss 会导致当前Fragment 以其子Fragment 丢失状态 如 getVisibility() isHidden() 等等 而且会出现很多不可预知的问题&lt;/p&gt;
&lt;p&gt;出现问题:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 在不正确的时机commit 如 onActivityResult 回调方法中
fragmentManager的 commit方法只能在onResume-onPause 的状态中使用
如必须在其他状态下改变状态 建议将状态记录下来 在onResume中执行操作&lt;/li&gt;
&lt;li&gt;2 重复add 同一个Fragment
由于commit属于异步操作 可能由于commit 过快导致 判断fragment是否为空的方法失效
应先使用executePendingTransactions让上一次commit执行完成后再执行新的操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="不要在初始化方法中直接生成新的fragment"&gt;不要在初始化方法中直接生成新的Fragment&lt;/h3&gt;
&lt;p&gt;在很多Activity 被重建的情况下 直接生成新的Fragment 会导致旧Fragment 无法回收导致泄露 如果在这些fragment中还有轮训或者socket 回调会导致崩溃
解决方案:
为每个Fragment添加tag
使用findFragmentByTag() 获取Fragment 若为null 再创建新的Fragment&lt;/p&gt;</description></item><item><title>BTC 笔记</title><link>https://saucewu.github.io/posts/btc-%E7%AC%94%E8%AE%B0/</link><pubDate>Thu, 10 Aug 2017 15:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/btc-%E7%AC%94%E8%AE%B0/</guid><description>&lt;h1 id="btc-笔记"&gt;BTC 笔记&lt;/h1&gt;
&lt;h2 id="序"&gt;序&lt;/h2&gt;
&lt;p&gt;主要脉络为&lt;a href="https://github.com/bitcoinbook/bitcoinbook/blob/develop/ix.html"&gt;精通比特币(第二版)&lt;/a&gt; 以 &lt;a href="https://github.com/bitcoin/bitcoin"&gt;bitoin源码&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="比特币原理"&gt;比特币原理&lt;/h2&gt;
&lt;h3 id="21交易块挖矿和区块链"&gt;2.1交易，块，挖矿和区块链&lt;/h3&gt;
&lt;h4 id="221交易输入输出"&gt;2.2.1交易输入输出&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;交易就像复式记账法账簿中的行。简单来说，每一笔交易包含一个或多个“输入”，输入是针对一个比特币账号的负债&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="223交易链"&gt;2.2.3交易链&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;一（多）笔交易输出就是另一（多）笔交易的输入&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="224找零"&gt;2.2.4找零&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;重要的是，找零地址不必与输入时提供的地址相同，出于隐私的原因，通常是所有者钱包中的新地址&lt;/li&gt;
&lt;li&gt;比特币交易建立和签名时不用连接比特币网络。只有在执行交易时才需要将交易发送到网络。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="23-交易的构建"&gt;2.3 交易的构建&lt;/h3&gt;
&lt;p&gt;比特币交易建立和签名时不用连接比特币网络。只有在执行交易时才需要将交易发送到网络。&lt;/p&gt;
&lt;h4 id="231-获取正确的输入"&gt;2.3.1 获取正确的输入&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;完整客户端含有整个区块链中所有交易的所有未消费输出副本。这使得钱包既能拿这些输出构建交易，又能在收到新交易时很快地验证其输入是否正确。然而，完整客户端占太大的硬盘空间，所以大多数钱包使用轻量级的客户端，只保存用户自己的未消费输出。
&lt;code&gt;可交给后端处理&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="232-创建交易输出"&gt;2.3.2 创建交易输出&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;交易的输出会被创建成为一个包含这笔数额的脚本的形式，只能被引入这个脚本的一个解答后才能兑换。&lt;code&gt;使用私钥签名&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="24-比特币挖矿"&gt;2.4 比特币挖矿&lt;/h3&gt;
&lt;p&gt;▷ 挖矿节点通过参考比特币的共识规则验证所有交易。 因此，挖矿通过拒绝无效或畸形交易来提供比特币交易的安全性。&lt;/p&gt;</description></item><item><title>从零建站（基于Ubuntu 16.04）</title><link>https://saucewu.github.io/posts/%E4%BB%8E%E9%9B%B6%E5%BB%BA%E7%AB%99%E5%9F%BA%E4%BA%8Eubuntu-16.04/</link><pubDate>Fri, 14 Apr 2017 18:00:00 +0000</pubDate><guid>https://saucewu.github.io/posts/%E4%BB%8E%E9%9B%B6%E5%BB%BA%E7%AB%99%E5%9F%BA%E4%BA%8Eubuntu-16.04/</guid><description>&lt;h1 id="从零建站基于-ubuntu-1604"&gt;从零建站（基于 Ubuntu 16.04）&lt;/h1&gt;
&lt;h2 id="初始化"&gt;初始化&lt;/h2&gt;
&lt;h3 id="免密登录"&gt;免密登录&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;client生成ssh公钥&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;**ssh-keygen -t rsa -C &amp;ldquo;&lt;a href="mailto:xxxxx@xxxxx.com"&gt;xxxxx@xxxxx.com&lt;/a&gt;&amp;rdquo; **
Generating public/private rsa key pair&amp;hellip;
三次回车即可生成 ssh key&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;将ssh公钥放入service ~/.ssh/authorized_keys 文件下&lt;/li&gt;
&lt;li&gt;重新登陆&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker"&gt;Docker&lt;/h3&gt;
&lt;h4 id="install-docker"&gt;INSTALL Docker&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;切换为Docker 官方源&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;sudo apt-get update&lt;/li&gt;
&lt;li&gt;sudo apt-get install &lt;br&gt;
apt-transport-https &lt;br&gt;
ca-certificates &lt;br&gt;
curl &lt;br&gt;
software-properties-common&lt;/li&gt;
&lt;li&gt;curl -fsSL &lt;a href="https://download.docker.com/linux/ubuntu/gpg"&gt;https://download.docker.com/linux/ubuntu/gpg&lt;/a&gt; | &lt;br&gt;
sudo apt-key add -&lt;/li&gt;
&lt;li&gt;sudo add-apt-repository &lt;br&gt;
&amp;ldquo;deb [arch=amd64] &lt;a href="https://download.docker.com/linux/ubuntu"&gt;https://download.docker.com/linux/ubuntu&lt;/a&gt; &lt;br&gt;
$(lsb_release -cs) &lt;br&gt;
stable&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Install Docker CE&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;sudo apt-get update&lt;/li&gt;
&lt;li&gt;sudo apt-get install docker-ce&lt;/li&gt;
&lt;li&gt;apt-cache madison docker-ce&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4 id="创建docker内部网络"&gt;创建Docker内部网络&lt;/h4&gt;
&lt;p&gt;没有远程连接mySQL和Redis的需求且link方式连接 容器维护繁琐 使用Docker 内建网络连接 节省端口资源&lt;/p&gt;</description></item></channel></rss>