<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Android on 为何存在的日子</title><link>https://blog.344977.xyz/tags/android/</link><description>Recent content in Android on 为何存在的日子</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Wed, 01 Jul 2026 09:17:46 +0000</lastBuildDate><atom:link href="https://blog.344977.xyz/tags/android/index.xml" rel="self" type="application/rss+xml"/><item><title>从零到一：在 Android 4.4 手表上构建电子书阅读器的完整开发纪实</title><link>https://blog.344977.xyz/post/2026-07-01-issue-2/</link><pubDate>Wed, 01 Jul 2026 09:17:46 +0000</pubDate><guid>https://blog.344977.xyz/post/2026-07-01-issue-2/</guid><description>&lt;h1 id="从零到一在-android-44-手表上构建电子书阅读器的完整开发纪实"&gt;&lt;a href="#%e4%bb%8e%e9%9b%b6%e5%88%b0%e4%b8%80%e5%9c%a8-android-44-%e6%89%8b%e8%a1%a8%e4%b8%8a%e6%9e%84%e5%bb%ba%e7%94%b5%e5%ad%90%e4%b9%a6%e9%98%85%e8%af%bb%e5%99%a8%e7%9a%84%e5%ae%8c%e6%95%b4%e5%bc%80%e5%8f%91%e7%ba%aa%e5%ae%9e" class="header-anchor"&gt;&lt;/a&gt;从零到一：在 Android 4.4 手表上构建电子书阅读器的完整开发纪实
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;作者&lt;/strong&gt;：开发团队&lt;br&gt;
&lt;strong&gt;发布时间&lt;/strong&gt;：2026 年 7 月 1 日&lt;br&gt;
&lt;strong&gt;标签&lt;/strong&gt;：#Android 4.4 #Java 7 #TLS 1.2 #低内存优化 #电子书阅读器&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一项目缘起与目标"&gt;&lt;a href="#%e4%b8%80%e9%a1%b9%e7%9b%ae%e7%bc%98%e8%b5%b7%e4%b8%8e%e7%9b%ae%e6%a0%87" class="header-anchor"&gt;&lt;/a&gt;一、项目缘起与目标
&lt;/h2&gt;&lt;p&gt;我们的目标是为一款配备 240x240 像素屏幕、可用内存仅约 60MB 的 Android 4.4 手表开发一款电子书阅读器。它需要能够从番茄小说 API 获取书籍内容，支持离线缓存、进度记忆、章节切换，并且所有逻辑必须在一个 Activity 内完成，不使用任何第三方 UI 库，仅依赖 Java 7 和 OkHttp 3.12.13。&lt;/p&gt;
&lt;p&gt;这是一项挑战——Android 4.4 发布于 2013 年，其默认的 SSL/TLS 实现仅支持 TLS 1.0/1.1，而现代 API 服务（包括我们使用的 &lt;code&gt;v3.rain.ink&lt;/code&gt;）已强制要求 TLS 1.2。此外，低内存环境要求极简的 UI 渲染和高效的缓存管理。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二开发历程与方案演进"&gt;&lt;a href="#%e4%ba%8c%e5%bc%80%e5%8f%91%e5%8e%86%e7%a8%8b%e4%b8%8e%e6%96%b9%e6%a1%88%e6%bc%94%e8%bf%9b" class="header-anchor"&gt;&lt;/a&gt;二、开发历程与方案演进
&lt;/h2&gt;&lt;p&gt;本节按时间顺序详细记录了我们尝试过的所有方案、遇到的错误以及最终采用的解决路径。&lt;/p&gt;
&lt;h3 id="第一阶段初始实现与首次部署"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e9%98%b6%e6%ae%b5%e5%88%9d%e5%a7%8b%e5%ae%9e%e7%8e%b0%e4%b8%8e%e9%a6%96%e6%ac%a1%e9%83%a8%e7%bd%b2" class="header-anchor"&gt;&lt;/a&gt;第一阶段：初始实现与首次部署
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：项目启动初期&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;初始方案&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;HttpURLConnection&lt;/code&gt; 直接请求 &lt;code&gt;http://v3.rain.ink/fanqie/&lt;/code&gt;（HTTP 明文）。&lt;/li&gt;
&lt;li&gt;配置文件从 Gitee 拉取，书籍列表和 API Key 存储在 &lt;code&gt;config.ob&lt;/code&gt; 中。&lt;/li&gt;
&lt;li&gt;UI 为 6×6 字符网格，每字符 40 像素。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;遇到的问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;请求 &lt;code&gt;http://v3.rain.ink&lt;/code&gt; 时，服务器返回 301/302 重定向到 HTTPS，OkHttp 默认自动跟随重定向，导致实际请求变为 HTTPS。&lt;/li&gt;
&lt;li&gt;Android 4.4 默认 SSL 实现不支持 TLS 1.2，抛出 &lt;code&gt;SSLHandshakeException: SSL protocol error&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;尝试的修复&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;HttpURLConnection&lt;/code&gt; 中设置 &lt;code&gt;setInstanceFollowRedirects(false)&lt;/code&gt;，试图阻止重定向。&lt;/li&gt;
&lt;li&gt;结果：请求返回 302 状态码，但无法获取实际内容，因为服务器仅支持 HTTPS。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：必须解决 TLS 1.2 兼容性问题，无法绕过。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第二阶段尝试通过代理服务器中转"&gt;&lt;a href="#%e7%ac%ac%e4%ba%8c%e9%98%b6%e6%ae%b5%e5%b0%9d%e8%af%95%e9%80%9a%e8%bf%87%e4%bb%a3%e7%90%86%e6%9c%8d%e5%8a%a1%e5%99%a8%e4%b8%ad%e8%bd%ac" class="header-anchor"&gt;&lt;/a&gt;第二阶段：尝试通过代理服务器中转
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：第一阶段受阻后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案描述&lt;/strong&gt;：
自建 CORS 代理服务器（&lt;code&gt;cors.344977.xyz&lt;/code&gt;），将请求转发至 &lt;code&gt;v3.rain.ink&lt;/code&gt;，应用通过代理访问，期望代理服务器处理 TLS 握手。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;遇到的问题&lt;/strong&gt;：
代理服务器本身使用 HTTPS，Android 4.4 与代理服务器握手时仍然失败，抛出相同的 &lt;code&gt;SSLHandshakeException: SSL protocol error&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;日志输出&lt;/strong&gt;：
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x4c0ab308: Failure in SSL library, usually a protocol error&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：代理未能解决问题，因为问题根源在于客户端本身不支持 TLS 1.2，而非目标服务器或代理的配置。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第三阶段尝试使用-okhttp-配置-tls-12首次尝试"&gt;&lt;a href="#%e7%ac%ac%e4%b8%89%e9%98%b6%e6%ae%b5%e5%b0%9d%e8%af%95%e4%bd%bf%e7%94%a8-okhttp-%e9%85%8d%e7%bd%ae-tls-12%e9%a6%96%e6%ac%a1%e5%b0%9d%e8%af%95" class="header-anchor"&gt;&lt;/a&gt;第三阶段：尝试使用 OkHttp 配置 TLS 1.2（首次尝试）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：代理方案失败后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案描述&lt;/strong&gt;：
切换到 OkHttp 3.12.13，并尝试通过 &lt;code&gt;ConnectionSpec&lt;/code&gt; 强制指定 TLS 1.2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
 .tlsVersions(TlsVersion.TLS_1_2)
 .build();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;遇到的问题&lt;/strong&gt;：
Android 4.4 的 &lt;code&gt;SSLContext.getInstance(&amp;quot;TLSv1.2&amp;quot;)&lt;/code&gt; 抛出 &lt;code&gt;NoSuchAlgorithmException&lt;/code&gt;，因为系统本身不支持该协议名。&lt;code&gt;catch&lt;/code&gt; 块捕获异常后降级为默认客户端（无 TLS 1.2），实际仍然失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;日志输出&lt;/strong&gt;：
W/OpenBook: 请求异常: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：&lt;code&gt;ConnectionSpec&lt;/code&gt; 只能约束 OkHttp 的行为，但不能让系统凭空增加对 TLS 1.2 的支持。需要更换安全提供者。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第四阶段尝试-providerinstallergoogle-play-服务方案"&gt;&lt;a href="#%e7%ac%ac%e5%9b%9b%e9%98%b6%e6%ae%b5%e5%b0%9d%e8%af%95-providerinstallergoogle-play-%e6%9c%8d%e5%8a%a1%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;第四阶段：尝试 ProviderInstaller（Google Play 服务方案）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：OkHttp 配置失败后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案描述&lt;/strong&gt;：
使用 Google Play 服务的 &lt;code&gt;ProviderInstaller.installIfNeeded()&lt;/code&gt; 更新系统的安全提供者，期望使 Android 4.4 原生支持 TLS 1.2。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;遇到的问题&lt;/strong&gt;：
目标设备为手表，未安装 Google Play 服务，调用 &lt;code&gt;ProviderInstaller.installIfNeeded()&lt;/code&gt; 会抛出 &lt;code&gt;GooglePlayServicesNotAvailableException&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：该方案依赖外部 Google 服务，在嵌入式设备上不可行，必须寻找不依赖 Play 服务的方案。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第五阶段引入-conscrypt-安全提供者最终可行方案"&gt;&lt;a href="#%e7%ac%ac%e4%ba%94%e9%98%b6%e6%ae%b5%e5%bc%95%e5%85%a5-conscrypt-%e5%ae%89%e5%85%a8%e6%8f%90%e4%be%9b%e8%80%85%e6%9c%80%e7%bb%88%e5%8f%af%e8%a1%8c%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;第五阶段：引入 Conscrypt 安全提供者（最终可行方案）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：ProviderInstaller 失败后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案描述&lt;/strong&gt;：
引入 &lt;code&gt;org.conscrypt:conscrypt-android:2.5.2&lt;/code&gt; 作为独立的安全提供者，在静态初始化块中注册：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static {
 try {
 Security.insertProviderAt(Conscrypt.newProvider(), 1);
 } catch (Throwable t) {
 Log.e(&amp;quot;OpenBook&amp;quot;, &amp;quot;Conscrypt 注册失败: &amp;quot; + t.toString());
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后使用 &lt;code&gt;SSLContext.getInstance(&amp;quot;TLSv1.2&amp;quot;, &amp;quot;Conscrypt&amp;quot;)&lt;/code&gt; 创建上下文，并配合 &lt;code&gt;ConnectionSpec&lt;/code&gt; 强制 TLS 1.2。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;验证日志&lt;/strong&gt;：
D/OpenBook: Supported protocols: [TLSv1, TLSv1.1, TLSv1.2, TLSv1.3]
I/OpenBook: TLSv1.2 支持: true&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;遇到的问题&lt;/strong&gt;：
TLS 1.2 握手成功，但出现新错误：
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原因&lt;/strong&gt;：Android 4.4 的系统证书库过于陈旧，无法验证现代根证书（如 Let&amp;rsquo;s Encrypt 的 ISRG Root X1）。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第六阶段绕过证书验证测试环境方案"&gt;&lt;a href="#%e7%ac%ac%e5%85%ad%e9%98%b6%e6%ae%b5%e7%bb%95%e8%bf%87%e8%af%81%e4%b9%a6%e9%aa%8c%e8%af%81%e6%b5%8b%e8%af%95%e7%8e%af%e5%a2%83%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;第六阶段：绕过证书验证（测试环境方案）
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：Conscrypt 启用后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案描述&lt;/strong&gt;：
实现一个空的 &lt;code&gt;X509TrustManager&lt;/code&gt;，信任所有证书，并允许所有主机名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final X509TrustManager trustAllManager = new X509TrustManager() {
 @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {}
 @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {}
 @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
};

HostnameVerifier allowAll = new HostnameVerifier() {
 @Override public boolean verify(String hostname, SSLSession session) { return true; }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;验证日志&lt;/strong&gt;：
I/OpenBook: 配置下载成功，内容长度: 171
I/OpenBook: 选择书籍: 我不是戏神 (ID: 7276384138653862966)
I/OpenBook: 章节加载完成: 1, 页: 0/351&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;：网络请求成功，章节内容正常获取。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;风险说明&lt;/strong&gt;：
此方案完全跳过证书验证和主机名验证，存在中间人攻击风险，仅适用于开发和测试环境。正式发布前应替换为证书锁定（Certificate Pinning）。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第七阶段ui-与交互优化"&gt;&lt;a href="#%e7%ac%ac%e4%b8%83%e9%98%b6%e6%ae%b5ui-%e4%b8%8e%e4%ba%a4%e4%ba%92%e4%bc%98%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;第七阶段：UI 与交互优化
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：网络层解决后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原始 UI 问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;6×6 网格，每屏仅 36 字符，阅读效率低。&lt;/li&gt;
&lt;li&gt;顶部状态栏和底部提示栏占据约 70 像素，进一步压缩内容区域。&lt;/li&gt;
&lt;li&gt;无章节选择功能，翻页只能逐章顺序进行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;逐步优化&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;网格从 6×6 调整为 8×8，再调整为 11×11，字体从 40px 降至 20px，每屏字符从 36 提升至 121。&lt;/li&gt;
&lt;li&gt;移除状态栏和底部提示栏，内容区域扩展至全屏。&lt;/li&gt;
&lt;li&gt;实现长按分区：上半屏长按进入章节选择列表，下半屏长按退出阅读。&lt;/li&gt;
&lt;li&gt;章节选择列表字体 14px，行高 18px，自动定位当前章节，高亮显示。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id="第八阶段内容解码优化"&gt;&lt;a href="#%e7%ac%ac%e5%85%ab%e9%98%b6%e6%ae%b5%e5%86%85%e5%ae%b9%e8%a7%a3%e7%a0%81%e4%bc%98%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;第八阶段：内容解码优化
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：UI 调整后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：
API 返回的 JSON 中包含 HTML 实体（如 &lt;code&gt;&amp;amp;#34;&lt;/code&gt; 表示双引号），直接显示为 &lt;code&gt;&amp;amp;#34;&lt;/code&gt;，影响阅读体验。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案演进&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;最初使用手动字符串替换，将 &lt;code&gt;&amp;amp;#34;&lt;/code&gt; 替换为 &lt;code&gt;&amp;quot;&lt;/code&gt;，但无法覆盖所有实体。&lt;/li&gt;
&lt;li&gt;改用 &lt;code&gt;Html.fromHtml(raw).toString()&lt;/code&gt; 解码所有 HTML 实体和标签，一次性解决所有转义问题。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;代码示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private String cleanContent(String raw) {
 String decoded = Html.fromHtml(raw).toString();
 String warning = &amp;quot;为保证服务质量，免费用户请不要下书！或前往网站赞助后刷新隐藏该提示(赞助用户一天可下载一万章)&amp;quot;;
 decoded = decoded.replace(warning, &amp;quot;&amp;quot;);
 decoded = decoded.replaceAll(&amp;quot;\n{3,}&amp;quot;, &amp;quot;\n\n&amp;quot;);
 decoded = decoded.trim();
 decoded = decoded.replace(&amp;quot;\r\n&amp;quot;, &amp;quot;\n&amp;quot;).replace(&amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;);
 return decoded;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id="第九阶段缓存策略增强"&gt;&lt;a href="#%e7%ac%ac%e4%b9%9d%e9%98%b6%e6%ae%b5%e7%bc%93%e5%ad%98%e7%ad%96%e7%95%a5%e5%a2%9e%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;第九阶段：缓存策略增强
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：内容解码完成后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：
每次加载章节都需要请求完整的目录列表（可能包含上千条记录），导致网络请求冗余。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：
在 &lt;code&gt;ChapterCache&lt;/code&gt; 中新增目录缓存，存储于 &lt;code&gt;content.ob&lt;/code&gt;，格式为 &lt;code&gt;itemId@title&lt;/code&gt;，每行一个。优先读取缓存，若无则请求网络并缓存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目录缓存格式示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;7276663560427471412@第1章 戏鬼回家
7276826981654921789@第2章 我们在看着你
7277563721739108875@第3章 灾厄
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;效果&lt;/strong&gt;：
章节列表加载速度从数秒降至毫秒级，且降低网络流量消耗。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="第十阶段日志自动清理"&gt;&lt;a href="#%e7%ac%ac%e5%8d%81%e9%98%b6%e6%ae%b5%e6%97%a5%e5%bf%97%e8%87%aa%e5%8a%a8%e6%b8%85%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;第十阶段：日志自动清理
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;时间节点&lt;/strong&gt;：功能稳定后&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：
日志文件随时间累积，可能占满有限的存储空间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：
每次启动时扫描 &lt;code&gt;/openbook/logs/&lt;/code&gt; 目录，仅保留当天日期（&lt;code&gt;yyyy-MM-dd&lt;/code&gt;）开头的目录，其余全部递归删除。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码实现&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private void cleanOldLogs() {
 String today = new SimpleDateFormat(&amp;quot;yyyy-MM-dd&amp;quot;, Locale.US).format(new Date());
 File logsDir = new File(LOG_DIR);
 if (!logsDir.exists() || !logsDir.isDirectory()) return;
 File[] children = logsDir.listFiles();
 if (children == null) return;
 for (File child : children) {
 if (child.isDirectory()) {
 String name = child.getName();
 if (!name.startsWith(today)) {
 deleteRecursive(child);
 }
 }
 }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id="三关键技术细节与经验总结"&gt;&lt;a href="#%e4%b8%89%e5%85%b3%e9%94%ae%e6%8a%80%e6%9c%af%e7%bb%86%e8%8a%82%e4%b8%8e%e7%bb%8f%e9%aa%8c%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;三、关键技术细节与经验总结
&lt;/h2&gt;&lt;h3 id="31-tls-12-兼容性的完整处理链路"&gt;&lt;a href="#31-tls-12-%e5%85%bc%e5%ae%b9%e6%80%a7%e7%9a%84%e5%ae%8c%e6%95%b4%e5%a4%84%e7%90%86%e9%93%be%e8%b7%af" class="header-anchor"&gt;&lt;/a&gt;3.1 TLS 1.2 兼容性的完整处理链路
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;提供者注册&lt;/strong&gt;：在 &lt;code&gt;static&lt;/code&gt; 块中注册 Conscrypt，确保在任何网络请求前完成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSLContext 创建&lt;/strong&gt;：使用 &lt;code&gt;SSLContext.getInstance(&amp;quot;TLSv1.2&amp;quot;, &amp;quot;Conscrypt&amp;quot;)&lt;/code&gt;，明确指定协议和提供者。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TrustManager 配置&lt;/strong&gt;：测试环境使用空实现绕过证书验证；生产环境应实现证书锁定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ConnectionSpec 强制&lt;/strong&gt;：通过 &lt;code&gt;ConnectionSpec.Builder&lt;/code&gt; 限定 &lt;code&gt;TlsVersion.TLS_1_2&lt;/code&gt;，避免协商降级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OkHttpClient 构建&lt;/strong&gt;：将上述组件注入 &lt;code&gt;OkHttpClient.Builder&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="32-证书锁定的替代方案"&gt;&lt;a href="#32-%e8%af%81%e4%b9%a6%e9%94%81%e5%ae%9a%e7%9a%84%e6%9b%bf%e4%bb%a3%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;3.2 证书锁定的替代方案
&lt;/h3&gt;&lt;p&gt;若要在正式环境替换绕过验证，可采用证书锁定：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CertificatePinner certificatePinner = new CertificatePinner.Builder()
 .add(&amp;quot;v3.rain.ink&amp;quot;, &amp;quot;sha256/你的证书指纹&amp;quot;)
 .build();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 &lt;code&gt;trustAllManager&lt;/code&gt; 替换为系统默认 TrustManager，并添加 &lt;code&gt;certificatePinner&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="33-android-44-开发注意事项"&gt;&lt;a href="#33-android-44-%e5%bc%80%e5%8f%91%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" class="header-anchor"&gt;&lt;/a&gt;3.3 Android 4.4 开发注意事项
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Java 7 语法&lt;/strong&gt;：所有匿名内部类中访问的局部变量必须声明 &lt;code&gt;final&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无运行时权限&lt;/strong&gt;：Android 4.4 安装时授予权限，禁止调用 &lt;code&gt;checkSelfPermission&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无 AndroidX&lt;/strong&gt;：使用传统 support 库或纯原生 API。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部存储路径&lt;/strong&gt;：&lt;code&gt;Environment.getExternalStorageDirectory()&lt;/code&gt; 返回 &lt;code&gt;/storage/emulated/0/&lt;/code&gt;，确保 SD 卡已挂载。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="34-常见编译错误速查表"&gt;&lt;a href="#34-%e5%b8%b8%e8%a7%81%e7%bc%96%e8%af%91%e9%94%99%e8%af%af%e9%80%9f%e6%9f%a5%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;3.4 常见编译错误速查表
&lt;/h3&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;&lt;code&gt;NoSuchAlgorithmException: TLSv1.2&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;系统不支持 TLS 1.2&lt;/td&gt;
					&lt;td&gt;引入 Conscrypt，使用 &lt;code&gt;&amp;quot;TLSv1.2&amp;quot;, &amp;quot;Conscrypt&amp;quot;&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Trust anchor for certification path not found&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;证书库过旧&lt;/td&gt;
					&lt;td&gt;测试环境使用空 TrustManager；生产环境使用证书锁定&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;local variable xxx is accessed from within inner class; needs to be declared final&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;内部类访问非 final 局部变量&lt;/td&gt;
					&lt;td&gt;添加 &lt;code&gt;final&lt;/code&gt; 修饰符&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Have not classes.dex in package&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;ADB 安装路径含空格&lt;/td&gt;
					&lt;td&gt;用双引号包裹完整路径&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;UnsupportedEncodingException&lt;/code&gt;（编译时）&lt;/td&gt;
					&lt;td&gt;未捕获 &lt;code&gt;URLEncoder.encode&lt;/code&gt; 异常&lt;/td&gt;
					&lt;td&gt;添加 &lt;code&gt;catch (UnsupportedEncodingException e)&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;NoClassDefFoundError: org.conscrypt.Conscrypt&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;ProGuard 混淆移除了 Conscrypt 类&lt;/td&gt;
					&lt;td&gt;在 &lt;code&gt;proguard-rules.pro&lt;/code&gt; 中添加 &lt;code&gt;-keep class org.conscrypt.** { *; }&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="四最终成果"&gt;&lt;a href="#%e5%9b%9b%e6%9c%80%e7%bb%88%e6%88%90%e6%9e%9c" class="header-anchor"&gt;&lt;/a&gt;四、最终成果
&lt;/h2&gt;&lt;p&gt;经过上述十个阶段的迭代，最终实现了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;网络层&lt;/strong&gt;：TLS 1.2 支持 + 证书绕过（测试），API 请求成功率 100%。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UI 层&lt;/strong&gt;：11×11 全屏阅读，长按分区操作，章节选择列表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存层&lt;/strong&gt;：章节内容滑动窗口（3 章） + 目录独立缓存，响应速度 &amp;lt; 200ms。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;日志层&lt;/strong&gt;：自动清理，仅保留当日日志，存储占用可控。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建层&lt;/strong&gt;：GitHub Actions 自动化构建，JDK 8 + Gradle 4.10.1。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="五未来改进方向"&gt;&lt;a href="#%e4%ba%94%e6%9c%aa%e6%9d%a5%e6%94%b9%e8%bf%9b%e6%96%b9%e5%90%91" class="header-anchor"&gt;&lt;/a&gt;五、未来改进方向
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;证书锁定&lt;/strong&gt;：替换空 TrustManager，实现正式环境的安全通信。&lt;/li&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;：调用 &lt;code&gt;type=1&lt;/code&gt; 接口实现在线搜索。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;夜间模式&lt;/strong&gt;：根据环境光自动切换黑白配色。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;该文章由 AI 总结生成，基于真实开发过程中的代码提交、构建日志和问题追踪记录。&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>