<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Endlessyoung's Blog</title>
        <link>https://endlessyoung.github.io/Blog_/</link>
        <description>Endless Young 的个人技术博客，涵盖 Android、Java、Kotlin、AI/ML、Python 等领域的原创文章。</description>
        <lastBuildDate>Sat, 01 Aug 2026 10:05:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <image>
            <title>Endlessyoung's Blog</title>
            <url>https://endlessyoung.github.io/Blog_/index.png</url>
            <link>https://endlessyoung.github.io/Blog_/</link>
        </image>
        <copyright>© 2026 Endless Young</copyright>
        <item>
            <title><![CDATA[生成式AI导论：从“文字接龙”说起]]></title>
            <link>https://endlessyoung.github.io/Blog_/Ai/AI%E5%AF%BC%E8%AE%BA/%E7%94%9F%E4%BA%A7%E5%BC%8FAI%E7%9A%84%E6%9C%AC%E8%B4%A8.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Ai/AI导论/生产式AI的本质.html</guid>
            <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[LLM（大型语言模型，如 ChatGPT、Gemini、Claude、DeepSeek 等）的核心机制其实非常简单——就是文字接龙。]]></description>
            <content:encoded><![CDATA[<h1 id="生成式ai导论-从-文字接龙-说起" tabindex="-1">生成式AI导论：从“文字接龙”说起 <a class="header-anchor" href="#生成式ai导论-从-文字接龙-说起" aria-label="Permalink to “生成式AI导论：从“文字接龙”说起”">&#8203;</a></h1>
<h2 id="_1-生成式ai的本质-文字接龙" tabindex="-1">1. 生成式AI的本质：文字接龙 <a class="header-anchor" href="#_1-生成式ai的本质-文字接龙" aria-label="Permalink to “1. 生成式AI的本质：文字接龙”">&#8203;</a></h2>
<p>LLM（大型语言模型，如 ChatGPT、Gemini、Claude、DeepSeek 等）的核心机制其实非常简单——<strong>就是文字接龙</strong>。</p>
<p>举个直观的例子：</p>
<ul>
<li>你输入：“人工智” → 模型预测下一个内容很可能是“能”</li>
<li>你输入：“大预言模” → 模型会立刻纠正为“型”（基于上下文的概率推算）</li>
</ul>
<p>在这一过程中，有两个核心概念需要明确：</p>
<ul>
<li><strong>Prompt（提示词）</strong>：你输入给模型的前面内容，也就是模型进行“接龙”的上下文依据。</li>
<li><strong>Token（词元）</strong>：模型预测并输出的最小单位。它可以是单个汉字、一个词语片段、一个标点，甚至是一个空格。<strong>注意</strong>：这里说的 Token 不是指 API 计费消耗的那个量，而是指<strong>生成的基本语义单元</strong>。</li>
</ul>
<p><strong>完整接龙过程示例（以常见问答为例）：</strong></p>
<div class="language- line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre><!--::markdown-it-async::6x3zjh8134itb70pb3805::--><code>用户输入: 世界上最高的峰是？
模型内部一步一步接龙输出:
珠 → 穆 → 朗 → 玛 → 峰 → 。 → [结束]</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>模型每一步都在预测“接下来最合理出现的字或词是什么”。整个生成式AI做的事情，本质就是：<strong>给定一个 Prompt，不断预测并输出下一个 Token，直到结束</strong>。</p>
<p><strong>文本生成流程示意：</strong></p>
<div class="language- line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre><!--::markdown-it-async::mjmgf9y3iaje78ipe4sdlg::--><code>Prompt（输入内容）
      ↓
模型处理（计算概率）
      ↓
输出下一个 Token 的概率分布（从几万到几十万的 Vocabulary 中选择）
      ↓
采样选中一个 Token（如“珠”）
      ↓
将新 Token 拼接到上下文末尾 → 再次预测下一个 Token
      ↓
循环往复，直到生成特殊的结束标记（如 &lt;|endoftext|&gt;）</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><h2 id="_2-模型是怎么知道该接什么的" tabindex="-1">2. 模型是怎么知道该接什么的？ <a class="header-anchor" href="#_2-模型是怎么知道该接什么的" aria-label="Permalink to “2. 模型是怎么知道该接什么的？”">&#8203;</a></h2>
<p>模型输出的其实并不是一个确定的字词，而是一个<strong>概率分布</strong>。它内部维护着一个庞大的 <strong>Vocabulary（词汇表）</strong>，通常包含几万到几十万个 Token（涵盖常用汉字、英文子词、数字、符号等）。</p>
<p>例如输入“人工”后，模型内部的计算结果可能是：</p>
<ul>
<li>“智能” → 概率 <strong>45%</strong></li>
<li>“呼吸” → 概率 <strong>5%</strong></li>
<li>“降雨” → 概率 <strong>0.1%</strong></li>
<li>…… 其余概率极低的奇怪组合</li>
</ul>
<p>随后，模型会像“扔骰子”一样，根据这个概率分布<strong>随机采样</strong>下一个 Token。这也是为什么你问同一个问题两次，得到的回答可能在措辞上略有不同。</p>
<blockquote>
<p><strong>程序员视角类比</strong>：这很像 IDE 里的代码自动补全。你输入 <code>System.out.</code>，补全列表里 <code>println</code> 排第一，而不是随机乱码。</p>
</blockquote>
<p>因为模型是在“扔骰子”，所以它必须具备两种基础能力：</p>
<ol>
<li><strong>语言知识</strong>（语法、表达习惯、句式）——相对容易通过海量语料学到。</li>
<li><strong>世界知识</strong>（事实、常识、逻辑推理）——非常困难，因为真实世界的知识几乎是无穷无尽的，且不断更新。</li>
</ol>
<p><strong>案例对比：</strong></p>
<ul>
<li>问：“在标准大气压下，水的沸点是多少？”<br>
→ 模型大概率回答“100摄氏度”（因为这是训练语料里概率最高的搭配）。</li>
<li>问：“在海拔4000米的高山上，不盖锅盖水的沸点大约是多少？”<br>
→ 如果模型没见过相关物理常识的上下文，可能就胡乱编一个数字（比如还是100度），这就是“幻觉”的早期苗头。</li>
</ul>
<h2 id="_3-大模型是怎么学会这些的" tabindex="-1">3. 大模型是怎么学会这些的？ <a class="header-anchor" href="#_3-大模型是怎么学会这些的" aria-label="Permalink to “3. 大模型是怎么学会这些的？”">&#8203;</a></h2>
<p>我们可以把语言模型抽象成一个极其复杂的数学函数：</p>
<p><strong>f(x) = 极其复杂的神经网络运算</strong></p>
<ul>
<li><strong>x</strong> 是输入的 Prompt（转为向量后的数字）。</li>
<li><strong>f(x)</strong> 是输出的下一个 Token 的概率分布。</li>
</ul>
<p>这个函数内部有海量的<strong>参数</strong>（权重）。普通模型可能只有几亿参数，而真正意义上的“大模型”通常拥有<strong>数百亿甚至数千亿参数</strong>。这些参数并不是程序员一行行写的规则，而是模型通过“自学”从数据中挖掘出来的。</p>
<p><strong>训练数据的主要来源：</strong></p>
<ol>
<li><strong>互联网公开语料</strong>（网页、维基百科、书籍、开源代码库）。</li>
<li><strong>人工标注的高质量指令数据</strong>（一问一答，教会模型“对话感”）。</li>
<li><strong>用户使用过程中的反馈数据</strong>（点赞、点踩、重新生成）。</li>
</ol>
<h2 id="_4-为什么ai有时会回答-答案是-而不是直接说" tabindex="-1">4. 为什么AI有时会回答“答案是……”而不是直接说？ <a class="header-anchor" href="#_4-为什么ai有时会回答-答案是-而不是直接说" aria-label="Permalink to “4. 为什么AI有时会回答“答案是……”而不是直接说？”">&#8203;</a></h2>
<p>严格来说，未经特别调整的<strong>基座模型</strong>只会做<strong>续写</strong>。比如你问“1+1=”，它可能接着写“2，这是一个简单的数学题……”。</p>
<p>我们之所以觉得 AI 像助手一样直接回答，是因为平台在<strong>你的输入前面自动拼接了一段隐藏指令</strong>，这叫做 <strong>Chat Template（对话模板）</strong>。</p>
<p>一个典型的隐式 Chat Template 大概长这样：</p>
<div class="language-text line-numbers-mode" data-lang="text"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre><!--::markdown-it-async::x8en9506h5zi9kd9nc22::--><code>&lt;|system|&gt;你是一个乐于助人、知识渊博的AI助手。回答要清晰、准确。&lt;/s&gt;
&lt;|user|&gt;世界上最高的峰是？&lt;/s&gt;
&lt;|assistant|&gt;</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>正是基于这个前缀，模型看到 <code>assistant</code> 标记后，就知道接下来应该直接输出答案内容了。</p>
<h3 id="_4-1-幻觉-hallucination-的根源" tabindex="-1">4.1 幻觉（Hallucination）的根源 <a class="header-anchor" href="#_4-1-幻觉-hallucination-的根源" aria-label="Permalink to “4.1 幻觉（Hallucination）的根源”">&#8203;</a></h3>
<p>正因为 AI 本质上是在做概率续写，它<strong>并没有一个像数据库那样的“真实资料库”</strong>。当遇到记忆模糊或没见过的事实，它会“编造”出概率上通顺、但事实上错误的文本。这在学术上被称为<strong>幻觉</strong>。</p>
<h3 id="_4-2-应对幻觉的利器-rag-检索增强生成" tabindex="-1">4.2 应对幻觉的利器：RAG（检索增强生成） <a class="header-anchor" href="#_4-2-应对幻觉的利器-rag-检索增强生成" aria-label="Permalink to “4.2 应对幻觉的利器：RAG（检索增强生成）”">&#8203;</a></h3>
<p><strong>RAG（Retrieval-Augmented Generation）</strong> 是目前工业界最常用的“外挂记忆”方案。</p>
<p><strong>原理简述</strong>：模型在动笔回答之前，先跑去查阅一下“小抄”（外部知识库），把查到的真实内容塞进 Prompt 里，再开始文字接龙。</p>
<p><strong>RAG 简化流程：</strong></p>
<div class="language- line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre><!--::markdown-it-async::7hs9ky3gz29ti45xznyzp8::--><code>用户问题：公司今年的年假政策是什么？
      ↓
Step 1: 检索（Retrieve）
在内部 Wiki / PDF / 数据库 中搜索“年假政策”
找到相关文档片段：“员工入职满一年享有5天年假...”
      ↓
Step 2: 增强（Augment）
构建新 Prompt = [检索到的文档内容] + [用户原始问题]
      ↓
Step 3: 生成（Generate）
模型阅读文档后回答：“根据公司政策，年假为5天...”（大幅减少幻觉）</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><blockquote>
<p><strong>程序员实例</strong>：当你问一个 2025 年新发布的框架用法时，RAG 可以先实时抓取最新的 GitHub README 或官方 Changelog，再为你生成代码，而不是靠模型 2024 年的过期记忆瞎编一个不存在的 API。</p>
</blockquote>
<h2 id="_5-大模型其实是一个-黑盒子" tabindex="-1">5. 大模型其实是一个“黑盒子” <a class="header-anchor" href="#_5-大模型其实是一个-黑盒子" aria-label="Permalink to “5. 大模型其实是一个“黑盒子””">&#8203;</a></h2>
<p>我们可以把模型理解成一个<strong>状态不断变化的黑箱</strong>。输入的内容不同、语境不同，输出的结果就会截然不同。</p>
<ul>
<li>问：“今天是几号？”
<ul>
<li>如果模型没联网：它可能随机回答一个训练数据里的常见日期（通常是错的）。</li>
<li>如果模型调用了时间查询工具：它能准确回答当前时间。</li>
</ul>
</li>
</ul>
<p>因此，我们对<strong>输入（Prompt）的设计</strong>直接决定了输出的质量。这个设计过程就是 <strong>Prompt Engineering（提示词工程）</strong>。</p>
<h3 id="_5-1-核心概念区分" tabindex="-1">5.1 核心概念区分 <a class="header-anchor" href="#_5-1-核心概念区分" aria-label="Permalink to “5.1 核心概念区分”">&#8203;</a></h3>
<ul>
<li><strong>System Prompt（系统指令）</strong>：设定 AI 的<strong>角色、语气、安全边界</strong>（比如“你是一个严谨的律师”），在整场对话中持续生效。</li>
<li><strong>User Prompt（用户指令）</strong>：具体的任务指令或问题。</li>
</ul>
<p><strong>一个完整的 Prompt 后台拼接示意：</strong></p>
<div class="language- line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre><!--::markdown-it-async::epsbihqchn63nkhn6fnq99::--><code>[System Prompt] 你是一个程序员导师，回答时要附带关键代码注释。
[User Prompt]  如何在 Python 中读取 JSON 文件？
      ↓
（Chat Template 自动包装成模型能理解的 Token 序列）
      ↓
模型生成结构清晰、带代码示例的回答</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><blockquote>
<p><strong>进阶小贴士</strong>：现代 Prompt 工程不仅仅指写话术，还包括 <strong>Context Engineering（上下文工程）</strong>——即管理模型的“记忆窗口”，决定把哪些历史对话、哪些文档片段塞进有限的 Token 长度里。</p>
</blockquote>
<h2 id="_6-不只是文本-图像、声音、视频的生成" tabindex="-1">6. 不只是文本：图像、声音、视频的生成 <a class="header-anchor" href="#_6-不只是文本-图像、声音、视频的生成" aria-label="Permalink to “6. 不只是文本：图像、声音、视频的生成”">&#8203;</a></h2>
<p>生成式 AI 的原理并不局限于文字。图像、声音、甚至视频的生成，本质上也是一种“接龙”——只是接龙的对象发生了变化。</p>
<ul>
<li><strong>图像生成</strong>：对像素块进行接龙（扩散模型去噪过程可视作一种接龙）。</li>
<li><strong>声音生成</strong>：对音频采样点序列进行接龙。</li>
</ul>
<p>然而，直接在原始像素或波形上计算极其消耗算力。目前的<strong>多模态大模型</strong>普遍采用 <strong>“压缩-处理-还原”</strong> 的架构：</p>
<p><strong>多模态生成流程：</strong></p>
<div class="language- line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre><!--::markdown-it-async::ea9c4gdxg885vsdn6ktv0w::--><code>原始图像/声音（高维信号）
      ↓
[Encoder 编码器] 压缩成 Token 序列（离散的符号表示）
      ↓
大模型 Transformer 处理（在 Token 空间里做文字/图像接龙）
      ↓
[Decoder 解码器] 将 Token 还原成图像像素/声音波形
      ↓
最终输出的图片或音频</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p><strong>扩展开来</strong>：生成式 AI 的基本逻辑就是<strong>学习复杂对象的序列概率分布</strong>。这些对象的基本构成单元，我们都可以抽象地称为 <strong>Token</strong>。比如：</p>
<ul>
<li>文本的 Token：字、词。</li>
<li>图像的 Token：压缩后的视觉特征块。</li>
<li>蛋白质的 Token：氨基酸序列（生物学里的天然 Token）。</li>
</ul>
<h2 id="_7-实战小建议与延伸阅读" tabindex="-1">7. 实战小建议与延伸阅读 <a class="header-anchor" href="#_7-实战小建议与延伸阅读" aria-label="Permalink to “7. 实战小建议与延伸阅读”">&#8203;</a></h2>
<p>如果你想自己动手试一试，以下是入门配置建议：</p>
<ul>
<li><strong>开源模型推荐</strong>：Gemma（Google）、Llama（Meta）、Mistral、Qwen（通义千问）。</li>
<li><strong>运行平台</strong>：Google Colab（免费 T4 GPU）、Kaggle Notebooks。</li>
<li><strong>本地工具</strong>：Ollama（一键运行大模型）、LM Studio。</li>
</ul>
<p><strong>总结回顾：</strong></p>
<ol>
<li><strong>核心本质</strong>：生成式 AI = 给定 Prompt 预测下一个 Token 的接龙游戏。</li>
<li><strong>能力边界</strong>：擅长语言表达（语法、文采），但事实知识有限（需要 RAG 辅助）。</li>
<li><strong>统一抽象</strong>：无论文本、图像还是蛋白质，一切皆为 <strong>Token</strong> 的有序生成。</li>
</ol>
<hr>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Ai</category>
        </item>
        <item>
            <title><![CDATA[Compose布局系统与 Modifier]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Compose/Compose%E5%B8%83%E5%B1%80%E7%B3%BB%E7%BB%9F%20&amp;%20Modifier.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Compose/Compose布局系统 &amp; Modifier.html</guid>
            <pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[文档级 · 教学级 · 指南级]]></description>
            <content:encoded><![CDATA[<h1 id="compose布局系统与-modifier" tabindex="-1">Compose布局系统与 Modifier <a class="header-anchor" href="#compose布局系统与-modifier" aria-label="Permalink to “Compose布局系统与 Modifier”">&#8203;</a></h1>
<blockquote>
<p>文档级 · 教学级 · 指南级</p>
</blockquote>
<hr>
<h2 id="_0-本文档的定位与阅读方式" tabindex="-1">0. 本文档的定位与阅读方式 <a class="header-anchor" href="#_0-本文档的定位与阅读方式" aria-label="Permalink to “0. 本文档的定位与阅读方式”">&#8203;</a></h2>
<p><strong>目标读者</strong>：</p>
<ul>
<li>有多年 Android / View / XML 经验的工程师</li>
<li>已掌握 Compose 的基础 State 管理（remember / mutableStateOf / State Hoisting 的概念）</li>
</ul>
<p><strong>本文目标</strong>：</p>
<ul>
<li>建立 <em>Compose 布局系统的完整心智模型</em></li>
<li>从&quot;能写 UI&quot;升级到&quot;<strong>知道为什么这样写</strong>&quot;</li>
<li>能在工程中 <strong>预判布局结果、性能与边界行为</strong></li>
</ul>
<p>本文不是 API 罗列，而是 <strong>规则 → 机制 → 结论</strong>。</p>
<hr>
<h2 id="_1-compose-布局系统的根本模型" tabindex="-1">1. Compose 布局系统的根本模型 <a class="header-anchor" href="#_1-compose-布局系统的根本模型" aria-label="Permalink to “1. Compose 布局系统的根本模型”">&#8203;</a></h2>
<h3 id="_1-1-布局的三阶段模型-不可跳过" tabindex="-1">1.1 布局的三阶段模型（不可跳过） <a class="header-anchor" href="#_1-1-布局的三阶段模型-不可跳过" aria-label="Permalink to “1.1 布局的三阶段模型（不可跳过）”">&#8203;</a></h3>
<p>每一个 Composable 节点都会经历三个阶段：</p>
<ol>
<li><strong>Constraints（约束）</strong>：父节点向子节点下发</li>
<li><strong>Measure（测量）</strong>：子节点在约束内决定自身尺寸</li>
<li><strong>Placement（放置）</strong>：父节点决定子节点在自身坐标系中的位置</li>
</ol>
<p>这三阶段是 Compose 布局系统的<strong>物理定律</strong>。</p>
<blockquote>
<p>任何你觉得&quot;奇怪&quot;的布局结果，都能回溯到这三步。</p>
</blockquote>
<hr>
<h3 id="_1-2-constraints-的本质" tabindex="-1">1.2 Constraints 的本质 <a class="header-anchor" href="#_1-2-constraints-的本质" aria-label="Permalink to “1.2 Constraints 的本质”">&#8203;</a></h3>
<p>Constraints 由四个值组成：</p>
<ul>
<li>minWidth</li>
<li>maxWidth</li>
<li>minHeight</li>
<li>maxHeight</li>
</ul>
<p>它们描述的是：</p>
<blockquote>
<p><strong>子节点允许使用的尺寸范围</strong></p>
</blockquote>
<p>重要结论：</p>
<ul>
<li>子节点 <strong>不能突破 max</strong></li>
<li>子节点 <strong>不能小于 min</strong></li>
<li>子节点只能在区间内选择</li>
</ul>
<hr>
<h3 id="_1-3-与-view-系统的本质差异" tabindex="-1">1.3 与 View 系统的本质差异 <a class="header-anchor" href="#_1-3-与-view-系统的本质差异" aria-label="Permalink to “1.3 与 View 系统的本质差异”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>View 系统</th>
<th>Compose</th>
</tr>
</thead>
<tbody>
<tr>
<td>LayoutParams</td>
<td>Constraints</td>
</tr>
<tr>
<td>子 View 决定自己大小</td>
<td>父限定，子选择</td>
</tr>
<tr>
<td>match_parent / wrap_content</td>
<td>fill / 固定 / 约束协商</td>
</tr>
<tr>
<td>measure/layout 强耦合</td>
<td>函数式、可组合</td>
</tr>
</tbody>
</table>
<p><strong>关键转变</strong>：</p>
<blockquote>
<p>在 Compose 中，<strong>父掌控一切尺寸边界</strong>。</p>
</blockquote>
<hr>
<h2 id="_2-基础布局容器的测量与放置规则" tabindex="-1">2. 基础布局容器的测量与放置规则 <a class="header-anchor" href="#_2-基础布局容器的测量与放置规则" aria-label="Permalink to “2. 基础布局容器的测量与放置规则”">&#8203;</a></h2>
<h3 id="_2-1-column-的完整规则" tabindex="-1">2.1 Column 的完整规则 <a class="header-anchor" href="#_2-1-column-的完整规则" aria-label="Permalink to “2.1 Column 的完整规则”">&#8203;</a></h3>
<p>Column 的核心行为：<strong>纵向堆叠</strong>。</p>
<p>测量流程：</p>
<ol>
<li>父向 Column 下发约束</li>
<li>Column 将 <em>相同的横向约束</em> 下发给每个子项</li>
<li>子项逐个测量，高度累加</li>
<li>Column 高度 = 子项高度总和（受父约束限制）</li>
</ol>
<p>重要结论：</p>
<ul>
<li>Column 不会&quot;压缩&quot;子项高度</li>
<li>Column 的宽度由父约束决定</li>
</ul>
<hr>
<h3 id="_2-2-row-的完整规则" tabindex="-1">2.2 Row 的完整规则 <a class="header-anchor" href="#_2-2-row-的完整规则" aria-label="Permalink to “2.2 Row 的完整规则”">&#8203;</a></h3>
<p>Row 的核心行为：<strong>横向排布</strong>。</p>
<p>测量流程：</p>
<ol>
<li>父向 Row 下发约束</li>
<li>Row 先测量 <em>非 weight</em> 子项</li>
<li>计算剩余宽度</li>
<li>将剩余宽度按 weight 比例分配给 weight 子项</li>
</ol>
<p>结论：</p>
<ul>
<li>weight 是 <strong>后处理规则</strong></li>
<li>weight 不参与 wrap_content</li>
</ul>
<hr>
<h3 id="_2-3-box-的完整规则" tabindex="-1">2.3 Box 的完整规则 <a class="header-anchor" href="#_2-3-box-的完整规则" aria-label="Permalink to “2.3 Box 的完整规则”">&#8203;</a></h3>
<p>Box 的核心行为：<strong>叠加</strong>。</p>
<p>测量流程：</p>
<ol>
<li>父向 Box 下发约束</li>
<li>Box 将 <em>完整约束</em> 下发给所有子项</li>
<li>子项独立测量</li>
<li>Box 尺寸 = 子项最大尺寸</li>
</ol>
<p>结论：</p>
<ul>
<li>Box 不关心子项顺序（只影响绘制层级）</li>
</ul>
<hr>
<h2 id="_3-modifier-的真实执行模型" tabindex="-1">3. Modifier 的真实执行模型 <a class="header-anchor" href="#_3-modifier-的真实执行模型" aria-label="Permalink to “3. Modifier 的真实执行模型”">&#8203;</a></h2>
<h3 id="_3-1-modifier-的本质" tabindex="-1">3.1 Modifier 的本质 <a class="header-anchor" href="#_3-1-modifier-的本质" aria-label="Permalink to “3.1 Modifier 的本质”">&#8203;</a></h3>
<p>Modifier 不是属性集合，而是：</p>
<blockquote>
<p><strong>一条从外到内的节点装饰执行链</strong></p>
</blockquote>
<p>每一个 Modifier 都可能影响：</p>
<ul>
<li>Constraints</li>
<li>Measure 结果</li>
<li>Placement</li>
<li>Draw</li>
<li>Input</li>
</ul>
<hr>
<h3 id="_3-2-modifier-顺序的真实含义" tabindex="-1">3.2 Modifier 顺序的真实含义 <a class="header-anchor" href="#_3-2-modifier-顺序的真实含义" aria-label="Permalink to “3.2 Modifier 顺序的真实含义”">&#8203;</a></h3>
<p>Modifier 链：</p>
<ul>
<li>从上到下声明</li>
<li>从外到内执行</li>
</ul>
<p>这意味着：</p>
<ul>
<li>前面的 Modifier 会改变后面 Modifier 的执行环境</li>
</ul>
<p>顺序不是&quot;写法差异&quot;，而是<strong>逻辑差异</strong>。</p>
<hr>
<h2 id="_4-尺寸-modifier-的约束行为" tabindex="-1">4. 尺寸 Modifier 的约束行为 <a class="header-anchor" href="#_4-尺寸-modifier-的约束行为" aria-label="Permalink to “4. 尺寸 Modifier 的约束行为”">&#8203;</a></h2>
<h3 id="_4-1-fill-系列" tabindex="-1">4.1 fill 系列 <a class="header-anchor" href="#_4-1-fill-系列" aria-label="Permalink to “4.1 fill 系列”">&#8203;</a></h3>
<p>fillMaxWidth / fillMaxHeight：</p>
<ul>
<li>含义：使用父提供的最大约束</li>
<li>不等价于 match_parent</li>
</ul>
<p>生效条件：</p>
<ul>
<li>父必须提供有限 max 约束</li>
</ul>
<hr>
<h3 id="_4-2-固定尺寸" tabindex="-1">4.2 固定尺寸 <a class="header-anchor" href="#_4-2-固定尺寸" aria-label="Permalink to “4.2 固定尺寸”">&#8203;</a></h3>
<p>size / width / height：</p>
<ul>
<li>会直接修改 Constraints</li>
<li>优先级高于 fill</li>
</ul>
<p>优先级规则：</p>
<div class="language- line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre><!--::markdown-it-async::s17458tk23suu3v9v5jla::--><code>size &gt; width/height &gt; fill &gt; wrap</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><hr>
<h3 id="_4-3-wrapcontent-的陷阱" tabindex="-1">4.3 wrapContent 的陷阱 <a class="header-anchor" href="#_4-3-wrapcontent-的陷阱" aria-label="Permalink to “4.3 wrapContent 的陷阱”">&#8203;</a></h3>
<p>wrapContentWidth / wrapContentHeight：</p>
<ul>
<li>允许子节点突破父的最小约束</li>
<li>常用于 <code>Text(Modifier.wrapContentWidth())</code> 实现基线对齐</li>
</ul>
<hr>
<h2 id="_5-padding-的测量级影响" tabindex="-1">5. padding 的测量级影响 <a class="header-anchor" href="#_5-padding-的测量级影响" aria-label="Permalink to “5. padding 的测量级影响”">&#8203;</a></h2>
<p>padding 的行为不是绘制，而是：</p>
<ul>
<li><strong>减少子节点可用测量空间</strong></li>
</ul>
<p>执行过程：</p>
<ol>
<li>外层接收父约束</li>
<li>扣除 padding 后传给内容</li>
<li>内容测量完成后再加回 padding</li>
</ol>
<p>结论：</p>
<blockquote>
<p>padding 是测量阶段的参与者</p>
</blockquote>
<hr>
<h2 id="_6-weight-的底层工作方式" tabindex="-1">6. weight 的底层工作方式 <a class="header-anchor" href="#_6-weight-的底层工作方式" aria-label="Permalink to “6. weight 的底层工作方式”">&#8203;</a></h2>
<p>weight 的本质：</p>
<ul>
<li>剩余空间分配器</li>
</ul>
<p>执行顺序：</p>
<ol>
<li>测量非 weight 子项</li>
<li>计算剩余空间</li>
<li>按权重比例分配</li>
<li>强制子项使用分配尺寸</li>
</ol>
<p>注意：</p>
<ul>
<li>weight 子项不再 wrap_content</li>
</ul>
<hr>
<h2 id="_7-clickable-background-的顺序语义" tabindex="-1">7. clickable / background 的顺序语义 <a class="header-anchor" href="#_7-clickable-background-的顺序语义" aria-label="Permalink to “7. clickable / background 的顺序语义”">&#8203;</a></h2>
<p>clickable 的作用区域：</p>
<ul>
<li>等于当前 Modifier 链处理完成后的区域</li>
</ul>
<p>因此：</p>
<ul>
<li>clickable 放在 padding 前后，点击区域不同</li>
<li>background 同理</li>
</ul>
<hr>
<h2 id="_8-modifier-与重组、性能的关系" tabindex="-1">8. Modifier 与重组、性能的关系 <a class="header-anchor" href="#_8-modifier-与重组、性能的关系" aria-label="Permalink to “8. Modifier 与重组、性能的关系”">&#8203;</a></h2>
<ul>
<li>Modifier 是不可变对象</li>
<li>每次重组都会重新创建 Modifier 链</li>
<li>Modifier 本身应无状态</li>
</ul>
<p>错误模式：</p>
<ul>
<li>在 Modifier 中承载业务逻辑</li>
<li>在 Modifier 中存储状态</li>
</ul>
<hr>
<h2 id="_9-工程级布局设计原则" tabindex="-1">9. 工程级布局设计原则 <a class="header-anchor" href="#_9-工程级布局设计原则" aria-label="Permalink to “9. 工程级布局设计原则”">&#8203;</a></h2>
<ol>
<li><strong>外层 Modifier</strong>：布局与约束（size、padding、fill 等）</li>
<li><strong>内层 Modifier</strong>：绘制与交互（background、clickable、border 等）</li>
<li><strong>长 Modifier 链必须拆分</strong>：超过 3-4 个 Modifier 应提取为扩展函数</li>
<li><strong>顺序写错 = 潜在 Bug</strong>：每个工程都应建立团队的 Modifier 顺序规范</li>
<li><strong>性能敏感处避免重复计算</strong>：对于频繁重组的组件，考虑 Modifier.composed 的缓存策略</li>
</ol>
<hr>
<h2 id="_10-实际案例解析" tabindex="-1">10. 实际案例解析 <a class="header-anchor" href="#_10-实际案例解析" aria-label="Permalink to “10. 实际案例解析”">&#8203;</a></h2>
<h3 id="案例1-为什么这个-text-看起来没居中" tabindex="-1">案例1：为什么这个 Text 看起来没居中？ <a class="header-anchor" href="#案例1-为什么这个-text-看起来没居中" aria-label="Permalink to “案例1：为什么这个 Text 看起来没居中？”">&#8203;</a></h3>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::xdxhvucr66h3xfx6p2sw7h::--><code>Box(modifier = Modifier.fillMaxSize()) {
    Text(
        text = &quot;Hello&quot;,
        modifier = Modifier.fillMaxWidth()
    )
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p><strong>分析</strong>：fillMaxWidth 导致 Text 占满宽度，无法居中。解决方案：使用 <code>wrapContentWidth(Alignment.CenterHorizontally)</code>。</p>
<h3 id="案例2-padding-与-size-的顺序" tabindex="-1">案例2：padding 与 size 的顺序 <a class="header-anchor" href="#案例2-padding-与-size-的顺序" aria-label="Permalink to “案例2：padding 与 size 的顺序”">&#8203;</a></h3>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::pd6lof8ky3id6nphlfro::--><code>// 版本A
Box(Modifier.size(100.dp).padding(20.dp).background(Color.Red))
// 版本B  
Box(Modifier.padding(20.dp).size(100.dp).background(Color.Red))</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p><strong>差异</strong>：</p>
<ul>
<li>A：内容区域 100dp，加 padding 后总尺寸 140dp</li>
<li>B：先有 padding，再设置 size，总尺寸仍为 100dp，但内容区域只剩 60dp</li>
</ul>
<h3 id="案例3-row-中的-weight-陷阱" tabindex="-1">案例3：Row 中的 weight 陷阱 <a class="header-anchor" href="#案例3-row-中的-weight-陷阱" aria-label="Permalink to “案例3：Row 中的 weight 陷阱”">&#8203;</a></h3>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::wkl7dbuk8brkklea0nvor::--><code>Row {
    Box(Modifier.weight(1f).background(Color.Red))
    Box(Modifier.width(100.dp).background(Color.Blue))
    Box(Modifier.weight(1f).background(Color.Green))
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p><strong>行为</strong>：蓝盒固定 100dp，剩余空间由红盒和绿盒平分。</p>
<hr>
<h2 id="_11-调试与验证工具" tabindex="-1">11. 调试与验证工具 <a class="header-anchor" href="#_11-调试与验证工具" aria-label="Permalink to “11. 调试与验证工具”">&#8203;</a></h2>
<h3 id="_11-1-modifier-debuginspectorinfo" tabindex="-1">11.1 Modifier.debugInspectorInfo <a class="header-anchor" href="#_11-1-modifier-debuginspectorinfo" aria-label="Permalink to “11.1 Modifier.debugInspectorInfo”">&#8203;</a></h3>
<p>查看 Modifier 链的实际信息：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::vlxfw8p3kx07x9m14gh36x::--><code>modifier.debugInspectorInfo { /* 调试信息 */ }</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h3 id="_11-2-layout-inspector" tabindex="-1">11.2 Layout Inspector <a class="header-anchor" href="#_11-2-layout-inspector" aria-label="Permalink to “11.2 Layout Inspector”">&#8203;</a></h3>
<p>Android Studio 的 Compose Layout Inspector 可以：</p>
<ul>
<li>查看 Constraints 传递</li>
<li>查看实际测量尺寸</li>
<li>分析 Modifier 链</li>
</ul>
<h3 id="_11-3-自定义-modifier-调试" tabindex="-1">11.3 自定义 Modifier 调试 <a class="header-anchor" href="#_11-3-自定义-modifier-调试" aria-label="Permalink to “11.3 自定义 Modifier 调试”">&#8203;</a></h3>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::ukuv753tpwg3p4djfeddvo::--><code>fun Modifier.debugConstraints(tag: String) = this.then(
    object : LayoutModifier {
        override fun MeasureScope.measure(
            measurable: Measurable,
            constraints: Constraints
        ): MeasureResult {
            println(&quot;[$tag] constraints: $constraints&quot;)
            val placeable = measurable.measure(constraints)
            println(&quot;[$tag] measured size: ${placeable.width}x${placeable.height}&quot;)
            return layout(placeable.width, placeable.height) {
                placeable.placeRelative(0, 0)
            }
        }
    }
)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><h2 id="_12-布局系统的-单次测量-定律-single-pass" tabindex="-1">12 布局系统的“单次测量”定律 (Single Pass) <a class="header-anchor" href="#_12-布局系统的-单次测量-定律-single-pass" aria-label="Permalink to “12 布局系统的“单次测量”定律 (Single Pass)”">&#8203;</a></h2>
<p>这是 <code>Compose</code> 优于 <code>View</code> 系统最核心的性能基石。</p>
<ul>
<li>物理规则：在一次重组循环中，<code>Compose</code> 规定每个节点只能被测量一次。</li>
<li>对比 <code>View</code> 模式：<code>View</code> 系统中 <code>RelativeLayout</code> 或 <code>LinearLayout</code> 使用 <code>weight</code> 时，经常会触发多次 <code>onMeasure</code>，导致性能随层级深度指数级下降（<span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>O</mi><mo>(</mo><msup><mn>2</mn><mi>n</mi></msup><mo>)</mo></mrow><annotation encoding="application/x-tex">O(2^n)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="strut" style="height:0.75em;"></span><span class="strut bottom" style="height:1em;vertical-align:-0.25em;"></span><span class="base textstyle uncramped"><span class="mord mathit" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord"><span class="mord mathrm">2</span><span class="vlist"><span style="top:-0.363em;margin-right:0.05em;"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span><span class="reset-textstyle scriptstyle uncramped"><span class="mord mathit">n</span></span></span><span class="baseline-fix"><span class="fontsize-ensurer reset-size5 size5"><span style="font-size:0em;">​</span></span>​</span></span></span><span class="mclose">)</span></span></span></span>）。</li>
<li><code>Compose</code> 的强制性：如果你尝试在自定义布局中对同一个子项调用两次 <code>measure()</code>，<code>Compose</code> 会直接抛出运行时异常。</li>
</ul>
<h2 id="_13-alignment-vs-arrangement" tabindex="-1">13 Alignment vs Arrangement <a class="header-anchor" href="#_13-alignment-vs-arrangement" aria-label="Permalink to “13 Alignment vs Arrangement”">&#8203;</a></h2>
<p><code>Alignment</code>：单个子项, 可用于 Box / Column / Row, 子项在交叉轴（Cross Axis）上的对齐方式
<code>Arrangement</code>：全部子项, 可用于 Column / Row, &quot;子项在主轴（Main Axis）上的分布方式（SpaceBetween, Center等）&quot;</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose 状态管理详解]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Compose/ComposeStateManagement.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Compose/ComposeStateManagement.html</guid>
            <pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[在 Jetpack Compose 中，状态 (State) 是驱动 UI 更新的核心。与传统的 View 体系不同，Compose 是声明式的：UI 是状态的函数。当应用的状态发生变化时，Compose 会重新执行受影响的 Composable 函数（这个过?]]></description>
            <content:encoded><![CDATA[<h1 id="jetpack-compose-状态管理详解" tabindex="-1">Jetpack Compose 状态管理详解 <a class="header-anchor" href="#jetpack-compose-状态管理详解" aria-label="Permalink to “Jetpack Compose 状态管理详解”">&#8203;</a></h1>
<p>在 Jetpack Compose 中，<strong>状态 (State)</strong> 是驱动 UI 更新的核心。与传统的 View 体系不同，Compose 是声明式的：UI 是状态的函数。当应用的状态发生变化时，Compose 会重新执行受影响的 Composable 函数（这个过程称为 <strong>重组 (Recomposition)</strong>），从而更新 UI。</p>
<p>掌握状态管理是成为 Compose 高级开发者的必经之路。本文将深入探讨 Compose 状态管理的各个方面。</p>
<h2 id="_1-什么是状态-state" tabindex="-1">1. 什么是状态 (State)？ <a class="header-anchor" href="#_1-什么是状态-state" aria-label="Permalink to “1. 什么是状态 (State)？”">&#8203;</a></h2>
<p>简单来说，状态就是随时间变化的数据。在 Compose 中，当状态发生变化时，UI 会自动刷新。</p>
<h3 id="mutablestate-t" tabindex="-1"><code>MutableState&lt;T&gt;</code> <a class="header-anchor" href="#mutablestate-t" aria-label="Permalink to “MutableState&lt;T&gt;”">&#8203;</a></h3>
<p>Compose 使用 <code>MutableState&lt;T&gt;</code> 来存储可变状态。它是 Compose 运行时集成的观察者模式实现。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::26w6qwx77ooz3xnibni5vb::--><code>interface MutableState&lt;T&gt; : State&lt;T&gt; {
    override var value: T
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>当 <code>value</code> 发生变化时，读取该 <code>value</code> 的 Composable 函数会自动进行重组。</p>
<h3 id="remember" tabindex="-1"><code>remember</code> <a class="header-anchor" href="#remember" aria-label="Permalink to “remember”">&#8203;</a></h3>
<p>Composable 函数可能会被频繁调用（重组）。如果在函数内部直接定义变量，每次重组时变量都会被重置。为了在重组之间保存状态，我们需要使用 <code>remember</code>。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::kijs1fkm9vj87ce1fehyb::--><code>@Composable
fun Counter() {
    // 使用 remember 保存状态，防止重组时重置
    // mutableStateOf 创建一个可观察的状态
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text(&quot;Count: $count&quot;)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><ul>
<li><code>mutableStateOf(v)</code>: 创建一个初始值为 <code>v</code> 的 <code>MutableState</code>。</li>
<li><code>remember { ... }</code>: 在内存中缓存计算结果，直到 Composable 离开组合树。</li>
</ul>
<h2 id="_2-状态提升-state-hoisting" tabindex="-1">2. 状态提升 (State Hoisting) <a class="header-anchor" href="#_2-状态提升-state-hoisting" aria-label="Permalink to “2. 状态提升 (State Hoisting)”">&#8203;</a></h2>
<p><strong>状态提升</strong> 是一种设计模式，通过将状态移动到调用者（父组件）中，使子组件变为 <strong>无状态 (Stateless)</strong> 的。</p>
<h3 id="为什么要状态提升" tabindex="-1">为什么要状态提升？ <a class="header-anchor" href="#为什么要状态提升" aria-label="Permalink to “为什么要状态提升？”">&#8203;</a></h3>
<ol>
<li><strong>单一数据源 (Single Source of Truth)</strong>：状态只在一个地方管理，减少 bug。</li>
<li><strong>可复用性</strong>：无状态组件更容易复用和测试。</li>
<li><strong>解耦</strong>：UI 逻辑与业务逻辑分离。</li>
</ol>
<h3 id="如何做" tabindex="-1">如何做？ <a class="header-anchor" href="#如何做" aria-label="Permalink to “如何做？”">&#8203;</a></h3>
<p>通常将状态变量替换为两个参数：</p>
<ol>
<li><strong>value: T</strong>：当前显示的值。</li>
<li><strong>onValueChange: (T) -&gt; Unit</strong>：请求修改值的事件回调。</li>
</ol>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::90s2eqrap8ps9iyjq37fgs::--><code>// 有状态组件 (Stateful) - 内部管理状态，难以复用
@Composable
fun StatefulCounter() {
    var count by remember { mutableStateOf(0) }
    StatelessCounter(count = count, onIncrement = { count++ })
}

// 无状态组件 (Stateless) - 纯 UI 展示，易于复用和测试
@Composable
fun StatelessCounter(
    count: Int, 
    onIncrement: () -&gt; Unit
) {
    Button(onClick = onIncrement) {
        Text(&quot;Count: $count&quot;)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><h2 id="_3-remember-vs-remembersaveable" tabindex="-1">3. <code>remember</code> vs <code>rememberSaveable</code> <a class="header-anchor" href="#_3-remember-vs-remembersaveable" aria-label="Permalink to “3. remember vs rememberSaveable”">&#8203;</a></h2>
<p><code>remember</code> 可以跨重组保存状态，但如果发生 <strong>配置变更</strong>（如屏幕旋转、深色模式切换）或进程被系统回收，<code>remember</code> 中的状态会丢失。</p>
<p>如果你希望状态在这些情况下依然保留，应使用 <code>rememberSaveable</code>。它会将状态保存到 <code>Bundle</code> 中。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::dieb5v508kfmcsytwn3zw::--><code>@Composable
fun SurvivingCounter() {
    // 屏幕旋转后 count 值依然保留
    var count by rememberSaveable { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text(&quot;Count: $count&quot;)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>对于自定义对象，你需要提供 <code>Saver</code> 来告诉 Compose 如何序列化和反序列化该对象。</p>
<h2 id="_4-状态持有者-state-holders-与-viewmodel" tabindex="-1">4. 状态持有者 (State Holders) 与 ViewModel <a class="header-anchor" href="#_4-状态持有者-state-holders-与-viewmodel" aria-label="Permalink to “4. 状态持有者 (State Holders) 与 ViewModel”">&#8203;</a></h2>
<p>对于简单的 UI 逻辑，可以使用 <code>remember</code> 管理状态。但随着逻辑变复杂，应将状态管理逻辑提取到 <strong>状态持有者</strong> 类或 <strong>ViewModel</strong> 中。</p>
<h3 id="使用-viewmodel-管理状态" tabindex="-1">使用 ViewModel 管理状态 <a class="header-anchor" href="#使用-viewmodel-管理状态" aria-label="Permalink to “使用 ViewModel 管理状态”">&#8203;</a></h3>
<p>ViewModel 是管理屏幕级 UI 状态的首选方案，它能感知生命周期并在配置变更后存活。</p>
<p>推荐在 ViewModel 中暴露 <code>StateFlow</code> 或 <code>LiveData</code>，并在 Compose 中将其转换为 <code>State</code>。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::l1lrbz7fgaprf5p01go9f::--><code>class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(MyUiState())
    val uiState: StateFlow&lt;MyUiState&gt; = _uiState.asStateFlow()

    fun updateName(newName: String) {
        _uiState.update { it.copy(name = newName) }
    }
}

data class MyUiState(
    val name: String = &quot;&quot;,
    val isLoading: Boolean = false
)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>在 Composable 中使用：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::zw8mmunuonapvhxl91eim::--><code>@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    // collectAsStateWithLifecycle 需要依赖 androidx.lifecycle:lifecycle-runtime-compose
    // 它是生命周期感知的，比 collectAsState 更安全（应用后台时停止收集）
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    TextField(
        value = uiState.name,
        onValueChange = { viewModel.updateName(it) }
    )
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><h2 id="_5-高级状态-api" tabindex="-1">5. 高级状态 API <a class="header-anchor" href="#_5-高级状态-api" aria-label="Permalink to “5. 高级状态 API”">&#8203;</a></h2>
<h3 id="derivedstateof" tabindex="-1"><code>derivedStateOf</code> <a class="header-anchor" href="#derivedstateof" aria-label="Permalink to “derivedStateOf”">&#8203;</a></h3>
<p>如果一个状态是从另一个状态计算得来的，并且计算代价较大，或者你希望减少不必要的重组，可以使用 <code>derivedStateOf</code>。</p>
<p>它会将多个状态转换为一个状态，并且只有当 <strong>计算结果发生变化</strong> 时，才会触发读取该状态的 Composable 重组。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::c4h2zibg08dkyt9udb3gxg::--><code>val listState = rememberLazyListState()

// 只有当 showButton 的值从 true 变 false 或反之时，才会触发重组
// 如果直接使用 listState.firstVisibleItemIndex &gt; 0，每次滚动都会触发重组
val showButton by remember {
    derivedStateOf {
        listState.firstVisibleItemIndex &gt; 0
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><h3 id="sideeffect-与副作用-api" tabindex="-1"><code>SideEffect</code> 与副作用 API <a class="header-anchor" href="#sideeffect-与副作用-api" aria-label="Permalink to “SideEffect 与副作用 API”">&#8203;</a></h3>
<p>状态改变通常会触发 UI 更新，但有时我们需要在状态改变时执行非 UI 操作（副作用），如打日志、启动协程、显示 Toast 等。</p>
<ul>
<li><strong><code>LaunchedEffect(key)</code></strong>: 当 <code>key</code> 变化时，启动一个协程。适合执行挂起函数（如网络请求、动画）。</li>
<li><strong><code>DisposableEffect(key)</code></strong>: 当 <code>key</code> 变化或组件销毁时，执行清理操作（如注册/注销监听器）。</li>
<li><strong><code>SideEffect</code></strong>: 每次重组成功后执行。适合将 Compose 状态同步到非 Compose 代码系统。</li>
</ul>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::spyz73va8dpxgfxpj9vhz::--><code>// 示例：当 snackbarHostState 变化时显示 Snackbar
LaunchedEffect(snackbarHostState) {
    snackbarHostState.showSnackbar(&quot;Hello Compose&quot;)
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h2 id="_6-compositionlocal" tabindex="-1">6. CompositionLocal <a class="header-anchor" href="#_6-compositionlocal" aria-label="Permalink to “6. CompositionLocal”">&#8203;</a></h2>
<p>通常状态通过参数层层向下传递。但对于一些全局数据（如主题颜色、Context、登录用户信息），层层传递非常繁琐。</p>
<p><code>CompositionLocal</code> 允许我们隐式地在组合树中传递数据。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::zdbkf4y2fzvrio3uos15a::--><code>// 定义一个 CompositionLocal
val LocalActiveUser = compositionLocalOf&lt;User&gt; { error(&quot;No user found!&quot;) }

@Composable
fun App() {
    val user = User(&quot;Alice&quot;)
    // 提供值
    CompositionLocalProvider(LocalActiveUser provides user) {
        Descendant()
    }
}

@Composable
fun Descendant() {
    // 读取值，无需通过参数传递
    val user = LocalActiveUser.current
    Text(&quot;User: ${user.name}&quot;)
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p><strong>注意</strong>：不要滥用 <code>CompositionLocal</code>，它会使数据流向变得不透明。只用于确实是“隐式环境”的数据。</p>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to “总结”">&#8203;</a></h2>
<ol>
<li><strong>单一数据源</strong>：尽量将状态提升到父组件或 ViewModel。</li>
<li><strong><code>remember</code> vs <code>ViewModel</code></strong>：UI 逻辑用 <code>remember</code>，业务逻辑用 ViewModel。</li>
<li><strong>不可变性</strong>：状态对象最好是不可变的（Immutable），修改状态时创建新对象（如 <code>data class</code> 的 <code>copy</code>）。</li>
<li><strong>生命周期感知</strong>：使用 <code>collectAsStateWithLifecycle</code> 收集 Flow。</li>
</ol>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[JobScheduler]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/JobScheduler.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/JobScheduler.html</guid>
            <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[JobScheduler 是 Android 5.0（API 21）引入的系统级后台任务调度 API，旨在帮助开发者以低功耗、系统友好的方式执行后台任务。它提供条件触发、持久化、系统统一调度等机制，是 Google 在 Doze 模式、App Standby?]]></description>
            <content:encoded><![CDATA[<h1 id="jobscheduler" tabindex="-1">JobScheduler <a class="header-anchor" href="#jobscheduler" aria-label="Permalink to “JobScheduler”">&#8203;</a></h1>
<h2 id="_1-什么是jobscheduler" tabindex="-1">1. 什么是JobScheduler <a class="header-anchor" href="#_1-什么是jobscheduler" aria-label="Permalink to “1. 什么是JobScheduler”">&#8203;</a></h2>
<p><code>JobScheduler</code> 是 Android 5.0（API 21）引入的系统级后台任务调度 API，旨在帮助开发者以低功耗、系统友好的方式执行后台任务。它提供条件触发、持久化、系统统一调度等机制，是 Google 在 Doze 模式、App Standby 等电量管理策略下推荐使用的后台执行方案。</p>
<h2 id="_2-为什么使用-jobscheduler" tabindex="-1">2.  为什么使用 JobScheduler？ <a class="header-anchor" href="#_2-为什么使用-jobscheduler" aria-label="Permalink to “2.  为什么使用 JobScheduler？”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>传统方案</th>
<th>问题</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AlarmManager</code> + <code>Service</code></td>
<td>精度差、执行时机不明确、难以管理任务重试</td>
</tr>
<tr>
<td>常驻 Service</td>
<td>高耗电，容易被系统限制或杀死</td>
</tr>
<tr>
<td>Thread / HandlerThread</td>
<td>生命周期不可控、受 App 状态影响</td>
</tr>
</tbody>
</table>
<p><strong>JobScheduler的优势</strong></p>
<ol>
<li>系统统一调度，降低电量消耗</li>
<li>支持任务约束条件（网络/充电/空闲等）</li>
<li>支持任务重试与失败恢复</li>
<li>支持持久化（设备重启后仍可恢复）</li>
<li>系统自动拉起 App 并调用任务执行（无须常驻服务）</li>
</ol>
<h2 id="_3-jobscheduler-架构与工作机制" tabindex="-1">3. JobScheduler 架构与工作机制 <a class="header-anchor" href="#_3-jobscheduler-架构与工作机制" aria-label="Permalink to “3. JobScheduler 架构与工作机制”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>组件</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>JobInfo</code></td>
<td>描述任务，包括执行条件、延迟策略等</td>
</tr>
<tr>
<td><code>JobService</code></td>
<td>接收调度器回调，执行任务逻辑</td>
</tr>
<tr>
<td><code>JobScheduler</code></td>
<td>提供任务调度 API，由 App 侧调用</td>
</tr>
<tr>
<td><code>JobSchedulerService</code></td>
<td>系统服务，统一管理 Job 条件监听与调度执行</td>
</tr>
</tbody>
</table>
<h2 id="_4-工作流程" tabindex="-1">4. 工作流程 <a class="header-anchor" href="#_4-工作流程" aria-label="Permalink to “4. 工作流程”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-132" id="tab-133" checked><label data-title="txt" for="tab-133">txt</label></div><div class="blocks">
<div class="language-scss active line-numbers-mode" data-lang="scss"><button title="Copy Code" class="copy"></button><span class="lang">SCSS</span><pre><!--::markdown-it-async::88m5dhy1cpkbi7w5edhkc7::--><code>JobScheduler.schedule(JobInfo)
    ↓
系统保存 JobInfo（JobStore）
    ↓
监听执行条件（如网络状态、是否充电）
    ↓
条件满足，唤醒 App（即使已被杀）
    ↓
系统自动调用 JobService.onStartJob()
    ↓
任务异步执行，完成后调用 jobFinished()</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div></div></div>
<h2 id="_5-使用方法" tabindex="-1">5. 使用方法 <a class="header-anchor" href="#_5-使用方法" aria-label="Permalink to “5. 使用方法”">&#8203;</a></h2>
<ol>
<li><strong>JobInfo：任务描述</strong></li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-145" id="tab-146" checked><label data-title="txt" for="tab-146">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::dpvnixem9mgpw1965c4rl::--><code>new JobInfo.Builder(jobId, new ComponentName(context, MyJobService.class))
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // 需要 WiFi
    .setRequiresCharging(true) // 设备充电时执行
    .setRequiresDeviceIdle(false) // 空闲时执行
    .setMinimumLatency(3000) // 延迟至少3秒
    .setOverrideDeadline(10000) // 最迟10秒必须执行
    .setPersisted(true) // 重启后仍保留
    .setBackoffCriteria(3000, JobInfo.BACKOFF_POLICY_EXPONENTIAL) // 重试策略
    .build();</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div></div></div>
<ol start="2">
<li><strong>支持的网络类型</strong></li>
</ol>
<p>NETWORK_TYPE_NONE（无网络要求）
NETWORK_TYPE_ANY（任意网络）
NETWORK_TYPE_UNMETERED（非计费网络，如 Wi-Fi）
NETWORK_TYPE_NOT_ROAMING（非漫游）
NETWORK_TYPE_CELLULAR（仅蜂窝）</p>
<ol start="3">
<li><strong>通过 <code>MyJobService</code> 执行任务</strong></li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-165" id="tab-166" checked><label data-title="txt" for="tab-166">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::xbi558o3dcw26oefmu4i::--><code>public class MyJobService extends JobService {
    @Override
    public boolean onStartJob(JobParameters params) {
        new Thread(() -&gt; {
            // 执行任务逻辑
            jobFinished(params, false); // false 表示任务完成，不重试
        }).start();
        return true; // 异步任务
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return true; // true 表示系统会尝试重试任务
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div></div></div>
<ol start="4">
<li><strong>调度器使用</strong></li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-175" id="tab-176" checked><label data-title="txt" for="tab-176">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::x8dok6794jnhuyh489slnb::--><code>JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
scheduler.schedule(jobInfo);</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div></div></div>
<ul>
<li><code>schedule(JobInfo)</code>：提交任务</li>
<li><code>cancel(jobId)</code>：取消任务</li>
<li><code>cancelAll()</code>：取消所有任务</li>
<li><code>getAllPendingJobs()</code>：获取当前所有挂起任务</li>
</ul>
<h2 id="_6-系统行为与限制说明" tabindex="-1">6. 系统行为与限制说明 <a class="header-anchor" href="#_6-系统行为与限制说明" aria-label="Permalink to “6. 系统行为与限制说明”">&#8203;</a></h2>
<p>与 <code>Doze</code> 模式的关系
在 <code>Doze</code> 模式下，只有通过 <code>JobScheduler</code> 提交的任务才能被系统延迟批量调度（<code>Idle maintenance window</code>）。
<strong>频繁提交 Job 不会立即执行，系统会智能合并执行以省电。</strong></p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[Channel]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/Channel.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/Channel.html</guid>
            <pubDate>Mon, 21 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Channel 是一种热流（Hot Stream） 的通信机制，类似于Go语言中的channel。它允许协程之间进行数据传递和同步。Channel可以被多个生产者和消费者共享，支持双向通信。]]></description>
            <content:encoded><![CDATA[<h1 id="channel" tabindex="-1">Channel <a class="header-anchor" href="#channel" aria-label="Permalink to “Channel”">&#8203;</a></h1>
<h2 id="_1-什么是channel" tabindex="-1">1. 什么是Channel <a class="header-anchor" href="#_1-什么是channel" aria-label="Permalink to “1. 什么是Channel”">&#8203;</a></h2>
<p><code>Channel</code> 是一种热流（Hot Stream） 的通信机制，类似于Go语言中的channel。它允许协程之间进行数据传递和同步。Channel可以被多个生产者和消费者共享，支持<strong>双向通信</strong>。</p>
<p>其特点为：</p>
<ol>
<li><strong>热流</strong>：一旦创建，即使没有收集器，Channel也可以开始发送数据。</li>
<li><strong>阻塞操作</strong>：发送到无缓冲的Channel时会阻塞，直到有消费者接收数据；同样，从空的Channel接收数据也会阻塞。</li>
<li><strong>生命周期管理</strong>：需要显式关闭Channel以通知消费者没有更多数据。</li>
</ol>
<h2 id="_2-使用场景" tabindex="-1">2. 使用场景 <a class="header-anchor" href="#_2-使用场景" aria-label="Permalink to “2. 使用场景”">&#8203;</a></h2>
<ol>
<li>需要在<strong>多个协程之间安全地传递数据时</strong>。</li>
<li>适合实现<strong>生产者-消费者模式</strong>。</li>
<li>数据流是<strong>有限的</strong>、明确结束的情况。</li>
</ol>
<h2 id="_3-使用方法" tabindex="-1">3. 使用方法 <a class="header-anchor" href="#_3-使用方法" aria-label="Permalink to “3. 使用方法”">&#8203;</a></h2>
<ol>
<li>简单示例</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-59" id="tab-60" checked><label data-title="txt" for="tab-60">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::5ik4j1lwl7i6wkv3ndclbs::--><code>import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel

fun main() = runBlocking {
    val channel = Channel&lt;Int&gt;()

    // 生产者协程
    launch {
        for (i in 1..5) {
            println(&quot;Sending $i&quot;)
            channel.send(i)
        }
        channel.close() // 显式关闭Channel
    }

    // 消费者协程
    launch {
        for (value in channel) { // 遍历Channel
            println(&quot;Received $value&quot;)
        }
    }
}

// 输出
// Sending 1
// Received 1
// Sending 2
// Received 2
// Sending 3
// Received 3
// Sending 4
// Received 4
// Sending 5
// Received 5</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br></div></div></div></div>
<ol start="2">
<li>Buffered Channels</li>
</ol>
<p>默认情况下，Channel是无缓冲的，这意味着发送操作会挂起直到有接收者准备就绪。可以通过指定容量创建一个带缓冲的通道，从而允许发送方在没有立即对应的接收方时继续执行。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-72" id="tab-73" checked><label data-title="txt" for="tab-73">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::262dbwqdep4alexezdeowa::--><code>val bufferedChannel = Channel&lt;Int&gt;(capacity = 5)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></div></div>
<ol start="3">
<li>Conflated Channel</li>
</ol>
<p>对于只需要最新值的应用场景，可以使用<code>ConflatedChannel</code>（现在可以通过Channel(Conflated)创建）。这种类型的通道只保留最新的元素，当新的元素被发送时，旧的未被消费的元素将被覆盖。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-85" id="tab-86" checked><label data-title="txt" for="tab-86">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::uer7cv4a68rzflanekcxne::--><code>val conflatedChannel = Channel&lt;Int&gt;(Channel.CONFLATED)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></div></div>
<ol start="4">
<li>Rendezvous Channel</li>
</ol>
<p>这是默认的无缓冲通道行为，发送和接收必须配对发生。如果尝试在一个Rendezvous通道上发送而没有相应的接收等待，则发送将被挂起。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-98" id="tab-99" checked><label data-title="txt" for="tab-99">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::6rixdtnzqy8wegg2txza9l::--><code>val rendezvousChannel = Channel&lt;Int&gt;()</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></div></div>
<ol start="5">
<li></li>
</ol>
<h2 id="_4-channel-和-flow-的对比" tabindex="-1">4. Channel 和 Flow 的对比 <a class="header-anchor" href="#_4-channel-和-flow-的对比" aria-label="Permalink to “4. Channel 和 Flow 的对比”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>特性</th>
<th>Channel</th>
<th>Flow</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>类型</strong></td>
<td>热流（Hot Stream）</td>
<td>冷流（Cold Stream）</td>
</tr>
<tr>
<td><strong>生命周期</strong></td>
<td>需要显式关闭</td>
<td>自动管理，无需显式关闭</td>
</tr>
<tr>
<td><strong>阻塞行为</strong></td>
<td>可能阻塞（取决于是否有缓冲区）</td>
<td>非阻塞，基于挂起函数</td>
</tr>
<tr>
<td><strong>适用场景</strong></td>
<td>协程间通信，生产者-消费者模式</td>
<td>异步数据流处理，复杂操作管道</td>
</tr>
<tr>
<td><strong>是否可重用</strong></td>
<td>不可重用，关闭后不能再使用</td>
<td>每次收集都会重新运行</td>
</tr>
<tr>
<td><strong>操作符支持</strong></td>
<td>支持有限的操作符</td>
<td>提供丰富的操作符（如<code>map</code>、<code>filter</code>等）</td>
</tr>
<tr>
<td><strong>性能开销</strong></td>
<td>较高，因为需要维护内部队列和状态</td>
<td>较低，基于挂起函数</td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[协程上下文]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/%E5%8D%8F%E7%A8%8B%E4%B8%8A%E4%B8%8B%E6%96%87.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/协程上下文.html</guid>
            <pubDate>Mon, 21 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[CoroutineContext 是一个协程的运行环境容器，决定协程的生命周期、运行线程、错误处理机制等核心行为。它由若干个元素组成。]]></description>
            <content:encoded><![CDATA[<h1 id="协程上下文" tabindex="-1">协程上下文 <a class="header-anchor" href="#协程上下文" aria-label="Permalink to “协程上下文”">&#8203;</a></h1>
<h2 id="一、什么是协程上下文" tabindex="-1">一、什么是协程上下文？ <a class="header-anchor" href="#一、什么是协程上下文" aria-label="Permalink to “一、什么是协程上下文？”">&#8203;</a></h2>
<p><code>CoroutineContext</code> 是一个协程的运行环境容器，决定协程的生命周期、运行线程、错误处理机制等核心行为。它由若干个元素组成。</p>
<h3 id="常见元素及其作用" tabindex="-1">常见元素及其作用： <a class="header-anchor" href="#常见元素及其作用" aria-label="Permalink to “常见元素及其作用：”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>元素类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Job</code> / <code>SupervisorJob</code></td>
<td>控制协程的生命周期、结构化并发</td>
</tr>
<tr>
<td><code>CoroutineDispatcher</code></td>
<td>决定协程在哪个线程/线程池运行（如：<code>Main</code>, <code>IO</code>, <code>Default</code>）</td>
</tr>
<tr>
<td><code>CoroutineName</code></td>
<td>用于日志标识与调试</td>
</tr>
<tr>
<td><code>CoroutineExceptionHandler</code></td>
<td>捕获未处理的异常，防止崩溃</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="二、协程上下文的组合方式" tabindex="-1">二、协程上下文的组合方式 <a class="header-anchor" href="#二、协程上下文的组合方式" aria-label="Permalink to “二、协程上下文的组合方式”">&#8203;</a></h2>
<p>上下文可以通过 <code>+</code> 进行组合，不同类型元素会被合并，相同类型后者覆盖前者。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::2qz7faod7oa78upd3lqpa::--><code>val context = Dispatchers.IO + SupervisorJob() + CoroutineName(&quot;OcrScope&quot;)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><blockquote>
<p>注意：组合顺序无影响，但同一类型（如两个 <code>Dispatcher</code>）会后者覆盖前者。</p>
</blockquote>
<hr>
<h2 id="三、实际应用场景分析" tabindex="-1">三、实际应用场景分析 <a class="header-anchor" href="#三、实际应用场景分析" aria-label="Permalink to “三、实际应用场景分析”">&#8203;</a></h2>
<h3 id="假设-ocr-实时翻译系统中-包含以下任务" tabindex="-1">假设：OCR 实时翻译系统中，包含以下任务： <a class="header-anchor" href="#假设-ocr-实时翻译系统中-包含以下任务" aria-label="Permalink to “假设：OCR 实时翻译系统中，包含以下任务：”">&#8203;</a></h3>
<ol>
<li>实时图像帧 OCR（IO 密集）</li>
<li>翻译文字（CPU 密集）</li>
<li>UI 更新（主线程）</li>
<li>需要可取消/优先级切换/错误处理</li>
</ol>
<h3 id="推荐协程上下文组合" tabindex="-1">推荐协程上下文组合 <a class="header-anchor" href="#推荐协程上下文组合" aria-label="Permalink to “推荐协程上下文组合”">&#8203;</a></h3>
<h4 id="_1-任务作用域初始化" tabindex="-1">1. 任务作用域初始化 <a class="header-anchor" href="#_1-任务作用域初始化" aria-label="Permalink to “1. 任务作用域初始化”">&#8203;</a></h4>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::o5zago5jics5lu920qid8::--><code>val scope = CoroutineScope(
    SupervisorJob() +
    Dispatchers.Default +
    CoroutineName(&quot;TranslateScope&quot;) +
    CoroutineExceptionHandler { _, throwable -&gt;
        Log.e(&quot;TranslateScope&quot;, &quot;Unhandled error: $throwable&quot;)
    }
)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h4 id="_2-使用示例-低优先级-ocr-翻译任务" tabindex="-1">2. 使用示例：低优先级 OCR 翻译任务 <a class="header-anchor" href="#_2-使用示例-低优先级-ocr-翻译任务" aria-label="Permalink to “2. 使用示例：低优先级 OCR 翻译任务”">&#8203;</a></h4>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::w86ldltsmphiqzl6j5hfvf::--><code>scope.launch {
    val result = ocrClient.recognize(bitmap) // IO
    val text = result.lines.joinToString(&quot;\n&quot;)
    val translation = translator.translate(text) // CPU
    withContext(Dispatchers.Main) {
        updateUi(translation) // UI更新
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><hr>
<h2 id="四、不同上下文组合的实际对比" tabindex="-1">四、不同上下文组合的实际对比 <a class="header-anchor" href="#四、不同上下文组合的实际对比" aria-label="Permalink to “四、不同上下文组合的实际对比”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>场景</th>
<th>组合方式</th>
<th>适合任务</th>
</tr>
</thead>
<tbody>
<tr>
<td>快速 UI 绑定</td>
<td><code>Job() + Dispatchers.Main</code></td>
<td>动画、按钮状态更新</td>
</tr>
<tr>
<td>网络/文件访问</td>
<td><code>SupervisorJob() + Dispatchers.IO</code></td>
<td>Retrofit, 文件读写</td>
</tr>
<tr>
<td>多任务并发处理</td>
<td><code>SupervisorJob() + Dispatchers.Default</code></td>
<td>翻译、计算</td>
</tr>
<tr>
<td>强错误控制</td>
<td><code>+ CoroutineExceptionHandler {...}</code></td>
<td>错误集中处理、防止崩溃</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="五、协程作用域的生命周期管理" tabindex="-1">五、协程作用域的生命周期管理 <a class="header-anchor" href="#五、协程作用域的生命周期管理" aria-label="Permalink to “五、协程作用域的生命周期管理”">&#8203;</a></h2>
<h3 id="viewmodel-中推荐这样" tabindex="-1">ViewModel 中推荐这样： <a class="header-anchor" href="#viewmodel-中推荐这样" aria-label="Permalink to “ViewModel 中推荐这样：”">&#8203;</a></h3>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::p9kcaw3gzpeupyowft6dn::--><code>class MyViewModel : ViewModel() {
    private val scope = viewModelScope + CoroutineName(&quot;MyViewModelScope&quot;)

    fun launchTranslateJob(input: List&lt;String&gt;) {
        scope.launch {
            val translated = translate(input)
            _translationResult.postValue(translated)
        }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><h3 id="非-viewmodel-场景如自定义调度类" tabindex="-1">非 ViewModel 场景如自定义调度类： <a class="header-anchor" href="#非-viewmodel-场景如自定义调度类" aria-label="Permalink to “非 ViewModel 场景如自定义调度类：”">&#8203;</a></h3>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::716mgu3gi8nagmtclzkrzk::--><code>val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><p>关闭资源：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::5aec6mh6r0m7qzdnzqyzp2::--><code>scope.cancel() // 清理所有任务</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><hr>
<h2 id="六、最佳实践总结" tabindex="-1">六、最佳实践总结 <a class="header-anchor" href="#六、最佳实践总结" aria-label="Permalink to “六、最佳实践总结”">&#8203;</a></h2>
<ul>
<li>使用 <code>SupervisorJob()</code>，避免子任务失败影响全局</li>
<li>任务调度用 <code>Dispatchers.IO</code> 和 <code>Dispatchers.Default</code> 区分 IO 和 CPU 密集型</li>
<li>永远不要在主线程运行耗时任务</li>
<li>使用 <code>CoroutineName</code> 追踪调试大型项目</li>
<li>通过 <code>CoroutineExceptionHandler</code> 保证稳定性</li>
</ul>
<hr>
<h2 id="七、附录-调度器说明表" tabindex="-1">七、附录：调度器说明表 <a class="header-anchor" href="#七、附录-调度器说明表" aria-label="Permalink to “七、附录：调度器说明表”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>调度器</th>
<th>特点</th>
<th>适合场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Dispatchers.Main</code></td>
<td>UI 线程</td>
<td>更新界面、交互响应</td>
</tr>
<tr>
<td><code>Dispatchers.IO</code></td>
<td>IO 优化线程池</td>
<td>网络、数据库、文件 IO</td>
</tr>
<tr>
<td><code>Dispatchers.Default</code></td>
<td>CPU 密集线程池</td>
<td>算法、解析、计算、翻译</td>
</tr>
<tr>
<td><code>Dispatchers.Unconfined</code></td>
<td>当前线程启动，协程恢复位置不固定</td>
<td>高级用法（如测试）</td>
</tr>
</tbody>
</table>
<hr>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[Compose的gradle配置]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Compose/Compose%E7%9A%84gradle%E9%85%8D%E7%BD%AE.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Compose/Compose的gradle配置.html</guid>
            <pubDate>Sun, 20 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Jetpack Compose 是一个用于构建原生 Android UI 的现代工具包。它使用声明式函数来构建UI组件，不需要我们使用任何xml布局。]]></description>
            <content:encoded><![CDATA[<h1 id="compose的gradle配置" tabindex="-1">Compose的gradle配置 <a class="header-anchor" href="#compose的gradle配置" aria-label="Permalink to “Compose的gradle配置”">&#8203;</a></h1>
<h2 id="_1-初识compose" tabindex="-1">1. 初识Compose <a class="header-anchor" href="#_1-初识compose" aria-label="Permalink to “1. 初识Compose”">&#8203;</a></h2>
<p><code>Jetpack Compose </code>是一个用于构建原生 <code>Android UI</code> 的现代工具包。它使用声明式函数来构建UI组件，不需要我们使用任何xml布局。</p>
<h2 id="_2-compose的gradle配置" tabindex="-1">2. Compose的gradle配置 <a class="header-anchor" href="#_2-compose的gradle配置" aria-label="Permalink to “2. Compose的gradle配置”">&#8203;</a></h2>
<ol>
<li>settings.gradle.kts</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-19" id="tab-20" checked><label data-title="txt" for="tab-20">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::u3ujpihw13ggy73a7g0qz8::--><code>pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex(&quot;com\\.android.*&quot;)
                includeGroupByRegex(&quot;com\\.google.*&quot;)
                includeGroupByRegex(&quot;androidx.*&quot;)
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = &quot;My Application&quot;
include(&quot;:app&quot;)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div></div></div>
<ol start="2">
<li>gradle/libs.versions.toml</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-29" id="tab-30" checked><label data-title="txt" for="tab-30">txt</label></div><div class="blocks">
<div class="language-xml active line-numbers-mode" data-lang="xml"><button title="Copy Code" class="copy"></button><span class="lang">XML</span><pre><!--::markdown-it-async::qd9kzhnla4omcgojq0tp::--><code>[versions]
agp = &quot;8.9.0&quot;
kotlin = &quot;2.0.21&quot;
coreKtx = &quot;1.12.0&quot;
junit = &quot;4.13.2&quot;
junitVersion = &quot;1.1.5&quot;
espressoCore = &quot;3.5.1&quot;
appcompat = &quot;1.6.1&quot;
material = &quot;1.10.0&quot;
constraintlayout = &quot;2.1.4&quot;
navigationCompose = &quot;2.7.7&quot;
navigationFragmentKtx = &quot;2.6.0&quot;
navigationUiKtx = &quot;2.6.0&quot;
navigationComposeVersion = &quot;2.8.9&quot;

[libraries]
androidx-activity-compose = { module = &quot;androidx.activity:activity-compose&quot; }
androidx-core-ktx = { group = &quot;androidx.core&quot;, name = &quot;core-ktx&quot;, version.ref = &quot;coreKtx&quot; }
androidx-material3 = { module = &quot;androidx.compose.material3:material3&quot; }
androidx-navigation-compose = { module = &quot;androidx.navigation:navigation-compose&quot;, version.ref = &quot;navigationCompose&quot; }
androidx-ui = { module = &quot;androidx.compose.ui:ui&quot; }
androidx-ui-tooling = { module = &quot;androidx.compose.ui:ui-tooling&quot; }
androidx-ui-tooling-preview = { module = &quot;androidx.compose.ui:ui-tooling-preview&quot; }
junit = { group = &quot;junit&quot;, name = &quot;junit&quot;, version.ref = &quot;junit&quot; }
androidx-junit = { group = &quot;androidx.test.ext&quot;, name = &quot;junit&quot;, version.ref = &quot;junitVersion&quot; }
androidx-espresso-core = { group = &quot;androidx.test.espresso&quot;, name = &quot;espresso-core&quot;, version.ref = &quot;espressoCore&quot; }
androidx-appcompat = { group = &quot;androidx.appcompat&quot;, name = &quot;appcompat&quot;, version.ref = &quot;appcompat&quot; }
material = { group = &quot;com.google.android.material&quot;, name = &quot;material&quot;, version.ref = &quot;material&quot; }
androidx-constraintlayout = { group = &quot;androidx.constraintlayout&quot;, name = &quot;constraintlayout&quot;, version.ref = &quot;constraintlayout&quot; }
androidx-navigation-fragment-ktx = { group = &quot;androidx.navigation&quot;, name = &quot;navigation-fragment-ktx&quot;, version.ref = &quot;navigationFragmentKtx&quot; }
androidx-navigation-ui-ktx = { group = &quot;androidx.navigation&quot;, name = &quot;navigation-ui-ktx&quot;, version.ref = &quot;navigationUiKtx&quot; }
navigation-compose = { group = &quot;androidx.navigation&quot;, name = &quot;navigation-compose&quot;, version.ref = &quot;navigationComposeVersion&quot; }

[plugins]
android-application = { id = &quot;com.android.application&quot;, version.ref = &quot;agp&quot; }
kotlin-android = { id = &quot;org.jetbrains.kotlin.android&quot;, version.ref = &quot;kotlin&quot; }
compose-compiler = { id = &quot;org.jetbrains.kotlin.plugin.compose&quot;, version.ref = &quot;kotlin&quot; }</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br></div></div></div></div>
<ol start="3">
<li>build.gralde.kts</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-39" id="tab-40" checked><label data-title="txt" for="tab-40">txt</label></div><div class="blocks">
<div class="language-kotlinScripts active line-numbers-mode" data-lang="kotlinScripts"><button title="Copy Code" class="copy"></button><span class="lang">kotlinScripts</span><pre><!--::markdown-it-async::mk60wqpg8qs2jfahl009j::--><code>plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.kotlin.android) apply false
    alias(libs.plugins.compose.compiler) apply false
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></div></div>
<ol start="4">
<li>app:build.gralde.kts</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-49" id="tab-50" checked><label data-title="txt" for="tab-50">txt</label></div><div class="blocks">
<div class="language-kotlinScripts active line-numbers-mode" data-lang="kotlinScripts"><button title="Copy Code" class="copy"></button><span class="lang">kotlinScripts</span><pre><!--::markdown-it-async::6emrdocarfdoz1whqzi::--><code>plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.compose.compiler)
}

android {
    namespace = &quot;com.endlessyoung.myapplication&quot;
    compileSdk = 35

    defaultConfig {
        applicationId = &quot;com.endlessyoung.myapplication&quot;
        minSdk = 34
        targetSdk = 35
        versionCode = 1
        versionName = &quot;1.0&quot;

        testInstrumentationRunner = &quot;androidx.test.runner.AndroidJUnitRunner&quot;
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile(&quot;proguard-android-optimize.txt&quot;),
                &quot;proguard-rules.pro&quot;
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = &quot;17&quot;
    }
    buildFeatures {
        compose = true
    }
}

dependencies {
    implementation(libs.navigation.compose)
    val composeBom = platform(&quot;androidx.compose:compose-bom:2024.02.01&quot;)
    implementation(composeBom)
    implementation(libs.androidx.activity.compose)
    implementation(libs.androidx.navigation.compose)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.constraintlayout)
    implementation(libs.androidx.navigation.fragment.ktx)
    implementation(libs.androidx.navigation.ui.ktx)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    implementation(libs.androidx.ui)
    implementation(libs.androidx.material3)
    implementation(libs.androidx.ui.tooling.preview)
    debugImplementation(libs.androidx.ui.tooling)
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[dart语言简介]]></title>
            <link>https://endlessyoung.github.io/Blog_/Flutter/dart%E8%AF%AD%E8%A8%80%E7%AE%80%E4%BB%8B.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Flutter/dart语言简介.html</guid>
            <pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Object 是 Dart 所有对象的根基类，也就是说在 Dart 中所有类型都是Object的子类(包括Function和Null)，所以任何类型的数据都可以赋值给Object声明的对象。 dynamic与Object声明的变量都可以赋值任意对象，且后期可以?]]></description>
            <content:encoded><![CDATA[<h2 id="🚀-基础语法" tabindex="-1">🚀 基础语法 <a class="header-anchor" href="#🚀-基础语法" aria-label="Permalink to “🚀 基础语法”">&#8203;</a></h2>
<h3 id="变量定义" tabindex="-1">变量定义 <a class="header-anchor" href="#变量定义" aria-label="Permalink to “变量定义”">&#8203;</a></h3>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::hzbxse75euqp56pg4m9m0d::--><code>var name = &#039;Flutter&#039;;   // 自动推断类型
// 类似于 JavaScript 中的var，它可以接收任何类型的变量，但最大的不同是 Dart 中 var 变量一旦赋值，类型便会确定，则不能再改变其类型
String name = &#039;Flutter&#039;;
int count = 10;
double ratio = 3.14;
bool isOpen = true;

dynamic t;
Object x;
t = &quot;hi world&quot;;
x = &#039;Hello Object&#039;;
//下面代码没有问题
t = 1000;
x = 1000;</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><blockquote>
<p>Object 是 Dart 所有对象的根基类，也就是说在 Dart 中所有类型都是Object的子类(包括Function和Null)，所以任何类型的数据都可以赋值给Object声明的对象。 dynamic与Object声明的变量都可以赋值任意对象，且后期可以改变赋值的类型，这和 var 是不同的。</p>
</blockquote>
<h3 id="常量" tabindex="-1">常量 <a class="header-anchor" href="#常量" aria-label="Permalink to “常量”">&#8203;</a></h3>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::3jhwxksz1p2lf9v17cd0x::--><code>final time = DateTime.now(); // 运行时常量，只赋值一次
const pi = 3.14159;          // 编译时常量，值必须在编译期已知</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><hr>
<h2 id="📦-集合类型" tabindex="-1">📦 集合类型 <a class="header-anchor" href="#📦-集合类型" aria-label="Permalink to “📦 集合类型”">&#8203;</a></h2>
<h3 id="list-数组" tabindex="-1">List（数组） <a class="header-anchor" href="#list-数组" aria-label="Permalink to “List（数组）”">&#8203;</a></h3>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::eodebh38pgkullbv0jkvn::--><code>List&lt;String&gt; names = [&#039;Alice&#039;, &#039;Bob&#039;];
var numbers = [1, 2, 3];</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><h3 id="map-字典" tabindex="-1">Map（字典） <a class="header-anchor" href="#map-字典" aria-label="Permalink to “Map（字典）”">&#8203;</a></h3>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::qjeu5w2enwclkrtmlz31wf::--><code>Map&lt;String, int&gt; scores = {&#039;Tom&#039;: 90, &#039;Jerry&#039;: 85};</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><h3 id="set-集合" tabindex="-1">Set（集合） <a class="header-anchor" href="#set-集合" aria-label="Permalink to “Set（集合）”">&#8203;</a></h3>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::54y1jhlg16v6v6f0n1zugp::--><code>var fruits = {&#039;apple&#039;, &#039;banana&#039;};</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div><hr>
<h2 id="🎯-控制流程" tabindex="-1">🎯 控制流程 <a class="header-anchor" href="#🎯-控制流程" aria-label="Permalink to “🎯 控制流程”">&#8203;</a></h2>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::oq1tyj3o4kkml051gjlc9e::--><code>if (age &gt; 18) {
  print(&#039;Adult&#039;);
} else {
  print(&#039;Minor&#039;);
}

for (var i = 0; i &lt; 5; i++) {
  print(i);
}

while (condition) {
  // do something
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><hr>
<h2 id="🧱-函数定义" tabindex="-1">🧱 函数定义 <a class="header-anchor" href="#🧱-函数定义" aria-label="Permalink to “🧱 函数定义”">&#8203;</a></h2>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::omnrzct1dy2nfvqcp18pe::--><code>int add(int a, int b) {
  return a + b;
}

void sayHello([String name = &#039;World&#039;]) {
  print(&#039;Hello, $name!&#039;);
}

// 匿名函数
var multiply = (a, b) =&gt; a * b;</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><hr>
<h2 id="🧩-类与对象" tabindex="-1">🧩 类与对象 <a class="header-anchor" href="#🧩-类与对象" aria-label="Permalink to “🧩 类与对象”">&#8203;</a></h2>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::z57vuy9gaoad15oc1y3hua::--><code>class Person {
  String name;
  int age;

  Person(this.name, this.age);

  void sayHi() {
    print(&#039;Hi, I am $name&#039;);
  }
}

var p = Person(&#039;Tom&#039;, 18);
p.sayHi();</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><hr>
<h2 id="🧬-继承与重写" tabindex="-1">🧬 继承与重写 <a class="header-anchor" href="#🧬-继承与重写" aria-label="Permalink to “🧬 继承与重写”">&#8203;</a></h2>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::bc3yi1dwioo2rp3jz1p5op::--><code>class Animal {
  void speak() {
    print(&#039;Animal sound&#039;);
  }
}

class Dog extends Animal {
  @override
  void speak() {
    print(&#039;Bark&#039;);
  }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><hr>
<h2 id="❓-空安全与可空类型" tabindex="-1">❓ 空安全与可空类型 <a class="header-anchor" href="#❓-空安全与可空类型" aria-label="Permalink to “❓ 空安全与可空类型”">&#8203;</a></h2>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::3rax8vxcfzwkaod65h5r8h::--><code>String? name; // 可以为 null
name = null;

String nonNullName = name ?? &#039;default&#039;; // null 合并运算符</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><hr>
<h2 id="🔑-工具语法糖" tabindex="-1">🔑 工具语法糖 <a class="header-anchor" href="#🔑-工具语法糖" aria-label="Permalink to “🔑 工具语法糖”">&#8203;</a></h2>
<h3 id="构造函数简写" tabindex="-1">构造函数简写 <a class="header-anchor" href="#构造函数简写" aria-label="Permalink to “构造函数简写”">&#8203;</a></h3>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::vfz2azoi5l51dgsy4gj4v::--><code>class Point {
  int x, y;
  Point(this.x, this.y); // 自动赋值
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h3 id="命名参数-默认值" tabindex="-1">命名参数 &amp; 默认值 <a class="header-anchor" href="#命名参数-默认值" aria-label="Permalink to “命名参数 &amp; 默认值”">&#8203;</a></h3>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::1g53t4zclmhdfhu8bynp7p::--><code>void printInfo({required String name, int age = 0}) {
  print(&#039;$name is $age years old&#039;);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><hr>
<h2 id="📚-异步编程" tabindex="-1">📚 异步编程 <a class="header-anchor" href="#📚-异步编程" aria-label="Permalink to “📚 异步编程”">&#8203;</a></h2>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::gnz1tqlfyvj59u4arzwvj2::--><code>Future&lt;void&gt; fetchData() async {
  var result = await getHttp();  // 等待异步返回
  print(result);
}

Future&lt;String&gt; getHttp() async {
  return &#039;data&#039;;
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><hr>
<h2 id="🔄-stream-和事件流" tabindex="-1">🔄 Stream 和事件流 <a class="header-anchor" href="#🔄-stream-和事件流" aria-label="Permalink to “🔄 Stream 和事件流”">&#8203;</a></h2>
<div class="language-dart line-numbers-mode" data-lang="dart"><button title="Copy Code" class="copy"></button><span class="lang">dart</span><pre><!--::markdown-it-async::4xos0q76yy64lnhty0a46::--><code>Stream&lt;int&gt; counter() async* {
  for (int i = 0; i &lt; 5; i++) {
    yield i;
    await Future.delayed(Duration(seconds: 1));
  }
}

counter().listen((val) =&gt; print(val));</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><hr>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Flutter</category>
        </item>
        <item>
            <title><![CDATA[ForkJoinPool]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/ForkJoinPool.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/ForkJoinPool.html</guid>
            <pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[把大任务分成小任务，分而治之；让每个线程都有本地任务队列，空闲线程可以“偷”别的线程的任务来做（Work Stealing）]]></description>
            <content:encoded><![CDATA[<h1 id="forkjoinpool" tabindex="-1">ForkJoinPool <a class="header-anchor" href="#forkjoinpool" aria-label="Permalink to “ForkJoinPool”">&#8203;</a></h1>
<h2 id="_1-forkjoinpool-的核心设计思想" tabindex="-1">1. ForkJoinPool 的核心设计思想 <a class="header-anchor" href="#_1-forkjoinpool-的核心设计思想" aria-label="Permalink to “1. ForkJoinPool 的核心设计思想”">&#8203;</a></h2>
<p>把大任务分成小任务，分而治之；让每个线程都有本地任务队列，空闲线程可以“偷”别的线程的任务来做（Work Stealing）</p>
<h2 id="_2-forkjoinpool的结构" tabindex="-1">2. ForkJoinPool的结构 <a class="header-anchor" href="#_2-forkjoinpool的结构" aria-label="Permalink to “2. ForkJoinPool的结构”">&#8203;</a></h2>
<p>ForkJoinPool = 多个Worker线程 + 每个线程的 <strong>双端队列（Deque）</strong></p>
<ul>
<li>每个线程有自己的独立的任务队列（不共享）</li>
<li>自己提交的任务优先放到“尾部”执行</li>
<li>其他线程“偷任务”时，从“头部”拿任务（Work Stealing）</li>
</ul>
<h2 id="总结" tabindex="-1">总结 <a class="header-anchor" href="#总结" aria-label="Permalink to “总结”">&#8203;</a></h2>
<p>ForkJoinPool 是为“可分治、可并行”的任务而生的调度器，吞吐高、调度灵活、线程利用率极高。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Mutex]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/Mutex.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/Mutex.html</guid>
            <pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Mutex 是一个轻量级的同步原语，是kt协程中用于实现互斥锁的工具，它通过确保同一时刻只有一个协程能访问某个资源，避免竟态条件和数据不一致的问题。工作原理类似于传统的线程互斥锁。是协程级别的，不会阻塞线程，而是挂起当前协程直到锁被释放。]]></description>
            <content:encoded><![CDATA[<h1 id="mutex" tabindex="-1">Mutex <a class="header-anchor" href="#mutex" aria-label="Permalink to “Mutex”">&#8203;</a></h1>
<h2 id="_1-基本概念" tabindex="-1">1. 基本概念 <a class="header-anchor" href="#_1-基本概念" aria-label="Permalink to “1. 基本概念”">&#8203;</a></h2>
<p><code>Mutex</code> 是一个轻量级的同步原语，是kt协程中用于实现互斥锁的工具，它通过确保同一时刻只有一个协程能访问某个资源，避免竟态条件和数据不一致的问题。工作原理类似于传统的线程互斥锁。是协程级别的，不会阻塞线程，而是挂起当前协程直到锁被释放。</p>
<h2 id="_2-使用方法" tabindex="-1">2. 使用方法 <a class="header-anchor" href="#_2-使用方法" aria-label="Permalink to “2. 使用方法”">&#8203;</a></h2>
<p><code>Mutex</code> 主要通过两种方法来操作：</p>
<ol>
<li><code>lock()</code>: 获取锁，如果锁已经被其他协程持有，则当前协程将挂起，直到锁可用。</li>
<li><code>unlock()</code>: 释放锁，允许其他协程获取锁。</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-27" id="tab-28" checked><label data-title="txt" for="tab-28">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::7kxdv1566mo5xdyt30ha::--><code>import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

val mutex = Mutex()

var counter = 0

suspend fun incrementCounter() {
    mutex.withLock {
        // 临界区代码
        val current = counter
        delay(100)  // 模拟耗时操作
        counter = current + 1
        println(&quot;Counter: $counter&quot;)
    }
}

fun main() = runBlocking {
    val jobs = List(10) {
        launch {
            incrementCounter()
        }
    }
    jobs.forEach { it.join() }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br></div></div></div></div>
<p><code>mutex.withLock</code> 可以在获取到锁的情况下执行代码块，代码块执行完毕后自动释放锁。</p>
<h2 id="_3-高级用法" tabindex="-1">3. 高级用法 <a class="header-anchor" href="#_3-高级用法" aria-label="Permalink to “3. 高级用法”">&#8203;</a></h2>
<p><code>Mutex</code> 与协程取消和超时控制</p>
<p>使用超时 <code>withTimeout</code> 来限制锁等待时间</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-42" id="tab-43" checked><label data-title="txt" for="tab-43">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::bqx3cqijx0hu8zemv1ha6::--><code>import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.timeout

val mutex = Mutex()

suspend fun safeIncrementCounter() {
    try {
        // 尝试在100ms内获取锁
        if (mutex.tryLock()) {
            try {
                delay(100)
                println(&quot;Incremented safely&quot;)
            } finally {
                mutex.unlock()  // 确保解锁
            }
        } else {
            println(&quot;Failed to acquire lock within timeout&quot;)
        }
    } catch (e: Exception) {
        println(&quot;Error occurred: ${e.message}&quot;)
    }
}

fun main() = runBlocking {
    val jobs = List(10) {
        launch {
            safeIncrementCounter()
        }
    }
    jobs.forEach { it.join() }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br></div></div></div></div>
<p>Mutex 确保对共享资源（sharedFlow）的线程安全访问</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-48" id="tab-49" checked><label data-title="txt" for="tab-49">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::q9qt2jb05qtp35gqca9gq::--><code>import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect

val mutex = Mutex()
val sharedFlow = MutableSharedFlow&lt;String&gt;()

suspend fun produceData() {
    mutex.withLock {
        sharedFlow.emit(&quot;data produced&quot;)
    }
}

suspend fun consumeData() {
    sharedFlow.collect {
        println(&quot;Consumed: $it&quot;)
    }
}

fun main() = runBlocking {
    val producer = launch {
        repeat(5) {
            produceData()
            delay(100)
        }
    }

    val consumer = launch {
        consumeData()
    }

    producer.join()
    consumer.cancel()
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br></div></div></div></div>
<p>在多个资源上加锁时，如果不小心，可能会导致死锁。Mutex 允许通过锁的排序策略避免此类问题。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-54" id="tab-55" checked><label data-title="txt" for="tab-55">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::zlurn0wx9pkw4fuykdyo3::--><code>val mutexA = Mutex()
val mutexB = Mutex()

suspend fun taskA() {
    mutexA.lock()
    delay(100)
    mutexB.lock()
    println(&quot;Task A complete&quot;)
    mutexB.unlock()
    mutexA.unlock()
}

suspend fun taskB() {
    mutexB.lock()
    delay(100)
    mutexA.lock()
    println(&quot;Task B complete&quot;)
    mutexA.unlock()
    mutexB.unlock()
}

fun main() = runBlocking {
    val jobA = launch { taskA() }
    val jobB = launch { taskB() }
    
    jobA.join()
    jobB.join()
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div></div></div>
<p>这种代码会在 mutexA 和 mutexB 锁的顺序不一致时导致死锁（如 taskA 先锁 mutexA，taskB 先锁 mutexB）。
解决办法是按固定顺序获取锁（例如，mutexA 一定先锁）</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[高并发场景下的协程调度与调优]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/%E9%AB%98%E5%B9%B6%E5%8F%91%E5%9C%BA%E6%99%AF%E4%B8%8B%E7%9A%84%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E4%B8%8E%E8%B0%83%E4%BC%98.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/高并发场景下的协程调度与调优.html</guid>
            <pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[| 特性 | withContext() | launch() | |:--------------:|:-----------------------------:|:----------------------------------:| | 是否为挂起函?]]></description>
            <content:encoded><![CDATA[<h2 id="是否为挂起函-categories-kotlin-tags-kotlin-created-2025-04-06" tabindex="-1">-----------😐:-----------------------------😐:----------------------------------😐 | 是否为挂起函?&quot;
categories: [Kotlin]
tags: [kotlin]
created: 2025-04-06 <a class="header-anchor" href="#是否为挂起函-categories-kotlin-tags-kotlin-created-2025-04-06" aria-label="Permalink to “-----------:-----------------------------:---------------------------------- | 是否为挂起函?&quot;categories: [Kotlin]tags: [kotlin]created: 2025-04-06”">&#8203;</a></h2>
<h1 id="高并发场景下的协程调度与调优" tabindex="-1">高并发场景下的协程调度与调优 <a class="header-anchor" href="#高并发场景下的协程调度与调优" aria-label="Permalink to “高并发场景下的协程调度与调优”">&#8203;</a></h1>
<hr>
<h2 id="一、-前言" tabindex="-1">一、 前言 <a class="header-anchor" href="#一、-前言" aria-label="Permalink to “一、 前言”">&#8203;</a></h2>
<ol>
<li><strong><code>Dispatchers.Default</code>，<code>Dispatchers.IO</code>，<code>Dispatchers.Main</code>的线程池底层区别</strong></li>
</ol>
<ul>
<li>
<p><strong>Dispatchers.Default</strong>：适用于<code>CPU密集型任务</code>（计算、JSON解析等），其线程池大小为<code>CPU核心数</code> 到 <code>2X核心数</code> 之间的固定线程池，调度策略是基于 <code>ForkJoinPool</code> 或 kotlin的 <code>CommonPool</code>。如果线程不够，会挂起协程而非扩展线程。</p>
</li>
<li>
<p><strong>Dispatcher.IO</strong>: 适用于<code>IO密集型任务</code>（网络请求、数据库、磁盘读写），其线程池大小为 <code>无限制线程池</code>（<strong>实际64 X 核心数</strong>），底层上复用了 <code>Default</code> 线程池，但是允许更多协程切入。适合同时发起大量短时间的IO任务，如果IO操作阻塞时间长，线程会被挂起，释放资源给其他协程。如果把CPU密集任务跑在IO上，可能导致线程数膨胀，调度开销大，甚至OOM。</p>
</li>
<li>
<p><strong>Dispatchers.Main</strong>：适用于所有需要与 <code>UI</code> 交互的任务，严格只有一个线程（UI线程），<strong>执行时间必须短，超过16ms可能会掉帧，避免 delay()、withContext(IO) 这类长时间操作。</strong></p>
</li>
</ul>
<ol start="2">
<li><strong>withContext() vs launch() 的调度差异</strong></li>
</ol>
<table tabindex="0">
<thead>
<tr>
<th style="text-align:center">特性</th>
<th style="text-align:center">withContext()</th>
<th style="text-align:center">launch()</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">是否为挂起函数</td>
<td style="text-align:center">必须是<code>suspend</code></td>
<td style="text-align:center">否</td>
</tr>
<tr>
<td style="text-align:center">是否阻塞当前协程</td>
<td style="text-align:center">会等待执行完，返回后继续</td>
<td style="text-align:center">不会阻塞，异步执行</td>
</tr>
<tr>
<td style="text-align:center">是否返回结果</td>
<td style="text-align:center">有返回值（像普通函数一样）</td>
<td style="text-align:center">没有返回值（只能依靠回调或共享变量）</td>
</tr>
<tr>
<td style="text-align:center">异常传播</td>
<td style="text-align:center">抛出异常给调用者，可以捕获</td>
<td style="text-align:center">在同一个CoroutineScope会联动取消</td>
</tr>
<tr>
<td style="text-align:center">是否是结构化并发</td>
<td style="text-align:center">是</td>
<td style="text-align:center">是</td>
</tr>
<tr>
<td style="text-align:center">常见用途</td>
<td style="text-align:center">切换线程并执行任务 + 获取结果</td>
<td style="text-align:center">启动一个协程处理异步任务</td>
</tr>
</tbody>
</table>
<p><strong>适用总结</strong>：</p>
<ol>
<li><strong>需要结果 → 用 withContext()</strong></li>
<li><strong>只需要并发启动任务 → 用 launch()</strong></li>
<li><strong>复杂任务组合 → async + await 更合适</strong></li>
<li><strong>大量 withContext() 嵌套需慎用，易调度开销过大</strong></li>
</ol>
<p><strong>示例如下</strong>：</p>
<p><strong>方法一：用 <code>withContext()</code> 穿行处理（低效）</strong></p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-155" id="tab-156" checked><label data-title="txt" for="tab-156">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::apax9lm9dvqbu31yznq47::--><code>suspend fun loadDataSerial(): UserOrderInfo {
    val user = withContext(Dispatchers.IO) {
        getUserInfo() // 网络请求
    }

    val orders = withContext(Dispatchers.IO) {
        getOrderList(user.id) // 数据库加载或网络请求
    }

    return UserOrderInfo(user, orders)
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div></div></div>
<p><strong>缺点：两个请求是串行的，第二个必须等第一个完成 → <code>总耗时 = A + B</code>，不适合高并发聚合场景。</strong></p>
<p><strong>方法二：用 async + await 并发处理（高效）</strong></p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-164" id="tab-165" checked><label data-title="txt" for="tab-165">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::4uzf0vwi188ta4ye5csz4j::--><code>suspend fun loadDataConcurrent(): UserOrderInfo = CoroutineScope {
    val userDefered = async(Dispatchers.IO) {
        getUserInfo()
    }

    val ordersDeferred = async(Dispatchers.IO) {
        val user = userDefered.await()
        getOrderList(user.id)
    }

    val user = userDefered.await()
    val orders = ordersDeferred.await()

    UserOrderInfo(user, orders)
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div></div></div>
<p><strong>总耗时 ≈ Max(A, B)</strong></p>
<p><strong>以上两种方法的区别在于 <code>async 启动的是 独立协程</code>，而 <code>withContext() 是“切线程 + 等执行完再返回”</code></strong></p>
<h2 id="二、调度策略分析-default-vs-io-vs-unconfined" tabindex="-1">二、调度策略分析：Default vs IO vs Unconfined <a class="header-anchor" href="#二、调度策略分析-default-vs-io-vs-unconfined" aria-label="Permalink to “二、调度策略分析：Default vs IO vs Unconfined”">&#8203;</a></h2>
<h2 id="三、sharedflow、channel-高并发背压" tabindex="-1">三、SharedFlow、Channel 高并发背压 <a class="header-anchor" href="#三、sharedflow、channel-高并发背压" aria-label="Permalink to “三、SharedFlow、Channel 高并发背压”">&#8203;</a></h2>
<h2 id="四、协程性能调优技巧" tabindex="-1">四、协程性能调优技巧 <a class="header-anchor" href="#四、协程性能调优技巧" aria-label="Permalink to “四、协程性能调优技巧”">&#8203;</a></h2>
<ol>
<li><strong>尽量减少 withContext(IO) 嵌套调用，切换开销大</strong></li>
<li><strong>大量计算使用 Default，但也要限流（Semaphore、Chunk 分片）</strong></li>
<li><strong>利用 SupervisorScope 局部处理异常防止协程传播 cancel</strong></li>
</ol>
<h2 id="五、调试与排查" tabindex="-1">五、调试与排查 <a class="header-anchor" href="#五、调试与排查" aria-label="Permalink to “五、调试与排查”">&#8203;</a></h2>
<p><code>DebugProbes.install() + DebugProbes.dumpCoroutines()</code> 打印活跃协程信息</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[多进程模式]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%A4%9A%E8%BF%9B%E7%A8%8B%E6%A8%A1%E5%BC%8F.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/多进程模式.html</guid>
            <pubDate>Mon, 24 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在安卓中使用多进程只有一种方法，就是给四大组件在 AndroidManifest 中指定 android:process 属性，我们无法给一个线程或者一个实体类指定其运行时所在的进程。还有另外一种非常规的多进程方法，那就是通过 JNI 在 Nativte 层中?]]></description>
            <content:encoded><![CDATA[<h1 id="多进程模式" tabindex="-1">多进程模式 <a class="header-anchor" href="#多进程模式" aria-label="Permalink to “多进程模式”">&#8203;</a></h1>
<h2 id="_1-安卓中实现多进程的方法" tabindex="-1">1. 安卓中实现多进程的方法 <a class="header-anchor" href="#_1-安卓中实现多进程的方法" aria-label="Permalink to “1. 安卓中实现多进程的方法”">&#8203;</a></h2>
<p>在安卓中使用多进程只有一种方法，就是给四大组件在 <code>AndroidManifest</code> 中指定 <code>android:process</code> 属性，我们无法给一个线程或者一个实体类指定其运行时所在的进程。还有另外一种非常规的多进程方法，那就是通过 <code>JNI</code> 在 <code>Nativte</code> 层中去 <code>fork</code> 一个新的进程。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-9" id="tab-10" checked><label data-title="txt" for="tab-10">txt</label></div><div class="blocks">
<div class="language-xml active line-numbers-mode" data-lang="xml"><button title="Copy Code" class="copy"></button><span class="lang">XML</span><pre><!--::markdown-it-async::gochvjccm4p2xz1lfz7fzv::--><code>&lt;activity
    android:name=&quot;com.ryg.chapter_2.MainActivity&quot;
    android:configChanges=&quot;orientation|screenSize&quot;
    android:label=&quot;@string/app_name&quot;
    android:launchMode=&quot;standard&quot; &gt;
        &lt;intent-filter&gt;
        &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
        &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
        &lt;/intent-filter&gt;
&lt;/activity&gt;

&lt;activity
    android:name=&quot;com.ryg.chapter_2.SecondActivity&quot;
    android:configChanges=&quot;screenLayout&quot;
    android:label=&quot;@string/app_name&quot;
    android:process=&quot;:remote&quot; /&gt;

&lt;activity
    android:name=&quot;com.ryg.chapter_2.ThirdActivity&quot;
    android:configChanges=&quot;screenLayout&quot;
    android:label=&quot;@string/app_name&quot;
    android:process=&quot;com.ryg.chapter_2.remote&quot; /&gt;</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br></div></div></div></div>
<p>上面的示例分别为SecondActivity和ThirdActivity指定了 <code>process</code> 属性，并且它们的属性值不同，这意味着当前应用又增加了两个新进程。假设当前应用的包名为<code>“com.ryg.chapter_2”</code>，当SecondActivity启动时，系统会为它创建一个单独的进程，进程名为 <code>“com.ryg.chapter_2：remote”</code> ；当ThirdActivity启动时，系统也会为它创建一个单独的进程，进程名为 <code>“com.ryg.chapter_2.remote”</code> 。同时入口Activity是MainActivity，没有为它指定 <code>process</code> 属性，那么它运行在默认进程中，默认进程的进程
名是包名。</p>
<p>示例中两个 android:process 的属性分别为 <code>:remote</code> 和 <code>“com.ryg.chapter_2.remote”</code> 。这两种方式的区别是：</p>
<ol>
<li>以&quot;:&quot;开头的进程属于当前应用的私有进程，其他应用的组件不可以和它在同一个进程中。</li>
<li>而进程名不以&quot;:&quot;开头的进程属于全局进程，其他应用通过 <code>ShareUID</code> 方式可以和它在同一个进程中运行。</li>
</ol>
<blockquote>
<p>安卓系统会为每个应用分配一个唯一的 <code>UID</code>, 具有相同 <code>UID</code> 的应用才能共享数据。两个应用通过 <code>ShareUID</code> 运行在同一个进程中是有要求的，需要这两个应用有相同的 <code>ShareUID</code> 并且签名相同才可以。在这种情况下，它们可以互相访问对方的私有数据，比如 data目录、组件信息，还可以共享内存数据。或者说看起来就像是一个应用的两个部分。</p>
</blockquote>
<p>可以使用以下命令来查看进程：
<code>adb shell ps</code>
或者
<code>adb shell ps| grep com.ryg.chapter_2</code></p>
<h2 id="_2-安卓多进程带来的问题和影响" tabindex="-1">2. 安卓多进程带来的问题和影响 <a class="header-anchor" href="#_2-安卓多进程带来的问题和影响" aria-label="Permalink to “2. 安卓多进程带来的问题和影响”">&#8203;</a></h2>
<ol>
<li><strong>资源隔离与同步问题</strong></li>
</ol>
<ul>
<li>
<p><strong>静态成员失效</strong>：因为每个进程都拥有独立的虚拟机示例，静态变量在内存中是独立存在的。会造成子进程无法读取在主进程的静态变量，无法获取主进程的缓存数据。所以在使用ContentProvider 或者 Sharedpreference的时候需要配合 MODE_MULTI_PROCESS 模式进行跨进程同步.</p>
</li>
<li>
<p><strong>文件共享冲突</strong>: 主进程和子进程同时操作 SQLite 数据库,导致SQLiteDatabaseLockedException, 需要子进程通过IPC机制请求主进程执行(AIDL等)</p>
</li>
</ul>
<ol start="2">
<li><strong>性能与资源开销</strong></li>
</ol>
<ul>
<li>
<p><strong>Application重复初始化</strong></p>
</li>
<li>
<p><strong>进程间通信延迟</strong></p>
</li>
</ul>
<ol start="3">
<li><strong>稳定性与内存管理</strong></li>
</ol>
<ul>
<li>
<p><strong>独立进程崩溃容错</strong></p>
</li>
<li>
<p><strong>内存分配优化</strong></p>
</li>
</ul>
<ol start="4">
<li><strong>调试与维护</strong></li>
</ol>
<ul>
<li>
<p><strong>断点调试失效</strong></p>
</li>
<li>
<p><strong>单例模式失效</strong></p>
</li>
</ul>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[Android ip 命令详解]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Android%20ip%20%E5%91%BD%E4%BB%A4%E8%AF%A6%E8%A7%A3.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Android ip 命令详解.html</guid>
            <pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[ip 命令用于管理 Android 设备的网络接口、IP 地址、路由等网络配置。Android 使用 toybox 或 busybox 实现 ip 命令，并且可能缺少部分 GNU/Linux 发行版中的功能。]]></description>
            <content:encoded><![CDATA[<h1 id="android-ip-命令详解" tabindex="-1">Android <code>ip</code> 命令详解 <a class="header-anchor" href="#android-ip-命令详解" aria-label="Permalink to “Android ip 命令详解”">&#8203;</a></h1>
<p><img src="./image-94.png" alt="alt text" loading="lazy" decoding="async"></p>
<p><code>ip</code> 命令用于管理 Android 设备的网络接口、IP 地址、路由等网络配置。Android 使用 <code>toybox</code> 或 <code>busybox</code> 实现 <code>ip</code> 命令，并且可能缺少部分 GNU/Linux 发行版中的功能。</p>
<hr>
<h2 id="_1-基本用法" tabindex="-1"><strong>1. 基本用法</strong> <a class="header-anchor" href="#_1-基本用法" aria-label="Permalink to “1. 基本用法”">&#8203;</a></h2>
<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::xji2818e7lelq7l5zfj3n::--><code>ip [ OPTIONS ] OBJECT { COMMAND | help }
ip [ -force ] -batch filename</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><ul>
<li><code>OBJECT</code>（对象）：指定要操作的网络组件，如 <code>link</code>（网络接口）、<code>address</code>（IP 地址）、<code>route</code>（路由表）等。</li>
<li><code>COMMAND</code>（命令）：对对象执行的操作，如 <code>show</code>（查看）、<code>add</code>（添加）、<code>del</code>（删除）。</li>
<li><code>OPTIONS</code>（选项）：控制输出格式，如 <code>-s</code>（统计信息）、<code>-d</code>（详细信息）、<code>-r</code>（解析地址）。</li>
</ul>
<hr>
<h2 id="_2-主要对象-object" tabindex="-1"><strong>2. 主要对象 (<code>OBJECT</code>)</strong> <a class="header-anchor" href="#_2-主要对象-object" aria-label="Permalink to “2. 主要对象 (OBJECT)”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>对象 (<code>OBJECT</code>)</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>link</code></td>
<td>管理网络接口（类似 <code>ifconfig</code>）</td>
</tr>
<tr>
<td><code>address</code></td>
<td>管理 IP 地址（类似 <code>ifconfig</code> 和 <code>ip addr</code>）</td>
</tr>
<tr>
<td><code>addrlabel</code></td>
<td>配置地址标签</td>
</tr>
<tr>
<td><code>route</code></td>
<td>管理路由表（类似 <code>route</code> 命令）</td>
</tr>
<tr>
<td><code>rule</code></td>
<td>配置路由规则</td>
</tr>
<tr>
<td><code>neigh</code></td>
<td>查看和管理 ARP/NDP 表（邻居发现协议）</td>
</tr>
<tr>
<td><code>ntable</code></td>
<td>显示邻居表缓存信息</td>
</tr>
<tr>
<td><code>tunnel</code></td>
<td>管理网络隧道（VPN/隧道接口）</td>
</tr>
<tr>
<td><code>tuntap</code></td>
<td>配置 TUN/TAP 设备</td>
</tr>
<tr>
<td><code>maddress</code></td>
<td>配置多播地址</td>
</tr>
<tr>
<td><code>mroute</code></td>
<td>配置多播路由</td>
</tr>
<tr>
<td><code>mrule</code></td>
<td>配置多播规则</td>
</tr>
<tr>
<td><code>monitor</code></td>
<td>监听网络事件</td>
</tr>
<tr>
<td><code>xfrm</code></td>
<td>配置 IPsec（传输模式）</td>
</tr>
<tr>
<td><code>netns</code></td>
<td>管理网络命名空间（仅部分 Android 设备支持）</td>
</tr>
<tr>
<td><code>l2tp</code></td>
<td>管理 L2TP 隧道</td>
</tr>
<tr>
<td><code>fou</code></td>
<td>配置 Foo Over UDP（FOU）</td>
</tr>
<tr>
<td><code>macsec</code></td>
<td>配置 MACsec（以太网安全）</td>
</tr>
<tr>
<td><code>tcp_metrics</code></td>
<td>查看 TCP 统计信息</td>
</tr>
<tr>
<td><code>token</code></td>
<td>配置 IPv6 令牌</td>
</tr>
<tr>
<td><code>netconf</code></td>
<td>配置网络参数</td>
</tr>
<tr>
<td><code>ila</code></td>
<td>配置 IPv6 ILA</td>
</tr>
<tr>
<td><code>vrf</code></td>
<td>配置 VRF（虚拟路由转发）</td>
</tr>
<tr>
<td><code>sr</code></td>
<td>配置 Segment Routing（SRv6）</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="_3-选项-options" tabindex="-1"><strong>3. 选项 (<code>OPTIONS</code>)</strong> <a class="header-anchor" href="#_3-选项-options" aria-label="Permalink to “3. 选项 (OPTIONS)”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>选项</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-V</code></td>
<td>显示 <code>ip</code> 命令的版本信息</td>
</tr>
<tr>
<td><code>-s</code></td>
<td>显示统计信息</td>
</tr>
<tr>
<td><code>-d</code></td>
<td>显示详细信息（debug）</td>
</tr>
<tr>
<td><code>-r</code></td>
<td>解析主机名（如果适用）</td>
</tr>
<tr>
<td><code>-h</code></td>
<td>以可读格式显示数值（human-readable）</td>
</tr>
<tr>
<td><code>-iec</code></td>
<td>以 IEC 单位显示大小（如 KiB、MiB）</td>
</tr>
<tr>
<td>`-f {inet</td>
<td>inet6</td>
</tr>
<tr>
<td><code>-4</code></td>
<td>仅显示 IPv4 信息</td>
</tr>
<tr>
<td><code>-6</code></td>
<td>仅显示 IPv6 信息</td>
</tr>
<tr>
<td><code>-o</code></td>
<td>以单行格式显示（oneline）</td>
</tr>
<tr>
<td><code>-br</code></td>
<td>以紧凑格式显示（brief）</td>
</tr>
<tr>
<td><code>-batch filename</code></td>
<td>以批量模式执行多个 <code>ip</code> 命令</td>
</tr>
<tr>
<td><code>-force</code></td>
<td>强制执行某些操作</td>
</tr>
<tr>
<td><code>-color</code></td>
<td>使用彩色输出（如果支持）</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="_4-ip-命令的完整功能" tabindex="-1"><strong>4. <code>ip</code> 命令的完整功能</strong> <a class="header-anchor" href="#_4-ip-命令的完整功能" aria-label="Permalink to “4. ip 命令的完整功能”">&#8203;</a></h2>
<h3 id="_4-1-网络接口管理" tabindex="-1"><strong>4.1 网络接口管理</strong> <a class="header-anchor" href="#_4-1-网络接口管理" aria-label="Permalink to “4.1 网络接口管理”">&#8203;</a></h3>
<ul>
<li>显示所有网络接口：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::jnxp7kebnmbo9f71y8p90e::--><code>ip link show</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>启用或禁用接口：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::nwrbpff2edgvwbhagjm0p::--><code>ip link set wlan0 up   # 启用 wlan0
ip link set wlan0 down # 禁用 wlan0</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div></li>
</ul>
<h3 id="_4-2-ip-地址管理" tabindex="-1"><strong>4.2 IP 地址管理</strong> <a class="header-anchor" href="#_4-2-ip-地址管理" aria-label="Permalink to “4.2 IP 地址管理”">&#8203;</a></h3>
<ul>
<li>显示 IP 地址：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::jjpchs04drn9hi0bgn9cci::--><code>ip address show</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>添加 IP 地址：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::e01a3i7zyepul4tzmlevn::--><code>ip address add 192.168.1.100/24 dev wlan0</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>删除 IP 地址：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::a9xfsecvr1tfjirzbs575f::--><code>ip address del 192.168.1.100/24 dev wlan0</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
<h3 id="_4-3-路由管理" tabindex="-1"><strong>4.3 路由管理</strong> <a class="header-anchor" href="#_4-3-路由管理" aria-label="Permalink to “4.3 路由管理”">&#8203;</a></h3>
<ul>
<li>显示路由表：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::7afkf5akhxy6vyuy4mdvgl::--><code>ip route show</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>添加默认网关：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::sx689uixoofnierw5029l::--><code>ip route add default via 192.168.1.1 dev wlan0</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>删除路由：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::5jaak97es2syths1roz7l::--><code>ip route del default</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
<h3 id="_4-4-邻居-arp-ndp-管理" tabindex="-1"><strong>4.4 邻居（ARP/NDP）管理</strong> <a class="header-anchor" href="#_4-4-邻居-arp-ndp-管理" aria-label="Permalink to “4.4 邻居（ARP/NDP）管理”">&#8203;</a></h3>
<ul>
<li>显示 ARP 表：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::w2ykg6jk8cbk4v68qf1ot::--><code>ip neigh show</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>删除 ARP 记录：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::6jkandpjlme2ougulvjewa::--><code>ip neigh del 192.168.1.1 dev wlan0</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
<h3 id="_4-5-监听网络事件" tabindex="-1"><strong>4.5 监听网络事件</strong> <a class="header-anchor" href="#_4-5-监听网络事件" aria-label="Permalink to “4.5 监听网络事件”">&#8203;</a></h3>
<ul>
<li>监听所有 <code>ip</code> 相关事件：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::cjfxrjooj6oeokfuku63ib::--><code>ip monitor all</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>仅监听路由变化：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::t2ivj8hb5k3phfv5hbwyv::--><code>ip monitor route</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
<h3 id="_4-6-其他网络配置" tabindex="-1"><strong>4.6 其他网络配置</strong> <a class="header-anchor" href="#_4-6-其他网络配置" aria-label="Permalink to “4.6 其他网络配置”">&#8203;</a></h3>
<ul>
<li>配置多播地址：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::kp92t8u0i9kcaipf0c7avs::--><code>ip maddress add 01:00:5e:00:00:01 dev wlan0</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>配置 MACsec（部分设备支持）：<div class="language-bash line-numbers-mode" data-lang="bash"><button title="Copy Code" class="copy"></button><span class="lang">Bash</span><pre><!--::markdown-it-async::j99q8dkyqwfzcs9htib78::--><code>ip macsec add link wlan0 type macsec</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
<hr>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[相机扭曲的解决办法]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E7%9B%B8%E6%9C%BA%E6%89%AD%E6%9B%B2%E7%9A%84%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/相机扭曲的解决办法.html</guid>
            <pubDate>Sat, 01 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[发生相机扭曲的主要原因是surfaceview的宽高比与相机输出图像的最佳宽高比不一致，相机输出图像适配surfaceview的时候被拉伸导致的扭曲。]]></description>
            <content:encoded><![CDATA[<h1 id="相机扭曲的解决办法" tabindex="-1">相机扭曲的解决办法 <a class="header-anchor" href="#相机扭曲的解决办法" aria-label="Permalink to “相机扭曲的解决办法”">&#8203;</a></h1>
<blockquote>
<p>发生相机扭曲的主要原因是surfaceview的宽高比与相机输出图像的最佳宽高比不一致，相机输出图像适配surfaceview的时候被拉伸导致的扭曲。</p>
</blockquote>
<h2 id="步骤1-获取相机支持的输出比例" tabindex="-1">步骤1：获取相机支持的输出比例 <a class="header-anchor" href="#步骤1-获取相机支持的输出比例" aria-label="Permalink to “步骤1：获取相机支持的输出比例”">&#8203;</a></h2>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::f88iv57dbgphcggewb9cf::--><code>// 在Fragment中初始化预览尺寸
private fun initPreviewSize() {
    val characteristics = cameraManager.getCameraCharacteristics(cameraId)
    previewSize = getPreviewOutputSize(
        binding.surfaceView.display, 
        characteristics, 
        SurfaceHolder::class.java
    )
}

// 核心尺寸选择算法
private fun getPreviewOutputSize(
    display: Display?,
    characteristics: CameraCharacteristics,
    targetClass: Class&lt;*&gt;
): Size {
    // 1. 获取相机支持的所有分辨率
    val config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
    val outputSizes = config.getOutputSizes(targetClass)

    // 2. 计算显示区域宽高比
    val displaySize = Point().apply { display?.getSize(this) }
    val displayRatio = displaySize.x.toFloat() / displaySize.y

    // 3. 选择最接近屏幕比例的相机分辨率
    return outputSizes.minByOrNull { 
        abs((it.width.toFloat()/it.height) - displayRatio)
    } ?: outputSizes.maxByOrNull { it.width * it.height }!!
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br></div></div><p><strong>原理说明</strong>：通过比较相机支持的分辨率宽高比与屏幕显示比例的差异，选择最匹配的预览尺寸</p>
<hr>
<h2 id="步骤2-设置视图动态宽高比" tabindex="-1">步骤2：设置视图动态宽高比 <a class="header-anchor" href="#步骤2-设置视图动态宽高比" aria-label="Permalink to “步骤2：设置视图动态宽高比”">&#8203;</a></h2>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::q7dju5ha3n9t0kz8werxac::--><code>// 在SurfaceView创建时绑定比例
private fun setAspectRatio() {
    binding.surfaceView.setAspectRatio(previewSize.width, previewSize.height)
}

// AutoFitSurfaceView核心实现
class AutoFitSurfaceView(...) : SurfaceView(...) {
    private var aspectRatio = 0f

    fun setAspectRatio(width: Int, height: Int) {
        aspectRatio = width.toFloat() / height
        requestLayout()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        
        // 动态计算适配尺寸
        when {
            aspectRatio == 0f -&gt; super.onMeasure(...)
            width &gt; height -&gt; handleLandscape(width, height)
            else -&gt; handlePortrait(width, height)
        }
    }

    private fun handleLandscape(width: Int, height: Int) {
        val expectedHeight = (width / aspectRatio).roundToInt()
        setMeasuredDimension(width, expectedHeight.coerceAtMost(height))
    }

    private fun handlePortrait(width: Int, height: Int) {
        val expectedWidth = (height * aspectRatio).roundToInt()
        setMeasuredDimension(expectedWidth.coerceAtMost(width), height)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br></div></div><p><strong>核心机制</strong>：基于相机分辨率比例动态计算视图尺寸，保证画面始终居中完整显示</p>
<hr>
<h2 id="步骤3-处理设备方向变化" tabindex="-1">步骤3：处理设备方向变化 <a class="header-anchor" href="#步骤3-处理设备方向变化" aria-label="Permalink to “步骤3：处理设备方向变化”">&#8203;</a></h2>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::dde3p45cmd50xjq3gb7en::--><code>// 在Fragment中监听方向变化
private val orientationListener = object : OrientationEventListener(context) {
    override fun onOrientationChanged(orientation: Int) {
        val newRotation = when {
            orientation in 45..135 -&gt; Surface.ROTATION_270
            orientation in 135..225 -&gt; Surface.ROTATION_180
            orientation in 225..315 -&gt; Surface.ROTATION_90
            else -&gt; Surface.ROTATION_0
        }
        
        if (newRotation != lastRotation) {
            adjustPreviewForRotation(newRotation)
        }
    }
}

private fun adjustPreviewForRotation(rotation: Int) {
    // 1. 重新计算传感器方向
    val sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
    val totalRotation = (sensorOrientation + rotation) % 360

    // 2. 更新预览方向
    captureSession?.setDisplayOrientation(totalRotation)

    // 3. 重新设置视图比例
    setAspectRatio()
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br></div></div><p><strong>方向适配逻辑</strong>：</p>
<ol>
<li>计算传感器物理方向与设备旋转的合成方向</li>
<li>通过<code>setDisplayOrientation</code>同步预览方向</li>
<li>强制重绘视图保持比例正确</li>
</ol>
<hr>
<h2 id="步骤4-建立预览会话时验证参数" tabindex="-1">步骤4：建立预览会话时验证参数 <a class="header-anchor" href="#步骤4-建立预览会话时验证参数" aria-label="Permalink to “步骤4：建立预览会话时验证参数”">&#8203;</a></h2>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::hpr10votuqjh2fbr5b4717::--><code>private fun createCaptureSession() {
    val targets = listOf(surfaceView.holder.surface)
    
    cameraDevice?.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(session: CameraCaptureSession) {
            // 验证实际预览尺寸
            val sessionParams = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            val activeSize = sessionParams.surface?.let { 
                it.getSize() // 实际生效的分辨率
            }
            
            if (activeSize != previewSize) {
                Log.w(TAG, &quot;实际预览尺寸($activeSize)与预期($previewSize)不符&quot;)
                adjustPreviewSize(activeSize)
            }
        }
    }, null)
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><p><strong>尺寸验证机制</strong>：在会话建立后检查实际生效的预览尺寸，必要时动态调整</p>
<hr>
<h2 id="步骤5-异常情况处理方案" tabindex="-1">步骤5：异常情况处理方案 <a class="header-anchor" href="#步骤5-异常情况处理方案" aria-label="Permalink to “步骤5：异常情况处理方案”">&#8203;</a></h2>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::fnohfjd1906jjv9dxmrb7j::--><code>// 尺寸不匹配时的自动修复
private fun adjustPreviewSize(actualSize: Size) {
    // 1. 关闭当前会话
    closeCamera()

    // 2. 更新目标尺寸
    previewSize = actualSize

    // 3. 重新打开相机
    openCamera(cameraId).also {
        binding.surfaceView.setAspectRatio(actualSize.width, actualSize.height)
    }
}

// 在AutoFitSurfaceView中添加保护逻辑
override fun setAspectRatio(width: Int, height: Int) {
    if (width == 0 || height == 0) {
        throw IllegalArgumentException(&quot;无效的尺寸参数: ${width}x$height&quot;)
    }
    aspectRatio = width.toFloat() / height
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br></div></div><p><strong>容错机制</strong>：</p>
<ul>
<li>自动检测尺寸偏差</li>
<li>异常参数校验</li>
<li>安全重连机制</li>
</ul>
<hr>
<h2 id="实施效果验证" tabindex="-1">实施效果验证 <a class="header-anchor" href="#实施效果验证" aria-label="Permalink to “实施效果验证”">&#8203;</a></h2>
<h3 id="测试用例1-基础比例适配" tabindex="-1">测试用例1：基础比例适配 <a class="header-anchor" href="#测试用例1-基础比例适配" aria-label="Permalink to “测试用例1：基础比例适配”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>设备类型</th>
<th>相机分辨率</th>
<th>屏幕比例</th>
<th>预期效果</th>
</tr>
</thead>
<tbody>
<tr>
<td>16:9手机竖屏</td>
<td>1920x1080</td>
<td>9:16</td>
<td>上下黑边，无拉伸</td>
</tr>
<tr>
<td>4:3平板横屏</td>
<td>1280x720</td>
<td>4:3</td>
<td>左右黑边，比例正确</td>
</tr>
</tbody>
</table>
<h3 id="测试用例2-动态方向适配" tabindex="-1">测试用例2：动态方向适配 <a class="header-anchor" href="#测试用例2-动态方向适配" aria-label="Permalink to “测试用例2：动态方向适配”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>操作步骤</th>
<th>预期效果</th>
</tr>
</thead>
<tbody>
<tr>
<td>竖屏启动后旋转至横屏</td>
<td>画面自动旋转并保持比例</td>
</tr>
<tr>
<td>快速连续旋转设备</td>
<td>画面平滑过渡无撕裂</td>
</tr>
</tbody>
</table>
<h3 id="测试用例3-异常场景恢复" tabindex="-1">测试用例3：异常场景恢复 <a class="header-anchor" href="#测试用例3-异常场景恢复" aria-label="Permalink to “测试用例3：异常场景恢复”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>异常类型</th>
<th>系统响应</th>
</tr>
</thead>
<tbody>
<tr>
<td>相机返回非常规尺寸</td>
<td>自动调整视图比例</td>
</tr>
<tr>
<td>参数传0触发异常</td>
<td>抛出明确错误信息</td>
</tr>
</tbody>
</table>
<hr>
<p>通过以上五步实施方案，可系统性地解决Android相机预览画面扭曲问题。建议配合GPU呈现模式分析工具实时监控渲染性能，确保方案在不同设备上的兼容性。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[启动相机的方法]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%90%AF%E5%8A%A8%E7%9B%B8%E6%9C%BA%E7%9A%84%E6%96%B9%E6%B3%95.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/启动相机的方法.html</guid>
            <pubDate>Mon, 24 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Camera1 API 是安卓早期的相机接口，尽管它在新版本的 Android 中已被弃用，但它仍然被一些旧设备和应用所使用。]]></description>
            <content:encoded><![CDATA[<h1 id="启动相机的方法" tabindex="-1">启动相机的方法 <a class="header-anchor" href="#启动相机的方法" aria-label="Permalink to “启动相机的方法”">&#8203;</a></h1>
<h2 id="_1-camera1-api" tabindex="-1">1. <strong>Camera1 API</strong> <a class="header-anchor" href="#_1-camera1-api" aria-label="Permalink to “1. Camera1 API”">&#8203;</a></h2>
<p><code>Camera1 API</code> 是安卓早期的相机接口，尽管它在新版本的 Android 中已被弃用，但它仍然被一些旧设备和应用所使用。</p>
<h3 id="打开相机的步骤" tabindex="-1">打开相机的步骤： <a class="header-anchor" href="#打开相机的步骤" aria-label="Permalink to “打开相机的步骤：”">&#8203;</a></h3>
<ol>
<li>
<p><strong>获取 <code>Camera</code> 实例</strong>：</p>
<ul>
<li><strong>作用</strong>：通过 <code>Camera.open()</code> 打开相机硬件。</li>
<li><strong>代码</strong>：<code>Camera camera = Camera.open();</code></li>
</ul>
</li>
<li>
<p><strong>设置相机预览</strong>：</p>
<ul>
<li><strong>作用</strong>：将相机输出的图像数据显示到 <code>SurfaceView</code> 或 <code>TextureView</code> 上。</li>
<li><strong>代码</strong>：<code>camera.setPreviewDisplay(surfaceHolder);</code></li>
</ul>
</li>
<li>
<p><strong>启动预览</strong>：</p>
<ul>
<li><strong>作用</strong>：开始相机的实时预览。</li>
<li><strong>代码</strong>：<code>camera.startPreview();</code></li>
</ul>
</li>
<li>
<p><strong>释放相机资源</strong>：</p>
<ul>
<li><strong>作用</strong>：停止预览并释放相机资源。</li>
<li><strong>代码</strong>：<code>camera.stopPreview();</code> 和 <code>camera.release();</code></li>
</ul>
</li>
</ol>
<h3 id="简洁总结" tabindex="-1">简洁总结： <a class="header-anchor" href="#简洁总结" aria-label="Permalink to “简洁总结：”">&#8203;</a></h3>
<ol>
<li><strong>获取相机实例</strong>：<code>Camera.open()</code> 打开相机。</li>
<li><strong>设置预览显示</strong>：<code>setPreviewDisplay()</code> 将预览显示到 <code>SurfaceView</code>。</li>
<li><strong>开始预览</strong>：<code>startPreview()</code> 启动相机预览。</li>
<li><strong>释放相机资源</strong>：<code>stopPreview()</code> 和 <code>release()</code> 释放资源。</li>
</ol>
<hr>
<h2 id="_2-camerax-api" tabindex="-1">2. <strong>CameraX API</strong> <a class="header-anchor" href="#_2-camerax-api" aria-label="Permalink to “2. CameraX API”">&#8203;</a></h2>
<p><code>CameraX API</code> 是 Google 提供的一个相机库，它是基于 <code>Camera2 API</code> 构建的，旨在简化相机开发，提供一致性和更好的兼容性，特别是在不同设备和 Android 版本上。</p>
<h3 id="打开相机的步骤-1" tabindex="-1">打开相机的步骤： <a class="header-anchor" href="#打开相机的步骤-1" aria-label="Permalink to “打开相机的步骤：”">&#8203;</a></h3>
<ol>
<li>
<p><strong>添加依赖项</strong>：</p>
<ul>
<li><strong>作用</strong>：首先，需要在项目中添加 <code>CameraX</code> 的依赖项。</li>
<li><strong>代码</strong>（<code>build.gradle</code>）：<div class="language-gradle line-numbers-mode" data-lang="gradle"><button title="Copy Code" class="copy"></button><span class="lang">Gradle</span><pre><!--::markdown-it-async::es1qmel1z8rqb4rq16mhu::--><code>dependencies {
    implementation &quot;androidx.camera:camera-core:1.0.0&quot;
    implementation &quot;androidx.camera:camera-camera2:1.0.0&quot;
    implementation &quot;androidx.camera:camera-lifecycle:1.0.0&quot;
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li>
</ul>
</li>
<li>
<p><strong>初始化 <code>CameraX</code> 配置</strong>：</p>
<ul>
<li><strong>作用</strong>：配置 <code>CameraX</code> 并选择相机的用例（如预览、图片捕获等）。</li>
<li><strong>代码</strong>：<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::grtwyrz98496jk9wbq7z::--><code>val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
    val cameraProvider = cameraProviderFuture.get()
    val preview = Preview.Builder().build()
    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}, ContextCompat.getMainExecutor(context))</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div></li>
</ul>
</li>
<li>
<p><strong>绑定生命周期</strong>：</p>
<ul>
<li><strong>作用</strong>：将相机与生命周期绑定，确保相机资源在生命周期结束时自动释放。</li>
<li><strong>代码</strong>：<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::zrar8k18yenruj8g9qbk::--><code>cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
</li>
<li>
<p><strong>显示相机预览</strong>：</p>
<ul>
<li><strong>作用</strong>：通过 <code>Preview</code> 用例将相机预览显示到 <code>PreviewView</code>。</li>
<li><strong>代码</strong>：<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::s63euso1djn36zcd626767::--><code>preview.setSurfaceProvider(previewView.createSurfaceProvider())</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
</li>
<li>
<p><strong>释放相机资源</strong>：</p>
<ul>
<li><strong>作用</strong>：<code>CameraX</code> 会自动管理相机资源，通常无需手动释放，但可以在 <code>onPause()</code> 等生命周期方法中进行一些清理工作。</li>
</ul>
</li>
</ol>
<h2 id="_3-camera2-api" tabindex="-1">3. <strong>Camera2 API</strong> <a class="header-anchor" href="#_3-camera2-api" aria-label="Permalink to “3. Camera2 API”">&#8203;</a></h2>
<h3 id="_1-初始化-cameramanager" tabindex="-1">1. <strong>初始化 <code>CameraManager</code></strong> <a class="header-anchor" href="#_1-初始化-cameramanager" aria-label="Permalink to “1. 初始化 CameraManager”">&#8203;</a></h3>
<ul>
<li><strong>作用</strong>：获取系统的相机管理器，用于管理和访问设备的相机。</li>
<li><strong>代码</strong>：<code>mCameraManager = getSystemService(CAMERA_SERVICE) as CameraManager</code></li>
</ul>
<h3 id="_2-选择相机-id" tabindex="-1">2. <strong>选择相机 ID</strong> <a class="header-anchor" href="#_2-选择相机-id" aria-label="Permalink to “2. 选择相机 ID”">&#8203;</a></h3>
<ul>
<li><strong>作用</strong>：通过 <code>CameraManager</code> 获取所有可用的相机 ID，选择一个相机（例如后置摄像头）。</li>
<li><strong>代码</strong>：<code>val cameraId = mCameraManager.cameraIdList[0]</code></li>
</ul>
<h3 id="_3-打开相机" tabindex="-1">3. <strong>打开相机</strong> <a class="header-anchor" href="#_3-打开相机" aria-label="Permalink to “3. 打开相机”">&#8203;</a></h3>
<ul>
<li><strong>作用</strong>：使用 <code>CameraManager.openCamera()</code> 方法打开选择的相机，并创建一个 <code>CameraDevice</code> 实例，开始相机的操作。</li>
<li><strong>代码</strong>：<code>mCameraManager.openCamera(cameraId, stateCallback, handler)</code></li>
</ul>
<h3 id="_4-创建预览会话" tabindex="-1">4. <strong>创建预览会话</strong> <a class="header-anchor" href="#_4-创建预览会话" aria-label="Permalink to “4. 创建预览会话”">&#8203;</a></h3>
<ul>
<li><strong>作用</strong>：在相机打开后，使用 <code>CameraDevice.createCaptureSession()</code> 创建一个预览会话，将 <code>SurfaceView</code> 显示内容与相机预览绑定。</li>
<li><strong>代码</strong>：<code>camera.createCaptureSession(listOf(surface), stateCallback, handler)</code></li>
</ul>
<h3 id="_5-启动预览" tabindex="-1">5. <strong>启动预览</strong> <a class="header-anchor" href="#_5-启动预览" aria-label="Permalink to “5. 启动预览”">&#8203;</a></h3>
<ul>
<li><strong>作用</strong>：配置相机的预览请求并开始显示相机内容。</li>
<li><strong>代码</strong>：<code>mCaptureSession.setRepeatingRequest(previewRequest, null, mBackgroundHandler)</code></li>
</ul>
<h3 id="_6-释放相机" tabindex="-1">6. <strong>释放相机</strong> <a class="header-anchor" href="#_6-释放相机" aria-label="Permalink to “6. 释放相机”">&#8203;</a></h3>
<ul>
<li><strong>作用</strong>：在不需要相机时，释放相机资源，关闭会话，释放硬件资源。</li>
<li><strong>代码</strong>：<code>mCameraDevice.close()</code></li>
</ul>
<h3 id="简洁总结-1" tabindex="-1">简洁总结： <a class="header-anchor" href="#简洁总结-1" aria-label="Permalink to “简洁总结：”">&#8203;</a></h3>
<ol>
<li><strong>初始化管理器</strong>：获取 <code>CameraManager</code>。</li>
<li><strong>选择相机</strong>：选择需要的相机（前置/后置）。</li>
<li><strong>打开相机</strong>：打开相机并获取 <code>CameraDevice</code>。</li>
<li><strong>创建会话</strong>：创建相机预览会话并绑定到 <code>SurfaceView</code>。</li>
<li><strong>启动预览</strong>：开始相机预览。</li>
<li><strong>释放资源</strong>：停止预览并释放相机资源。</li>
</ol>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[LeakCanary原理分析]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/LeakCanary%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/LeakCanary原理分析.html</guid>
            <pubDate>Fri, 21 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[| 模块 | 核心组件/类 | 功能关联 | | ------------ | ------------------------------ | -------------------- | | 监控模块 | ActivityDestroyWatcher |?]]></description>
            <content:encoded><![CDATA[<h2 id="监控模块-activitydestroywatcher-categories-android-tags-android-leakcanary-created-2025-02-21" tabindex="-1">--------- | ------------------------------ | -------------------- | | 监控模块 | ActivityDestroyWatcher |?&quot;
categories: [Android]
tags: [android, LeakCanary]
created: 2025-02-21 <a class="header-anchor" href="#监控模块-activitydestroywatcher-categories-android-tags-android-leakcanary-created-2025-02-21" aria-label="Permalink to “--------- | ------------------------------ | -------------------- | | 监控模块 | ActivityDestroyWatcher |?&quot;categories: [Android]tags: [android, LeakCanary]created: 2025-02-21”">&#8203;</a></h2>
<h1 id="leakcanary原理分析" tabindex="-1">LeakCanary原理分析 <a class="header-anchor" href="#leakcanary原理分析" aria-label="Permalink to “LeakCanary原理分析”">&#8203;</a></h1>
<h2 id="_1-leakcanary的组成" tabindex="-1">1. LeakCanary的组成： <a class="header-anchor" href="#_1-leakcanary的组成" aria-label="Permalink to “1. LeakCanary的组成：”">&#8203;</a></h2>
<h3 id="总结架构层级" tabindex="-1"><strong>总结架构层级</strong> <a class="header-anchor" href="#总结架构层级" aria-label="Permalink to “总结架构层级”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>模块</th>
<th>核心组件/类</th>
<th>功能关联</th>
</tr>
</thead>
<tbody>
<tr>
<td>监控模块</td>
<td>ActivityDestroyWatcher</td>
<td>监听组件生命周期</td>
</tr>
<tr>
<td>对象观察模块</td>
<td>ObjectWatcher</td>
<td>跟踪弱引用和引用队列</td>
</tr>
<tr>
<td>堆转储模块</td>
<td>HeapDumper</td>
<td>生成堆快照文件</td>
</tr>
<tr>
<td>堆分析模块</td>
<td>Shark/HeapAnalyzer</td>
<td>解析泄漏路径</td>
</tr>
<tr>
<td>结果展示模块</td>
<td>DisplayLeakService</td>
<td>展示泄漏详情</td>
</tr>
<tr>
<td>基础设施模块</td>
<td>MainProcessAppWatcherInstaller</td>
<td>自动初始化和配置管理</td>
</tr>
</tbody>
</table>
<h2 id="_2-leakcanary监听泄漏的核心原理" tabindex="-1">2. LeakCanary监听泄漏的核心原理 <a class="header-anchor" href="#_2-leakcanary监听泄漏的核心原理" aria-label="Permalink to “2. LeakCanary监听泄漏的核心原理”">&#8203;</a></h2>
<p><code>LeakCanary</code> 的核心原理是利用Java的 <code>弱引用</code> 和 <code>引用队列</code>。弱引用的特性是当对象只有弱引用时，GC就会回收它。LeakCanary通过监听 <code>Activity</code> 或 <code>Fragment</code> 、<code>ViewModel</code>、<code>RootView</code>、<code>Service</code>的销毁，然后创建对应的弱引用，并关联一个引用队列。如果在Activity销毁后，弱引用没有被加入队列，说明可能泄漏了。</p>
<p><img src="./image-92.png" alt="alt text" loading="lazy" decoding="async">
<img src="./image-93.png" alt="alt text" loading="lazy" decoding="async"></p>
<p>举一个通俗的比喻：你在快递站寄出了一个包裹（Activity/Fragment），正常的流程应该是：</p>
<ol>
<li>你寄出后，快递员（GC）来及时取走（回收内存）</li>
<li>如果有包裹长时间未被取走（内存泄漏），就发出报警。</li>
</ol>
<p>在这个过程中，<code>LeakCanary</code> 的检测机制就像快递站的监控系统：</p>
<ul>
<li>标记包裹：每个包裹贴上[弱引用]标签（容易被撕掉，代表容易被回收）</li>
<li>定期检查：过段时间检查标签是否还在（未被回收）</li>
<li>报警分析：如果标签仍在，说明快递员没来取（GC未回收），触发报警并分析原因。</li>
</ul>
<p>早期的版本需要在应用 <code>Application</code> 的 <code>onCreate</code> 中处理 <code>LeakCanary</code> 的初始化, 在新版本中为了降低接入的成本，将 <code>LeakCanary</code> 的初始化放在了库定义的 <code>AppWatcherInstaller</code> 中，原理是利用 <code>ContentProvider</code> 的 <code>onCreate</code> 的初始化时机早于 <code>Application</code> 中的 <code>onCreate</code>(晚于 <code>Application</code> 的 <code>attchBaseContext</code>)。</p>
<h2 id="_3-技术原理详解" tabindex="-1">3. 技术原理详解 <a class="header-anchor" href="#_3-技术原理详解" aria-label="Permalink to “3. 技术原理详解”">&#8203;</a></h2>
<ol>
<li>标记阶段：监听销毁 + 创建弱引用
<ul>
<li>监听对象的销毁：通过 <code>ActivityLifecycleCallbacks</code> 监听Activity/Fragment的 <code>onDestory()</code> 事件</li>
</ul>
</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-159" id="tab-160" checked><label data-title="txt" for="tab-160">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::iql8h655gulqadhfw76rfh::--><code>// Application
@HiltAndroidApp
class LeakCanaryApp : Application() {

  override fun onCreate() {
    super.onCreate()
    registerActivityLifecycleCallbacks(ActivityProviderCallbacks())
  }
}

// ActivityProviderCallbacks
class ActivityProviderCallbacks : ActivityLifecycleCallbacks {

  override fun onActivityCreated(
    activity: Activity,
    savedInstanceState: Bundle?
  ) {
    CurrentActivityProvider.onActivityCreated(activity)
  }

  override fun onActivityDestroyed(activity: Activity) {
    CurrentActivityProvider.onActivityDestroyed(activity)
  }

  override fun onActivityStarted(activity: Activity) = Unit

  override fun onActivityResumed(activity: Activity) = Unit

  override fun onActivityPaused(activity: Activity) = Unit

  override fun onActivityStopped(activity: Activity) = Unit

  override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br></div></div></div></div>
<ul>
<li>创建弱引用标记：在对象销毁时，<code>LeakCanary</code> 会为它创建一个带有唯一标识的弱引用，并关联到一个引用队列（<code>ReferenceQueue</code>）</li>
</ul>
<ol start="2">
<li>追踪阶段：延迟检查 + 强制GC
<ul>
<li>延迟等待：销毁后等待5秒（让GC有机会回收对象）</li>
<li>主动触发GC：若发现弱引用未进入队列（未被回收），手动触发一次GC再次确认。</li>
<li>确认泄漏：两次检查后对象仍然未被回收，则判定未内存泄漏。</li>
</ul>
</li>
<li>分析阶段：转储内存 + 逆向追踪
<ul>
<li>生成内存快照：调用 <code>Debug.dumpHprofData()</code> 生成 <code>.hprof</code> 文件。</li>
<li>解析引用链：使用 <code>Shark</code> 库分析快照，找到泄漏对象的GC Root（如静态变量、线程等）的强引用路径，精确定位泄漏点。</li>
</ul>
</li>
<li>通知栏发送可视化路径：发送通知提醒开发者存在内存泄漏，并展示泄漏路径。</li>
</ol>
<p>源码解析详见：
<a href="./LeakCanary源码分析.html" title="可选标题">LeakCanary源码分析</a></p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[双亲委派机制]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/双亲委派机制.html</guid>
            <pubDate>Wed, 05 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[双亲委派机制 是Java类加载器（ClassLoader）的核心工作机制，它通过层次化的类加载模型保障了Java程序的稳定性和安全性。]]></description>
            <content:encoded><![CDATA[<h1 id="双亲委派机制" tabindex="-1">双亲委派机制 <a class="header-anchor" href="#双亲委派机制" aria-label="Permalink to “双亲委派机制”">&#8203;</a></h1>
<p><code>双亲委派机制</code> 是Java类加载器（<code>ClassLoader</code>）的核心工作机制，它通过<code>层次化的类加载模型</code>保障了Java程序的稳定性和安全性。</p>
<h2 id="_1-双亲委派的核心机制" tabindex="-1">1. 双亲委派的核心机制 <a class="header-anchor" href="#_1-双亲委派的核心机制" aria-label="Permalink to “1. 双亲委派的核心机制”">&#8203;</a></h2>
<p>双亲委派的核心机制是：<strong>当一个类加载器需要加载类时，不会立即尝试自己加载，而是先将请求委派给父类加载器处理。</strong>流程如下：</p>
<ol>
<li>自底向上委派：子类加载器逐层向上委托，直到启动类加载器（Bootstrap ClassLoader）。</li>
<li>自顶向下尝试加载：若父类加载器无法加载（在自己的搜索范围内未找到类），子类加载器才会尝试加载。</li>
</ol>
<h2 id="_2-代码示例-classloader-loadclass" tabindex="-1">2. 代码示例：ClassLoader.loadClass() <a class="header-anchor" href="#_2-代码示例-classloader-loadclass" aria-label="Permalink to “2. 代码示例：ClassLoader.loadClass()”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-27" id="tab-28" checked><label data-title="txt" for="tab-28">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::ygbfmw3lihak52zhe3fe::--><code>protected Class&lt;?&gt; loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查类是否已加载
        Class&lt;?&gt; c = findLoadedClass(name);
        if (c == null) {
            try {
                // 2. 委派给父类加载器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 3. 父类为null时，使用Bootstrap ClassLoader（由JVM实现）
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父类加载失败，不立即抛出异常
            }

            if (c == null) {
                // 4. 父类未找到，调用自身的findClass()加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div></div></div>
<h2 id="_3-类加载器的层次结构" tabindex="-1">3. 类加载器的层次结构 <a class="header-anchor" href="#_3-类加载器的层次结构" aria-label="Permalink to “3. 类加载器的层次结构”">&#8203;</a></h2>
<p>Java类加载器分为四层，形成树状结构：</p>
<ol>
<li><strong>Bootstrap ClassLoader（启动类加载器）</strong>：
<ul>
<li>由C++实现，负责加载 <code>JAVA_HOME/lib</code> 下的核心类（如<code>java.lang.*</code>）。</li>
<li>唯一没有父类的加载器，是其他所有类加载器的根。</li>
</ul>
</li>
<li><strong>Extension ClassLoader(扩展类加载器)</strong>：
<ol>
<li>加载 JAVA_HOME/lib/ext 目录或 java.ext.dirs 指定的类库。</li>
</ol>
</li>
<li><strong>Application ClassLoader(应用类加载器)</strong>:
<ol>
<li>加载用户路径（<strong>ClassPath</strong>）下的类，是 <strong>ClassLoader.getSystemClassLoader()</strong> 的默认返回值。</li>
</ol>
</li>
<li><strong>自定义ClassLoader</strong>:
<ol>
<li>用户可继承<code>ClassLoader</code>，重写 <code>findClass()</code> 实现自定义加载逻辑（如从网络、加密文件中加载类）。</li>
</ol>
</li>
</ol>
<h2 id="_4-双亲委派的优势" tabindex="-1">4. 双亲委派的优势 <a class="header-anchor" href="#_4-双亲委派的优势" aria-label="Permalink to “4. 双亲委派的优势”">&#8203;</a></h2>
<ol>
<li>
<p><strong>安全性</strong>：</p>
<ol>
<li><strong>防止核心API被篡改</strong>：例如自定义 <code>java.lang.Object</code> 类时，由于 Bootstrap ClassLoader已加载核心类，自定义类不会被加载，避免了恶意代码注入。</li>
<li><strong>沙箱保护机制</strong>：通过层级隔离，不同层次的类加载器加载的类无法互相访问（扩展类无法访问应用类）。</li>
</ol>
</li>
<li>
<p><strong>避免重复加载</strong>：</p>
<ol>
<li><strong>类的唯一性标识</strong>：一个类由加载它的类加载器和全限定名（如 <code>java.lang.Object</code>）共同确定。双亲委派确保类只被加载一次，避免内存浪费。</li>
</ol>
</li>
<li>
<p><strong>保证基础类的一致性</strong>：</p>
<ol>
<li>所有子类加载器最终都会委托到Bootstrap ClassLoader。确保核心类（如Object、String）全局唯一，避免多版本冲突。</li>
</ol>
</li>
<li>
<p>打破双亲委派的场景
虽然双亲委派是默认机制，但在某些场景下需要逆向委派或平级委派：</p>
</li>
<li>
<p><code>SPI</code>（Service Provider Interface）：
<code>JDBC</code>、<code>JNDI</code>等接口由Bootstrap ClassLoader加载，但具体实现（如<code>MySQL</code>驱动）需由线程上下文类加载器（<code>TCCL</code>）加载，打破了双亲委派。</p>
</li>
<li>
<p><strong>OSGi模块化：</strong>
每个<code>Bundle</code>使用独立的类加载器，通过图结构的依赖关系实现类共享和隔离。</p>
</li>
<li>
<p><strong>热部署：</strong>
通过自定义类加载器重新加载修改后的类（如<code>Tomcat</code>对<code>JSP</code>的热更新）。</p>
</li>
</ol>
<h2 id="_5-自定义类加载器" tabindex="-1">5. 自定义类加载器 <a class="header-anchor" href="#_5-自定义类加载器" aria-label="Permalink to “5. 自定义类加载器”">&#8203;</a></h2>
<p>自定义类加载器需遵循双亲委派：</p>
<p>继承<code>ClassLoader</code>，重写<code>findClass()</code>（而非<code>loadClass()</code>，否则需手动实现委派逻辑）。
在<code>findClass()</code>中从指定路径（如文件、网络）读取类字节码，调用<code>defineClass()</code>生成类对象。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-166" id="tab-167" checked><label data-title="txt" for="tab-167">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::yuqtcqm815k62zkr4o8fq::--><code>public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class&lt;?&gt; findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name); // 从自定义路径加载字节码
        return defineClass(name, classData, 0, classData.length);
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Retrofit]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Retrofit.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Retrofit.html</guid>
            <pubDate>Wed, 22 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Retrofit 是一个类型安全的 HTTP 客户端，主要用于与 RESTful API 进行交互，简化了网络请求的流程。]]></description>
            <content:encoded><![CDATA[<h1 id="retrofit" tabindex="-1">Retrofit <a class="header-anchor" href="#retrofit" aria-label="Permalink to “Retrofit”">&#8203;</a></h1>
<p><code>Retrofit</code> 是一个类型安全的 <code>HTTP</code> 客户端，主要用于与 <code>RESTful API</code> 进行交互，简化了网络请求的流程。</p>
<p><code>Retrofit</code> 适用 Converter 来处理请求和响应的转换，支持 <code>JSON</code>、<code>XML</code>、<code>Protobuf</code> 等格式。常见的转换器有：</p>
<ol>
<li><code>GsonConvertFactory</code>: 用于 <code>JSON</code> 格式的解析。</li>
<li><code>MoshiConvertFactory</code>: 用于 <code>Moshi</code> 的 <code>JSON</code> 解析。</li>
<li><code>SimpleXMLConvertFactory</code>: 用于 <code>XML</code> 格式的解析。</li>
</ol>
<p><code>Retrofit</code> 支持同步（<code>execute()</code>）和异步（<code>enqueue()</code>）请求。Retrofit 和 OkHttp 一起适用，能使用拦截器（<code>Interceptor</code>）来处理请求和响应</p>
<h2 id="_2-retrofit-工作流程" tabindex="-1">2. Retrofit 工作流程 <a class="header-anchor" href="#_2-retrofit-工作流程" aria-label="Permalink to “2. Retrofit 工作流程”">&#8203;</a></h2>
<ol>
<li>定义接口：
使用 <code>Retrofit</code> 定义 HTTP 请求，利用注解来描述 API 的请求路径、HTTP 方法、请求体等。</li>
<li>创建 Retrofit 实例：
配置 <code>Retrofit</code> 实例，指定 <code>Base URL</code> 和 <code>转换器</code>（例如，使用 Gson 解析 JSON）。</li>
<li>执行请求：
通过调用接口方法来执行请求，Retrofit 会根据接口定义自动生成网络请求，并返回相应的结果。</li>
</ol>
<h2 id="_3-主要功能和接口" tabindex="-1">3. 主要功能和接口 <a class="header-anchor" href="#_3-主要功能和接口" aria-label="Permalink to “3. 主要功能和接口”">&#8203;</a></h2>
<ol>
<li><code>GET</code> 请求：用于获取数据。</li>
<li><code>POST</code> 请求：用于提交数据。</li>
<li><code>PUT</code> 请求：用于更新数据。</li>
<li><code>DELETE</code> 请求：用于删除数据。</li>
</ol>
<h2 id="_4-示例" tabindex="-1">4. 示例 <a class="header-anchor" href="#_4-示例" aria-label="Permalink to “4. 示例”">&#8203;</a></h2>
<ol>
<li>添加 <code>Retrofit</code> 依赖</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-84" id="tab-85" checked><label data-title="txt" for="tab-85">txt</label></div><div class="blocks">
<div class="language-xml active line-numbers-mode" data-lang="xml"><button title="Copy Code" class="copy"></button><span class="lang">XML</span><pre><!--::markdown-it-async::1sorxaok31z17alvsvdvks::--><code>dependencies {
    implementation &#039;com.squareup.retrofit2:retrofit:2.9.0&#039;
    implementation &#039;com.squareup.retrofit2:converter-gson:2.9.0&#039;
    implementation &#039;com.squareup.okhttp3:okhttp:4.9.0&#039;
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></div></div>
<ol start="2">
<li>定义 API 接口</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-94" id="tab-95" checked><label data-title="txt" for="tab-95">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::w9jiu2lkft7bsad54epa0s::--><code>public interface ApiService {

    // 定义一个 GET 请求，获取用户信息
    @GET(&quot;users/{user}/repos&quot;)
    Call&lt;List&lt;Repo&gt;&gt; getUserRepos(@Path(&quot;user&quot;) String user);

    // 定义一个 POST 请求，提交 JSON 数据
    @POST(&quot;users&quot;)
    Call&lt;User&gt; createUser(@Body User user);

    // 定义一个异步请求的 GET 方法
    @GET(&quot;users/{user}/repos&quot;)
    Call&lt;List&lt;Repo&gt;&gt; getUserReposAsync(@Path(&quot;user&quot;) String user);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div></div></div>
<ol start="3">
<li>创建 Retrofit 实例</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-104" id="tab-105" checked><label data-title="txt" for="tab-105">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::dvstjdo8rrqt27poik6tih::--><code>Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(&quot;https://api.github.com/&quot;)
        .addConverterFactory(GsonConverterFactory.create()) // 使用 Gson 转换器
        .build();</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div></div></div>
<ol start="4">
<li>创建 API 服务</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-114" id="tab-115" checked><label data-title="txt" for="tab-115">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::ckxvv9ztm7kb9awnob227::--><code>ApiService apiService = retrofit.create(ApiService.class);</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></div></div>
<ol start="5">
<li>发起同步请求</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-124" id="tab-125" checked><label data-title="txt" for="tab-125">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::jh1c6fipueoed5ob0rdpr::--><code>Call&lt;List&lt;Repo&gt;&gt; call = apiService.getUserRepos(&quot;octocat&quot;);
try {
    Response&lt;List&lt;Repo&gt;&gt; response = call.execute();  // 同步执行
    if (response.isSuccessful()) {
        List&lt;Repo&gt; repos = response.body();
        // 处理 repos 数据
    } else {
        // 处理请求失败的情况
    }
} catch (IOException e) {
    e.printStackTrace();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div></div></div>
<ol start="6">
<li>发起异步请求</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-134" id="tab-135" checked><label data-title="txt" for="tab-135">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::y5tv1jyta5ygq5xsnpre::--><code>Call&lt;List&lt;Repo&gt;&gt; call = apiService.getUserReposAsync(&quot;octocat&quot;);
call.enqueue(new Callback&lt;List&lt;Repo&gt;&gt;() {
    @Override
    public void onResponse(Call&lt;List&lt;Repo&gt;&gt; call, Response&lt;List&lt;Repo&gt;&gt; response) {
        if (response.isSuccessful()) {
            List&lt;Repo&gt; repos = response.body();
            // 处理 repos 数据
        }
    }

    @Override
    public void onFailure(Call&lt;List&lt;Repo&gt;&gt; call, Throwable t) {
        // 处理请求失败
    }
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div></div></div>
<h2 id="_5-进阶功能" tabindex="-1">5. 进阶功能 <a class="header-anchor" href="#_5-进阶功能" aria-label="Permalink to “5. 进阶功能”">&#8203;</a></h2>
<ol>
<li>请求和响应拦截器：</li>
</ol>
<p>可以使用 <code>OkHttp</code> 拦截器来对请求或响应进行处理。例如，添加统一的请求头，处理错误响应等。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-150" id="tab-151" checked><label data-title="txt" for="tab-151">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::j6mhm8t34sudwc40awdvq::--><code>OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request().newBuilder()
                        .addHeader(&quot;Authorization&quot;, &quot;Bearer token&quot;)
                        .build();
                return chain.proceed(request);
            }
        })
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(&quot;https://api.github.com/&quot;)
        .client(client) // 使用自定义的 OkHttpClient
        .addConverterFactory(GsonConverterFactory.create())
        .build();</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div></div></div>
<ol start="2">
<li>自定义转换器：</li>
</ol>
<p>除了 <code>Gson</code> 和 <code>Moshi</code> 等常见的转换器，你也可以自定义转换器来支持其他数据格式。</p>
<ol start="3">
<li>支持分页：</li>
</ol>
<p>对于分页请求，可以通过 <code>@Query</code> 注解来传递分页参数，获取不同页面的数据。</p>
<ol start="4">
<li>缓存：
可以使用 OkHttp 的缓存机制，配置请求的缓存策略，减少不必要的网络请求。</li>
</ol>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[安卓进程通信的方法]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%AE%89%E5%8D%93%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1%E7%9A%84%E6%96%B9%E6%B3%95.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/安卓进程通信的方法.html</guid>
            <pubDate>Mon, 20 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在 Android 中，进程间通信（IPC，Inter-Process Communication）主要有以下几种方法：]]></description>
            <content:encoded><![CDATA[<h1 id="安卓进程通信的方法" tabindex="-1">安卓进程通信的方法 <a class="header-anchor" href="#安卓进程通信的方法" aria-label="Permalink to “安卓进程通信的方法”">&#8203;</a></h1>
<p>在 Android 中，进程间通信（IPC，Inter-Process Communication）主要有以下几种方法：</p>
<h2 id="_1-aidl-android-interface-definition-language" tabindex="-1">1. <strong>AIDL（Android Interface Definition Language）</strong> <a class="header-anchor" href="#_1-aidl-android-interface-definition-language" aria-label="Permalink to “1. AIDL（Android Interface Definition Language）”">&#8203;</a></h2>
<ul>
<li>适用于复杂的跨进程通信，尤其是多进程的客户端-服务端架构。</li>
<li>需要定义 <code>.aidl</code> 接口文件，由 Android 自动生成对应的 Binder 代码。</li>
<li>适用于涉及大量数据传输或需要回调的场景。</li>
</ul>
<h2 id="_2-messenger" tabindex="-1">2. <strong>Messenger</strong> <a class="header-anchor" href="#_2-messenger" aria-label="Permalink to “2. Messenger”">&#8203;</a></h2>
<ul>
<li>依赖于 <code>Handler</code> 和 <code>Message</code> 机制的简化版 AIDL。</li>
<li>适用于简单的进程间单向或双向消息传递，不适合高频率、大量数据的通信。</li>
<li>通过 <code>Messenger.send(Message)</code> 进行消息发送。</li>
</ul>
<h2 id="_3-contentprovider" tabindex="-1">3. <strong>ContentProvider</strong> <a class="header-anchor" href="#_3-contentprovider" aria-label="Permalink to “3. ContentProvider”">&#8203;</a></h2>
<ul>
<li>适用于进程间共享结构化数据（如数据库）。</li>
<li>主要用于跨进程访问数据库数据、文件等，如 <code>ContactsProvider</code> 提供通讯录数据。</li>
<li>通过 <code>ContentResolver.query()/insert()/update()/delete()</code> 进行操作。</li>
</ul>
<h2 id="_4-broadcastreceiver" tabindex="-1">4. <strong>BroadcastReceiver</strong> <a class="header-anchor" href="#_4-broadcastreceiver" aria-label="Permalink to “4. BroadcastReceiver”">&#8203;</a></h2>
<ul>
<li>适用于发送全局或本地广播，实现进程间的通知和消息传递。</li>
<li>分为<strong>标准广播（normal broadcast）</strong>和<strong>有序广播（ordered broadcast）</strong>。</li>
<li>适合事件驱动型的通信，如应用状态变化（如网络状态变更）。</li>
</ul>
<h2 id="_5-socket-基于网络通信" tabindex="-1">5. <strong>Socket（基于网络通信）</strong> <a class="header-anchor" href="#_5-socket-基于网络通信" aria-label="Permalink to “5. Socket（基于网络通信）”">&#8203;</a></h2>
<ul>
<li>适用于跨设备、远程进程间通信，如基于 <code>TCP</code> 或 <code>UDP</code> 进行通信。</li>
<li>可以使用 <code>ServerSocket</code> 进行监听，<code>Socket</code> 进行连接通信。</li>
<li>适合 IM、在线游戏等网络通信场景。</li>
</ul>
<h2 id="_6-sharedpreferences-文件共享" tabindex="-1">6. <strong>SharedPreferences / 文件共享</strong> <a class="header-anchor" href="#_6-sharedpreferences-文件共享" aria-label="Permalink to “6. SharedPreferences / 文件共享”">&#8203;</a></h2>
<ul>
<li>适用于简单的进程间数据共享，如配置文件、缓存数据等。</li>
<li>通过 <code>MODE_MULTI_PROCESS</code>（已废弃）或 <code>ContentProvider</code> 进行跨进程访问。</li>
<li>适合存储简单的配置信息，而非高频数据交换。</li>
</ul>
<h2 id="_7-memoryfile-ashmem-共享内存" tabindex="-1">7. <strong>MemoryFile（ashmem 共享内存）</strong> <a class="header-anchor" href="#_7-memoryfile-ashmem-共享内存" aria-label="Permalink to “7. MemoryFile（ashmem 共享内存）”">&#8203;</a></h2>
<ul>
<li>Android 提供 <code>MemoryFile</code> 作为共享内存的方式，可以用于多个进程间的文件共享。</li>
<li>适合大数据量的共享，如音频、视频等流媒体数据传输。</li>
</ul>
<h2 id="_8-jobscheduler-workmanager" tabindex="-1">8. <strong>JobScheduler / WorkManager</strong> <a class="header-anchor" href="#_8-jobscheduler-workmanager" aria-label="Permalink to “8. JobScheduler / WorkManager”">&#8203;</a></h2>
<ul>
<li>适用于跨进程的任务调度，而不是传统的 IPC。</li>
<li>例如 <code>JobScheduler</code> 可以在不同进程中调度任务，而 <code>WorkManager</code> 适用于后台任务执行。</li>
</ul>
<h2 id="总结" tabindex="-1"><strong>总结</strong> <a class="header-anchor" href="#总结" aria-label="Permalink to “总结”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th><strong>方法</strong></th>
<th><strong>适用场景</strong></th>
<th><strong>特点</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>AIDL</td>
<td>复杂跨进程通信</td>
<td>高性能，灵活，支持回调</td>
</tr>
<tr>
<td>Messenger</td>
<td>简单跨进程消息通信</td>
<td>基于 <code>Handler</code>，较易使用</td>
</tr>
<tr>
<td>ContentProvider</td>
<td>数据共享（数据库、文件）</td>
<td>适用于结构化数据</td>
</tr>
<tr>
<td>BroadcastReceiver</td>
<td>进程间通知</td>
<td>事件驱动，适合广播消息</td>
</tr>
<tr>
<td>Socket</td>
<td>网络通信</td>
<td>适合远程或本地通信</td>
</tr>
<tr>
<td>SharedPreferences / 文件共享</td>
<td>简单数据存储</td>
<td>适用于轻量级数据共享</td>
</tr>
<tr>
<td>MemoryFile (ashmem)</td>
<td>共享大数据（如音视频）</td>
<td>高效，避免 Binder 限制</td>
</tr>
<tr>
<td>JobScheduler / WorkManager</td>
<td>任务调度</td>
<td>适用于异步任务管理</td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[CAS]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/CAS.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/CAS.html</guid>
            <pubDate>Wed, 15 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[CAS (Compare And Swap，比较和交换)，是非阻塞同步的实现原理，它是CPU硬件的指令，从CPU层面保证比较与交换两个操作的原子性。 CAS 指令操作包括三个参数：]]></description>
            <content:encoded><![CDATA[<h1 id="cas" tabindex="-1">CAS <a class="header-anchor" href="#cas" aria-label="Permalink to “CAS”">&#8203;</a></h1>
<h2 id="_1-cas" tabindex="-1">1. CAS <a class="header-anchor" href="#_1-cas" aria-label="Permalink to “1. CAS”">&#8203;</a></h2>
<p><code>CAS</code> (Compare And Swap，比较和交换)，是非阻塞同步的实现原理，它是CPU硬件的指令，从CPU层面保证比较与交换两个操作的原子性。 <code>CAS</code> 指令操作包括三个参数：</p>
<ol>
<li><strong>内存位置V</strong>（memory location）：需要操作的内存位置，通常是一个共享变量。</li>
<li><strong>预期值E</strong>（expected value）：期望该内存位置的值与之相等。</li>
<li><strong>新值N</strong>（new value）：如果内存位置的当前值与预期值相等，将其更新为新值。</li>
</ol>
<p><code>CAS</code> 的基本概念是：<strong>在一个内存位置上，比较当前值与预期值是否相等，如果相等，则将该位置的值更新为新值；如果不相等，则表示有其他线程已经修改了该位置的值，操作失败。</strong></p>
<p><img src="./image-11.png" alt="alt text" loading="lazy" decoding="async"></p>
<h2 id="_2-具体步骤" tabindex="-1">2. 具体步骤 <a class="header-anchor" href="#_2-具体步骤" aria-label="Permalink to “2. 具体步骤”">&#8203;</a></h2>
<ol>
<li>线程读取共享内存位置的当前值</li>
<li>将当前值与预期值进行比较</li>
<li>如果当前值与预期值相等，则将该内存位置的值更新为新值，并返回成功</li>
<li>如果当前值与预期值不相等，则操作失败，不更新内存位置。</li>
</ol>
<h2 id="_3-应用场景" tabindex="-1">3. 应用场景 <a class="header-anchor" href="#_3-应用场景" aria-label="Permalink to “3. 应用场景”">&#8203;</a></h2>
<p><code>CAS</code> 操作在许多并发数据结构和算法中应用较多，比如：</p>
<ul>
<li><strong>乐观锁</strong>：<code>CAS</code>常用于实现乐观锁机制，如 <code>java.util.concurrent.atomic</code> 包中的类（<code>AtomicInteger</code>、<code>AtomicReference</code>等）。</li>
<li><strong>无锁数据结构</strong>：如无锁队列、无锁栈等，使用CAS操作来保证线程安全。</li>
</ul>
<p><strong>优点</strong>：</p>
<ol>
<li>原子操作，可以在不阻塞线程的情况下完成，效率极高。</li>
<li>不需要加锁，避免了锁带来的性能开销和死锁问题。</li>
</ol>
<p><strong>缺点</strong>：</p>
<ol>
<li>ABA问题：<code>CAS</code>只能比较当前值和预期值，无法判断一个值是否被修改过多次，导致可能出现ABA问题。可以通过引入版本号或时间戳等机制来解决。</li>
<li>循环重试：当多个线程竞争同一个变量时，<code>CAS</code>操作可能会失败，并需要多次重试，导致性能下降。</li>
</ol>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Java如何保证线程T1，T2，T3 顺序执行]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/Java%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E7%BA%BF%E7%A8%8BT1%EF%BC%8CT2%EF%BC%8CT3%20%E9%A1%BA%E5%BA%8F%E6%89%A7%E8%A1%8C.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/Java如何保证线程T1，T2，T3 顺序执行.html</guid>
            <pubDate>Wed, 15 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Thread.join() 方法会使当前线程等待另一个线程执行完毕后再继续执行。通过在 T1、T2、T3 之间调用 join()，可以确保线程按顺序执行。]]></description>
            <content:encoded><![CDATA[<h1 id="java如何保证线程t1-t2-t3-顺序执行" tabindex="-1">Java如何保证线程T1，T2，T3 顺序执行 <a class="header-anchor" href="#java如何保证线程t1-t2-t3-顺序执行" aria-label="Permalink to “Java如何保证线程T1，T2，T3 顺序执行”">&#8203;</a></h1>
<h2 id="_1-方法1-join-方法" tabindex="-1">1. 方法1：join()方法 <a class="header-anchor" href="#_1-方法1-join-方法" aria-label="Permalink to “1. 方法1：join()方法”">&#8203;</a></h2>
<p><code>Thread.join()</code> 方法会使当前线程等待另一个线程执行完毕后再继续执行。通过在 T1、T2、T3 之间调用 <code>join()</code>，可以确保线程按顺序执行。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-9" id="tab-10" checked><label data-title="txt" for="tab-10">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::83zfot4cjxbd3ayqvun8n::--><code>public class ThreadOrderExample {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -&gt; {
            System.out.println(&quot;T1 is executing&quot;);
        });

        Thread t2 = new Thread(() -&gt; {
            System.out.println(&quot;T2 is executing&quot;);
        });

        Thread t3 = new Thread(() -&gt; {
            System.out.println(&quot;T3 is executing&quot;);
        });

        t1.start();  // 启动线程T1
        t1.join();   // 主线程等待T1执行完

        t2.start();  // 启动线程T2
        t2.join();   // 主线程等待T2执行完

        t3.start();  // 启动线程T3
        t3.join();   // 主线程等待T3执行完
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br></div></div></div></div>
<h2 id="_2-方法2-countdownlatch方法" tabindex="-1">2. 方法2：countDownLatch方法 <a class="header-anchor" href="#_2-方法2-countdownlatch方法" aria-label="Permalink to “2. 方法2：countDownLatch方法”">&#8203;</a></h2>
<p><code>CountDownLatch</code> 是一个同步辅助类，它允许一个或多个线程一直等待，直到其他线程完成操作。可以用它来确保线程按照特定顺序执行。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-18" id="tab-19" checked><label data-title="txt" for="tab-19">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::8gi6yy4mcgie55a1grtic8::--><code>import java.util.concurrent.CountDownLatch;

public class ThreadOrderExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch1 = new CountDownLatch(1);  // T1 完成后释放
        CountDownLatch latch2 = new CountDownLatch(1);  // T2 完成后释放

        Thread t1 = new Thread(() -&gt; {
            System.out.println(&quot;T1 is executing&quot;);
            latch1.countDown();  // T1 完成后释放 latch1
        });

        Thread t2 = new Thread(() -&gt; {
            try {
                latch1.await();  // 等待 T1 执行完
                System.out.println(&quot;T2 is executing&quot;);
                latch2.countDown();  // T2 完成后释放 latch2
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread t3 = new Thread(() -&gt; {
            try {
                latch2.await();  // 等待 T2 执行完
                System.out.println(&quot;T3 is executing&quot;);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br></div></div></div></div>
<h2 id="_3-方法3-cyclicbarrier" tabindex="-1">3. 方法3：CyclicBarrier <a class="header-anchor" href="#_3-方法3-cyclicbarrier" aria-label="Permalink to “3. 方法3：CyclicBarrier”">&#8203;</a></h2>
<p><code>CyclicBarrier</code> 是一种可以同步多个线程的工具，但它通常用于所有线程都要在某一点等待并且一旦达到同步点后再一起继续执行。如果想确保多个线程按顺序执行，<code>CyclicBarrier</code> 可以配合 <code>Runnable</code> 来实现。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-27" id="tab-28" checked><label data-title="txt" for="tab-28">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::mmlzc06lqgtp1uvq62a6e::--><code>import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class ThreadOrderExample {

    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier1 = new CyclicBarrier(2);  // 第一个同步点
        CyclicBarrier barrier2 = new CyclicBarrier(2);  // 第二个同步点

        Thread t1 = new Thread(() -&gt; {
            try {
                System.out.println(&quot;T1 is executing&quot;);
                barrier1.await();  // 到达第一个同步点
            } catch (InterruptedException | BrokenBarrierException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread t2 = new Thread(() -&gt; {
            try {
                barrier1.await();  // 等待 T1 到达同步点
                System.out.println(&quot;T2 is executing&quot;);
                barrier2.await();  // 到达第二个同步点
            } catch (InterruptedException | BrokenBarrierException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread t3 = new Thread(() -&gt; {
            try {
                barrier2.await();  // 等待 T2 到达同步点
                System.out.println(&quot;T3 is executing&quot;);
            } catch (InterruptedException | BrokenBarrierException e) {
                Thread.currentThread().interrupt();
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br></div></div></div></div>
<h2 id="_4-方法4-使用-semaphore" tabindex="-1">4. 方法4：使用 Semaphore <a class="header-anchor" href="#_4-方法4-使用-semaphore" aria-label="Permalink to “4. 方法4：使用 Semaphore”">&#8203;</a></h2>
<p><code>Semaphore</code> 是一种控制访问共享资源的计数信号量，也可以用来控制线程执行的顺序。通过 <code>acquire()</code> 和 <code>release()</code> 方法，可以控制线程在特定点的执行顺序。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-36" id="tab-37" checked><label data-title="txt" for="tab-37">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::avw8hrs53ypi3bjvmdsftc::--><code>import java.util.concurrent.Semaphore;

public class ThreadOrderExample {

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphoreT2 = new Semaphore(0);  // 控制T2执行的信号量
        Semaphore semaphoreT3 = new Semaphore(0);  // 控制T3执行的信号量

        Thread t1 = new Thread(() -&gt; {
            System.out.println(&quot;T1 is executing&quot;);
            semaphoreT2.release();  // 释放信号量，允许 T2 执行
        });

        Thread t2 = new Thread(() -&gt; {
            try {
                semaphoreT2.acquire();  // 等待 T1 完成
                System.out.println(&quot;T2 is executing&quot;);
                semaphoreT3.release();  // 释放信号量，允许 T3 执行
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread t3 = new Thread(() -&gt; {
            try {
                semaphoreT3.acquire();  // 等待 T2 完成
                System.out.println(&quot;T3 is executing&quot;);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br></div></div></div></div>
<h2 id="_5-方法5-使用-executorservice-和-future" tabindex="-1">5. 方法5：使用 ExecutorService 和 Future <a class="header-anchor" href="#_5-方法5-使用-executorservice-和-future" aria-label="Permalink to “5. 方法5：使用 ExecutorService 和 Future”">&#8203;</a></h2>
<p>通过 <code>ExecutorService</code> 提交线程任务，并使用 <code>Future.get()</code> 方法来确保任务按顺序执行。<code>Future.get()</code> 会阻塞当前线程，直到对应的任务完成，因此可以通过它来控制线程的顺序。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-45" id="tab-46" checked><label data-title="txt" for="tab-46">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::0jfsz4cw89n96dpvnciqzh::--><code>import java.util.concurrent.*;

public class ThreadOrderExample {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(3);  // 创建一个线程池

        // 提交任务
        Future&lt;?&gt; future1 = executor.submit(() -&gt; {
            System.out.println(&quot;T1 is executing&quot;);
        });

        // 使用 Future.get() 阻塞，确保 T1 执行完毕后再执行 T2
        future1.get();

        Future&lt;?&gt; future2 = executor.submit(() -&gt; {
            System.out.println(&quot;T2 is executing&quot;);
        });

        // 阻塞，确保 T2 执行完毕后再执行 T3
        future2.get();

        Future&lt;?&gt; future3 = executor.submit(() -&gt; {
            System.out.println(&quot;T3 is executing&quot;);
        });

        // 阻塞，确保 T3 执行完毕
        future3.get();

        executor.shutdown();  // 关闭线程池
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br></div></div></div></div>
<h2 id="_6-方法6-使用-lock-和-condition" tabindex="-1">6. 方法6：使用 Lock 和 Condition <a class="header-anchor" href="#_6-方法6-使用-lock-和-condition" aria-label="Permalink to “6. 方法6：使用 Lock 和 Condition”">&#8203;</a></h2>
<p><code>Lock</code> 和 <code>Condition</code> 是更低级别的同步工具，它们提供了比 <code>synchronized</code> 更精细的控制。通过 <code>Condition</code> 对象，可以让线程等待特定的条件，从而控制线程按顺序执行。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-54" id="tab-55" checked><label data-title="txt" for="tab-55">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::8vuz1p748yx7wunmp1j9hh::--><code>import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

public class ThreadOrderExample {

    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Condition conditionT2 = lock.newCondition();
        Condition conditionT3 = lock.newCondition();

        Thread t1 = new Thread(() -&gt; {
            lock.lock();
            try {
                System.out.println(&quot;T1 is executing&quot;);
                conditionT2.signal();  // 唤醒 T2
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -&gt; {
            lock.lock();
            try {
                conditionT2.await();  // 等待 T1 完成
                System.out.println(&quot;T2 is executing&quot;);
                conditionT3.signal();  // 唤醒 T3
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        });

        Thread t3 = new Thread(() -&gt; {
            lock.lock();
            try {
                conditionT3.await();  // 等待 T2 完成
                System.out.println(&quot;T3 is executing&quot;);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br></div></div></div></div>
<p><code>ReentrantLock</code> 和 <code>Condition</code> 可以精确控制线程的执行顺序。通过 <code>conditionT2.signal()</code> 和 <code>conditionT3.signal()</code> 控制 T2 和 T3 在适当时机开始执行。
<code>await()</code> 会使线程进入等待状态，直到其他线程调用 <code>signal()</code> 唤醒它。</p>
<h2 id="_7-方法7-使用-atomicboolean-或-atomicinteger" tabindex="-1">7. 方法7：使用 AtomicBoolean 或 AtomicInteger <a class="header-anchor" href="#_7-方法7-使用-atomicboolean-或-atomicinteger" aria-label="Permalink to “7. 方法7：使用 AtomicBoolean 或 AtomicInteger”">&#8203;</a></h2>
<p>可以通过使用 <code>AtomicBoolean</code> 或 <code>AtomicInteger</code> 来控制线程的执行顺序。这些原子变量提供线程安全的操作，允许在多个线程之间共享状态。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-66" id="tab-67" checked><label data-title="txt" for="tab-67">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::cng20fw046g5jzvd83hw1u::--><code>import java.util.concurrent.atomic.AtomicInteger;

public class ThreadOrderExample {

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger counter = new AtomicInteger(0);  // 用于控制线程顺序

        Thread t1 = new Thread(() -&gt; {
            while (counter.get() != 0) {}  // 等待 counter 为 0
            System.out.println(&quot;T1 is executing&quot;);
            counter.incrementAndGet();  // T1 执行完后将 counter 设置为 1
        });

        Thread t2 = new Thread(() -&gt; {
            while (counter.get() != 1) {}  // 等待 counter 为 1
            System.out.println(&quot;T2 is executing&quot;);
            counter.incrementAndGet();  // T2 执行完后将 counter 设置为 2
        });

        Thread t3 = new Thread(() -&gt; {
            while (counter.get() != 2) {}  // 等待 counter 为 2
            System.out.println(&quot;T3 is executing&quot;);
        });

        t1.start();
        t2.start();
        t3.start();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[线程池核心线程空闲时处于什么状态]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E6%A0%B8%E5%BF%83%E7%BA%BF%E7%A8%8B%E7%A9%BA%E9%97%B2%E6%97%B6%E5%A4%84%E4%BA%8E%E4%BB%80%E4%B9%88%E7%8A%B6%E6%80%81.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/线程池核心线程空闲时处于什么状态.html</guid>
            <pubDate>Wed, 15 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在线程池中，核心线程在空闲时的状态取决于线程池的配置，尤其是线程池的 keepAliveTime 和相关的线程管理策略。]]></description>
            <content:encoded><![CDATA[<h1 id="线程池核心线程空闲时处于什么状态" tabindex="-1">线程池核心线程空闲时处于什么状态 <a class="header-anchor" href="#线程池核心线程空闲时处于什么状态" aria-label="Permalink to “线程池核心线程空闲时处于什么状态”">&#8203;</a></h1>
<p>在线程池中，核心线程在空闲时的状态取决于线程池的配置，尤其是线程池的 <code>keepAliveTime</code> 和相关的线程管理策略。</p>
<h2 id="_1-核心线程空闲时的默认行为" tabindex="-1">1. 核心线程空闲时的默认行为 <a class="header-anchor" href="#_1-核心线程空闲时的默认行为" aria-label="Permalink to “1. 核心线程空闲时的默认行为”">&#8203;</a></h2>
<ul>
<li>默认情况下，核心线程是不会被销毁的，即使它们处于空闲状态。线程池会保持一定数量的核心线程，即使没有任务可以执行，也会一直存在，直到线程池关闭。</li>
<li>这样是为了保证线程池在高并发时能够快速响应任务，避免频繁创建和销毁线程的性能开销。</li>
</ul>
<h2 id="_2-空闲时是否会被回收" tabindex="-1">2. 空闲时是否会被回收 <a class="header-anchor" href="#_2-空闲时是否会被回收" aria-label="Permalink to “2. 空闲时是否会被回收”">&#8203;</a></h2>
<ul>
<li>如果线程池的 <code>allowCoreThreadTimeOut</code> 设置为 <code>true</code>，那么核心线程在空闲时会超时并被销毁。具体行为取决于线程池的配置：
<ul>
<li><code>keepAliveTime</code> ：空闲的线程会在超过指定的存活时间后被销毁。</li>
<li><code>TimeUnit</code>：<code>keepAliveTime</code>的单位，秒、毫秒等。</li>
</ul>
</li>
<li>如果设置为 <code>false</code> (即默认行为)，空闲的核心会一直存活到线程池关闭。</li>
</ul>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[AIDL的关键字]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/AIDL%E7%9A%84%E5%85%B3%E9%94%AE%E5%AD%97.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/AIDL的关键字.html</guid>
            <pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[好的，下面是 AIDL 中所有关键字和概念的完整列表：]]></description>
            <content:encoded><![CDATA[<h1 id="aidl的关键字" tabindex="-1">AIDL的关键字 <a class="header-anchor" href="#aidl的关键字" aria-label="Permalink to “AIDL的关键字”">&#8203;</a></h1>
<p>好的，下面是 <code>AIDL</code> 中所有关键字和概念的完整列表：</p>
<h2 id="_1-interface" tabindex="-1">1. <strong>interface</strong> <a class="header-anchor" href="#_1-interface" aria-label="Permalink to “1. interface”">&#8203;</a></h2>
<ul>
<li>用于定义 AIDL 接口。AIDL 接口包含客户端和服务端之间可以调用的方法。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::usot4sfdwnizkid4of7zf::--><code>interface IMyAidlInterface {
    void myMethod();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_2-in" tabindex="-1">2. <strong>in</strong> <a class="header-anchor" href="#_2-in" aria-label="Permalink to “2. in”">&#8203;</a></h2>
<ul>
<li><code>in</code> 关键字用于表示方法的输入参数。AIDL 中的输入参数是只读的，不能被修改。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::ru6uef3918jh6t7l4f00wo::--><code>interface IMyAidlInterface {
    void myMethod(in String input);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_3-out" tabindex="-1">3. <strong>out</strong> <a class="header-anchor" href="#_3-out" aria-label="Permalink to “3. out”">&#8203;</a></h2>
<ul>
<li><code>out</code> 关键字用于表示输出参数，即方法执行后返回的值。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::om615dlkez2fbbzevefrh::--><code>interface IMyAidlInterface {
    void myMethod(out String result);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_4-inout" tabindex="-1">4. <strong>inout</strong> <a class="header-anchor" href="#_4-inout" aria-label="Permalink to “4. inout”">&#8203;</a></h2>
<ul>
<li><code>inout</code> 关键字表示既是输入参数又是输出参数，方法执行后该参数的值会发生变化。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::3adt6de5xhhew1z1xstpm8::--><code>interface IMyAidlInterface {
    void myMethod(inout int value);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_5-parcelable" tabindex="-1">5. <strong>parcelable</strong> <a class="header-anchor" href="#_5-parcelable" aria-label="Permalink to “5. parcelable”">&#8203;</a></h2>
<ul>
<li><code>parcelable</code> 关键字用于标识一个可以在进程之间传递的对象类。<code>Parcelable</code> 是 Android 特有的接口，用于高效地序列化对象。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::8yy8x1whspa3zdafaq6mh6::--><code>parcelable MyData {
    int id;
    String name;
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div></li>
</ul>
<h2 id="_6-enum" tabindex="-1">6. <strong>enum</strong> <a class="header-anchor" href="#_6-enum" aria-label="Permalink to “6. enum”">&#8203;</a></h2>
<ul>
<li><code>enum</code> 关键字用于定义枚举类型，枚举可以是传递的数据类型之一。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::llfdxl9rwplu0prrrdig::--><code>enum Status {
    OK,
    ERROR
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div></li>
</ul>
<h2 id="_7-throws" tabindex="-1">7. <strong>throws</strong> <a class="header-anchor" href="#_7-throws" aria-label="Permalink to “7. throws”">&#8203;</a></h2>
<ul>
<li><code>throws</code> 关键字用于声明 AIDL 方法可能抛出的异常。AIDL 方法可以抛出 <code>RemoteException</code>。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::3fkn1ut07vt62atfdy34e5::--><code>interface IMyAidlInterface {
    void myMethod() throws RemoteException;
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_8-extends" tabindex="-1">8. <strong>extends</strong> <a class="header-anchor" href="#_8-extends" aria-label="Permalink to “8. extends”">&#8203;</a></h2>
<ul>
<li><code>extends</code> 关键字用于继承其他 AIDL 接口。继承后，接口会包含父接口的所有方法。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::a8tit7k6exldoj9fn1a6::--><code>interface IMyAidlInterface extends IBaseInterface {
    void myMethod();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_9-callback" tabindex="-1">9. <strong>callback</strong> <a class="header-anchor" href="#_9-callback" aria-label="Permalink to “9. callback”">&#8203;</a></h2>
<ul>
<li><code>callback</code> 用于定义回调接口，服务端通过回调将数据传递给客户端。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::icq6v9k7yxc7gufx9v23u::--><code>interface IMyCallback {
    void onSuccess(String result);
}

interface IMyAidlInterface {
    void registerCallback(in IMyCallback callback);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div></li>
</ul>
<h2 id="_10-import" tabindex="-1">10. <strong>import</strong> <a class="header-anchor" href="#_10-import" aria-label="Permalink to “10. import”">&#8203;</a></h2>
<ul>
<li><code>import</code> 用于引入其他 AIDL 文件中的类型或接口。可以导入类、接口、枚举或 <code>parcelable</code>。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::3uilfo6t6lvsiowumeye9q::--><code>import com.example.otherpackage.IMyOtherInterface;

interface IMyAidlInterface {
    void myMethod();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li>
</ul>
<h2 id="_11-boolean" tabindex="-1">11. <strong>boolean</strong> <a class="header-anchor" href="#_11-boolean" aria-label="Permalink to “11. boolean”">&#8203;</a></h2>
<ul>
<li><code>boolean</code> 用于定义布尔类型的参数。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::i6zsabh349ijd6f8jc5im::--><code>interface IMyAidlInterface {
    boolean isValid();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_12-byte-short-int-long-float-double-char-string" tabindex="-1">12. <strong>byte, short, int, long, float, double, char, String</strong> <a class="header-anchor" href="#_12-byte-short-int-long-float-double-char-string" aria-label="Permalink to “12. byte, short, int, long, float, double, char, String”">&#8203;</a></h2>
<ul>
<li>这些是 AIDL 中支持的基本数据类型。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::6estea3xucv2y3nmo8y6e7::--><code>interface IMyAidlInterface {
    void myMethod(in int x, in String message);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_13-void" tabindex="-1">13. <strong>void</strong> <a class="header-anchor" href="#_13-void" aria-label="Permalink to “13. void”">&#8203;</a></h2>
<ul>
<li><code>void</code> 关键字用于表示方法没有返回值。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::k0wfpcyxx1kjxudb6k7ozb::--><code>interface IMyAidlInterface {
    void doSomething();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_14-remote" tabindex="-1">14. <strong>remote</strong> <a class="header-anchor" href="#_14-remote" aria-label="Permalink to “14. remote”">&#8203;</a></h2>
<ul>
<li><code>remote</code> 关键字用来标识一个 AIDL 接口是远程接口，通常需要通过 <code>Binder</code> 在不同进程之间进行通信。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::n7vb57k1cc3m00gp69g09::--><code>interface IMyAidlInterface extends android.os.IInterface {
    void myMethod();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<h2 id="_15-const" tabindex="-1">15. <strong>const</strong> <a class="header-anchor" href="#_15-const" aria-label="Permalink to “15. const”">&#8203;</a></h2>
<ul>
<li><code>const</code> 用于声明常量。常量可以在 AIDL 文件中定义，通常用于传递固定值。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::z9mmi95i7qb2ltl8cks8k::--><code>const int ERROR_CODE = -1;</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
</ul>
<h2 id="_16-map" tabindex="-1">16. <strong>Map</strong> <a class="header-anchor" href="#_16-map" aria-label="Permalink to “16. Map”">&#8203;</a></h2>
<ul>
<li>AIDL 也支持 <code>Map</code> 类型，但它只能传递简单类型（例如 <code>String</code> 和基本数据类型）的映射。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::j9w6byj858tqp12y7yjm9::--><code>import java.util.Map;

interface IMyAidlInterface {
    void sendData(Map&lt;String, Integer&gt; data);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li>
</ul>
<h2 id="_17-list" tabindex="-1">17. <strong>List</strong> <a class="header-anchor" href="#_17-list" aria-label="Permalink to “17. List”">&#8203;</a></h2>
<ul>
<li>AIDL 支持传递 <code>List</code> 类型的数据集合，通常是基本数据类型的列表。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::ckdbn7k44ud5df3nswi6qu::--><code>import java.util.List;

interface IMyAidlInterface {
    void sendList(in List&lt;String&gt; list);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></li>
</ul>
<h2 id="_18-binder" tabindex="-1">18. <strong>Binder</strong> <a class="header-anchor" href="#_18-binder" aria-label="Permalink to “18. Binder”">&#8203;</a></h2>
<ul>
<li>AIDL 本身是一种通信协议，它利用 <code>Binder</code> 在客户端和服务端之间传递数据。<code>Binder</code> 是 Android 的 IPC 机制的核心类。</li>
<li>示例：<div class="language-aidl line-numbers-mode" data-lang="aidl"><button title="Copy Code" class="copy"></button><span class="lang">AIDL</span><pre><!--::markdown-it-async::j9hb2nu7j1ne4mwn9v0r99::--><code>interface IMyAidlInterface {
    void bindService(in Binder service);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></li>
</ul>
<p>这些是 AIDL 中的关键字和常用概念，涵盖了定义接口、传递数据、异常处理、继承、回调等功能的所有基本内容。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[HashMap的高级用法]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/HashMap.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/HashMap.html</guid>
            <pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在 HashMap 中，负载因子用于决定何时需要增加 HashMap 的容量。默认的负载因子是 0.75，也就是说，当哈希表的元素数量达到容量的 75% 时，它会自动扩容。]]></description>
            <content:encoded><![CDATA[<h1 id="hashmap的高级用法" tabindex="-1">HashMap的高级用法 <a class="header-anchor" href="#hashmap的高级用法" aria-label="Permalink to “HashMap的高级用法”">&#8203;</a></h1>
<h2 id="_1-初始化时设置初始容量和负载因子" tabindex="-1">1. 初始化时设置初始容量和负载因子 <a class="header-anchor" href="#_1-初始化时设置初始容量和负载因子" aria-label="Permalink to “1. 初始化时设置初始容量和负载因子”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-6" id="tab-7" checked><label data-title="txt" for="tab-7">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::ze7ujcnksxe7qq0xijhr::--><code>Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;(64, 0.8f);
// 64 初始容量
// 0.8 负载因子（load factor）</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></div></div>
<p>在 <code>HashMap</code> 中，负载因子用于决定何时需要增加 <code>HashMap</code> 的容量。<strong>默认的负载因子是 0.75</strong>，也就是说，当哈希表的元素数量达到容量的 <code>75%</code> 时，它会自动扩容。</p>
<h2 id="_2-使用compute方法动态计算值" tabindex="-1">2. 使用compute方法动态计算值 <a class="header-anchor" href="#_2-使用compute方法动态计算值" aria-label="Permalink to “2. 使用compute方法动态计算值”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-15" id="tab-16" checked><label data-title="txt" for="tab-16">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::eruqf38rdylw9iasijoedl::--><code>Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;apple&quot;, 1);

map.compute(&quot;apple&quot;, (key, value) -&gt; value == null ? 1 : value + 1);
System.out.println(map.get(&quot;apple&quot;)); // 2</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></div></div>
<h2 id="_3-使用merge方法合并值" tabindex="-1">3. 使用merge方法合并值 <a class="header-anchor" href="#_3-使用merge方法合并值" aria-label="Permalink to “3. 使用merge方法合并值”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-21" id="tab-22" checked><label data-title="txt" for="tab-22">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::6f8bbmybg88aki2w6lffd4::--><code>Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;apple&quot;, 1);
map.merge(&quot;apple&quot;, 1, Integer::sum);
System.out.println(map.get(&quot;apple&quot;)); // 2</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div></div></div>
<h2 id="_4-使用getordefault方法避免空指针" tabindex="-1">4. 使用getOrDefault方法避免空指针 <a class="header-anchor" href="#_4-使用getordefault方法避免空指针" aria-label="Permalink to “4. 使用getOrDefault方法避免空指针”">&#8203;</a></h2>
<p><code>getOrDefault</code> 方法可以在键不存在的时候返回默认值，避免空指针异常。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-30" id="tab-31" checked><label data-title="txt" for="tab-31">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::2vkcvh8nylg7pa2e1fo2gc::--><code>Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;apple&quot;, 1);

int count = map.getOrDefault(&quot;banana&quot;, 0);
System.out.println(count) // 0</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></div></div>
<h2 id="_5-使用putifabsent方法避免覆盖" tabindex="-1">5. 使用putIfAbsent方法避免覆盖 <a class="header-anchor" href="#_5-使用putifabsent方法避免覆盖" aria-label="Permalink to “5. 使用putIfAbsent方法避免覆盖”">&#8203;</a></h2>
<p><code>putIfAbsent</code>方法只有在键不存在时才会插入值，避免覆盖已有的值。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-39" id="tab-40" checked><label data-title="txt" for="tab-40">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::dmu0pu0chqnu2j2cez5c::--><code>Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;apple&quot;, 1);

map.putIfAbsent(&quot;apple&quot;, 2);
System.out.println(map.get(&quot;apple&quot;)); // 1</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div></div></div>
<h2 id="_6-使用replaceall方法批量更新值" tabindex="-1">6. 使用replaceAll方法批量更新值 <a class="header-anchor" href="#_6-使用replaceall方法批量更新值" aria-label="Permalink to “6. 使用replaceAll方法批量更新值”">&#8203;</a></h2>
<p><code>replaceAll</code> 方法可以批量更新 <code>HashMap</code> 中的所有值。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-48" id="tab-49" checked><label data-title="txt" for="tab-49">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::zwzsul4c1t30v1kvmno1t::--><code>Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
map.put(&quot;apple&quot;, 1);
map.put(&quot;banana&quot;, 2);

map.replaceAll((key, value) -&gt; value + 2);
System.out.println(map); // {apple=2, banana=4}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div></div></div>
<h2 id="_7-使用computeifabsent延迟计算值" tabindex="-1">7. 使用computeIfAbsent延迟计算值 <a class="header-anchor" href="#_7-使用computeifabsent延迟计算值" aria-label="Permalink to “7. 使用computeIfAbsent延迟计算值”">&#8203;</a></h2>
<p><code>computeIfAbsent</code> 方法可以在键不存在时动态计算值，并将其放入 <code>HashMap</code> 中。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-57" id="tab-58" checked><label data-title="txt" for="tab-58">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::q75gw7v1mjmgxunt0oz0zd::--><code>Map&lt;String, List&lt;String&gt;&gt; map = new HashMap&lt;&gt;();
map.computeIfAbsent(&quot;fruits&quot;, k -&gt; new ArrayList&lt;&gt;()).add(&quot;apple&quot;);
System.out.println(map.get(&quot;fruits&quot;)); // [apple]</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[策略模式实现状态校验]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E7%8A%B6%E6%80%81%E6%A0%A1%E9%AA%8C.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/策略模式实现状态校验.html</guid>
            <pubDate>Fri, 03 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[在登录校验的场景下，建造者模式可以将每个校验策略当做一个“部件”，通过建造者来逐步构建出一个完整的校验流程。每个部件（策略）可以根据需要进行选择或组合。最终的构建结果是一个包含所选策略的校验器（LoginValidator）。]]></description>
            <content:encoded><![CDATA[<h1 id="策略模式实现状态校验" tabindex="-1">策略模式实现状态校验 <a class="header-anchor" href="#策略模式实现状态校验" aria-label="Permalink to “策略模式实现状态校验”">&#8203;</a></h1>
<p>在登录校验的场景下，建造者模式可以将每个校验策略当做一个“部件”，通过建造者来逐步构建出一个完整的校验流程。每个部件（策略）可以根据需要进行选择或组合。最终的构建结果是一个包含所选策略的校验器（<code>LoginValidator</code>）。</p>
<h2 id="_1-建造者模式的基本结构" tabindex="-1">1. <strong>建造者模式的基本结构</strong> <a class="header-anchor" href="#_1-建造者模式的基本结构" aria-label="Permalink to “1. 建造者模式的基本结构”">&#8203;</a></h2>
<p>建造者模式包括以下角色：</p>
<ul>
<li><strong>Builder</strong>：抽象建造者，定义创建各个部件的接口。</li>
<li><strong>ConcreteBuilder</strong>：具体建造者，负责实现 <code>Builder</code> 中定义的接口，构建出完整的产品。</li>
<li><strong>Product</strong>：最终构建的产品，这里是 <code>LoginValidator</code>。</li>
<li><strong>Director</strong>：指挥者，负责调用建造者的接口来构建最终产品。</li>
</ul>
<h2 id="_2-实现步骤" tabindex="-1">2. <strong>实现步骤</strong> <a class="header-anchor" href="#_2-实现步骤" aria-label="Permalink to “2. 实现步骤”">&#8203;</a></h2>
<h3 id="_2-1-定义校验策略接口" tabindex="-1">2.1 定义校验策略接口 <a class="header-anchor" href="#_2-1-定义校验策略接口" aria-label="Permalink to “2.1 定义校验策略接口”">&#8203;</a></h3>
<p>我们依然使用 <code>ValidationStrategy</code> 接口来定义校验策略。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::ioi2aga1jbafxo4ukkaoud::--><code>// 校验策略接口
interface ValidationStrategy {
    fun validate(input: String): Boolean
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><h3 id="_2-2-具体校验策略" tabindex="-1">2.2 具体校验策略 <a class="header-anchor" href="#_2-2-具体校验策略" aria-label="Permalink to “2.2 具体校验策略”">&#8203;</a></h3>
<p>具体的校验策略保持不变：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::yr639rzhvakly4qdm6hi3s::--><code>class UsernameValidation : ValidationStrategy {
    override fun validate(input: String): Boolean {
        return input.matches(&quot;^[a-zA-Z0-9]{3,20}$&quot;.toRegex())
    }
}

class PasswordValidation : ValidationStrategy {
    override fun validate(input: String): Boolean {
        return input.matches(&quot;^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$&quot;.toRegex())
    }
}

class CaptchaValidation : ValidationStrategy {
    override fun validate(input: String): Boolean {
        return input.matches(&quot;^[0-9]{6}$&quot;.toRegex())
    }
}

class EmailValidation : ValidationStrategy {
    override fun validate(input: String): Boolean {
        return input.matches(&quot;^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$&quot;.toRegex())
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><h3 id="_2-3-定义建造者接口" tabindex="-1">2.3 定义建造者接口 <a class="header-anchor" href="#_2-3-定义建造者接口" aria-label="Permalink to “2.3 定义建造者接口”">&#8203;</a></h3>
<p><code>ValidationBuilder</code> 定义了添加校验策略的接口方法。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::6jcbuzrfr467wfva71fz4m::--><code>// 校验建造者接口
interface ValidationBuilder {
    fun addUsernameValidation(): ValidationBuilder
    fun addPasswordValidation(): ValidationBuilder
    fun addCaptchaValidation(): ValidationBuilder
    fun addEmailValidation(): ValidationBuilder
    fun build(): LoginValidator
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h3 id="_2-4-具体建造者实现" tabindex="-1">2.4 具体建造者实现 <a class="header-anchor" href="#_2-4-具体建造者实现" aria-label="Permalink to “2.4 具体建造者实现”">&#8203;</a></h3>
<p><code>DefaultValidationBuilder</code> 是具体的建造者，它会按照特定的顺序和条件构建一个完整的校验器。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::iq8voi7rr1cskyqcxtczx::--><code>// 具体建造者
class DefaultValidationBuilder : ValidationBuilder {
    private val strategies = mutableListOf&lt;ValidationStrategy&gt;()

    override fun addUsernameValidation(): ValidationBuilder {
        strategies.add(UsernameValidation())
        return this
    }

    override fun addPasswordValidation(): ValidationBuilder {
        strategies.add(PasswordValidation())
        return this
    }

    override fun addCaptchaValidation(): ValidationBuilder {
        strategies.add(CaptchaValidation())
        return this
    }

    override fun addEmailValidation(): ValidationBuilder {
        strategies.add(EmailValidation())
        return this
    }

    override fun build(): LoginValidator {
        return LoginValidator(strategies)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div><h3 id="_2-5-定义-loginvalidator" tabindex="-1">2.5 定义 <code>LoginValidator</code> <a class="header-anchor" href="#_2-5-定义-loginvalidator" aria-label="Permalink to “2.5 定义 LoginValidator”">&#8203;</a></h3>
<p><code>LoginValidator</code> 现在不再依赖工厂来创建策略，而是由建造者来负责策略的组合。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::cqncu3wi24uq7t9tz2ratd::--><code>class LoginValidator(private val strategies: List&lt;ValidationStrategy&gt;) {

    // 校验用户登录信息
    fun validateLogin(username: String, password: String, captcha: String): Boolean {
        val validations = strategies.map { it.validate(username) }
        return validations.all { it }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h3 id="_2-6-使用建造者模式" tabindex="-1">2.6 使用建造者模式 <a class="header-anchor" href="#_2-6-使用建造者模式" aria-label="Permalink to “2.6 使用建造者模式”">&#8203;</a></h3>
<p>通过使用建造者模式，用户可以根据需求逐步选择要加入的校验策略，而不需要一次性构建所有校验逻辑。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::pvbalg0taue69svvy96tky::--><code>fun main() {
    // 使用建造者来构建 LoginValidator
    val loginValidator = DefaultValidationBuilder()
        .addUsernameValidation()
        .addPasswordValidation()
        .addCaptchaValidation()
        .build()

    // 模拟登录信息
    val username = &quot;user123&quot;
    val password = &quot;Password1&quot;
    val captcha = &quot;123456&quot;

    // 执行登录校验
    if (loginValidator.validateLogin(username, password, captcha)) {
        println(&quot;登录成功&quot;)
    } else {
        println(&quot;登录失败&quot;)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div><h3 id="_2-7-动态选择校验策略" tabindex="-1">2.7 <strong>动态选择校验策略</strong> <a class="header-anchor" href="#_2-7-动态选择校验策略" aria-label="Permalink to “2.7 动态选择校验策略”">&#8203;</a></h3>
<p>建造者模式的一个重要优势是，你可以灵活地选择需要的校验策略，而无需更改 <code>LoginValidator</code> 类。</p>
<p>假如用户需要邮箱校验，可以简单地在建造器链中添加 <code>addEmailValidation()</code> 方法：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::tvgmht8jxhjk2v6q5mu0e::--><code>fun main() {
    // 使用建造者来构建支持邮箱校验的 LoginValidator
    val loginValidator = DefaultValidationBuilder()
        .addUsernameValidation()
        .addPasswordValidation()
        .addCaptchaValidation()
        .addEmailValidation()  // 添加邮箱校验
        .build()

    // 模拟登录信息
    val username = &quot;user123&quot;
    val password = &quot;Password1&quot;
    val captcha = &quot;123456&quot;
    val email = &quot;user@example.com&quot;

    // 执行登录校验
    if (loginValidator.validateLogin(username, password, captcha)) {
        println(&quot;登录成功&quot;)
    } else {
        println(&quot;登录失败&quot;)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br></div></div><h2 id="_3-优点与总结" tabindex="-1">3. <strong>优点与总结</strong> <a class="header-anchor" href="#_3-优点与总结" aria-label="Permalink to “3. 优点与总结”">&#8203;</a></h2>
<ul>
<li><strong>模块化和灵活性</strong>：每个校验策略都可以单独进行组合和选择，提供了很高的灵活性。例如，用户可以根据需要选择加入邮箱校验、验证码校验等。</li>
<li><strong>清晰的建造过程</strong>：通过建造者模式，校验策略的选择和组合过程变得清晰且易于维护。</li>
<li><strong>避免构造函数参数膨胀</strong>：<code>LoginValidator</code> 的构造函数不再依赖于传入大量的校验策略，而是由建造者来管理这些策略，使得代码结构更加简洁。</li>
<li><strong>扩展性强</strong>：若要增加新的校验策略，只需要新增一个 <code>ValidationStrategy</code> 实现类，并在建造者中提供相应的方法。</li>
</ul>
<p>这种方式让我们能够根据具体需求选择不同的策略组合，具有高度的灵活性和可扩展性，同时避免了修改核心逻辑类 <code>LoginValidator</code>。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[安卓性能优化]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%AE%89%E5%8D%93%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/安卓性能优化.html</guid>
            <pubDate>Mon, 30 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[ANR分析主要是通过查看/data/anr下的log，分析主线程堆栈、cpu、锁信息等。但是这种方法有一定的局限性，很多时候是没有办法查看堆栈信息的，例如有些高版本设备需要root权限才能访问/data/anr/目录。这时候就需要应用来实现卡顿、ANR、死锁的?]]></description>
            <content:encoded><![CDATA[<h1 id="安卓性能优化" tabindex="-1">安卓性能优化 <a class="header-anchor" href="#安卓性能优化" aria-label="Permalink to “安卓性能优化”">&#8203;</a></h1>
<h2 id="_1-前言" tabindex="-1">1. 前言 <a class="header-anchor" href="#_1-前言" aria-label="Permalink to “1. 前言”">&#8203;</a></h2>
<p>ANR分析主要是通过查看<code>/data/anr</code>下的log，分析<code>主线程堆栈</code>、<code>cpu</code>、<code>锁信息</code>等。但是这种方法有一定的局限性，很多时候是没有办法查看堆栈信息的，例如有些高版本设备需要root权限才能访问<code>/data/anr/</code>目录。这时候就需要应用来实现卡顿、ANR、死锁的监控方案。</p>
<h2 id="_2-卡顿原理" tabindex="-1">2. 卡顿原理 <a class="header-anchor" href="#_2-卡顿原理" aria-label="Permalink to “2. 卡顿原理”">&#8203;</a></h2>
<p>一般，主线程有耗时会导致卡顿，如果卡顿超过阈值，就会触发ANR。
应用进程启动的时候，<code>Zygote</code> 会反射调用 <code>ActivityThread</code> 的 <code>main</code> 方法，启动 <code>loop</code> 循环。</p>
<p><strong>ActivityThread</strong></p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-18" id="tab-19" checked><label data-title="txt" for="tab-19">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::qm9lafblmaifacuin2exft::--><code>public static void main(String[] args) {
      ...
    Looper.prepareMainLooper();
    Looper.loop();
    ...
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div></div></div>
<p><strong>Looper的loop方法</strong></p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-24" id="tab-25" checked><label data-title="txt" for="tab-25">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::5sno70rsjg1rzakuhmg34::--><code>public static void loop() {
      for (;;) {
            //1、取消息
            Message msg = queue.next(); // might block
            ...
            //2、消息处理前回调
            if (logging != null) {
                logging.println(&quot;&gt;&gt;&gt;&gt;&gt; Dispatching to &quot; + msg.target + &quot; &quot; +
                        msg.callback + &quot;: &quot; + msg.what);
            }
            ...

            //3、消息开始处理
            msg.target.dispatchMessage(msg);// 分发处理消息
            ...

            //4、消息处理完回调
            if (logging != null) {
                logging.println(&quot;&lt;&lt;&lt;&lt;&lt; Finished to &quot; + msg.target + &quot; &quot; + msg.callback);
            }
       }
       ...
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div></div></div>
<p>由于loop循环存在，所以主线程可以长时间运行。如果想要在主线程执行某个任务，唯一的办法就是通过主线程Handler post一个任务到消息队列里去，然后loop循环中拿到这个msg，交给这个msg的target处理，这个target是Handler。</p>
<p>从上面看，导致卡顿的原因可能有两个地方：</p>
<ol>
<li>注释1的<code>queue.next()</code>阻塞。</li>
<li>注释3的<code>dispatchMessage</code>耗时太久。</li>
</ol>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[封装阻塞的任务队列]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%B0%81%E8%A3%85%E9%98%BB%E5%A1%9E%E7%9A%84%E4%BB%BB%E5%8A%A1%E9%98%9F%E5%88%97.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/封装阻塞的任务队列.html</guid>
            <pubDate>Mon, 30 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[多线程环境中，通过队列可以很容易实现数据共享，比如经典的“生产者”和“消费者”模型中，通过队列可以很便利地实现两者之间的数据共享。 假设我们有若干生产者线程，另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程，利用队列的方式来传递数据，?]]></description>
            <content:encoded><![CDATA[<h1 id="封装阻塞的任务队列" tabindex="-1">封装阻塞的任务队列 <a class="header-anchor" href="#封装阻塞的任务队列" aria-label="Permalink to “封装阻塞的任务队列”">&#8203;</a></h1>
<h2 id="_1-队列选型" tabindex="-1">1. 队列选型 <a class="header-anchor" href="#_1-队列选型" aria-label="Permalink to “1. 队列选型”">&#8203;</a></h2>
<p>多线程环境中，通过队列可以很容易实现数据共享，比如经典的“生产者”和“消费者”模型中，通过队列可以很便利地实现两者之间的数据共享。
假设我们有若干生产者线程，另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程，利用队列的方式来传递数据，
就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内，万一发生数据处理速度不匹配的情况呢？
理想情况下，如果生产者产出数据的速度大于消费者消费的速度，并且当生产出来的数据累积到一定程度的时候，
那么生产者必须暂停等待一下（阻塞生产者线程），以便等待消费者线程把累积的数据处理完毕，反之亦然。</p>
<p>如果我们想要封装一个队列来处理任务，选用的队列类型是非常重要的，接下来就来分析队列的特征并选择合适的类型。</p>
<ol>
<li><strong>普通队列（Queue）</strong></li>
</ol>
<ul>
<li><code>Queue</code> 本身没有没有阻塞特征，只能进行普通的队列操作（<code>add</code>、<code>remove</code>）等。</li>
<li>缺点：
<ul>
<li>需要自己管理队列是否已满或者是否为空</li>
<li>如果队列已满，调用 <code>add()</code> 可能会抛出 <code>IllegalStateException</code> 异常，如果队列为空，调用 <code>remove()</code> 会抛 <code>NoSuchElementException</code> 异常。</li>
</ul>
</li>
</ul>
<ol start="2">
<li><strong>无阻塞队列（ConcurrentLinkedQueue）</strong></li>
</ol>
<ul>
<li>
<p>ConcurrentLinkedQueue 是一个无阻塞的线程安全队列，它是一个基于 CAS（Compare-And-Swap）算法来实现高效的无锁操作。</p>
</li>
<li>
<p>优点：</p>
<ul>
<li>非阻塞队列适合高并发场景，可以在极短的时间内处理大量线程的队列操作，性能非常高。</li>
</ul>
</li>
<li>
<p>缺点：</p>
<ul>
<li>因为没有阻塞，无法有效控制队列的大小，如果消费者速度远低于生产者速度，队列可能会迅速挤压任务，导致资源浪费。</li>
<li>无法自动阻塞生产者线程，在队列满时生产者仍会继续向队列添加任务，可能导致内存问题。</li>
</ul>
</li>
</ul>
<ol start="3">
<li><strong>阻塞队列（BlockingQueue）</strong></li>
</ol>
<p>在多线程任务队列中，选择 <code>BlockingQueue</code> 主要是因为它简化了线程之间的协作和管理，尤其是在生产者和消费者之间的调度</p>
<ul>
<li>优点：
<ul>
<li>可以有效地控制任务的生产和消费速率，防止队列溢出或任务丢失，避免内存过度占用。</li>
<li>当队列满时，生产者会被阻塞；当队列为空时，消费者会被阻塞。这可以防止无效的CPU占用，保持系统的高效运行。</li>
<li>内置的线程安全保证队列在并发环境下操作时更加简单可靠，不需要开发者手动处理锁和同步问题。</li>
<li>通过阻塞机制，代码可以更简洁地实现任务的调度，不需要在生产者和消费者之间显式地协调。</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>这里的阻塞有两种常见的场景</strong>：</p>
<ol>
<li>当队列中没有数据，消费者端的所有线程都会被自动阻塞，直到有数据放入队列。</li>
<li>当队列中填满数据，生产者端的所有线程都会被自动阻塞，直到队列中有空位置，线程被自动唤醒。</li>
</ol>
</blockquote>
<ol>
<li><strong>固定大小的阻塞队列（ArrayBlockingQueue）</strong></li>
</ol>
<p><code>ArrayBlockingQueue</code> 是一个容量固定的阻塞队列，支持生产者在队列已满时被阻塞，消费者在队列为空时被阻塞。</p>
<ul>
<li>优点：</li>
<li>它的容量是固定的，因此能够有限限制内存的使用。</li>
<li>具备阻塞机制，避免了资源浪费和任务丢失。</li>
<li>任务处理队列不允许超过固定容量，避免了内存溢出。
缺点：</li>
<li>容量固定，如果需要动态扩容，无法实现。</li>
<li>如果生产者产生任务的速度远远超过消费者的速度。队列满了以后生产者会被阻塞，可能导致系统的吞吐量降低。</li>
</ul>
<ol start="5">
<li><strong>容量可选的阻塞队列（LinkedBlockingQueue）</strong></li>
</ol>
<p><code>LinkedBlockingQueue</code> 是一个支持容量控制的阻塞队列，可以选择是否设置队列的最大容量。如果没有指定容量，则默认容量为 <code>Integer.MAX_VALUE</code>。</p>
<ul>
<li>优点：
<ul>
<li>如果指定队列大小，它会像 <code>ArrayBlockingQueue</code> 一样进行容量控制，避免了内存溢出。</li>
<li>如果没有指定容量，它能够动态扩展队列的大小，适应不同的负载需求。</li>
<li>支持阻塞操作（<code>put</code> 和 <code>take</code>） ，保证线程安全。</li>
</ul>
</li>
<li>缺点：
<ul>
<li>如果队列的容量不受限制，可能就会导致内存消耗过多，特别是在负载过大的情况下。</li>
</ul>
</li>
</ul>
<ol start="6">
<li><strong>优先级阻塞队列（PriorityBlockingQueue）</strong></li>
</ol>
<p><code>PriorityBlockingQueue</code> 和 <code>ArrayBlockingQueue</code> 一样是基于数组实现的，<strong>但是 <code>ArrayBlockingQueue</code> 在初始化时需要指定长度，<code>PriorityBlockingQueue</code> 默认长度是11。</strong></p>
<ul>
<li><code>PriorityBlockingQueue</code> 是一个真正的无界队列，在队列满的时候会自动进行扩容，界限的最大值是 <code>2147483647</code>。</li>
<li><code>PriorityBlockingQueue</code> 是权重队列，可以理解为它能进行排序。但是排序不是从小到大或从大到小排列，是基于数组的堆结构（完全二叉树）。</li>
<li>出队方式也是按照权重进行出队。</li>
<li>其存入的元素必须实现Comparator，或者在创建队列的时候自定义Comparator。</li>
</ul>
<ol start="7">
<li>
<p><strong>SynchronousQueue</strong></p>
</li>
<li>
<p><strong>DelayQueue</strong></p>
</li>
</ol>
<p>综上，选用 <code>BlockingQueue</code> 进行任务队列的封装以满足，具体原因已在上述内容中给出。</p>
<h2 id="_2-封装设计" tabindex="-1">2. 封装设计 <a class="header-anchor" href="#_2-封装设计" aria-label="Permalink to “2. 封装设计”">&#8203;</a></h2>
<ol>
<li><strong>线程安全</strong>：任务队列需要支持多个线程并发操作，保证线程安全。</li>
<li><strong>优先级控制</strong>：支持任务优先级，确保优先级高的任务优先执行。</li>
<li><strong>流量控制和限制任务队列大小</strong>：当队列已满时，生产者线程应该被阻塞，直到有空间可以使用。</li>
<li><strong>任务回调机制</strong>：任务完成后，可以通过回调机制通知任务的执行结果（成功，失败，超时等）。</li>
<li><strong>异常和超时处理</strong>：对任务执行中的异常和超时进行处理，避免任务丢失。</li>
<li><strong>任务重试机制</strong>：对于失败或超时的任务，支持重试功能。</li>
</ol>
<h3 id="任务模型-task" tabindex="-1">任务模型（Task） <a class="header-anchor" href="#任务模型-task" aria-label="Permalink to “任务模型（Task）”">&#8203;</a></h3>
<p>任务类包含任务的基本信息，包括任务ID、优先级、执行逻辑、回调接口等。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-318" id="tab-319" checked><label data-title="txt" for="tab-319">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::xtd2zlitigkgtltirm4tim::--><code>data class Task {
  val id: String = UUID.randomUUID().toString(), // 任务id
  val taskName: String, // 任务名称
  val priority: Int, // 优先级
  val taskAction: Runnable, // 任务的执行逻辑
  val callback: TaskCallback?, // 任务的回调接口
  val retries: Int // 重试次数
  val maxRetries: Int, // 最大重试次数
  val status: TaskStatus, // 任务状态
  val executionHistory: MultableList&lt;String&gt; = mutableListOf() // 执行历史
}

interface TaskCallback {
  fun onSuccess(taskId: String, result: Any)
  fun onFailure(taskId: String, error: String)
  fun onTimeOut(taskId: String)
}

enum class TaskStatus {
    PENDING, // 等待中
    RUNNING, // 执行中
    COMPLETED, // 完成
    FAILED, // 失败
    TIMED_OUT // 超时
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br></div></div></div></div>
<h3 id="队列封装" tabindex="-1">队列封装 <a class="header-anchor" href="#队列封装" aria-label="Permalink to “队列封装”">&#8203;</a></h3>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-324" id="tab-325" checked><label data-title="txt" for="tab-325">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::xqvbhorqdweq2r7s610no::--><code>class BlockingTaskQueue(
    private val maxQueueSize: Int = 10, // 最大队列容量
    private val maxConcurrency: Int = 3, // 最大并发数
    private val taskTimeoutMs: Long = 5000L, // 任务超时
    private val retryDelayMs: Long = 1000L // 重试延迟
){
  private val taskQueue: BlockingQueue&lt;Task&gt; = PriorityBlockingQueue(maxQueueSize, compareBy&lt;Task&gt; { it.priority })
  private val executor: ExecutorService = ThreadPoolExecutor(
    maxConcurrency
    maxConcurrency,
    60L,
    TimeUnit.SECONDS,
    LinkedBlockingQueue&lt;Runnable&gt; ()
  )

  // 启动任务消费线程
  init {
    startTaskConsumer()
  }

  private fun startTaskConsumer(){
    executor.submit {
      while(true){
        try {
          val task = taskQueue.take() // 阻塞获取任务
          processTask(task)
        } catch(e: InterruptedExecution) {
          Thread.currentThread().interrupt()
          break
        }
      }
    }
  }

    private fun processTask(task: Task) {
        task.executionHistory.add(&quot;Task started at ${System.currentTimeMillis()}&quot;)

        // 执行任务并处理异常和超时
        val future = executor.submit(task.taskAction)

        try {
            // 等待任务执行结果，控制超时
            future.get(taskTimeoutMs, TimeUnit.MILLISECONDS)
            updateTaskStatus(task.id, TaskStatus.COMPLETED)
            task.callback?.onSuccess(task.id, &quot;Task completed successfully&quot;)
            task.executionHistory.add(&quot;Task completed at ${System.currentTimeMillis()}&quot;)
        } catch (e: TimeoutException) {
            if (task.retries &lt; task.maxRetries) {
                task.retries++
                taskQueue.offer(task)  // 任务重试
                task.callback?.onTimeout(task.id)
                task.executionHistory.add(&quot;Task timed out and retried at ${System.currentTimeMillis()}&quot;)
            } else {
                updateTaskStatus(task.id, TaskStatus.TIMED_OUT)
                task.callback?.onTimeout(task.id)
                task.executionHistory.add(&quot;Task timed out and reached max retries&quot;)
            }
        } catch (e: Exception) {
            updateTaskStatus(task.id, TaskStatus.FAILED)
            task.callback?.onFailure(task.id, e.message ?: &quot;Unknown error&quot;)
            task.executionHistory.add(&quot;Task failed at ${System.currentTimeMillis()}&quot;)
        }
    }

    private fun updateTaskStatus(taskId: String, status: TaskStatus) {
        // 更新任务的状态
        taskManager[taskId]?.status = status
        taskManager[taskId]?.executionHistory?.add(&quot;Status changed to $status at ${System.currentTimeMillis()}&quot;)
    }

    // 提交任务到队列
    fun submitTask(taskName: String, priority: Int, taskAction: Runnable, callback: TaskCallback): Boolean {
        val task = Task(taskName = taskName, priority = priority, taskAction = taskAction, callback = callback)

        return try {
            val success = taskQueue.offer(task, 1, TimeUnit.SECONDS)
            if (success) {
                logger.info(&quot;Task added to queue: ${task.id}&quot;)
            } else {
                logger.warning(&quot;Task queue is full. Could not add task: ${task.id}&quot;)
            }
            success
        } catch (e: InterruptedException) {
            logger.severe(&quot;Task submission interrupted: ${e.message}&quot;)
            Thread.currentThread().interrupt()
            false
        }
    }

    // 获取任务的状态
    fun getTaskStatus(taskId: String): Task? {
        return taskManager[taskId]
    }

    // 关闭队列
    fun shutdown() {
        executor.shutdown()
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow()
            }
        } catch (e: InterruptedException) {
            executor.shutdownNow()
        }
        logger.info(&quot;Task queue shutdown complete.&quot;)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br><span class="line-number">85</span><br><span class="line-number">86</span><br><span class="line-number">87</span><br><span class="line-number">88</span><br><span class="line-number">89</span><br><span class="line-number">90</span><br><span class="line-number">91</span><br><span class="line-number">92</span><br><span class="line-number">93</span><br><span class="line-number">94</span><br><span class="line-number">95</span><br><span class="line-number">96</span><br><span class="line-number">97</span><br><span class="line-number">98</span><br><span class="line-number">99</span><br><span class="line-number">100</span><br><span class="line-number">101</span><br><span class="line-number">102</span><br><span class="line-number">103</span><br><span class="line-number">104</span><br><span class="line-number">105</span><br><span class="line-number">106</span><br><span class="line-number">107</span><br></div></div></div></div>
<h2 id="_3-使用" tabindex="-1">3. 使用 <a class="header-anchor" href="#_3-使用" aria-label="Permalink to “3. 使用”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-330" id="tab-331" checked><label data-title="txt" for="tab-331">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::hqpnmalpnn6diaiwq58h5::--><code>fun main() {
    val taskQueue = BlockingTaskQueue(    
      maxQueueSize = 10,        // 最大队列容量
      maxConcurrency = 3,       // 最大并发线程数
      taskTimeoutMs = 5000L,    // 每个任务的超时时间，单位为毫秒
      retryDelayMs = 1000L     // 任务超时后的重试延迟，单位为毫秒
    )

    // 创建回调接口的实现
    val callback = object : TaskCallback {
        override fun onSuccess(taskId: String, result: Any) {
            println(&quot;Task $taskId succeeded: $result&quot;)
        }

        override fun onFailure(taskId: String, error: String) {
            println(&quot;Task $taskId failed: $error&quot;)
        }

        override fun onTimeout(taskId: String) {
            println(&quot;Task $taskId timed out&quot;)
        }
    }

    // 提交任务并传递回调
    repeat(5) { i -&gt;
        taskQueue.submitTask(&quot;Task $i&quot;, priority = i) {
            println(&quot;Executing task $i&quot;)
            if (i % 2 == 0) Thread.sleep(3000) // 模拟任务超时
            else println(&quot;Task $i completed successfully&quot;)
        }, callback)
    }

    // 等待任务完成
    Thread.sleep(10000)

    // 输出队列状态
    taskQueue.getTaskStatus(&quot;Task 0&quot;)?.let { task -&gt;
        println(&quot;Task ID: ${task.id}&quot;)
        println(&quot;Status: ${task.status}&quot;)
        println(&quot;Execution History: ${task.executionHistory.joinToString()}&quot;)
    }

    // 关闭队列
    taskQueue.shutdown()
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[Handler.postDelayed()消息时间准确吗]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Handler.postDelayed()%E6%B6%88%E6%81%AF%E6%97%B6%E9%97%B4%E5%87%86%E7%A1%AE%E5%90%97.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Handler.postDelayed()消息时间准确吗.html</guid>
            <pubDate>Sun, 22 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Handler.postDelayed() 方法用于在延迟一定时间后执行一个任务，它是 Android 中常用的延时执行操作的方式。尽管这种方法在许多场景中非常实用，但它的时间准确性和可靠性可能会受到多种因素的影响。]]></description>
            <content:encoded><![CDATA[<h1 id="handler-postdelayed-消息时间准确吗" tabindex="-1">Handler.postDelayed()消息时间准确吗 <a class="header-anchor" href="#handler-postdelayed-消息时间准确吗" aria-label="Permalink to “Handler.postDelayed()消息时间准确吗”">&#8203;</a></h1>
<p><code>Handler.postDelayed()</code> 方法用于在延迟一定时间后执行一个任务，它是 Android 中常用的延时执行操作的方式。尽管这种方法在许多场景中非常实用，但它的时间准确性和可靠性可能会受到多种因素的影响。</p>
<h2 id="_1-postdelayed-的工作原理" tabindex="-1">1. <strong><code>postDelayed()</code> 的工作原理</strong> <a class="header-anchor" href="#_1-postdelayed-的工作原理" aria-label="Permalink to “1. postDelayed() 的工作原理”">&#8203;</a></h2>
<p><code>Handler.postDelayed(Runnable, delayMillis)</code> 方法会将一个 <code>Runnable</code> 任务提交到消息队列，并指定一个延迟时间 <code>delayMillis</code>。任务会在主线程（或指定的线程）上的消息循环中执行，等待队列中执行的时间点在延迟之后触发。</p>
<h2 id="_2-时间准确性的问题" tabindex="-1">2. <strong>时间准确性的问题</strong> <a class="header-anchor" href="#_2-时间准确性的问题" aria-label="Permalink to “2. 时间准确性的问题”">&#8203;</a></h2>
<p>虽然 <code>postDelayed()</code> 允许指定任务延迟执行的时间，但 <strong>它的延迟时间并不完全精确</strong>，原因包括以下几点：</p>
<h3 id="_2-1-线程调度的开销" tabindex="-1">2.1 <strong>线程调度的开销</strong> <a class="header-anchor" href="#_2-1-线程调度的开销" aria-label="Permalink to “2.1 线程调度的开销”">&#8203;</a></h3>
<ul>
<li><code>postDelayed()</code> 本质上是将任务添加到消息队列中，在指定的延迟时间后，消息会被传递到主线程（或者指定的 <code>Looper</code> 所在的线程）。但是，主线程的调度和执行可能会受到系统负载、其他任务的优先级以及线程切换的影响，导致任务的实际执行时间与预定的延迟时间有所偏差。</li>
<li>如果系统当前有大量的任务等待处理，或者设备正在进行较为密集的操作，延迟时间可能会有一些偏差，通常是稍微延迟。</li>
</ul>
<h3 id="_2-2-精度受限" tabindex="-1">2.2 <strong>精度受限</strong> <a class="header-anchor" href="#_2-2-精度受限" aria-label="Permalink to “2.2 精度受限”">&#8203;</a></h3>
<ul>
<li>Android 的 <code>Handler</code> 基于系统的消息队列和调度机制，通常它依赖于主线程的消息循环。在大多数情况下，延迟任务的精度依赖于系统调度的精确度，而不是毫秒级的准确性。</li>
<li>在某些情况下，任务可能会因为线程被阻塞或消息队列积压等原因被推迟执行，导致延迟时间稍微偏差。</li>
</ul>
<h3 id="_2-3-与屏幕刷新率的关系" tabindex="-1">2.3 <strong>与屏幕刷新率的关系</strong> <a class="header-anchor" href="#_2-3-与屏幕刷新率的关系" aria-label="Permalink to “2.3 与屏幕刷新率的关系”">&#8203;</a></h3>
<ul>
<li>在一些设备上，尤其是高刷新率屏幕（如 120Hz 屏幕）下，消息的调度和 UI 刷新可能与帧的渲染周期有关，导致某些延迟任务可能会略微提前或推迟，尤其是如果执行任务的时机与屏幕刷新周期接近。</li>
</ul>
<h3 id="_2-4-系统休眠和节能模式" tabindex="-1">2.4 <strong>系统休眠和节能模式</strong> <a class="header-anchor" href="#_2-4-系统休眠和节能模式" aria-label="Permalink to “2.4 系统休眠和节能模式”">&#8203;</a></h3>
<ul>
<li>在某些情况下，如果设备进入休眠模式或者系统处于节能状态，可能会延迟任务的执行，尤其是在任务执行时主线程没有得到及时调度。</li>
</ul>
<h2 id="_3-如何改善延迟的准确性" tabindex="-1">3. <strong>如何改善延迟的准确性</strong> <a class="header-anchor" href="#_3-如何改善延迟的准确性" aria-label="Permalink to “3. 如何改善延迟的准确性”">&#8203;</a></h2>
<p>虽然 <code>Handler.postDelayed()</code> 不会提供完美的时间精度，但在一些情况下，可以采取以下方法来改进延迟的准确性：</p>
<h3 id="_3-1-使用-systemclock-elapsedrealtime" tabindex="-1">3.1 <strong>使用 <code>SystemClock.elapsedRealtime()</code></strong> <a class="header-anchor" href="#_3-1-使用-systemclock-elapsedrealtime" aria-label="Permalink to “3.1 使用 SystemClock.elapsedRealtime()”">&#8203;</a></h3>
<p><code>SystemClock.elapsedRealtime()</code> 返回系统开机后的时间（包括休眠时间），这使得它在处理定时任务时能够避免受系统时间变化的影响。例如，如果需要进行一个精确的延迟操作，可以使用以下方式：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::c2lmw0iarphmxfkywddt68::--><code>val delayTime = 1000L // 延迟 1 秒
val targetTime = SystemClock.elapsedRealtime() + delayTime

handler.postAtTime({
    // 在目标时间执行任务
    Log.d(&quot;Handler&quot;, &quot;Task executed after delay&quot;)
}, targetTime)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>这种方法确保了任务会在准确的时间点执行，不会受到 <code>postDelayed()</code> 的调度延迟影响。</p>
<h3 id="_3-2-使用-scheduledexecutorservice" tabindex="-1">3.2 <strong>使用 <code>ScheduledExecutorService</code></strong> <a class="header-anchor" href="#_3-2-使用-scheduledexecutorservice" aria-label="Permalink to “3.2 使用 ScheduledExecutorService”">&#8203;</a></h3>
<p>对于需要更高精度的定时任务，使用 <code>ScheduledExecutorService</code> 会更加适合，它提供了更高精度的定时和延时任务执行。相比于 <code>Handler.postDelayed()</code>，它更依赖于 Java 的并发库，精度上通常较高。</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::34q45dm4mchxj762gt462::--><code>val scheduler = Executors.newSingleThreadScheduledExecutor()
scheduler.schedule({
    // 执行任务
    Log.d(&quot;ScheduledExecutor&quot;, &quot;Task executed after delay&quot;)
}, 1, TimeUnit.SECONDS)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p><code>ScheduledExecutorService</code> 提供了类似 <code>scheduleAtFixedRate()</code> 和 <code>scheduleWithFixedDelay()</code> 等方法，可以精确控制任务的延时和执行频率。</p>
<h3 id="_3-3-避免过度依赖主线程" tabindex="-1">3.3 <strong>避免过度依赖主线程</strong> <a class="header-anchor" href="#_3-3-避免过度依赖主线程" aria-label="Permalink to “3.3 避免过度依赖主线程”">&#8203;</a></h3>
<p>如果依赖主线程的执行频率（例如 UI 更新、计时等），但对时间的精度要求较高，尽量避免在主线程中执行长时间的任务，尤其是在有其他高优先级任务（如 UI 渲染）的情况下。尽量将耗时任务放到后台线程中。</p>
<h2 id="_4-总结" tabindex="-1">4. <strong>总结</strong> <a class="header-anchor" href="#_4-总结" aria-label="Permalink to “4. 总结”">&#8203;</a></h2>
<p><code>Handler.postDelayed()</code> 提供了延时执行任务的功能，但它的延迟时间不总是非常精确。它的执行时间可能受到以下因素的影响：</p>
<ul>
<li>线程调度的开销和系统负载</li>
<li>主线程的其他任务和消息队列的积压</li>
<li>系统的休眠或节能模式</li>
</ul>
<p>对于高精度的定时任务，可以考虑使用 <code>SystemClock.elapsedRealtime()</code> 或者 <code>ScheduledExecutorService</code>，它们提供了更精确的延时控制。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[为什么主线程可以直接new Handler]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%BB%E7%BA%BF%E7%A8%8B%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5new%20Handler.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/为什么主线程可以直接new Handler.html</guid>
            <pubDate>Sun, 22 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[在 Android 中，Handler 是用来处理线程之间的消息通信和异步操作的一个重要工具，通常它用于在非主线程（如后台线程）中发送消息或执行任务，并确保这些任务最终在主线程中执行。对于 Handler 的使用，有一个重要的问题是为什么在 主线程 中可以直接?]]></description>
            <content:encoded><![CDATA[<h1 id="为什么主线程可以直接new-handler" tabindex="-1">为什么主线程可以直接new Handler <a class="header-anchor" href="#为什么主线程可以直接new-handler" aria-label="Permalink to “为什么主线程可以直接new Handler”">&#8203;</a></h1>
<p>在 Android 中，<code>Handler</code> 是用来处理线程之间的消息通信和异步操作的一个重要工具，通常它用于在非主线程（如后台线程）中发送消息或执行任务，并确保这些任务最终在主线程中执行。对于 <code>Handler</code> 的使用，有一个重要的问题是为什么在 <strong>主线程</strong> 中可以直接使用 <code>new Handler()</code>。</p>
<h2 id="_1-主线程和-looper-机制" tabindex="-1">1. <strong>主线程和 Looper 机制</strong> <a class="header-anchor" href="#_1-主线程和-looper-机制" aria-label="Permalink to “1. 主线程和 Looper 机制”">&#8203;</a></h2>
<p><code>Handler</code> 的工作原理依赖于 <code>Looper</code>。在 Android 中，<code>Looper</code> 是一个消息循环，它负责监听消息队列并执行对应的任务。</p>
<h3 id="_1-1-主线程和-looper-的关系" tabindex="-1">1.1 主线程和 Looper 的关系 <a class="header-anchor" href="#_1-1-主线程和-looper-的关系" aria-label="Permalink to “1.1 主线程和 Looper 的关系”">&#8203;</a></h3>
<ul>
<li>
<p>主线程在启动时，Android 会自动为其创建一个默认的 <code>Looper</code>，并让主线程运行一个消息循环。具体来说，<code>Looper.prepare()</code> 和 <code>Looper.loop()</code> 是主线程执行的主要流程，确保主线程能够处理并执行通过 <code>Handler</code> 发送到主线程的消息。</p>
</li>
<li>
<p>由于主线程在应用启动时已经有了一个默认的 <code>Looper</code>，因此可以直接在主线程中创建一个 <code>Handler</code>，无需显式地传递 <code>Looper</code>。</p>
</li>
</ul>
<h2 id="_2-handler-构造函数" tabindex="-1">2. <strong><code>Handler</code> 构造函数</strong> <a class="header-anchor" href="#_2-handler-构造函数" aria-label="Permalink to “2. Handler 构造函数”">&#8203;</a></h2>
<p><code>Handler</code> 有几个构造函数，其中最常用的是：</p>
<ul>
<li><code>Handler()</code>: 默认构造函数。如果没有指定 <code>Looper</code>，它会使用当前线程的 <code>Looper</code>（即主线程中的 <code>Looper</code>）。</li>
<li><code>Handler(Looper looper)</code>: 允许指定一个特定的 <code>Looper</code>，用于在该 <code>Looper</code> 上处理消息。</li>
</ul>
<h3 id="_2-1-默认构造函数-handler" tabindex="-1">2.1 默认构造函数：<code>Handler()</code> <a class="header-anchor" href="#_2-1-默认构造函数-handler" aria-label="Permalink to “2.1 默认构造函数：Handler()”">&#8203;</a></h3>
<p>当在主线程中调用 <code>new Handler()</code> 时，实际上是在使用默认构造函数。默认构造函数的行为是，<code>Handler</code> 会使用当前线程的 <code>Looper</code>，在主线程中，当前线程的 <code>Looper</code> 就是主线程的 <code>Looper</code>，因此不需要手动指定 <code>Looper</code>。</p>
<h2 id="_3-为什么主线程可以直接-new-handler" tabindex="-1">3. <strong>为什么主线程可以直接 <code>new Handler()</code></strong> <a class="header-anchor" href="#_3-为什么主线程可以直接-new-handler" aria-label="Permalink to “3. 为什么主线程可以直接 new Handler()”">&#8203;</a></h2>
<p>主线程在应用启动时已经初始化了 <code>Looper</code>，并且它在 <code>Looper.loop()</code> 运行时开始接受并处理消息。<code>Handler</code> 会将消息放入与 <code>Looper</code> 关联的消息队列，并通过 <code>Looper</code> 循环处理这些消息。</p>
<p>所以，在主线程中直接创建 <code>Handler</code> 时，默认的构造函数会自动使用主线程的 <code>Looper</code>。主线程并不需要额外配置，因为它已经有了自己的 <code>Looper</code>，这使得可以方便地在主线程中使用 <code>Handler</code> 进行消息处理。</p>
<h2 id="_4-主线程中-handler-的应用场景" tabindex="-1">4. <strong>主线程中 <code>Handler</code> 的应用场景</strong> <a class="header-anchor" href="#_4-主线程中-handler-的应用场景" aria-label="Permalink to “4. 主线程中 Handler 的应用场景”">&#8203;</a></h2>
<p>在主线程中，<code>Handler</code> 通常用于以下几种情况：</p>
<ul>
<li><strong>更新 UI</strong>：从非主线程（例如后台线程）发送数据或更新 UI，通常通过主线程的 <code>Handler</code> 来处理。</li>
<li><strong>定时任务</strong>：在主线程上执行周期性任务或延迟任务。</li>
</ul>
<h2 id="_5-举个例子" tabindex="-1">5. <strong>举个例子</strong> <a class="header-anchor" href="#_5-举个例子" aria-label="Permalink to “5. 举个例子”">&#8203;</a></h2>
<p>以下是一个在主线程中创建 <code>Handler</code> 的例子：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::ketz8bqhfoe6saay3vchrg::--><code>class MainActivity : AppCompatActivity() {
    private lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 在主线程中创建Handler，使用主线程的Looper
        handler = Handler(Looper.getMainLooper())  // 或者直接 new Handler() 也会使用主线程的Looper

        // 使用Handler在主线程更新UI
        handler.post {
            // 这是在主线程执行的代码
            Toast.makeText(this, &quot;Hello from the main thread!&quot;, Toast.LENGTH_SHORT).show()
        }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div><h2 id="_6-线程之间的消息传递" tabindex="-1">6. <strong>线程之间的消息传递</strong> <a class="header-anchor" href="#_6-线程之间的消息传递" aria-label="Permalink to “6. 线程之间的消息传递”">&#8203;</a></h2>
<p>如果希望从非主线程向主线程发送消息或任务，可以使用 <code>Handler</code>：</p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::zpi1q50s2ljykgyitm8r4q::--><code>val backgroundThread = Thread {
    // 在后台线程执行一些任务
    // 模拟耗时操作
    Thread.sleep(2000)

    // 向主线程发送一个任务
    handler.post {
        // 这是在主线程执行的代码
        Toast.makeText(this, &quot;Task completed&quot;, Toast.LENGTH_SHORT).show()
    }
}

backgroundThread.start()</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>在上述代码中，<code>handler.post()</code> 将任务放入消息队列，确保这个任务会在主线程执行。</p>
<h2 id="_7-总结" tabindex="-1">7. <strong>总结</strong> <a class="header-anchor" href="#_7-总结" aria-label="Permalink to “7. 总结”">&#8203;</a></h2>
<ul>
<li><strong>主线程中 <code>new Handler()</code> 可以直接工作</strong>，因为主线程在应用启动时自动初始化了 <code>Looper</code>，这使得 <code>Handler</code> 能够通过默认构造函数使用主线程的 <code>Looper</code>。</li>
<li><code>Handler</code> 的核心功能是通过 <code>Looper</code> 处理消息队列，<code>Handler</code> 发送的消息会被放入相应线程的消息队列并最终执行。</li>
<li>如果需要在非主线程中执行任务并返回结果到主线程，需要使用 <code>Handler</code> 来进行线程间的通信。</li>
</ul>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[深入理解crash问题]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3crash%E9%97%AE%E9%A2%98.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/深入理解crash问题.html</guid>
            <pubDate>Sun, 22 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[首先，明确崩溃的类型。崩溃问题通常可以分为以下几类：]]></description>
            <content:encoded><![CDATA[<h1 id="深入理解crash问题" tabindex="-1">深入理解crash问题 <a class="header-anchor" href="#深入理解crash问题" aria-label="Permalink to “深入理解crash问题”">&#8203;</a></h1>
<h2 id="_1-崩溃的基本分类" tabindex="-1">1. <strong>崩溃的基本分类</strong> <a class="header-anchor" href="#_1-崩溃的基本分类" aria-label="Permalink to “1. 崩溃的基本分类”">&#8203;</a></h2>
<p>首先，明确崩溃的类型。崩溃问题通常可以分为以下几类：</p>
<ul>
<li><strong>Runtime Exception</strong>：运行时异常，如 <code>NullPointerException</code>、<code>ArrayIndexOutOfBoundsException</code>、<code>IllegalArgumentException</code> 等。这类问题通常与代码逻辑错误、空指针引用或错误的输入数据有关。</li>
<li><strong>ANR (Application Not Responding)</strong>：应用无响应，通常是由于主线程阻塞导致的。</li>
<li><strong>Native 崩溃</strong>：涉及 C/C++ 层的崩溃，通常与 JNI、NDK 或与硬件直接交互相关的问题有关。</li>
<li><strong>OOM (Out Of Memory)</strong>：内存溢出，通常发生在应用程序消耗过多内存，导致系统无法分配更多内存时。</li>
<li><strong>线程和并发相关的崩溃</strong>：例如死锁、线程竞态条件等。</li>
</ul>
<h2 id="_2-分析崩溃日志" tabindex="-1">2. <strong>分析崩溃日志</strong> <a class="header-anchor" href="#_2-分析崩溃日志" aria-label="Permalink to “2. 分析崩溃日志”">&#8203;</a></h2>
<p>崩溃日志通常是理解和修复崩溃问题的起点。安卓应用的崩溃日志通常由以下几个部分组成：</p>
<ul>
<li><strong>堆栈信息</strong>：提供崩溃时线程的调用栈信息。最重要的是 <code>Exception</code> 栈顶的函数，这通常会指向崩溃的具体位置。</li>
<li><strong>日志标签</strong>：日志中可能包含的自定义标签（如 <code>Logcat</code>）可以定位到具体的模块或代码路径。</li>
<li><strong>设备信息和版本信息</strong>：包括设备型号、操作系统版本、应用版本等，有助于确认崩溃是否特定于某些设备或环境。</li>
</ul>
<h3 id="关键工具" tabindex="-1">关键工具 <a class="header-anchor" href="#关键工具" aria-label="Permalink to “关键工具”">&#8203;</a></h3>
<ul>
<li><strong>Logcat</strong>：通过 <code>adb logcat</code> 命令查看崩溃日志。</li>
<li><strong>Crashlytics / Firebase Crash Reporting</strong>：自动收集并上报崩溃信息，提供更为详细的崩溃报告，帮助定位问题。</li>
</ul>
<h2 id="_3-深入分析崩溃原因" tabindex="-1">3. <strong>深入分析崩溃原因</strong> <a class="header-anchor" href="#_3-深入分析崩溃原因" aria-label="Permalink to “3. 深入分析崩溃原因”">&#8203;</a></h2>
<h3 id="_1-nullpointerexception" tabindex="-1">1. <strong>NullPointerException</strong> <a class="header-anchor" href="#_1-nullpointerexception" aria-label="Permalink to “1. NullPointerException”">&#8203;</a></h3>
<p>这是最常见的崩溃类型，通常是因为访问了空对象的成员或方法。</p>
<p><strong>根本原因</strong>：</p>
<ul>
<li>空指针错误通常是因为在没有正确初始化对象的情况下尝试访问它，或者对象已经被设置为 <code>null</code>。</li>
</ul>
<p><strong>解决方法</strong>：</p>
<ul>
<li>使用 <code>Null Safety</code> 特性，如 Kotlin 的 <code>?.</code> 操作符，避免空指针。</li>
<li>检查每个对象的生命周期，确保在访问它之前已经初始化。</li>
</ul>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::4ilfc8sbb8kb1h248cuc9r::--><code>val item: Item? = getItem()
item?.doSomething()  // 使用安全调用符避免空指针异常</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><h3 id="_2-anr-application-not-responding" tabindex="-1">2. <strong>ANR (Application Not Responding)</strong> <a class="header-anchor" href="#_2-anr-application-not-responding" aria-label="Permalink to “2. ANR (Application Not Responding)”">&#8203;</a></h3>
<p>ANR 是由于主线程被阻塞而导致的应用无响应。ANR 通常发生在主线程上执行耗时操作时。</p>
<p><strong>根本原因</strong>：</p>
<ul>
<li>在主线程上执行耗时操作（如网络请求、大数据处理、数据库查询等）会阻塞 UI 更新，导致 ANR。</li>
<li>应用没有正确使用异步任务，如 <code>AsyncTask</code>、<code>Handler</code>、<code>Coroutines</code> 等。</li>
</ul>
<p><strong>解决方法</strong>：</p>
<ul>
<li>避免在主线程上进行长时间的阻塞操作。</li>
<li>使用后台线程（例如 <code>AsyncTask</code>、<code>ExecutorService</code>、<code>Coroutines</code>）处理繁重的工作。</li>
<li>使用 <code>StrictMode</code> 来发现主线程上执行的阻塞操作。</li>
</ul>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::fw2zus265zetund11o6nyg::--><code>// 示例：在后台线程进行网络请求
GlobalScope.launch(Dispatchers.IO) {
    val result = fetchDataFromNetwork()
    withContext(Dispatchers.Main) {
        updateUI(result)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h3 id="_3-oom-out-of-memory" tabindex="-1">3. <strong>OOM (Out Of Memory)</strong> <a class="header-anchor" href="#_3-oom-out-of-memory" aria-label="Permalink to “3. OOM (Out Of Memory)”">&#8203;</a></h3>
<p>OOM 通常是由于应用过度消耗内存，特别是加载大量数据或处理大图像时。</p>
<p><strong>根本原因</strong>：</p>
<ul>
<li>过多的数据或大对象保存在内存中，导致内存不足。</li>
<li>未释放不再使用的对象（例如图片、大数据集合等）。</li>
<li>内存泄漏：对象在不再需要时没有被回收，导致内存持续占用。</li>
</ul>
<p><strong>解决方法</strong>：</p>
<ul>
<li>使用内存分析工具，如 Android Studio Profiler，查看内存使用情况。</li>
<li>优化图像加载：使用 <code>Glide</code> 或 <code>Picasso</code> 等库，并且避免将整个图像加载到内存中。</li>
<li>使用 <code>WeakReference</code> 解决内存泄漏问题。</li>
</ul>
<h4 id="_4-native-崩溃-jni-ndk-崩溃" tabindex="-1">4. <strong>Native 崩溃 (JNI/NDK 崩溃)</strong> <a class="header-anchor" href="#_4-native-崩溃-jni-ndk-崩溃" aria-label="Permalink to “4. Native 崩溃 (JNI/NDK 崩溃)”">&#8203;</a></h4>
<p>Native 崩溃发生在使用 JNI 调用 C/C++ 代码时，通常是由于指针错误、内存访问错误或库函数的错误。</p>
<p><strong>根本原因</strong>：</p>
<ul>
<li>C/C++ 代码中存在指针错误、内存越界、资源释放不当等问题。</li>
<li>调用第三方库或设备相关代码时，传递了不正确的参数。</li>
</ul>
<p><strong>解决方法</strong>：</p>
<ul>
<li>使用 <strong>NDK Debugger</strong>（如 <code>gdb</code>）调试 C/C++ 层的代码，查看崩溃位置。</li>
<li>使用 <code>Android.mk</code> 或 <code>CMakeLists.txt</code> 配置编译时启用调试信息。</li>
<li>启用 <code>abort()</code> 和 <code>assert()</code> 来捕捉和定位异常情况。</li>
</ul>
<h3 id="_5-线程和并发崩溃" tabindex="-1">5. <strong>线程和并发崩溃</strong> <a class="header-anchor" href="#_5-线程和并发崩溃" aria-label="Permalink to “5. 线程和并发崩溃”">&#8203;</a></h3>
<p>线程同步问题通常导致死锁、线程竞态条件或资源冲突。</p>
<p><strong>根本原因</strong>：</p>
<ul>
<li>同步操作不当，导致死锁或竞态条件。</li>
<li>线程间共享资源未正确同步，可能导致数据不一致。</li>
</ul>
<p><strong>解决方法</strong>：</p>
<ul>
<li>使用适当的同步机制，如 <code>synchronized</code> 关键字、<code>ReentrantLock</code> 等，确保多线程访问共享资源时的安全性。</li>
<li>使用 <code>Thread.sleep()</code> 或 <code>CountDownLatch</code> 等控制线程的执行顺序，避免死锁。</li>
</ul>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::zv5v77cexh2s1nhfex6ox::--><code>val lock = ReentrantLock()

fun safeMethod() {
    lock.lock()
    try {
        // 临界区操作
    } finally {
        lock.unlock()
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><h2 id="_4-如何调试崩溃" tabindex="-1">4. <strong>如何调试崩溃</strong> <a class="header-anchor" href="#_4-如何调试崩溃" aria-label="Permalink to “4. 如何调试崩溃”">&#8203;</a></h2>
<ul>
<li><strong>调试工具</strong>：使用 Android Studio Profiler、Heap Dump、Logcat 等工具分析内存使用、CPU 性能、崩溃堆栈等信息。</li>
<li><strong>模拟不同场景</strong>：通过模拟不同的设备、操作系统版本、网络状况等，验证应用在各种情况下的稳定性。</li>
<li><strong>动态分析</strong>：通过动态分析（如日志输出、崩溃捕捉）结合实际用户的崩溃报告来定位和修复问题。</li>
</ul>
<h2 id="_5-崩溃修复的最佳实践" tabindex="-1">5. <strong>崩溃修复的最佳实践</strong> <a class="header-anchor" href="#_5-崩溃修复的最佳实践" aria-label="Permalink to “5. 崩溃修复的最佳实践”">&#8203;</a></h2>
<ul>
<li><strong>统一日志和崩溃报告</strong>：集成第三方崩溃报告工具（如 Crashlytics），实时获取崩溃信息，并通过日志排查问题。</li>
<li><strong>持续集成（CI）和自动化测试</strong>：通过自动化测试、单元测试、UI 测试等，早期发现潜在崩溃问题。</li>
<li><strong>内存和性能监控</strong>：使用工具如 <code>LeakCanary</code>（内存泄漏检测）、Android Profiler、Systrace 等来检测性能瓶颈和内存泄漏。</li>
</ul>
<h2 id="_6-总结" tabindex="-1">6. <strong>总结</strong> <a class="header-anchor" href="#_6-总结" aria-label="Permalink to “6. 总结”">&#8203;</a></h2>
<p>解决崩溃问题的根本在于理解崩溃的原因，分析代码、日志和环境因素，并利用合适的工具进行定位和修复。通过日志分析、调试工具和持续监控，不仅可以解决已有的崩溃，还能有效预防未来的问题。在解决崩溃问题时，关注细节，确保应用的鲁棒性和稳定性。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[获取位置信息]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E8%8E%B7%E5%8F%96%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/获取位置信息.html</guid>
            <pubDate>Sun, 22 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[在安卓中获取位置信息以及后台位置信息通常使用 FusedLocationProviderClient 和 LocationManager。为了在后台获取位置信息，需要结合使用位置服务、WorkManager 或 JobScheduler 等后台任务管理工具来确?]]></description>
            <content:encoded><![CDATA[<h1 id="获取位置信息" tabindex="-1">获取位置信息 <a class="header-anchor" href="#获取位置信息" aria-label="Permalink to “获取位置信息”">&#8203;</a></h1>
<p>在安卓中获取位置信息以及后台位置信息通常使用 <code>FusedLocationProviderClient</code> 和 <code>LocationManager</code>。为了在后台获取位置信息，需要结合使用位置服务、<code>WorkManager</code> 或 <code>JobScheduler</code> 等后台任务管理工具来确保即使应用被关闭或处于后台时仍能获取位置。</p>
<p>其中<code>FusedLocationProviderClient</code> 是推荐的获取位置信息的方式，它继承了多个 <code>LocationProvider</code> ，能提供一个准确、低功耗的定位服务。但是需要能访问GMS的API（Google Play服务）才能够调用。</p>
<h2 id="_1-androidmanifest中声明定位权限" tabindex="-1">1. AndroidManifest中声明定位权限 <a class="header-anchor" href="#_1-androidmanifest中声明定位权限" aria-label="Permalink to “1. AndroidManifest中声明定位权限”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-12" id="tab-13" checked><label data-title="txt" for="tab-13">txt</label></div><div class="blocks">
<div class="language-xml active line-numbers-mode" data-lang="xml"><button title="Copy Code" class="copy"></button><span class="lang">XML</span><pre><!--::markdown-it-async::384h79q20nv9iqecrg3v8s::--><code>&lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt; &lt;!-- 精确定位权限 --&gt;
&lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot; /&gt; &lt;!-- 粗略定位权限 --&gt;
&lt;uses-permission android:name=&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot; android:deprecated=&quot;true&quot;/&gt; &lt;!-- 后台定位权限 --&gt;</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></div></div>
<blockquote>
<p>注意： 如果只声明精确定位权限和粗略定位权限而不申请后台定位权限的话，在设置中的位置权限是没有 <code>Allow all the time</code> 这一选项的。前台定位权限均为运行时权限。后台定位权限申请不会有任何权限弹窗提示。Android 10及以后版本，需要请求 <code>ACCESS_BACKGROUND_LOCATION</code> 才能在后台获取位置。但是即便授予了这个权限也需要让用户到设置项中手动将位置权限置为 <code>Allow all the time</code>，这样应用在后台也能更新位置信息。</p>
</blockquote>
<h2 id="代码示例" tabindex="-1">代码示例 <a class="header-anchor" href="#代码示例" aria-label="Permalink to “代码示例”">&#8203;</a></h2>
<ol>
<li>FusedLocationProviderClient方法</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-30" id="tab-31" checked><label data-title="txt" for="tab-31">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::qwtjc2jmcuo1oqkz504ha5::--><code>import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import android.location.Location
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.Manifest
import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    private lateinit var fusedLocationClient: FusedLocationProviderClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

        // 请求权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            getLocation()
        } else {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1)
        }
    }

    private fun getLocation() {
        lifecycleScope.launch(Dispatchers.Main) {
            try {
                val location: Location? = fusedLocationClient.lastLocation.await()
                if (location != null) {
                    // 获取到位置，更新UI等操作
                    println(&quot;Location: ${location.latitude}, ${location.longitude}&quot;)
                } else {
                    // 无法获取到位置
                    println(&quot;Location not available&quot;)
                }
            } catch (e: Exception) {
                // 处理异常
                println(&quot;Error: ${e.localizedMessage}&quot;)
            }
        }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br></div></div></div></div>
<h2 id="_2-locationmanager" tabindex="-1">2. LocationManager <a class="header-anchor" href="#_2-locationmanager" aria-label="Permalink to “2. LocationManager”">&#8203;</a></h2>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-36" id="tab-37" checked><label data-title="txt" for="tab-37">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::sv3mkth0vnk4w9jmos98ig::--><code>import android.location.Location
import android.location.LocationListener
import android.os.Bundle
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.Manifest
import android.content.pm.PackageManager

class MainActivity : AppCompatActivity() {

    private lateinit var locationManager: LocationManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager

        // 检查权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            getLocationOnce()
        } else {
            // 请求权限
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1)
        }
    }

    private fun getLocationOnce() {
        try {
            // 请求单次位置更新
            locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, object : LocationListener {
                override fun onLocationChanged(location: Location) {
                    // 获取到位置后更新UI
                    println(&quot;Latitude: ${location.latitude}, Longitude: ${location.longitude}&quot;)
                }

                override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
                override fun onProviderEnabled(provider: String?) {}
                override fun onProviderDisabled(provider: String?) {
                    // 如果 GPS 被禁用，可以提示用户开启
                    println(&quot;GPS is disabled&quot;)
                }
            }, null) // 在此传入 Looper，可以为 null 以在主线程回调
        } catch (e: SecurityException) {
            e.printStackTrace()
        }
    }

    // 如果需要持续获取位置更新，可以使用 requestLocationUpdates 方法，它允许你设置更新频率、最小距离等。
    private fun getContinuousLocationUpdates() {
        try {
            // 设置位置更新间隔和最小移动距离
            val minTime = 1000L  // 1秒钟更新一次
            val minDistance = 5f  // 最小移动距离为 5 米

            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER, minTime, minDistance, object : LocationListener {
                    override fun onLocationChanged(location: Location) {
                        // 获取到新位置
                        println(&quot;Latitude: ${location.latitude}, Longitude: ${location.longitude}&quot;)
                    }

                    override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
                    override fun onProviderEnabled(provider: String?) {}
                    override fun onProviderDisabled(provider: String?) {}
                })
        } catch (e: SecurityException) {
            e.printStackTrace()
        }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[实现定时循环]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%AE%9E%E7%8E%B0%E5%AE%9A%E6%97%B6%E5%BE%AA%E7%8E%AF.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/实现定时循环.html</guid>
            <pubDate>Tue, 17 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[使用 Handler 的 postDelay(Runnable, delayMillis) 方法实现定时任务。 该方法适用于需要在主线程或其他线程中处理短时间间隔任务的场景，例如更新UI或轻量级任务。]]></description>
            <content:encoded><![CDATA[<h1 id="实现定时循环" tabindex="-1">实现定时循环 <a class="header-anchor" href="#实现定时循环" aria-label="Permalink to “实现定时循环”">&#8203;</a></h1>
<h2 id="方法1-handler-postdelayed" tabindex="-1">方法1：Handler + postDelayed <a class="header-anchor" href="#方法1-handler-postdelayed" aria-label="Permalink to “方法1：Handler + postDelayed”">&#8203;</a></h2>
<p>使用 <code>Handler</code> 的 <code>postDelay(Runnable, delayMillis)</code> 方法实现定时任务。
该方法适用于需要在主线程或其他线程中处理短时间间隔任务的场景，例如<strong>更新UI</strong>或<strong>轻量级任务</strong>。</p>
<p><strong>代码示例</strong>：</p>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::jlq9is171jsfk8tx1nipi::--><code>Handler handler = new Handler(Looper.getMainLooper());
Runnable runnable = new Runnable() {
  @Override
  public void run() {
    Log.d(&quot;HandlerExample&quot;, &quot;定时任务&quot;)；
    handler.postDelayed(this, 1000);
  }
}

// 开启定时任务
handler.postDelayed(runnable, 1000);

// 停止定时任务
handler.removeCallback(runnable);</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p><strong>优点</strong>：</p>
<ul>
<li>简单易用，适合短时间延迟任务。</li>
<li>与Looper紧密结合，能跟线程生命周期绑定。</li>
<li>在主线程运行时，不用担心线程安全问题。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>需要手动管理任务的取消，避免内存泄漏。</li>
<li>不适合高频或者精确的任务调度。</li>
<li>依赖线程的生命周期，线程结束后任务就会被清除。</li>
<li>主线程过载时可能会导致任务延迟。</li>
</ul>
<h2 id="方法2-alarmmanager" tabindex="-1">方法2：AlarmManager <a class="header-anchor" href="#方法2-alarmmanager" aria-label="Permalink to “方法2：AlarmManager”">&#8203;</a></h2>
<p>使用 <code>AlarmManager</code> 的 <code>setRepeating</code> 方法。
适用场景：需要在设备休眠时唤醒设备并执行任务的场景。例如，定时提醒，后台定时同步任务。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>即便设备进入休眠模式，也能触发任务。</li>
<li>节能高效，适合低频任务。</li>
<li>能指定触发时间的精度。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>适合较低频率的任务，间隔时间太短会影响性能。</li>
<li>会受到 <code>Doze</code> 模式的限制。</li>
</ul>
<p><strong>代码示例</strong>：</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-102" id="tab-103" checked><label data-title="txt" for="tab-103">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::ne2fgd1erakpmrq0ckz4bh::--><code>AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, MyBroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

long triggerAtMillis = SystemClock.elapsedRealtime() + 60000; // 1分钟后触发
long intervalMillis = 60 * 1000;
// 设置重复性报警
if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.M) {
    // 对于 API 23 及以上，使用 setAndAllowWhileIdle 或 setExactAndAllowWhileIdle
    // 注意: setRepeating 不支持高精度定时
    alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, intervalMillis, pendingIntent);
} else {
    // 对于较低版本的 Android，直接使用 setRepeating
    alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, intervalMillis, pendingIntent);
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div></div></div>
<h2 id="方法3-jobscheduler" tabindex="-1">方法3：JobScheduler <a class="header-anchor" href="#方法3-jobscheduler" aria-label="Permalink to “方法3：JobScheduler”">&#8203;</a></h2>
<p>使用 <code>JobScheduler</code> 调度任务，通过 <code>JobInfo</code> 配置定时条件。
适用于后台定时任务，如<strong>定期上传日志</strong>，<strong>清除缓存</strong>等。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>与系统资源管理紧密结合，任务调度智能</li>
<li>节能高效，可以根据网络、充电状态等条件执行任务</li>
<li>系统会自动处理任务的重复和恢复</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>配置复杂性高</li>
<li>精确度较低，依赖系统的调度优化</li>
</ul>
<p><strong>代码示例</strong>：</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-149" id="tab-150" checked><label data-title="txt" for="tab-150">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::h92lbla9pbw48pxz5kiln::--><code>import android.app.job.JobParameters;
import android.app.job.JobService;
import android.util.Log;

public class MyJobService extends JobService {

    private static final String TAG = &quot;MyJobService&quot;;

    @Override
    public boolean onStartJob(JobParameters params) {
        // 执行任务的代码
        Log.i(TAG, &quot;Job started!&quot;);
        
        // 执行定时任务后，重新安排任务，创建新的 JobInfo
        scheduleJob();
        
        // 返回 true 表示任务正在执行，系统需要等待任务完成
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // 任务被取消时调用
        Log.i(TAG, &quot;Job stopped!&quot;);
        return false; // 表示不需要重试
    }

    // 设置 JobScheduler 循环定时任务
    private void scheduleJob() {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
        
        // 创建一个周期性任务
        JobInfo jobInfo = new JobInfo.Builder(123, new ComponentName(this, MyJobService.class))
                .setPeriodic(60 * 1000)  // 设置任务周期为 60 秒
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) // 需要网络连接
                .setPersisted(true) // 设置任务重启后继续执行
                .build();

        // 调度任务
        if (jobScheduler != null) {
            jobScheduler.schedule(jobInfo);
        }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br></div></div></div></div>
<h2 id="方法4-workmanager" tabindex="-1">方法4：WorkManager <a class="header-anchor" href="#方法4-workmanager" aria-label="Permalink to “方法4：WorkManager”">&#8203;</a></h2>
<p>使用 <code>workManager</code> 配置定时任务，通过 <code>PeriodicWorkRequest</code> 设置任务的重复间隔。
适用于需要可靠、定时执行的任务，且不依赖高精度定时的场景，如<strong>定期同步数据</strong>、<strong>上传文件</strong>。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>高可靠性：即使设备重启也能恢复任务。</li>
<li>简单易用：api调用方便</li>
<li>支持约束条件，如网络状态、电池电量等。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>精度比较低，最小间隔15分钟（适用于非实时任务）。</li>
<li>配置和依赖比较复杂。</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-193" id="tab-194" checked><label data-title="txt" for="tab-194">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::hdvz9abajd9e7aj6raubm::--><code>PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.MINUTES).build();
WorkManager.getInstance(context).enqueue(workRequest);

// 自定义Worker
public class MyWorker extends Worker {
    public MyWorker(Context context, WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        Log.d(&quot;WorkManager&quot;, &quot;后台任务执行&quot;);
        return Result.success();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br></div></div></div></div>
<h2 id="方法5-timer-timetask" tabindex="-1">方法5：Timer + TimeTask <a class="header-anchor" href="#方法5-timer-timetask" aria-label="Permalink to “方法5：Timer + TimeTask”">&#8203;</a></h2>
<p>使用 <code>Timer</code> 和 <code>TimerTask</code> 实现定时循环
适用于简单的短期非定时任务，例如执行少量后台操作。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>使用简单，适合快速开发</li>
<li>可灵活指定延迟和周期</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>与线程绑定，线程结束后任务会被中断。</li>
<li>不推荐在安卓项目中使用，有潜在的问题。</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-232" id="tab-233" checked><label data-title="txt" for="tab-233">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::dg58rpmabiigjk3fcce2o9::--><code>Timer timer = new Timer();
TimerTask timerTask = new TimerTask(){
  @override
  public void run(){
    Log.d(&quot;timerTask&quot;, &quot;任务运行中&quot;)
  }
}

// 每3秒执行一次
timer.scheduleAtFixedRate(timerTask, 0, 3000);

// 停止任务
timer.cancel();</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div></div></div>
<h2 id="方法6-handlerthread-handler" tabindex="-1">方法6：HandlerThread + Handler <a class="header-anchor" href="#方法6-handlerthread-handler" aria-label="Permalink to “方法6：HandlerThread + Handler”">&#8203;</a></h2>
<p>在 HandlerThread 中运行 Handler 定时调度任务。
适用于需要在后台线程中定时处理轻量级任务的场景。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>线程独立，任务与UI无关</li>
<li>不会阻塞主线程</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>需要手动管理线程生命周期</li>
<li>不够现代化</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-271" id="tab-272" checked><label data-title="txt" for="tab-272">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::e4k7n1fwbbvo29j6tzmzw::--><code>import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

public class HandlerThreadExample {

    private static final String TAG = &quot;HandlerThreadExample&quot;;
    private HandlerThread handlerThread;
    private Handler backgroundHandler;

    public void start() {
        // 创建一个 HandlerThread
        handlerThread = new HandlerThread(&quot;BackgroundThread&quot;);
        handlerThread.start();

        // 创建一个与 HandlerThread 相关联的 Handler
        Looper looper = handlerThread.getLooper();
        backgroundHandler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                // 处理消息
                switch (msg.what) {
                    case 1:
                        Log.i(TAG, &quot;定时任务执行&quot;);
                        break;
                    default:
                        super.handleMessage(msg);
                }
                // 设置定时任务，发送下一个任务
                backgroundHandler.sendEmptyMessageDelayed(1, 1000); // 1秒后再次执行
            }
        };

        // 启动第一次定时任务
        backgroundHandler.sendEmptyMessage(1);
    }

    public void stop() {
        // 退出线程
        if (handlerThread != null) {
            handlerThread.quit();
        }
    }

    public static void main(String[] args) {
        HandlerThreadExample example = new HandlerThreadExample();
        example.start();

        // 为了演示，主线程等待一段时间，观察输出
        try {
            Thread.sleep(10000); // 主线程等待10秒，观察输出
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 停止定时任务
        example.stop();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br></div></div></div></div>
<h2 id="方法7-countdowntimer" tabindex="-1">方法7：CountDownTimer <a class="header-anchor" href="#方法7-countdowntimer" aria-label="Permalink to “方法7：CountDownTimer”">&#8203;</a></h2>
<p>使用 <code>CountDownTimer</code> 提供的回调方法，执行倒计时逻辑。
适用于倒计时场景，如倒计时按钮、限时操作提示。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>易于实现倒计时功能。</li>
<li>提供精确的时间控制。</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>无法直接实现重复循环任务</li>
<li>不适合复杂任务调度</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-310" id="tab-311" checked><label data-title="txt" for="tab-311">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::546hekqsr4nklkc9eru0r::--><code>CountDownTimer countDownTimer = new CountDownTimer(10000, 1000) {
    @Override
    public void onTick(long millisUntilFinished) {
        Log.d(&quot;CountDownTimer&quot;, &quot;剩余时间: &quot; + millisUntilFinished / 1000);
    }

    @Override
    public void onFinish() {
        Log.d(&quot;CountDownTimer&quot;, &quot;倒计时结束&quot;);
    }
};

countDownTimer.start();</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div></div></div>
<h2 id="方法8-scheduledexecutorservice" tabindex="-1">方法8：ScheduledExecutorService <a class="header-anchor" href="#方法8-scheduledexecutorservice" aria-label="Permalink to “方法8：ScheduledExecutorService”">&#8203;</a></h2>
<p>使用Java的 <code>ScheduledExecutorService</code> 提供的 <code>scheduleAtFixedRate</code> 或 <code>schedulewithFixedDelay</code> 方法。
适用于后台线程中高精度的定时任务，例如<strong>网络轮询</strong>或<strong>性能监控</strong>。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>精确的时间调度</li>
<li>线程池管理，支持并发</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>不会考虑Android系统的资源管理</li>
<li>手动处理线程生命周期和任务取消</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-349" id="tab-350" checked><label data-title="txt" for="tab-350">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::8tv1htho5bb3b5tj0n8p5::--><code>ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
Runnable task = new Runnable() {
    @Override
    public void run() {
        Log.d(&quot;ScheduledExecutor&quot;, &quot;后台定时任务&quot;);
    }
};

// 每隔2秒执行一次任务
executor.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS);

// 关闭任务
// executor.shutdown();</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div></div></div>
<h2 id="方法9-rxjava-interval-timer" tabindex="-1">方法9：RxJava + Interval/Timer <a class="header-anchor" href="#方法9-rxjava-interval-timer" aria-label="Permalink to “方法9：RxJava + Interval/Timer”">&#8203;</a></h2>
<p>使用 <code>RxJava</code> 的 <code>Observable.Interval</code> 或 <code>Observable.timer</code> 方法创建定时流。
适用于响应式编程场景，<strong>实时数据刷新</strong>、<strong>事件轮询</strong>。</p>
<p><strong>优点</strong>：</p>
<ul>
<li>代码简洁，与响应式编程风格一致。</li>
<li>可以轻松组合和转换数据流</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>容易导致内存泄漏，需要注意订阅管理。</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-383" id="tab-384" checked><label data-title="txt" for="tab-384">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::ifiit753zud9nv66rux6e::--><code>import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.schedulers.Schedulers;

public class RxJavaIntervalExample {

    public static void main(String[] args) {
        Observable.interval(1, TimeUnit.SECONDS) // 每秒发射一次
                .observeOn(Schedulers.io()) // 在后台线程执行
                .subscribe(time -&gt; {
                    System.out.println(&quot;定时任务执行: &quot; + time + &quot;秒&quot;);
                });

        // 为了演示，主线程等待一段时间
        try {
            Thread.sleep(10000); // 让主线程等待10秒，观察输出
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div></div></div>
<h2 id="方法10-kotlin-coroutine-delay" tabindex="-1">方法10：kotlin Coroutine + delay <a class="header-anchor" href="#方法10-kotlin-coroutine-delay" aria-label="Permalink to “方法10：kotlin Coroutine + delay”">&#8203;</a></h2>
<p>使用 Kotlin 的 delay 实现定时器
适用于安卓开发中轻量级异步定时任务。</p>
<p>优点：</p>
<ul>
<li>非阻塞，性能强</li>
<li>协程整合良好</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-407" id="tab-408" checked><label data-title="txt" for="tab-408">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::yxmwr2dlxyfytdwz2y25j::--><code>import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        while (isActive) {
            println(&quot;Coroutine working...&quot;)
            delay(1000) // 每秒执行一次
        }
    }

    delay(5000) // 5秒后取消任务
    job.cancelAndJoin()
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div></div></div>
<h3 id="总结对比" tabindex="-1">总结对比 <a class="header-anchor" href="#总结对比" aria-label="Permalink to “总结对比”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>方法</th>
<th>精度</th>
<th>适用场景</th>
<th>系统节能</th>
<th>易用性</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>Handler + <code>postDelayed</code></td>
<td>中</td>
<td>短时间任务</td>
<td>低</td>
<td>高</td>
<td>简单任务最佳选择</td>
</tr>
<tr>
<td>AlarmManager</td>
<td>高</td>
<td>唤醒设备的低频任务</td>
<td>高</td>
<td>中</td>
<td>系统资源占用低</td>
</tr>
<tr>
<td>JobScheduler</td>
<td>低</td>
<td>后台任务，考虑约束条件</td>
<td>高</td>
<td>中</td>
<td>适合后台管理任务</td>
</tr>
<tr>
<td>WorkManager</td>
<td>低</td>
<td>后台可靠任务，系统推荐</td>
<td>高</td>
<td>高</td>
<td>最现代化的选择</td>
</tr>
<tr>
<td>Timer + TimerTask</td>
<td>中</td>
<td>轻量循环任务</td>
<td>低</td>
<td>中</td>
<td>不推荐，过时</td>
</tr>
<tr>
<td>HandlerThread + Handler</td>
<td>中</td>
<td>线程独立的定时任务</td>
<td>中</td>
<td>中</td>
<td>传统方式之一</td>
</tr>
<tr>
<td>CountDownTimer</td>
<td>高</td>
<td>倒计时功能</td>
<td>中</td>
<td>高</td>
<td>简单高效倒计时</td>
</tr>
<tr>
<td>ScheduledExecutorService</td>
<td>高</td>
<td>精确后台任务调度</td>
<td>低</td>
<td>中</td>
<td>高性能需求适用</td>
</tr>
<tr>
<td>RxJava + Interval/Timer</td>
<td>高</td>
<td>响应式编程场景</td>
<td>中</td>
<td>中</td>
<td>现代化需求适用</td>
</tr>
<tr>
<td>Kotlin Coroutines + <code>delay</code></td>
<td>高</td>
<td>异步轻量任务</td>
<td>中</td>
<td>高</td>
<td>现代开发优先</td>
</tr>
</tbody>
</table>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[coroutineContext]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/coroutineContext.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/coroutineContext.html</guid>
            <pubDate>Wed, 11 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[coroutineContext 是一个 CoroutineContext 类型的对象，它是一个不可变的集合，能够存储和访问协程的多个不同上下文元素。每个元素在协程执行时都有特定的作用，允许你灵活控制协程的行为。]]></description>
            <content:encoded><![CDATA[<h1 id="coroutinecontext" tabindex="-1">coroutineContext <a class="header-anchor" href="#coroutinecontext" aria-label="Permalink to “coroutineContext”">&#8203;</a></h1>
<h2 id="_1-什么是coroutinecontext" tabindex="-1">1. 什么是coroutineContext <a class="header-anchor" href="#_1-什么是coroutinecontext" aria-label="Permalink to “1. 什么是coroutineContext”">&#8203;</a></h2>
<p><code>coroutineContext</code> 是一个 <code>CoroutineContext</code> 类型的对象，它是一个不可变的集合，能够存储和访问协程的多个不同上下文元素。每个元素在协程执行时都有特定的作用，允许你灵活控制协程的行为。</p>
<p><code>coroutineContext</code> 通过 <code>CoroutineScope</code> 或者协程本身来获取。它是协程的上下文对象，通常用来访问与协程相关的配置项。</p>
<h2 id="_2-coroutinecontext-的组成" tabindex="-1">2. CoroutineContext 的组成 <a class="header-anchor" href="#_2-coroutinecontext-的组成" aria-label="Permalink to “2. CoroutineContext 的组成”">&#8203;</a></h2>
<p><code>CoroutineContext</code> 是一个键值对类型的集合，每一个元素是一个 <code>CoroutineContext.Element</code>。常见的 <code>CoroutineContext</code>元素包括：</p>
<ul>
<li><code>Job</code>: 协程的生命管理周期</li>
<li><code>Dispatcher</code>: 决定协程在哪个线程或者线程池中执行。</li>
<li><code>CoroutineExeceptionHandler</code>: 处理协程异常。</li>
<li><code>ThreadLocal</code>: 用于协程内的共享数据（可以跨协程使用）</li>
<li><code>ContinuationInterceptor</code>：用于拦截协程的继续执行过程。</li>
</ul>
<ol>
<li>Job
Job 是协程的生命周期管理者，用来控制协程的启动、取消、等待等行为。当协程启动时，它会自动关联一个 Job，可以通过 <code>coroutineContext[Job]</code> 来访问。</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-52" id="tab-53" checked><label data-title="txt" for="tab-53">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::r5rcfbj3cxfimyv1uojba::--><code>val job = launch {
    delay(1000)
    println(&quot;协程完成！&quot;)
}

val jobFromContext = coroutineContext[Job] // 获取 Job
println(jobFromContext?.isActive) // true (表示协程还在运行)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div></div></div>
<ol start="2">
<li>Dispatcher
<code>Dispatcher</code> 决定了协程执行的线程或线程池。常用的 <code>Dispatcher</code> 有：</li>
</ol>
<ul>
<li><code>Dispatchers.Default</code>：默认调度器，适用于 CPU 密集型任务。</li>
<li><code>Dispatchers.IO</code>：适用于 I/O 密集型任务（如网络请求、文件操作）。</li>
<li><code>Dispatchers.Main</code>：适用于 UI 线程（用于 Android 开发中与 UI 相关的协程）。</li>
<li><code>Dispatchers.Unconfined</code>：不限制协程执行在哪个线程，适用于短小的、没有阻塞操作的协程。</li>
</ul>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-84" id="tab-85" checked><label data-title="txt" for="tab-85">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::zduqz6ik0zdewlekn3eump::--><code>val job = launch(Dispatchers.IO) {
    // 在 IO 线程池中执行
    delay(1000)
    println(&quot;IO 协程完成！&quot;)
}

val dispatcher = coroutineContext[CoroutineDispatcher] // 获取 Dispatcher
println(dispatcher) // Dispatchers.IO</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div></div></div>
<ol start="3">
<li>CoroutineExceptionHandler
<code>CoroutineExceptionHandler</code> 处理协程中的异常。它允许捕获和处理协程内部抛出的异常。可以在 <code>coroutineContext</code> 中设置一个异常处理器。</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-94" id="tab-95" checked><label data-title="txt" for="tab-95">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::wxdakycgw98u0nej7n27c::--><code>val exceptionHandler = CoroutineExceptionHandler { _, exception -&gt;
    println(&quot;Caught exception: $exception&quot;)
}

val job = launch(exceptionHandler) {
    throw Exception(&quot;Something went wrong!&quot;)
}

// 输出: Caught exception: java.lang.Exception: Something went wrong!
</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div></div></div>
<ol start="4">
<li>ContinuationInterceptor
<code>ContinuationInterceptor</code> 允许拦截协程的继续执行过程，通常用于实现协程的自定义调度策略。它用于管理协程的挂起和恢复行为。</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-104" id="tab-105" checked><label data-title="txt" for="tab-105">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::mks5k2o9o8p14hqpjkqnwkj::--><code>val interceptor = object : ContinuationInterceptor {
    override val key: CoroutineContext.Key&lt;*&gt;
        get() = ContinuationInterceptor
    override fun interceptContinuation(continuation: Continuation&lt;*&gt;): Continuation&lt;*&gt; {
        // 可以自定义如何处理协程的继续执行
        return continuation
    }
}

val job = launch(interceptor) {
    delay(1000)
    println(&quot;协程完成！&quot;)
}
</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div></div></div>
<ol start="5">
<li>ThreadLocal
<code>ThreadLocal</code> 允许在协程内保存特定的共享数据，可以在不同的协程间传递数据，或者确保每个协程有独立的副本。</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-114" id="tab-115" checked><label data-title="txt" for="tab-115">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::i7l9bp7zjpjshxkhse9kad::--><code>val threadLocal = ThreadLocal&lt;String&gt;()

val job = launch {
    threadLocal.set(&quot;Shared data in coroutine&quot;)
    println(threadLocal.get()) // 输出: Shared data in coroutine
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div></div></div>
<h2 id="_3-获取coroutinecontext的方法" tabindex="-1">3. 获取coroutineContext的方法 <a class="header-anchor" href="#_3-获取coroutinecontext的方法" aria-label="Permalink to “3. 获取coroutineContext的方法”">&#8203;</a></h2>
<ol>
<li>通过 CoroutineScope 获取
每个协程作用域都有一个 <code>coroutineContext</code> 属性，可以直接访问：</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-127" id="tab-128" checked><label data-title="txt" for="tab-128">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::s2wfz0ojpg0jqlv4ut0zis::--><code>val job = launch {
    println(&quot;Job is running in: ${coroutineContext[CoroutineDispatcher]}&quot;)
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div></div></div>
<ol start="2">
<li>通过 withContext 修改 CoroutineContext
使用 <code>withContext</code> 函数在协程内部切换到一个新的上下文，它会返回一个新的 <code>CoroutineContext</code>。在 withContext 中修改上下文时，原有的上下文元素会被保留，但你可以覆盖或添加新的元素。</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-137" id="tab-138" checked><label data-title="txt" for="tab-138">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::a425gjaqao0eas5nxf8ey6::--><code>val job = launch {
    withContext(Dispatchers.IO) {
        // 在 IO 线程中执行
        println(&quot;Switching to IO thread!&quot;)
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div></div></div>
<h2 id="_4-coroutinecontext和组合" tabindex="-1">4. coroutineContext和组合 <a class="header-anchor" href="#_4-coroutinecontext和组合" aria-label="Permalink to “4. coroutineContext和组合”">&#8203;</a></h2>
<p>可以通过<code>+</code>操作符组合多个 <code>CoroutineContext</code> 元素。当想要为协程指定多个不同上下文配置时，可以将他们组合在一起。</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-146" id="tab-147" checked><label data-title="txt" for="tab-147">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::gbi5t6bjoi6wfhaqtqb::--><code>val job = launch(Dispatcher.IO + CoroutineExceptionHandler {_, exception -&gt; 
    println(&quot;Caught Exception: $exception&quot;)
}) {
    throw Exception(&quot;Test exception&quot;)
}

// Dispatcher.IO 和 CoroutineExceptionHandler 组合成一个新的 CoroutineContext</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[协程Job]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/%E5%8D%8F%E7%A8%8BJob.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/协程Job.html</guid>
            <pubDate>Wed, 11 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Job 是协程的生命周期管理者。每一个协程都有一个关联的 Job, Job 可以用来控制协程的 执行、取消、等待等操作。]]></description>
            <content:encoded><![CDATA[<h1 id="协程job" tabindex="-1">协程Job <a class="header-anchor" href="#协程job" aria-label="Permalink to “协程Job”">&#8203;</a></h1>
<h2 id="_1-什么是job" tabindex="-1">1. 什么是Job <a class="header-anchor" href="#_1-什么是job" aria-label="Permalink to “1. 什么是Job”">&#8203;</a></h2>
<p><code>Job</code> 是协程的生命周期管理者。每一个协程都有一个关联的 <code>Job</code>, <code>Job</code> 可以用来控制协程的 <strong>执行</strong>、<strong>取消</strong>、<strong>等待</strong>等操作。</p>
<p>当启动一个协程时，<code>Job</code> 会自动关联到协程。例如，<code>launch</code> 或者 <code>async</code> 创建的协程都会返回一个 <code>Job</code>，可以使用这个 <code>Job</code> 来管理协程。</p>
<h2 id="_2-协程与job的关系" tabindex="-1">2. 协程与Job的关系 <a class="header-anchor" href="#_2-协程与job的关系" aria-label="Permalink to “2. 协程与Job的关系”">&#8203;</a></h2>
<p><code>Job</code> 可以通过以下方法控制协程：</p>
<ol>
<li><code>cancel()</code>: 取消协程。</li>
<li><code>join()</code>: 等待协程完成。</li>
<li><code>isActive()</code>: 检查协程是否处于活动状态</li>
<li><code>isCompleted()</code>: 检查协程是否已经完成</li>
<li><code>isCancelled()</code>: 检查协程是否已被取消</li>
</ol>
<h2 id="_3-job-和协程的生命周期" tabindex="-1">3. Job 和协程的生命周期 <a class="header-anchor" href="#_3-job-和协程的生命周期" aria-label="Permalink to “3. Job 和协程的生命周期”">&#8203;</a></h2>
<ul>
<li><strong>启动阶段</strong>：协程被创建并开始执行</li>
<li><strong>运行阶段</strong>：协程正在执行代码</li>
<li><strong>取消阶段</strong>：协程被取消后会中断执行，通常会抛出 <code>CancellationException</code>。</li>
<li><strong>完成阶段</strong>：协程的工作完成，退出执行。</li>
</ul>
<h2 id="_4-父子协程与job" tabindex="-1">4. 父子协程与Job <a class="header-anchor" href="#_4-父子协程与job" aria-label="Permalink to “4. 父子协程与Job”">&#8203;</a></h2>
<p>协程可以嵌套，也就是说一个协程可以启动另一个协程，这时父协程和子协程会形成父子关系。父协程的 <code>Job</code> 和子协程的 <code>Job</code> 相关联，子协程的 <code>Job</code> 会作为父协程的子任务。</p>
<ol>
<li>父协程取消子协程
如果父协程被取消，那么所有子协程也会被取消，除非子协程在启动时指定了不同的 Job。</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-83" id="tab-84" checked><label data-title="txt" for="tab-84">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::mzn9d6c1yzmviccfittbit::--><code>val parentJob = launch {
    val childJob = launch {
        delay(1000)
        println(&quot;子协程完成！&quot;)
    }
    delay(500)
    println(&quot;父协程取消！&quot;)
    parentJob.cancel() // 取消父协程
}
// 此时，parentJob 被取消后，childJob 会被自动取消。</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div></div></div>
<ol start="2">
<li>子协程独立于父协程
通过显式传递 Job，使子协程独立于父协程：</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-93" id="tab-94" checked><label data-title="txt" for="tab-94">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::d5ql3n7dkb5cq9puhaox4j::--><code>val parentJob = launch {
    val childJob = launch(parentJob) {
        delay(1000)
        println(&quot;子协程完成！&quot;)
    }
}
// 即使父协程被取消，子协程也不会被自动取消。</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div></div></div>
<h2 id="_5-代码示例" tabindex="-1">5. 代码示例 <a class="header-anchor" href="#_5-代码示例" aria-label="Permalink to “5. 代码示例”">&#8203;</a></h2>
<ol>
<li><strong>cancel</strong>方法</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-106" id="tab-107" checked><label data-title="txt" for="tab-107">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::kydw0rlv3v00isq46yqo5jt::--><code>val job = launch {
    delay(1000)
    println(&quot;这条不会被执行&quot;)
}

job.cancel()</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div></div></div>
<ol start="2">
<li><strong>join</strong>方法</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-116" id="tab-117" checked><label data-title="txt" for="tab-117">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::vlhllavgbjhnx956eeu78n::--><code>val job = launch {
    delay(1000)
    println(&quot;协程完成&quot;)
}

job.join()
println(&quot;主线程继续执行&quot;)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div></div></div>
<ol start="3">
<li><strong>isActive</strong>方法</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-126" id="tab-127" checked><label data-title="txt" for="tab-127">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::wa958iocfcnp8cpk9p1v8::--><code>val job = launch {
    delay(1000)
    println(&quot;协程完成&quot;)
}

println(job.isActive) // true 正在执行

job.join()

println(job.isActive) // false 协程已经完成</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div></div></div>
<ol start="4">
<li><strong>isCompleted</strong> 和 <strong>isCancelled</strong>方法不同</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-136" id="tab-137" checked><label data-title="txt" for="tab-137">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::iw07jopj7tr6n1kfqga9fn::--><code>val job = launch {
    delay(1000)
}

println(job.isCompleted) // false
println(job.isCancelled) // false

job.cancel()

println(job.isCompleted) // true
println(job.isCancelled) // true</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div></div></div>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[二分查找]]></title>
            <link>https://endlessyoung.github.io/Blog_/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/数据结构和算法/二分查找.html</guid>
            <pubDate>Wed, 04 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[二分搜索（Binary Search）是一种在有序数组或列表中查找目标值的高效算法，其时间复杂度是 O(log n)。以下是一些经典的二分搜索题目及其示例：]]></description>
            <content:encoded><![CDATA[<h1 id="二分查找" tabindex="-1">二分查找 <a class="header-anchor" href="#二分查找" aria-label="Permalink to “二分查找”">&#8203;</a></h1>
<p>二分搜索（Binary Search）是一种在有序数组或列表中查找目标值的高效算法，其时间复杂度是 O(log n)。以下是一些经典的二分搜索题目及其示例：</p>
<h3 id="_1-二分查找-查找目标值" tabindex="-1">1. <strong>二分查找：查找目标值</strong> <a class="header-anchor" href="#_1-二分查找-查找目标值" aria-label="Permalink to “1. 二分查找：查找目标值”">&#8203;</a></h3>
<p><strong>题目描述</strong>：
给定一个升序排序的数组 <code>nums</code> 和一个目标值 <code>target</code>，请在数组中查找目标值的位置。如果目标值存在，则返回其索引；如果目标值不存在，则返回 -1。</p>
<p><strong>示例代码（Kotlin）：</strong></p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::ypy536jw3pcfur51s9lmg::--><code>fun binarySearch(nums: IntArray, target: Int): Int {
    var left = 0
    var right = nums.size - 1
    while (left &lt;= right) {
        val mid = left + (right - left) / 2
        if (nums[mid] == target) {
            return mid  // 找到目标值，返回索引
        } else if (nums[mid] &lt; target) {
            left = mid + 1  // 目标值在右半部分
        } else {
            right = mid - 1  // 目标值在左半部分
        }
    }
    return -1  // 目标值不存在
}

fun main() {
    val nums = intArrayOf(1, 3, 5, 7, 9)
    println(binarySearch(nums, 5))  // 输出: 2
    println(binarySearch(nums, 8))  // 输出: -1
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br></div></div><h3 id="_2-寻找旋转排序数组中的最小值" tabindex="-1">2. <strong>寻找旋转排序数组中的最小值</strong> <a class="header-anchor" href="#_2-寻找旋转排序数组中的最小值" aria-label="Permalink to “2. 寻找旋转排序数组中的最小值”">&#8203;</a></h3>
<p><strong>题目描述</strong>：
给定一个已经升序排序的数组，但是该数组经过旋转，求旋转数组中的最小值。</p>
<p><strong>示例代码（Kotlin）：</strong></p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::kjx90j6uihmbr6fw52hgb9::--><code>fun findMin(nums: IntArray): Int {
    var left = 0
    var right = nums.size - 1
    while (left &lt; right) {
        val mid = left + (right - left) / 2
        if (nums[mid] &gt; nums[right]) {
            left = mid + 1  // 最小值在右半部分
        } else {
            right = mid  // 最小值在左半部分
        }
    }
    return nums[left]  // left 就是最小值的索引
}

fun main() {
    val nums = intArrayOf(4, 5, 6, 7, 0, 1, 2)
    println(findMin(nums))  // 输出: 0
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h3 id="_3-寻找一个元素在有序数组中的位置-变种" tabindex="-1">3. <strong>寻找一个元素在有序数组中的位置（变种）</strong> <a class="header-anchor" href="#_3-寻找一个元素在有序数组中的位置-变种" aria-label="Permalink to “3. 寻找一个元素在有序数组中的位置（变种）”">&#8203;</a></h3>
<p><strong>题目描述</strong>：
在一个升序排序的数组 <code>nums</code> 中，寻找目标值 <code>target</code> 的插入位置（即使目标值不存在，也应该返回目标值应当插入的位置）。</p>
<p><strong>示例代码（Kotlin）：</strong></p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::tgz2gvd1epeyt367lotie::--><code>fun searchInsert(nums: IntArray, target: Int): Int {
    var left = 0
    var right = nums.size
    while (left &lt; right) {
        val mid = left + (right - left) / 2
        if (nums[mid] &lt; target) {
            left = mid + 1  // 目标值在右半部分
        } else {
            right = mid  // 目标值在左半部分
        }
    }
    return left  // left 是目标值的插入位置
}

fun main() {
    val nums = intArrayOf(1, 3, 5, 6)
    println(searchInsert(nums, 5))  // 输出: 2
    println(searchInsert(nums, 2))  // 输出: 1
    println(searchInsert(nums, 7))  // 输出: 4
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div><h3 id="_4-查找旋转数组中的最大值" tabindex="-1">4. <strong>查找旋转数组中的最大值</strong> <a class="header-anchor" href="#_4-查找旋转数组中的最大值" aria-label="Permalink to “4. 查找旋转数组中的最大值”">&#8203;</a></h3>
<p><strong>题目描述</strong>：
给定一个旋转的升序数组，找到该数组中的最大值。</p>
<p><strong>示例代码（Kotlin）：</strong></p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::7c4fzlvigom19ccqwaocij::--><code>fun findMax(nums: IntArray): Int {
    var left = 0
    var right = nums.size - 1
    while (left &lt; right) {
        val mid = left + (right - left) / 2
        if (nums[mid] &gt; nums[right]) {
            left = mid + 1  // 最大值在左半部分
        } else {
            right = mid  // 最大值在右半部分
        }
    }
    return nums[left - 1]  // left 是最小值的索引，最大值在其左侧
}

fun main() {
    val nums = intArrayOf(4, 5, 6, 7, 0, 1, 2)
    println(findMax(nums))  // 输出: 7
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div><h3 id="_5-平方根的二分查找" tabindex="-1">5. <strong>平方根的二分查找</strong> <a class="header-anchor" href="#_5-平方根的二分查找" aria-label="Permalink to “5. 平方根的二分查找”">&#8203;</a></h3>
<p><strong>题目描述</strong>：
给定一个非负整数 <code>x</code>，返回 <code>x</code> 的平方根（结果只保留整数部分，即向下取整）。</p>
<p><strong>示例代码（Kotlin）：</strong></p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::wtbaxddx4qltzebmldbt8c::--><code>fun mySqrt(x: Int): Int {
    var left = 0
    var right = x
    while (left &lt;= right) {
        val mid = left + (right - left) / 2
        if (mid * mid == x) {
            return mid  // 找到精确平方根
        } else if (mid * mid &lt; x) {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return right  // 返回最大的整数平方根
}

fun main() {
    println(mySqrt(8))  // 输出: 2
    println(mySqrt(16))  // 输出: 4
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br></div></div><h3 id="_6-寻找一个数的第一个和最后一个位置" tabindex="-1">6. <strong>寻找一个数的第一个和最后一个位置</strong> <a class="header-anchor" href="#_6-寻找一个数的第一个和最后一个位置" aria-label="Permalink to “6. 寻找一个数的第一个和最后一个位置”">&#8203;</a></h3>
<p><strong>题目描述</strong>：
给定一个升序排序的数组 <code>nums</code> 和一个目标值 <code>target</code>，找出目标值在数组中的第一个和最后一个位置。如果目标值不存在，返回 [-1, -1]。</p>
<p><strong>示例代码（Kotlin）：</strong></p>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::1b7suy1pdv1rwj263r9okp::--><code>fun searchRange(nums: IntArray, target: Int): IntArray {
    fun findFirst(): Int {
        var left = 0
        var right = nums.size
        while (left &lt; right) {
            val mid = left + (right - left) / 2
            if (nums[mid] &lt; target) {
                left = mid + 1
            } else {
                right = mid
            }
        }
        return if (left &lt; nums.size &amp;&amp; nums[left] == target) left else -1
    }

    fun findLast(): Int {
        var left = 0
        var right = nums.size
        while (left &lt; right) {
            val mid = left + (right - left) / 2
            if (nums[mid] &lt;= target) {
                left = mid + 1
            } else {
                right = mid
            }
        }
        return if (left &gt; 0 &amp;&amp; nums[left - 1] == target) left - 1 else -1
    }

    val first = findFirst()
    return if (first == -1) {
        intArrayOf(-1, -1)
    } else {
        intArrayOf(first, findLast())
    }
}

fun main() {
    val nums = intArrayOf(5, 7, 7, 8, 8, 10)
    println(searchRange(nums, 8).joinToString())  // 输出: 3, 4
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br></div></div>]]></content:encoded>
            <author>Endless Young</author>
            <category>数据结构和算法</category>
        </item>
        <item>
            <title><![CDATA[Notification从发送到显示的流程简析]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Notification%E4%BB%8E%E5%8F%91%E9%80%81%E5%88%B0%E6%98%BE%E7%A4%BA%E7%9A%84%E6%B5%81%E7%A8%8B%E7%AE%80%E6%9E%90.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Notification从发送到显示的流程简析.html</guid>
            <pubDate>Sun, 01 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Notificatio主要由三部分组成：发送/更新/取消通知的应用app、系统通知服务、NotificationManagerService 和 通知监听器 NotificationListenerService。应用可以调用 NotificationMana?]]></description>
            <content:encoded><![CDATA[<h1 id="notification从发送到显示的流程简析" tabindex="-1">Notification从发送到显示的流程简析 <a class="header-anchor" href="#notification从发送到显示的流程简析" aria-label="Permalink to “Notification从发送到显示的流程简析”">&#8203;</a></h1>
<h2 id="_1-notification架构" tabindex="-1">1. Notification架构 <a class="header-anchor" href="#_1-notification架构" aria-label="Permalink to “1. Notification架构”">&#8203;</a></h2>
<p>Notificatio主要由三部分组成：发送/更新/取消通知的应用app、系统通知服务、<code>NotificationManagerService</code> 和 通知监听器 <code>NotificationListenerService</code>。应用可以调用 <code>NotificationManager.notify(int, notification)</code> 发送通知到系统框架层，框架层, 框架层中的 NoticationManagerSevice 对通知进行处理，判断是否拦截通知、播放声音、振动、调用通知灯等，再将通知发送给各个通知监听器 NotificationnService。SystemUI作为最重要的通知监听器，在接收到通知后会对通知UI进行更新显示。</p>
<p><img src="./image-85.png" alt="alt text" loading="lazy" decoding="async"></p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[Vsync机制]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Vsync%E6%9C%BA%E5%88%B6.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Vsync机制.html</guid>
            <pubDate>Sun, 01 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[VSYNC（Vertical Synchronization，垂直同步）是一种图形显示技术，用于在显示屏刷新内容时避免屏幕撕裂（Screen Tearing）现象。它通过将显示设备的刷新率与 GPU（图形处理器）的帧生成速度同步来实现。]]></description>
            <content:encoded><![CDATA[<h2 id="vsync机制" tabindex="-1">Vsync机制 <a class="header-anchor" href="#vsync机制" aria-label="Permalink to “Vsync机制”">&#8203;</a></h2>
<h3 id="vsync-垂直同步机制-简介" tabindex="-1"><strong>VSYNC（垂直同步机制）简介</strong> <a class="header-anchor" href="#vsync-垂直同步机制-简介" aria-label="Permalink to “VSYNC（垂直同步机制）简介”">&#8203;</a></h3>
<p>VSYNC（Vertical Synchronization，垂直同步）是一种图形显示技术，用于在显示屏刷新内容时避免屏幕撕裂（Screen Tearing）现象。它通过将显示设备的刷新率与 GPU（图形处理器）的帧生成速度同步来实现。</p>
<hr>
<h3 id="工作原理" tabindex="-1"><strong>工作原理</strong> <a class="header-anchor" href="#工作原理" aria-label="Permalink to “工作原理”">&#8203;</a></h3>
<ul>
<li>
<p><strong>显示屏刷新频率</strong><br>
显示屏以固定的频率刷新图像（例如 60Hz、120Hz），每次刷新从屏幕的顶部开始逐行到达底部，完成一次帧显示。</p>
</li>
<li>
<p><strong>GPU 渲染帧的速度</strong><br>
GPU 渲染一帧图像需要时间，通常是不固定的。如果 GPU 渲染的速度与显示屏的刷新频率不同步，就可能导致一帧未完成渲染的图像被部分或全部显示到屏幕上。</p>
</li>
<li>
<p><strong>屏幕撕裂（Screen Tearing）</strong><br>
如果 GPU 在显示屏刷新期间更新帧缓冲区，显示屏可能会显示来自两帧或多帧的内容，这会导致屏幕撕裂现象：</p>
<div class="language- line-numbers-mode"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre><!--::markdown-it-async::hr4ikrn4ydwd3klfb5swqh::--><code>上半部分显示上一帧，下半部分显示下一帧。</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br></div></div></li>
<li>
<p><strong>VSYNC 解决屏幕撕裂</strong><br>
开启 VSYNC 后，GPU 会在显示屏完成一帧的刷新（即垂直消隐期，Vertical Blanking Interval，VBI）后才将新的图像数据写入帧缓冲区。这样，GPU 和显示设备可以保持同步，从而避免屏幕撕裂。</p>
</li>
</ul>
<hr>
<h3 id="vsync-的优缺点" tabindex="-1"><strong>VSYNC 的优缺点</strong> <a class="header-anchor" href="#vsync-的优缺点" aria-label="Permalink to “VSYNC 的优缺点”">&#8203;</a></h3>
<h4 id="优点" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ol>
<li><strong>防止屏幕撕裂</strong>：GPU 和显示屏同步工作，输出的画面更加流畅。</li>
<li><strong>提升视觉效果</strong>：尤其在电影和高帧率游戏中，视觉体验更佳。</li>
</ol>
<h4 id="缺点" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ol>
<li><strong>输入延迟（Input Lag）</strong>：VSYNC 会强制 GPU 等待屏幕刷新，导致用户输入的响应时间增加。</li>
<li><strong>帧率降低</strong>：如果 GPU 无法在显示屏刷新间隔内完成渲染，会导致帧率大幅下降（例如从 60FPS 降到 30FPS）。</li>
<li><strong>性能浪费</strong>：如果 GPU 的渲染速度远快于显示屏刷新率，性能得不到充分利用。</li>
</ol>
<hr>
<h3 id="在-android-中的-vsync" tabindex="-1"><strong>在 Android 中的 VSYNC</strong> <a class="header-anchor" href="#在-android-中的-vsync" aria-label="Permalink to “在 Android 中的 VSYNC”">&#8203;</a></h3>
<p>在 Android 系统中，VSYNC 是图形渲染的关键机制。它用于协调屏幕刷新（Display Refresh）和渲染操作（UI 绘制）的节奏，以提供流畅的用户体验。</p>
<h4 id="android-的渲染流程" tabindex="-1"><strong>Android 的渲染流程</strong> <a class="header-anchor" href="#android-的渲染流程" aria-label="Permalink to “Android 的渲染流程”">&#8203;</a></h4>
<ol>
<li>
<p><strong>VSYNC 信号触发</strong><br>
Android 系统通过硬件抽象层（Hardware Composer HAL，HWC）接收 VSYNC 信号。</p>
</li>
<li>
<p><strong>触发渲染任务</strong><br>
当 VSYNC 信号到来时，系统会调度应用程序的绘制操作（如 <code>Choreographer</code>），触发界面重绘。</p>
</li>
<li>
<p><strong>同步绘制与显示</strong><br>
应用程序完成绘制后，图像通过 SurfaceFlinger 进行合成，并在下一次 VSYNC 信号时显示到屏幕上。</p>
</li>
</ol>
<h4 id="vsync-信号的处理" tabindex="-1"><strong>VSYNC 信号的处理</strong> <a class="header-anchor" href="#vsync-信号的处理" aria-label="Permalink to “VSYNC 信号的处理”">&#8203;</a></h4>
<ul>
<li>
<p><strong>Choreographer</strong><br>
Android 提供了 <code>Choreographer</code> 类来处理 VSYNC 信号，并以每 16.67 毫秒（60Hz）为周期触发 UI 更新。</p>
</li>
<li>
<p><strong>Frame Timing</strong><br>
UI 的更新（包括动画）被安排在每个 VSYNC 信号触发的时间点，以确保流畅的显示效果。</p>
</li>
</ul>
<hr>
<h3 id="改进的同步技术" tabindex="-1"><strong>改进的同步技术</strong> <a class="header-anchor" href="#改进的同步技术" aria-label="Permalink to “改进的同步技术”">&#8203;</a></h3>
<ol>
<li>
<p><strong>Adaptive VSYNC</strong><br>
当 GPU 渲染速度不足以跟上显示屏刷新率时，允许 GPU 放弃同步，降低输入延迟和帧率波动。</p>
</li>
<li>
<p><strong>G-Sync 和 FreeSync</strong></p>
<ul>
<li><strong>G-Sync</strong>：由 NVIDIA 提供，通过硬件模块动态调整显示器刷新率，使其与 GPU 渲染帧率同步。</li>
<li><strong>FreeSync</strong>：由 AMD 提供，通过软件实现动态刷新率同步。</li>
</ul>
</li>
<li>
<p><strong>Triple Buffering（三缓冲）</strong><br>
通过增加一个额外的帧缓冲区，减少 VSYNC 导致的输入延迟和帧率下降问题。</p>
</li>
</ol>
<hr>
<h3 id="总结" tabindex="-1"><strong>总结</strong> <a class="header-anchor" href="#总结" aria-label="Permalink to “总结”">&#8203;</a></h3>
<p>VSYNC 是一种重要的同步机制，用于解决屏幕撕裂问题，并在 Android 系统中扮演着关键角色。然而，它可能带来输入延迟和性能问题，因此需要根据具体场景选择适合的同步技术或改进方法，如 Adaptive VSYNC 或 G-Sync/FreeSync，以达到性能和视觉效果的平衡。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[WorkManager]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/WorkManager.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/WorkManager.html</guid>
            <pubDate>Sun, 01 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[WorkManager 是一种用于启动永久性工作的Jetpack的API。持久性工作是指能够在应用和系统重启后仍然保持工作调度状态的工作。]]></description>
            <content:encoded><![CDATA[<h1 id="workmanager" tabindex="-1">WorkManager <a class="header-anchor" href="#workmanager" aria-label="Permalink to “WorkManager”">&#8203;</a></h1>
<h2 id="_1-简介" tabindex="-1">1. 简介 <a class="header-anchor" href="#_1-简介" aria-label="Permalink to “1. 简介”">&#8203;</a></h2>
<p><code>WorkManager</code> 是一种用于启动永久性工作的Jetpack的API。持久性工作是指能够在应用和系统重启后仍然保持工作调度状态的工作。</p>
<p><code>WorkManager</code> 分为三种类型：</p>
<ol>
<li><code>立即执行</code>：必须立即开始并且很快就能完成的任务，可以加急。</li>
<li><code>长时间运行的任务</code>：任务的运行时间可能较长。</li>
<li><code>可延期的任务</code>：延期开始并且可以定期运行的预定任务。</li>
</ol>
<p><img src="./image-87.png" alt="alt text" loading="lazy" decoding="async"></p>
<table tabindex="0">
<thead>
<tr>
<th>类型</th>
<th>周期</th>
<th>查找方式</th>
</tr>
</thead>
<tbody>
<tr>
<td>即时</td>
<td>一次</td>
<td><code>OneTimeWorkRequest</code> 和 <code>Worker</code>。<br>如需加急工作，请对 <code>OneTimeWorkRequest</code> 调用 <code>setExpedited()</code>。</td>
</tr>
<tr>
<td>长跑</td>
<td>一次性或定期</td>
<td>任意 <code>WorkRequest</code> 或 <code>Worker</code>。<br>在 worker 中调用 <code>setForeground()</code> 来处理通知。</td>
</tr>
<tr>
<td>可延期</td>
<td>一次性或定期</td>
<td><code>PeriodicWorkRequest</code> 和 <code>Worker</code>。</td>
</tr>
</tbody>
</table>
<h2 id="work的状态" tabindex="-1">work的状态 <a class="header-anchor" href="#work的状态" aria-label="Permalink to “work的状态”">&#8203;</a></h2>
<h3 id="_1-一次性工作的状态" tabindex="-1">1. 一次性工作的状态 <a class="header-anchor" href="#_1-一次性工作的状态" aria-label="Permalink to “1. 一次性工作的状态”">&#8203;</a></h3>
<p>对于 <code>One-time</code> 工作请求，工作的初始状态为 <code>ENQUEUED</code>。
在 <code>ENQUEUED</code> 状态下，work 会在满足 <code>Constraints</code> 和 <code>initialDelay</code> 计时要求后立即运行。接下来，work 会切换到 <code>RUNNING</code> 状态，
然后可能会根据工作的结果切换为<code>SUCCEED</code> 、<code>FAILED</code> 状态；或者，结果是 <code>retry</code>，它可能会回到 <code>ENQUEUED</code> 状态。
在这个过程中，随时都能取消工作，取消后work进入到 <code>CANCELLED</code> 状态。</p>
<p><img src="./image-88.png" alt="alt text" loading="lazy" decoding="async"></p>
<p><code>SUCCEED</code> 、<code>FAILED</code> 、<code>CANCELLED</code> 都是 <code>work</code> 的终止状态，此时 <code>WorkInfo.State.isFinished()</code> 都会返回 <code>true</code></p>
<h3 id="_2-定期工作的状态" tabindex="-1">2. 定期工作的状态 <a class="header-anchor" href="#_2-定期工作的状态" aria-label="Permalink to “2. 定期工作的状态”">&#8203;</a></h3>
<p>成功和失败状态仅使用于 一次性工作 和 链式工作。定期工作只有一个终止状态 CANCELLED。这是因为定期工作永远不会结束。</p>
<p><img src="./image-89.png" alt="alt text" loading="lazy" decoding="async"></p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[帧率和卡顿监控方案]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%B8%A7%E7%8E%87%E5%92%8C%E5%8D%A1%E9%A1%BF%E7%9B%91%E6%8E%A7%E6%96%B9%E6%A1%88.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/帧率和卡顿监控方案.html</guid>
            <pubDate>Sun, 01 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[监控应用的帧率 FPS 是评估应用指标和发现卡顿问题的重要指标。常用的监控应用的FPS的方法有：]]></description>
            <content:encoded><![CDATA[<h1 id="帧率和卡顿监控方案" tabindex="-1">帧率和卡顿监控方案 <a class="header-anchor" href="#帧率和卡顿监控方案" aria-label="Permalink to “帧率和卡顿监控方案”">&#8203;</a></h1>
<p>监控应用的帧率 <code>FPS</code> 是评估应用指标和发现卡顿问题的重要指标。常用的监控应用的FPS的方法有：</p>
<h2 id="_1-使用-choreographer-api" tabindex="-1">1. 使用 <code>Choreographer API</code> <a class="header-anchor" href="#_1-使用-choreographer-api" aria-label="Permalink to “1. 使用 Choreographer API”">&#8203;</a></h2>
<p>使用 <code>Choreographer API</code> 判断掉帧的原理：</p>
<ol>
<li><strong>计算时间间隔</strong>：
<ol>
<li>一般每帧之间的时间间隔应该是设备的刷新时间，一般为16ms（60FPS）或 8.3ms（120FPS）或 6.9ms（144FPS）等。</li>
<li>监控 <code>doFrame</code> 方法种两次回调的时间差，如果明显高于目标帧时间间隔，说明出现掉帧或者卡顿。</li>
</ol>
</li>
<li><strong>设置阈值</strong>：
<ol>
<li>设置一个阈值：例如超过20ms就算掉帧，连续多次掉帧就是卡顿。</li>
</ol>
</li>
</ol>
<h3 id="实现示例" tabindex="-1">实现示例： <a class="header-anchor" href="#实现示例" aria-label="Permalink to “实现示例：”">&#8203;</a></h3>
<ol>
<li><code>监控</code> 类</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-53" id="tab-54" checked><label data-title="txt" for="tab-54">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::pcvoka281znab5e2zfzeyo::--><code>import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.view.Choreographer;

public class FPSMonitor implements Choreographer.FrameCallback {
    private static final String TAG = &quot;FrameMonitor&quot;;
    private static final long FRAME_INTERVAL_NANOS = 16666666; // 16.6ms in nanoseconds (roughly 60 FPS)
    private long lastFrameTimeNanos = 0;
    private long droppedFramesCount = 0;  // 统计掉帧次数

    public void start() {
        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.JELLY_BEAN) {
            Choreographer.getInstance().postFrameCallback(this);
        } else {
            Log.e(TAG, &quot;Device does not support Choreographer API&quot;);
        }
    }

    @Override
    public void doFrame(long frameTimeNanos) {
        if (lastFrameTimeNanos == 0) {
            lastFrameTimeNanos = frameTimeNanos;
        } else {
            long elapsedNanos = frameTimeNanos - lastFrameTimeNanos;

            if (elapsedNanos &gt; FRAME_INTERVAL_NANOS) {
                // 计算掉帧的数量
                long droppedFrames = elapsedNanos / FRAME_INTERVAL_NANOS;
                droppedFramesCount += droppedFrames;

                // 只有在掉帧的次数足够多时才输出日志
                if (droppedFramesCount &gt; 5) { // 比如超过 5 帧掉帧才输出日志
                    Log.w(TAG, &quot;Dropped frames: &quot; + droppedFramesCount);

                    // 打印堆栈信息，只在严重掉帧时才输出
                    Thread currentThread = Thread.currentThread();
                    StackTraceElement[] stackTraceElements = currentThread.getStackTrace();
                    StringBuilder stackTrace = new StringBuilder(&quot;Stack trace:\n&quot;);
                    for (StackTraceElement element : stackTraceElements) {
                        stackTrace.append(&quot;\tat &quot;).append(element.toString()).append(&quot;\n&quot;);
                    }
                    Log.i(TAG, stackTrace.toString());

                    // 重置掉帧计数器
                    droppedFramesCount = 0;
                }
            }

            lastFrameTimeNanos = frameTimeNanos;
        }

        // 继续调度下一帧的回调
        Choreographer.getInstance().postFrameCallback(this);
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br></div></div></div></div>
<ol start="2">
<li>在 <code>Activity</code> 中使用</li>
</ol>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-63" id="tab-64" checked><label data-title="txt" for="tab-64">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::ob29rnzoq5eylvt6w9a57::--><code>public class MainActivity extends AppCompatActivity {
    private FPSMonitor fpsMonitor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fpsMonitor = new FPSMonitor();
        fpsMonitor.start();
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div></div></div>
<h2 id="_2-使用-framemetrics-api-进行掉帧检测" tabindex="-1">2. 使用 FrameMetrics API 进行掉帧检测 <a class="header-anchor" href="#_2-使用-framemetrics-api-进行掉帧检测" aria-label="Permalink to “2. 使用 FrameMetrics API 进行掉帧检测”">&#8203;</a></h2>
<p><code>FrameMetrics API</code> 来监控应用的帧率的掉帧情况。<code>FrameMetrics API</code> 提供了一种强大的方法来衡量和分析视图的绘制性能。使用 window 对象注册一个 <code>OnFrameMetricsAvailableListener</code> , 这个监听器会在每一帧结束时提供 FrameMetrics 数据。</p>
<p><strong>实现示例：</strong></p>
<p>FrameMetrics API 进行掉帧检测并打印相关信息：</p>
<div class="vp-code-group"><div class="tabs"><input type="radio" name="group-78" id="tab-79" checked><label data-title="txt" for="tab-79">txt</label></div><div class="blocks">
<div class="language-java active line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::i3rqysp0tyiz2ledvodc99::--><code>import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.FrameMetrics;
import android.view.Window;
import android.view.Window.OnFrameMetricsAvailableListener;

public class FrameMetricsActivity extends Activity {
    private static final String TAG = &quot;FrameMetricsActivity&quot;;
    private static final int DROPPED_FRAME_LOG_THRESHOLD = 5;  // 当掉帧超过5帧时输出日志
    private static final long LOG_FRAME_DURATION_THRESHOLD = 20000000;  // 超过 20ms 的帧输出警告

    private long droppedFramesCount = 0;  // 用来统计掉帧数量

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.N) {
            getWindow().addOnFrameMetricsAvailableListener(new OnFrameMetricsAvailableListener() {
                @Override
                public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
                    long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
                    long intendedDuration = frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP) 
                            - frameMetrics.getMetric(FrameMetrics.FRAME_PRESENT_TIMESTAMP);
                    
                    // 增加掉帧的计数
                    droppedFramesCount += dropCountSinceLastInvocation;
                    
                    // 如果掉帧超过阈值才输出日志
                    if (droppedFramesCount &gt;= DROPPED_FRAME_LOG_THRESHOLD) {
                        Log.w(TAG, &quot;Dropped frames: &quot; + droppedFramesCount);

                        // 如果帧的持续时间超过了预期的时间，输出警告
                        if (totalDuration &gt; intendedDuration + LOG_FRAME_DURATION_THRESHOLD) {
                            Log.w(TAG, &quot;Frame duration exceeded: &quot; + totalDuration);
                        }

                        // 打印堆栈信息，限制频率
                        logStackTrace();

                        // 重置掉帧计数
                        droppedFramesCount = 0;
                    }
                }
            }, null);
        } else {
            Log.e(TAG, &quot;FrameMetrics API not available on this device&quot;);
        }
    }

    // 打印当前线程的堆栈信息
    private void logStackTrace() {
        Thread currentThread = Thread.currentThread();
        StackTraceElement[] stackTraceElements = currentThread.getStackTrace();
        StringBuilder stackTrace = new StringBuilder(&quot;Stack trace:\n&quot;);
        for (StackTraceElement element : stackTraceElements) {
            stackTrace.append(&quot;\tat &quot;).append(element.toString()).append(&quot;\n&quot;);
        }
        Log.i(TAG, stackTrace.toString());
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br></div></div></div></div>
<blockquote>
<p><code>TOTAL_DURATION</code>：表示完整帧的时间，包括应用、合成和发布时间。
<code>INTENDED_VSYNC_TIMESTAMP</code> 和 <code>FRAME_PRESENT_TIMESTAMP</code> 之间的差值被认为是理想的帧时间。
如果 <code>TOTAL_DURATION</code> 大于理想持续时间，则可能存在掉帧。</p>
</blockquote>
<h2 id="_3-traceview-和-systrace" tabindex="-1">3. TraceView 和 systrace <a class="header-anchor" href="#_3-traceview-和-systrace" aria-label="Permalink to “3. TraceView 和 systrace”">&#8203;</a></h2>
<h2 id="_4-surfaceview-的-fps-监控" tabindex="-1">4. SurfaceView 的 FPS 监控 <a class="header-anchor" href="#_4-surfaceview-的-fps-监控" aria-label="Permalink to “4. SurfaceView 的 FPS 监控”">&#8203;</a></h2>
<p>如果使用 SurfaceView，可以在绘制回调中统计帧率。</p>
<h2 id="_5-opengl-的-fps-监控" tabindex="-1">5. OpenGL 的 FPS 监控 <a class="header-anchor" href="#_5-opengl-的-fps-监控" aria-label="Permalink to “5. OpenGL 的 FPS 监控”">&#8203;</a></h2>
<p>在使用 OpenGL 渲染时，在每次 SwapBuffers 时统计帧率。</p>
<h2 id="_6-tinydancer、leakcanary" tabindex="-1">6. TinyDancer、LeakCanary <a class="header-anchor" href="#_6-tinydancer、leakcanary" aria-label="Permalink to “6. TinyDancer、LeakCanary”">&#8203;</a></h2>
<p>性能监控工具也提供了 FPS 监控功能，适合快速应用和调试。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[安卓常见架构]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%AE%89%E5%8D%93%E5%B8%B8%E8%A7%81%E6%9E%B6%E6%9E%84.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/安卓常见架构.html</guid>
            <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[在安卓开发中，常见的架构主要有以下几种，每种架构都有其特点、优缺点以及适用场景。以下是它们的详细介绍：]]></description>
            <content:encoded><![CDATA[<h1 id="安卓常见架构" tabindex="-1">安卓常见架构 <a class="header-anchor" href="#安卓常见架构" aria-label="Permalink to “安卓常见架构”">&#8203;</a></h1>
<p>在安卓开发中，常见的架构主要有以下几种，每种架构都有其特点、优缺点以及适用场景。以下是它们的详细介绍：</p>
<h2 id="_1-mvc-model-view-controller" tabindex="-1">1. MVC（Model-View-Controller） <a class="header-anchor" href="#_1-mvc-model-view-controller" aria-label="Permalink to “1. MVC（Model-View-Controller）”">&#8203;</a></h2>
<h4 id="结构" tabindex="-1"><strong>结构</strong> <a class="header-anchor" href="#结构" aria-label="Permalink to “结构”">&#8203;</a></h4>
<ul>
<li><strong>Model</strong>：负责管理数据和业务逻辑。</li>
<li><strong>View</strong>：负责显示数据和用户交互。</li>
<li><strong>Controller</strong>：负责协调 View 和 Model，处理用户输入。</li>
</ul>
<h4 id="优点" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ul>
<li>简单直观，适合小型项目。</li>
<li>分层清晰，易于理解和实现。</li>
</ul>
<h4 id="缺点" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ul>
<li>在 Android 中，<code>Activity/Fragment</code> 通常既承担了 <code>Controller</code> 的职责，也与 <code>View</code> 紧密耦合，导致类的职责不清晰。</li>
<li>随着项目增大，Controller 变得臃肿且难以维护。</li>
</ul>
<h4 id="适用场景" tabindex="-1"><strong>适用场景</strong> <a class="header-anchor" href="#适用场景" aria-label="Permalink to “适用场景”">&#8203;</a></h4>
<ul>
<li>适合小型、简单的应用。</li>
</ul>
<hr>
<h2 id="_2-mvp-model-view-presenter" tabindex="-1">2. MVP（Model-View-Presenter） <a class="header-anchor" href="#_2-mvp-model-view-presenter" aria-label="Permalink to “2. MVP（Model-View-Presenter）”">&#8203;</a></h2>
<h4 id="结构-1" tabindex="-1"><strong>结构</strong> <a class="header-anchor" href="#结构-1" aria-label="Permalink to “结构”">&#8203;</a></h4>
<ul>
<li><strong>Model</strong>：与 MVC 类似，负责数据操作和业务逻辑。</li>
<li><strong>View</strong>：负责界面展示，不直接处理业务逻辑。</li>
<li><strong>Presenter</strong>：作为 View 和 Model 的中间人，处理业务逻辑，并将数据传递给 View。</li>
</ul>
<h4 id="优点-1" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点-1" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ul>
<li>解耦了 View 和 Model，易于测试。</li>
<li>Presenter 更易维护，职责分明。</li>
<li>View 层可复用，替换成本较低。</li>
</ul>
<h4 id="缺点-1" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点-1" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ul>
<li>Presenter 可能变得复杂，尤其是当应用逻辑较为复杂时。</li>
<li>增加了代码量，尤其是需要频繁更新 View 时。</li>
</ul>
<h4 id="适用场景-1" tabindex="-1"><strong>适用场景</strong> <a class="header-anchor" href="#适用场景-1" aria-label="Permalink to “适用场景”">&#8203;</a></h4>
<ul>
<li>中小型应用，或者对 UI 和逻辑解耦要求较高的场景。</li>
</ul>
<hr>
<h2 id="_3-mvvm-model-view-viewmodel" tabindex="-1">3. MVVM（Model-View-ViewModel） <a class="header-anchor" href="#_3-mvvm-model-view-viewmodel" aria-label="Permalink to “3. MVVM（Model-View-ViewModel）”">&#8203;</a></h2>
<h4 id="结构-2" tabindex="-1"><strong>结构</strong> <a class="header-anchor" href="#结构-2" aria-label="Permalink to “结构”">&#8203;</a></h4>
<ul>
<li><strong>Model</strong>：负责数据和业务逻辑，与 MVP 类似。</li>
<li><strong>View</strong>：负责界面展示，但通过绑定机制与 ViewModel 交互。</li>
<li><strong>ViewModel</strong>：负责存储 View 的状态和处理业务逻辑，通过数据绑定更新 View。</li>
</ul>
<h4 id="优点-2" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点-2" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ul>
<li>数据绑定（DataBinding 或 ViewBinding）实现了 UI 的自动更新，减少了样板代码。</li>
<li>View 和 Model 的耦合度进一步降低。</li>
<li>易于测试，逻辑和 UI 独立。</li>
</ul>
<h4 id="缺点-2" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点-2" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ul>
<li>学习成本较高，对数据绑定技术有一定依赖。</li>
<li>数据绑定可能导致调试困难。</li>
<li>数据绑定代码可能在大型项目中变得复杂。</li>
</ul>
<h4 id="适用场景-2" tabindex="-1"><strong>适用场景</strong> <a class="header-anchor" href="#适用场景-2" aria-label="Permalink to “适用场景”">&#8203;</a></h4>
<ul>
<li>大型项目，复杂的 UI 界面，适合需要频繁更新界面的场景。</li>
</ul>
<hr>
<h2 id="_4-clean-architecture" tabindex="-1">4. Clean Architecture <a class="header-anchor" href="#_4-clean-architecture" aria-label="Permalink to “4. Clean Architecture”">&#8203;</a></h2>
<h4 id="结构-3" tabindex="-1"><strong>结构</strong> <a class="header-anchor" href="#结构-3" aria-label="Permalink to “结构”">&#8203;</a></h4>
<ul>
<li><strong>Entities</strong>：核心业务逻辑，独立于外部框架。</li>
<li><strong>Use Cases</strong>：应用的业务逻辑，连接 Entities 和外部接口。</li>
<li><strong>Interface Adapters</strong>：数据转换器，负责将数据从 Use Cases 转换为外部框架可以使用的形式。</li>
<li><strong>Frameworks &amp; Drivers</strong>：外部依赖，如数据库、网络等。</li>
</ul>
<h4 id="优点-3" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点-3" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ul>
<li>高度模块化，清晰的分层设计。</li>
<li>易于测试，业务逻辑完全独立于框架。</li>
<li>可扩展性和可维护性强，适合长期项目。</li>
</ul>
<h4 id="缺点-3" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点-3" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ul>
<li>实现较复杂，开发周期较长。</li>
<li>学习成本较高，代码量显著增加。</li>
</ul>
<h4 id="适用场景-3" tabindex="-1"><strong>适用场景</strong> <a class="header-anchor" href="#适用场景-3" aria-label="Permalink to “适用场景”">&#8203;</a></h4>
<ul>
<li>大型、复杂、需要长期维护的项目。</li>
</ul>
<hr>
<h2 id="_5-flux-和-redux" tabindex="-1">5. Flux 和 Redux <a class="header-anchor" href="#_5-flux-和-redux" aria-label="Permalink to “5. Flux 和 Redux”">&#8203;</a></h2>
<h4 id="结构-4" tabindex="-1"><strong>结构</strong> <a class="header-anchor" href="#结构-4" aria-label="Permalink to “结构”">&#8203;</a></h4>
<ul>
<li><strong>Action</strong>：描述用户行为或事件。</li>
<li><strong>Dispatcher</strong>：分发 Action 给 Store。</li>
<li><strong>Store</strong>：存储应用状态，并处理逻辑。</li>
<li><strong>View</strong>：从 Store 获取状态并展示。</li>
</ul>
<h4 id="优点-4" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点-4" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ul>
<li>状态管理清晰，单向数据流避免了数据混乱。</li>
<li>易于调试，历史记录易于追踪。</li>
</ul>
<h4 id="缺点-4" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点-4" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ul>
<li>不适合复杂的实时交互逻辑。</li>
<li>代码冗长，尤其是小型项目中。</li>
</ul>
<h4 id="适用场景-4" tabindex="-1"><strong>适用场景</strong> <a class="header-anchor" href="#适用场景-4" aria-label="Permalink to “适用场景”">&#8203;</a></h4>
<ul>
<li>状态管理复杂的项目，尤其是多模块需要共享状态的场景。</li>
</ul>
<hr>
<h2 id="_6-jetpack-compose-架构" tabindex="-1">6. Jetpack Compose 架构 <a class="header-anchor" href="#_6-jetpack-compose-架构" aria-label="Permalink to “6. Jetpack Compose 架构”">&#8203;</a></h2>
<h4 id="结构-5" tabindex="-1"><strong>结构</strong> <a class="header-anchor" href="#结构-5" aria-label="Permalink to “结构”">&#8203;</a></h4>
<ul>
<li><strong>State</strong>：描述 UI 的状态。</li>
<li><strong>Composable</strong>：声明式 UI，通过状态渲染界面。</li>
<li><strong>ViewModel</strong>：负责逻辑和状态管理。</li>
</ul>
<h4 id="优点-5" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点-5" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ul>
<li>声明式编程，代码简洁，降低 UI 复杂性。</li>
<li>与 Kotlin 深度结合，充分利用语言特性。</li>
<li>易于测试，逻辑与 UI 解耦。</li>
</ul>
<h4 id="缺点-5" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点-5" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ul>
<li>需要掌握新的开发方式。</li>
<li>兼容性问题（尤其是传统 View 和 Compose 混用时）。</li>
</ul>
<h4 id="适用场景-5" tabindex="-1"><strong>适用场景</strong> <a class="header-anchor" href="#适用场景-5" aria-label="Permalink to “适用场景”">&#8203;</a></h4>
<ul>
<li>新项目，尤其是需要快速开发复杂 UI 的场景。</li>
</ul>
<hr>
<h2 id="_7-mvi-model-view-intent" tabindex="-1">7. MVI（Model-View-Intent） <a class="header-anchor" href="#_7-mvi-model-view-intent" aria-label="Permalink to “7. MVI（Model-View-Intent）”">&#8203;</a></h2>
<h4 id="结构-6" tabindex="-1"><strong>结构</strong> <a class="header-anchor" href="#结构-6" aria-label="Permalink to “结构”">&#8203;</a></h4>
<ul>
<li>Model：表示应用的状态，通常是一个不可变的对象。</li>
<li>View：展示 UI，订阅状态更新，并将用户交互转换为 Intent。</li>
<li>Intent：用户行为或事件的抽象，描述用户的意图。</li>
<li>Reducer：根据当前状态和 Intent，生成新的状态。</li>
<li>ViewModel（或类似组件）：连接 View 和 Model，处理 Intent，并将状态流传递给 View。</li>
</ul>
<h4 id="核心思想" tabindex="-1"><strong>核心思想</strong> <a class="header-anchor" href="#核心思想" aria-label="Permalink to “核心思想”">&#8203;</a></h4>
<ul>
<li>单一数据流：状态在应用中单向流动，所有状态变化由 Reducer 处理。</li>
<li>不可变状态：所有状态对象都是不可变的，状态更新是通过复制原始对象生成新的状态。</li>
<li>纯函数：<code>Reducer</code> 仅根据输入生成输出，不引入副作用。</li>
</ul>
<h4 id="状态流动过程" tabindex="-1"><strong>状态流动过程</strong> <a class="header-anchor" href="#状态流动过程" aria-label="Permalink to “状态流动过程”">&#8203;</a></h4>
<ul>
<li>用户交互触发 Intent：用户在 View 中的交互被转化为 Intent。</li>
<li>Intent 传递给 ViewModel：ViewModel 接收 Intent，进行相应的处理。</li>
<li>Reducer 更新状态：ViewModel 使用 Reducer 根据当前状态和 Intent 生成新的状态。</li>
<li>状态传递给 View：新的状态流向 View，更新 UI。</li>
</ul>
<h4 id="优点-6" tabindex="-1"><strong>优点</strong> <a class="header-anchor" href="#优点-6" aria-label="Permalink to “优点”">&#8203;</a></h4>
<ul>
<li>状态管理清晰：
单一数据源，状态集中管理，便于追踪和调试。
明确了状态与 UI 的对应关系。</li>
<li>强大的响应式支持：
MVI 自然适应响应式编程（如 Flow 和 LiveData）。
非常适合 Jetpack Compose 的声明式 UI 模式。</li>
<li>易于测试：
纯函数和不可变状态使测试变得简单。
UI 和业务逻辑完全解耦。</li>
</ul>
<h4 id="缺点-6" tabindex="-1"><strong>缺点</strong> <a class="header-anchor" href="#缺点-6" aria-label="Permalink to “缺点”">&#8203;</a></h4>
<ul>
<li>小型项目会变得复杂和抽象，流程繁琐。</li>
</ul>
<hr>
<h4 id="示例代码" tabindex="-1"><strong>示例代码</strong> <a class="header-anchor" href="#示例代码" aria-label="Permalink to “示例代码”">&#8203;</a></h4>
<div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::inmc4bs3lwqx5f20fp1ojb::--><code>// 定义状态
data class CounterState(val count: Int = 0)

// 定义意图（Intent）
sealed class CounterIntent {
    object Increment : CounterIntent()
    object Decrement : CounterIntent()
}

// Reducer：根据当前状态和意图生成新的状态
fun reducer(state: CounterState, intent: CounterIntent): CounterState {
    return when (intent) {
        is CounterIntent.Increment -&gt; state.copy(count = state.count + 1)
        is CounterIntent.Decrement -&gt; state.copy(count = state.count - 1)
    }
}

// ViewModel
class CounterViewModel : ViewModel() {
    private val _state = MutableStateFlow(CounterState())
    val state: StateFlow&lt;CounterState&gt; = _state

    fun handleIntent(intent: CounterIntent) {
        _state.value = reducer(_state.value, intent)
    }
}

// View 层
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    val state by viewModel.state.collectAsState()

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = &quot;Count: ${state.count}&quot;)

        Row {
            Button(onClick = { viewModel.handleIntent(CounterIntent.Decrement) }) {
                Text(&quot;Decrement&quot;)
            }
            Button(onClick = { viewModel.handleIntent(CounterIntent.Increment) }) {
                Text(&quot;Increment&quot;)
            }
        }
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br></div></div><h3 id="对比总结" tabindex="-1"><strong>对比总结</strong> <a class="header-anchor" href="#对比总结" aria-label="Permalink to “对比总结”">&#8203;</a></h3>
<table tabindex="0">
<thead>
<tr>
<th>架构</th>
<th>优点</th>
<th>缺点</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>MVC</td>
<td>简单直观，适合小项目</td>
<td>难以扩展，容易导致臃肿</td>
<td>小型应用</td>
</tr>
<tr>
<td>MVP</td>
<td>解耦 View 和 Model，易测试</td>
<td>Presenter 复杂，代码量大</td>
<td>中小型项目</td>
</tr>
<tr>
<td>MVVM</td>
<td>自动更新 UI，解耦度高</td>
<td>学习曲线高，调试困难</td>
<td>复杂 UI 的项目</td>
</tr>
<tr>
<td>Clean</td>
<td>高模块化，易扩展和维护</td>
<td>实现复杂，开发周期长</td>
<td>大型、长期维护的项目</td>
</tr>
<tr>
<td>Flux/Redux</td>
<td>状态管理清晰，单向数据流</td>
<td>代码冗长，不适合实时交互</td>
<td>状态管理复杂的项目</td>
</tr>
<tr>
<td>Jetpack</td>
<td>声明式 UI，简化 UI 逻辑</td>
<td>需要学习新范式，兼容性问题</td>
<td>新项目或复杂 UI 的快速开发</td>
</tr>
</tbody>
</table>
<p>每种架构都有其适用场景，选择时应根据项目规模、开发团队熟悉程度以及维护需求进行综合考虑。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[协程生命周期]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/%E5%8D%8F%E7%A8%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/协程生命周期.html</guid>
            <pubDate>Wed, 06 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[协程中的执行任务由job代表。其能控制协程的生命周期。job可以通过状态来反映协程的执行进程。]]></description>
            <content:encoded><![CDATA[<h1 id="协程生命周期" tabindex="-1">协程生命周期 <a class="header-anchor" href="#协程生命周期" aria-label="Permalink to “协程生命周期”">&#8203;</a></h1>
<p>协程中的执行任务由job代表。其能控制协程的生命周期。job可以通过状态来反映协程的执行进程。</p>
<h2 id="_1-job的生命周期状态" tabindex="-1">1. job的生命周期状态 <a class="header-anchor" href="#_1-job的生命周期状态" aria-label="Permalink to “1. job的生命周期状态”">&#8203;</a></h2>
<ol>
<li><code>new</code>状态</li>
<li><code>active</code>状态</li>
<li><code>completing</code>状态</li>
<li><code>completed</code>状态</li>
<li><code>canceling</code>状态</li>
<li><code>cancelled</code>状态</li>
</ol>
<p>协程转换示意图</p>
<div class="language-txt line-numbers-mode" data-lang="txt"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre><!--::markdown-it-async::r3sin4h4a6g47381gv2qi::--><code>                                等待子协程
 +-----+ start  +--------+ complete      +-------------+  finish    +-----------+
 | 新建 | -----&gt; | Active  | --------&gt; | Completing      | -------&gt; | 已完成     |
 +-----+        +--------+           +-------------+          +-----------+
                   |  取消 / 失败      |
                   |   +----------------+
                   |   |
                   V   V
             +------------+                          完成  +-----------+
             | 取消中    | --------------------------------&gt; | 已取消     |
             +------------+                                   +-----------+</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><div class="language-kotlin line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::if2entr11wax7vxeekgx8::--><code>import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        // 协程体
        delay(1000)
        println(&quot;协程完成&quot;)
    }

    println(&quot;协程是否启动: ${job.isActive}&quot;) // true
    println(&quot;协程是否完成: ${job.isCompleted}&quot;) // false

    job.join() // 等待协程完成

    println(&quot;协程是否完成: ${job.isCompleted}&quot;) // true
    println(&quot;协程是否被取消: ${job.isCancelled}&quot;) // false
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br></div></div>]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[协程的异常处理]]></title>
            <link>https://endlessyoung.github.io/Blog_/Kotlin/%E5%8D%8F%E7%A8%8B%E7%9A%84%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Kotlin/协程的异常处理.html</guid>
            <pubDate>Wed, 06 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[被取消的协程会挂在起点抛出 CancellationException 并且会被协程的机制忽略。 协程构建器有两种形式：自动传播异常（launch：启动不返回值的协程） 或 向用户暴露异常（async 与 produce: 启动协程并期望返回结果）。当构建器用?]]></description>
            <content:encoded><![CDATA[<h1 id="协程的异常处理" tabindex="-1">协程的异常处理 <a class="header-anchor" href="#协程的异常处理" aria-label="Permalink to “协程的异常处理”">&#8203;</a></h1>
<h2 id="_1-协程异常处理框架要点" tabindex="-1">1. 协程异常处理框架要点 <a class="header-anchor" href="#_1-协程异常处理框架要点" aria-label="Permalink to “1. 协程异常处理框架要点”">&#8203;</a></h2>
<ol>
<li>全局异常处理：捕获所有协程中的未处理异常。</li>
<li>局部异常处理：特定协程的异常捕获和处理。</li>
<li>异常重试</li>
<li>异常日志记录</li>
</ol>
<h2 id="_2-协程构建器与异常的传播" tabindex="-1">2. 协程构建器与异常的传播 <a class="header-anchor" href="#_2-协程构建器与异常的传播" aria-label="Permalink to “2. 协程构建器与异常的传播”">&#8203;</a></h2>
<ol>
<li><strong>launch协程构建器</strong>: 用于启动不返回值的协程。当使用<code>launch</code>时，如果协程内部发生了异常，会自动将异常传播到外部，类似于java的<code>Thread.uncaughtExceptionHandler</code>。<code>launch</code> 启动的协程如果抛出未捕获的异常，协程将崩溃，异常将会被传递到应用程序的全局异常处理机制，会爆出 <code>CoroutineExceptionHandler</code>。</li>
<li><strong>async 和 produce 协程构建器</strong>：用于启动期望返回结果的协程。与launch不同，<code>async</code> 和 <code>produce</code>启动的协程不会自动处理异常，而是将异常暴露给调用者。<code>async</code> 启动的协程会返回一个 <code>Deferred</code> 对象，该对象可以通过 <code>await()</code> 方法来获取结果。<code>await()</code> 方法会抛出协程中的异常，因此，调用者需要显示处理这些异常。而 <code>produce</code> 启动的是一个协程生产者（通过通道返回数据）。异常会通过 <code>receive()</code> 方法传递给调用者。</li>
</ol>
<h2 id="_2-协程异常的传播" tabindex="-1">2. 协程异常的传播 <a class="header-anchor" href="#_2-协程异常的传播" aria-label="Permalink to “2. 协程异常的传播”">&#8203;</a></h2>
<p>被取消的协程会挂在起点抛出 CancellationException 并且会被协程的机制忽略。
协程构建器有两种形式：自动传播异常（<code>launch：启动不返回值的协程</code>） 或 向用户暴露异常（async 与 produce: <code>启动协程并期望返回结果</code>）。当构建器用于创建一个根协程时，</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Kotlin</category>
        </item>
        <item>
            <title><![CDATA[Android文件系统]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Android%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Android文件系统.html</guid>
            <pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[好的，让我们更全面地深入 Android 文件系统的各个方面，包括所有主要目录及其用途，以及文件系统的架构和管理。]]></description>
            <content:encoded><![CDATA[<p>好的，让我们更全面地深入 Android 文件系统的各个方面，包括所有主要目录及其用途，以及文件系统的架构和管理。</p>
<h2 id="_1-android-文件系统结构" tabindex="-1">1. Android 文件系统结构 <a class="header-anchor" href="#_1-android-文件系统结构" aria-label="Permalink to “1. Android 文件系统结构”">&#8203;</a></h2>
<p>Android 文件系统主要由以下几个重要目录组成，每个目录都有其特定的功能和数据存储方式：</p>
<ol>
<li>
<p><strong>根目录（/）</strong></p>
<ul>
<li>文件系统的起始点，所有其他目录都在此之下。</li>
</ul>
</li>
<li>
<p><strong>/system</strong></p>
<ul>
<li>存储 Android 操作系统核心文件和系统应用程序。</li>
<li>包含 <code>/system/app</code> 和 <code>/system/priv-app</code> 目录，分别存放系统应用和具有特权的系统应用。</li>
<li>该目录通常是只读的，除非设备经过 root 权限或系统更新。</li>
</ul>
</li>
<li>
<p><strong>/data</strong></p>
<ul>
<li>存储用户应用的数据，是 Android 文件系统中最重要的部分。</li>
<li>每个应用在此目录下都有一个独立的子目录，路径为 <code>/data/data/&lt;package_name&gt;/</code>，存储应用的私有数据（如数据库、文件等）。</li>
<li>包含 <code>/data/app</code> 目录，存放已安装的 APK 文件。</li>
<li>该目录通常只能被应用自身访问，确保数据安全。</li>
</ul>
</li>
<li>
<p><strong>/cache</strong></p>
<ul>
<li>用于存储临时文件和缓存数据，例如应用的缓存内容。</li>
<li>存储的文件在设备需要空间时可能被清除。</li>
</ul>
</li>
<li>
<p><strong>/vendor</strong></p>
<ul>
<li>存储由设备制造商提供的自定义硬件驱动和系统组件。</li>
<li>允许硬件制造商为特定设备添加自定义功能而不影响 Android 操作系统的核心部分。</li>
</ul>
</li>
<li>
<p><strong>/storage</strong></p>
<ul>
<li>包含外部存储的目录，通常映射到设备的 SD 卡或内部存储。</li>
<li>包括 <code>/storage/emulated/0/</code> 代表用户的外部存储，通常是可访问的。</li>
</ul>
</li>
<li>
<p><strong>/proc</strong></p>
<ul>
<li>虚拟文件系统，提供关于系统进程和内核的信息。</li>
<li>主要用于调试和监控，不是应用程序存储数据的地方。</li>
</ul>
</li>
<li>
<p><strong>/sys</strong></p>
<ul>
<li>另一个虚拟文件系统，用于提供关于内核和系统硬件的信息。</li>
<li>包含设备驱动程序的信息，可以通过此目录监控硬件状态。</li>
</ul>
</li>
<li>
<p><strong>/dev</strong></p>
<ul>
<li>包含设备文件，用于与硬件设备进行交互。</li>
<li>主要用于底层硬件访问。</li>
</ul>
</li>
<li>
<p><strong>/mnt</strong></p>
<ul>
<li>用于挂载外部存储设备的点，通常会包含指向实际存储位置的符号链接。</li>
</ul>
</li>
<li>
<p><strong>/lost+found</strong></p>
<ul>
<li>在文件系统崩溃后，存放恢复的文件碎片。</li>
</ul>
</li>
</ol>
<h2 id="_2-其他重要概念" tabindex="-1">2. 其他重要概念 <a class="header-anchor" href="#_2-其他重要概念" aria-label="Permalink to “2. 其他重要概念”">&#8203;</a></h2>
<ul>
<li>
<p><strong>文件权限和安全性</strong></p>
<ul>
<li>Android 应用使用 Linux 权限模型，每个应用作为独立的用户运行。</li>
<li>应用需在 <code>AndroidManifest.xml</code> 中声明权限，某些权限（如外部存储）需要在运行时请求。</li>
</ul>
</li>
<li>
<p><strong>存储选项</strong></p>
<ul>
<li><strong>内部存储</strong>：适合存储私有数据，应用使用 <code>Context.getFilesDir()</code> 获取路径。</li>
<li><strong>外部存储</strong>：适合存储可共享数据，使用 <code>Environment.getExternalStorageDirectory()</code> 获取路径。</li>
<li><strong>缓存存储</strong>：使用 <code>getCacheDir()</code> 和 <code>getExternalCacheDir()</code> 存储缓存数据。</li>
</ul>
</li>
<li>
<p><strong>数据持久性</strong></p>
<ul>
<li><strong>SQLite 数据库</strong>：适用于结构化数据存储，使用 <code>SQLiteOpenHelper</code> 类进行管理。</li>
<li><strong>SharedPreferences</strong>：适用于保存轻量级的键值对数据。</li>
</ul>
</li>
</ul>
<h2 id="_3-文件操作的代码示例" tabindex="-1">3. 文件操作的代码示例 <a class="header-anchor" href="#_3-文件操作的代码示例" aria-label="Permalink to “3. 文件操作的代码示例”">&#8203;</a></h2>
<p>以下是一些常见的文件操作示例，展示如何在 Android 中读取和写入文件：</p>
<h3 id="写入文件到内部存储" tabindex="-1">写入文件到内部存储 <a class="header-anchor" href="#写入文件到内部存储" aria-label="Permalink to “写入文件到内部存储”">&#8203;</a></h3>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::kdhv0pp3claredyihi8vd::--><code>String filename = &quot;example.txt&quot;;
String fileContents = &quot;Hello, Android!&quot;;
try (FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h3 id="从内部存储读取文件" tabindex="-1">从内部存储读取文件 <a class="header-anchor" href="#从内部存储读取文件" aria-label="Permalink to “从内部存储读取文件”">&#8203;</a></h3>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::6dtf12ivevqp6kf86w41ua::--><code>try (FileInputStream fis = openFileInput(filename)) {
    StringBuilder stringBuilder = new StringBuilder();
    int c;
    while ((c = fis.read()) != -1) {
        stringBuilder.append((char) c);
    }
    String fileData = stringBuilder.toString();
    Log.d(&quot;FileData&quot;, fileData);
} catch (IOException e) {
    e.printStackTrace();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><h3 id="使用-external-storage-api-写入文件" tabindex="-1">使用 External Storage API 写入文件 <a class="header-anchor" href="#使用-external-storage-api-写入文件" aria-label="Permalink to “使用 External Storage API 写入文件”">&#8203;</a></h3>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::ehcgnsa1ctqbxijj9thu2q::--><code>String fileName = &quot;external_example.txt&quot;;
File file = new File(Environment.getExternalStorageDirectory(), fileName);
try (FileOutputStream fos = new FileOutputStream(file)) {
    fos.write(&quot;Hello from external storage!&quot;.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div>]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[如何实现应用重启]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E5%BA%94%E7%94%A8%E9%87%8D%E5%90%AF.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/如何实现应用重启.html</guid>
            <pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[在一些手游中经常会有资源更新后自动重启应用的功能，这个是怎么实现的呢？]]></description>
            <content:encoded><![CDATA[<h1 id="如何实现应用重启" tabindex="-1">如何实现应用重启 <a class="header-anchor" href="#如何实现应用重启" aria-label="Permalink to “如何实现应用重启”">&#8203;</a></h1>
<h2 id="_1-前言" tabindex="-1">1. 前言 <a class="header-anchor" href="#_1-前言" aria-label="Permalink to “1. 前言”">&#8203;</a></h2>
<p>在一些手游中经常会有资源更新后自动重启应用的功能，这个是怎么实现的呢？</p>
<h2 id="_2-实现思路" tabindex="-1">2. 实现思路 <a class="header-anchor" href="#_2-实现思路" aria-label="Permalink to “2. 实现思路”">&#8203;</a></h2>
<p>主要思路就是利用Android对<code>多进程</code>的支持，实现双进程互相拉起。</p>
<p>假设在<code>主进程A</code>外还有另外一个<code>进程B</code>。主要的流程如下：</p>
<ol>
<li><strong>在主进程A执行完一些列业务逻辑后，如果想要重启，先拉起进程B</strong></li>
<li><strong>进程B启动后，主进程A kill掉自己</strong></li>
<li><strong>进程B拉起主进程，再kill掉自己。</strong></li>
</ol>
<p>这样，主进程就完成了自动重启。</p>
<h2 id="_3-实践" tabindex="-1">3. 实践 <a class="header-anchor" href="#_3-实践" aria-label="Permalink to “3. 实践”">&#8203;</a></h2>
<div class="language-xml line-numbers-mode" data-lang="xml"><button title="Copy Code" class="copy"></button><span class="lang">XML</span><pre><!--::markdown-it-async::ufh6fof1nvgzon99ydl8f::--><code>&lt;activiy
    android:name=&quot;.killerActivity&quot;
    android:exported=&quot;false&quot;
    android:launchMode=&quot;singleTask&quot;
    android:process=&quot;:killer&quot;/&gt;</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><div class="vp-code-group"><div class="tabs"><input type="radio" name="group-42" id="tab-43" checked><label data-title="txt" for="tab-43">txt</label></div><div class="blocks">
<div class="language-kotlin active line-numbers-mode" data-lang="kotlin"><button title="Copy Code" class="copy"></button><span class="lang">Kotlin</span><pre><!--::markdown-it-async::ua5k7bn68shn3uy5u6hpab::--><code>class KillerActivity : FragmentActivity() {

    companion object {
        private const val EXTRA_MAIN_PID = &quot;extra_main_pid&quot;

        // 当主进程需要重启时，就直接调用此方法启动KillerActivity
        fun launch(activity: FragmentActivity) {
            activity.startActivity(
                Intent(activity, KillerActivity::class.java).apply {
                    putExtra(EXTRA_MAIN_PID, Process.myPid())
                }
            )
            activity.finish() // 主进程的Activity先关闭

            GlobalScope.launch {
                // 稍作延迟后，主进程kill掉自己
                delay(500L)
                killProcess()
            }
        }

        fun killProcess(pid: Int = Process.myPid()) {
            Process.killProcess(pid)
            exitProcess(0)
        }

        fun isMainProcessAlive(context: Context): Boolean = runCatching {
            (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
                .runningAppProcesses.find { it.processName == context.packageName } != null
        }.getOrDefault(false)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            // 此处可以插入一些Loading的UI显示，比如ProgressDialog之类的
            // ...

            // 二次检查，防止主进程没杀掉
            doubleCheckMainProcess()

            // 稍作等待后，再次启动主进程的Activity
            startActivity(Intent(this@KillerActivity, MainActivity::class.java).apply {
                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
            })

            // 进程B的KillerActivity先关闭，再kill掉自己
            finish()
            killProcess()
        }
    }

    private suspend fun doubleCheckMainProcess() {
        delay(1500L)
        if (isMainProcessAlive(this)) {
            val mainPid = intent.getIntExtra(EXTRA_MAIN_PID, 0)
            if (mainPid != 0) {
                killProcess(mainPid)
                delay(1500L)
            }
        }
    }
}
</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br></div></div></div></div>
<p>简述上述代码：</p>
<blockquote>
<ol>
<li>启动 <code>KillerActivity</code> 并关闭主进程 Activity：当需要重启主进程时，launch 方法启动 <code>KillerActivity</code> 并关闭主进程的 Activity。</li>
<li>延迟终止主进程：启动后，通过协程延迟 <code>500</code> 毫秒，确保 <code>KillerActivity</code> 启动成功后再结束主进程。</li>
<li>双重检查主进程状态：在 <code>KillerActivity</code> 中的 <code>onCreate</code> 方法启动后，使用 <code>doubleCheckMainProcess</code> 方法检查主进程是否已关闭，如果没有，则强制结束该进程。</li>
<li>重启主界面：确认主进程已关闭后，重新启动应用的主界面 MainActivity。</li>
</ol>
</blockquote>
<p>最后，只要搞清楚 killProcess 方法哪些时候是kill的哪个进程，就理解了。在任意一个需要重启当前进程（可以不只是主进程）的地方，调用：<strong>KillerActivity.launch(this)</strong></p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[CompletableFuture]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/CompletableFuture.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/CompletableFuture.html</guid>
            <pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[CompletableFuture 是 Java 8 引入的一个类，属于 java.util.concurrent 包，主要用于异步编程和非阻塞操作。它提供了一种更简单和强大的方式来处理异步计算，相较于传统的 Future 类，CompletableFutur?]]></description>
            <content:encoded><![CDATA[<h1 id="completablefuture" tabindex="-1">CompletableFuture <a class="header-anchor" href="#completablefuture" aria-label="Permalink to “CompletableFuture”">&#8203;</a></h1>
<p><code>CompletableFuture</code> 是 Java 8 引入的一个类，属于 <code>java.util.concurrent</code> 包，主要用于异步编程和非阻塞操作。它提供了一种更简单和强大的方式来处理异步计算，相较于传统的 Future 类，<code>CompletableFuture</code> 提供了更丰富的功能和灵活性。</p>
<h2 id="_1-理解-completablefuture-的基本概念" tabindex="-1">1. <strong>理解 <code>CompletableFuture</code> 的基本概念</strong> <a class="header-anchor" href="#_1-理解-completablefuture-的基本概念" aria-label="Permalink to “1. 理解 CompletableFuture 的基本概念”">&#8203;</a></h2>
<p><code>CompletableFuture</code> 是一个用于异步编程的类，它能够以非阻塞的方式处理任务。理解其核心概念：</p>
<ul>
<li><strong>异步执行</strong>：<code>CompletableFuture</code> 允许在后台线程中异步执行计算，主线程不必等待结果。</li>
<li><strong>任务组合</strong>：可以通过链式调用将多个异步任务组合在一起，形成复杂的工作流。</li>
<li><strong>异常处理</strong>：提供内置的机制来处理异步任务中可能出现的异常。</li>
</ul>
<h2 id="_2-核心-api-的详细使用" tabindex="-1">2. <strong>核心 API 的详细使用</strong> <a class="header-anchor" href="#_2-核心-api-的详细使用" aria-label="Permalink to “2. 核心 API 的详细使用”">&#8203;</a></h2>
<h4 id="创建-completablefuture" tabindex="-1">创建 CompletableFuture <a class="header-anchor" href="#创建-completablefuture" aria-label="Permalink to “创建 CompletableFuture”">&#8203;</a></h4>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::11relxjwnb9h6c6idmn51w::--><code>CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; {
    // 模拟长时间计算
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return &quot;Hello, World!&quot;;
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><ul>
<li><strong>supplyAsync</strong>：接受一个返回值的 <code>Supplier</code>，在另一个线程中异步执行，返回 <code>CompletableFuture</code> 对象。</li>
</ul>
<h4 id="处理结果" tabindex="-1">处理结果 <a class="header-anchor" href="#处理结果" aria-label="Permalink to “处理结果”">&#8203;</a></h4>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::wu6xk6om14b3eas6wkgq4::--><code>future.thenApply(result -&gt; {
    // 处理计算结果
    return result.length();
}).thenAccept(length -&gt; {
    // 使用结果
    System.out.println(&quot;Length: &quot; + length);
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><ul>
<li><strong>thenApply</strong>：接收前一个计算的结果并转换为新的结果。</li>
<li><strong>thenAccept</strong>：接收前一个计算的结果但不返回新结果。</li>
</ul>
<h4 id="异常处理" tabindex="-1">异常处理 <a class="header-anchor" href="#异常处理" aria-label="Permalink to “异常处理”">&#8203;</a></h4>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::jlqyuiwclutv5zjuyhc87::--><code>future.exceptionally(ex -&gt; {
    System.err.println(&quot;Error: &quot; + ex.getMessage());
    return null; // 处理异常后返回一个默认值
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><ul>
<li><strong>exceptionally</strong>：处理在计算过程中发生的异常。</li>
</ul>
<h3 id="_3-任务组合示例" tabindex="-1">3. <strong>任务组合示例</strong> <a class="header-anchor" href="#_3-任务组合示例" aria-label="Permalink to “3. 任务组合示例”">&#8203;</a></h3>
<h4 id="同步组合任务" tabindex="-1">同步组合任务 <a class="header-anchor" href="#同步组合任务" aria-label="Permalink to “同步组合任务”">&#8203;</a></h4>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::fd97ctea5v78l729s8djme::--><code>CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; {
    return 100;
});

CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; {
    return 200;
});

future1.thenCombine(future2, (result1, result2) -&gt; {
    return result1 + result2; // 合并结果
}).thenAccept(sum -&gt; {
    System.out.println(&quot;Sum: &quot; + sum);
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><ul>
<li><strong>thenCombine</strong>：将两个异步计算的结果组合在一起。</li>
</ul>
<h3 id="_4-处理多个任务" tabindex="-1">4. <strong>处理多个任务</strong> <a class="header-anchor" href="#_4-处理多个任务" aria-label="Permalink to “4. 处理多个任务”">&#8203;</a></h3>
<h4 id="等待所有任务完成" tabindex="-1">等待所有任务完成 <a class="header-anchor" href="#等待所有任务完成" aria-label="Permalink to “等待所有任务完成”">&#8203;</a></h4>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::gboqv8kaprtsd6ke3ugc7p::--><code>CompletableFuture&lt;Void&gt; allOfFuture = CompletableFuture.allOf(future1, future2);
allOfFuture.thenRun(() -&gt; {
    System.out.println(&quot;All tasks are completed!&quot;);
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><ul>
<li><strong>allOf</strong>：等待所有给定的 <code>CompletableFuture</code> 完成。</li>
</ul>
<h4 id="等待任意一个任务完成" tabindex="-1">等待任意一个任务完成 <a class="header-anchor" href="#等待任意一个任务完成" aria-label="Permalink to “等待任意一个任务完成”">&#8203;</a></h4>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::8b72ndfn3z21mveg6zh5ef::--><code>CompletableFuture&lt;Object&gt; anyOfFuture = CompletableFuture.anyOf(future1, future2);
anyOfFuture.thenAccept(result -&gt; {
    System.out.println(&quot;First completed result: &quot; + result);
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><ul>
<li><strong>anyOf</strong>：等待其中一个 <code>CompletableFuture</code> 完成并返回结果。</li>
</ul>
<h2 id="_5-深入理解和优化" tabindex="-1">5. <strong>深入理解和优化</strong> <a class="header-anchor" href="#_5-深入理解和优化" aria-label="Permalink to “5. 深入理解和优化”">&#8203;</a></h2>
<ul>
<li><strong>线程池管理</strong>：使用自定义的 <code>Executor</code> 来管理线程池，避免使用默认的 ForkJoinPool，以便更好地控制线程数量和性能。</li>
</ul>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::wanz9nzt6zegdii12rixrq::--><code>ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture&lt;Void&gt; future = CompletableFuture.supplyAsync(() -&gt; {
    // 任务逻辑
}, executor);</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><ul>
<li><strong>监控和调试</strong>：使用 <code>whenComplete</code> 或 <code>handle</code> 来监控任务完成的状态，无论是正常完成还是异常完成。</li>
</ul>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::hdn2wezkplcdtkx0i5kjj5::--><code>future.whenComplete((result, throwable) -&gt; {
    if (throwable != null) {
        System.err.println(&quot;Error occurred: &quot; + throwable.getMessage());
    } else {
        System.out.println(&quot;Result: &quot; + result);
    }
});</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><h2 id="_6-实践应用" tabindex="-1">6. <strong>实践应用</strong> <a class="header-anchor" href="#_6-实践应用" aria-label="Permalink to “6. 实践应用”">&#8203;</a></h2>
<ul>
<li><strong>网络请求</strong>：使用 <code>CompletableFuture</code> 发起异步 HTTP 请求，以提高应用的响应性。</li>
<li><strong>数据处理</strong>：在大数据处理场景中，将数据处理任务分配到多个线程中，利用 <code>CompletableFuture</code> 合并和处理结果。</li>
</ul>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[ThreadLocal]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/ThreadLocal.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/ThreadLocal.html</guid>
            <pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[ThreadLocal是java提供的一个用于线程本地存储的类。它为每个线程提供独立的变量副本。确保变量再多线程环境下的线程安全。每个线程访问ThreadLocal时，都会有自己专属的变量副本，互不干扰，避免了并发访问时共享变量的竞争问题。]]></description>
            <content:encoded><![CDATA[<h1 id="threadlocal" tabindex="-1">ThreadLocal <a class="header-anchor" href="#threadlocal" aria-label="Permalink to “ThreadLocal”">&#8203;</a></h1>
<h2 id="_1-threadlocal是什么" tabindex="-1">1. ThreadLocal是什么 <a class="header-anchor" href="#_1-threadlocal是什么" aria-label="Permalink to “1. ThreadLocal是什么”">&#8203;</a></h2>
<p><code>ThreadLocal</code>是java提供的一个用于线程本地存储的类。它为每个线程提供独立的变量副本。确保变量再多线程环境下的线程安全。每个线程访问<code>ThreadLocal</code>时，都会有自己专属的变量副本，互不干扰，避免了并发访问时共享变量的竞争问题。</p>
<h2 id="_2-thread的原理" tabindex="-1">2. Thread的原理 <a class="header-anchor" href="#_2-thread的原理" aria-label="Permalink to “2. Thread的原理”">&#8203;</a></h2>
<p><code>ThreadLocal</code>的核心机制是为每个线程创建的一个独立的副本，并且这个副本是存储在线程自身的内部结构中，而不是ThreadLocal实例中。它主要依赖于Thread类中的ThreadLocalMap来实现这一功能。</p>
<ol>
<li>ThreadLocalMap
每个 <code>Thread</code> 对象内部维护了一个 <code>ThreadLocalMap</code>，用于存储线程的本地变量。<code>ThreadLocalMap</code> 是一个类似hash表的结构，其中ThreadLocal对象作为键，线程的本地变量副本作为值。
每次线程调用<code>ThreadLocal.set()</code>方法时，实际上是将变量存储到该线程的ThreadLocalMap中，ThreadLocal实例作为键。
当线程调用<code>ThreadLocal.get()</code>方法时，会从当前线程的ThreadLocalMap中读取与ThreadLocal对象相关联的值。</li>
</ol>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::78w9rt3sqj90zki7hk2cq0j::--><code>public class ThreadLocalExample {
    // 创建一个 ThreadLocal 变量，用于存储每个线程的本地变量副本
    private static ThreadLocal&lt;Integer&gt; threadLocal = ThreadLocal.withInitial(() -&gt; 0);

    public static void main(String[] args) {
        // 创建第一个线程
        Thread thread1 = new Thread(() -&gt; {
            // 设置线程本地变量
            threadLocal.set(100);
            System.out.println(Thread.currentThread().getName() + &quot; : &quot; + threadLocal.get());
        }, &quot;Thread-1&quot;);

        // 创建第二个线程
        Thread thread2 = new Thread(() -&gt; {
            // 设置线程本地变量
            threadLocal.set(200);
            System.out.println(Thread.currentThread().getName() + &quot; : &quot; + threadLocal.get());
        }, &quot;Thread-2&quot;);

        // 启动两个线程
        thread1.start();
        thread2.start();
    }
}

// 输出
// Thread-1 : 100
// Thread-2 : 200</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br></div></div><p><code>ThreadLocal</code> 为每个线程提供了它自己的变量副本。<code>Thread-1</code> 设置了值 100，<code>Thread-2</code> 设置了值 200。尽管它们都使用了相同的 <code>ThreadLocal</code> 实例，但由于每个线程都有独立的变量副本，因此线程之间的数据互不影响。这就相当于每个线程维护了自己的 <code>ThreadLocalMap</code>，并在其中存储该 <code>ThreadLocal</code> 及其对应的变量值。</p>
<h2 id="_3-使用场景" tabindex="-1">3. 使用场景 <a class="header-anchor" href="#_3-使用场景" aria-label="Permalink to “3. 使用场景”">&#8203;</a></h2>
<p>ThreadLocal的使用场景主要包括以下几种：</p>
<ol>
<li>线程安全的状态管理：ThreadLocal 适用于需要在多线程环境中保存线程持有的状态，而不想使用同步机制。每个线程可以安全地修改自己的 ThreadLocal 变量，而不会影响其他线程</li>
<li>数据库连接：在多线程应用中，使用ThreadLocal存储数据库连接可以避免多个线程共享同一个连接的竞争问题。每个线程都可以从ThreadLocal中获取自己的连接，使用后再关闭</li>
<li>用户会话信息：在web应用中，使用ThreadLocal存储与当前请求相关的用户会话信息。这样可以确保每个请求都能安全地访问和修改自己的会话数据。</li>
<li>性能优化：在一些情况下，使用ThreadLocal可以提高性能。例如，某些对象的创建和销毁代价比较高，可以使用ThreadLocal缓存这些对象，以减少重复创建的开销。</li>
<li>上下文传递：在分布式系统中，可以使用ThreadLocal存储上下文信息（如跟踪ID），便于在整个调试链中传递而不需要通过参数逐层传递。</li>
</ol>
<h2 id="_4-风险" tabindex="-1">4. 风险 <a class="header-anchor" href="#_4-风险" aria-label="Permalink to “4. 风险”">&#8203;</a></h2>
<p>ThreadLocal 存在内存泄漏的风险</p>
<p>是的，<code>ThreadLocal</code> 可能存在内存泄漏的风险，特别是在以下情况下：</p>
<h3 id="_1-长生命周期的线程" tabindex="-1">1. <strong>长生命周期的线程</strong> <a class="header-anchor" href="#_1-长生命周期的线程" aria-label="Permalink to “1. 长生命周期的线程”">&#8203;</a></h3>
<p>当一个 <code>ThreadLocal</code> 对象与长生命周期的线程（如线程池中的线程）关联时，如果没有及时清理 <code>ThreadLocal</code> 变量，这些变量可能会保持对外部对象的引用，从而导致这些对象无法被垃圾回收，造成内存泄漏。</p>
<h3 id="_2-static-变量" tabindex="-1">2. <strong>Static 变量</strong> <a class="header-anchor" href="#_2-static-变量" aria-label="Permalink to “2. Static 变量”">&#8203;</a></h3>
<p>如果 <code>ThreadLocal</code> 被声明为 <code>static</code>，它将对整个应用程序的生命周期存在，且会在同一线程中保留其值。此时，如果 <code>ThreadLocal</code> 变量持有的对象的生命周期超过了线程的生命周期，也会导致内存泄漏。</p>
<h3 id="_3-忘记清理" tabindex="-1">3. <strong>忘记清理</strong> <a class="header-anchor" href="#_3-忘记清理" aria-label="Permalink to “3. 忘记清理”">&#8203;</a></h3>
<p>在使用 <code>ThreadLocal</code> 后，应该在不再需要时调用 <code>remove()</code> 方法来清除其值，以避免引用保留在 <code>ThreadLocal</code> 中，尤其是在处理线程池时。</p>
<h3 id="预防措施" tabindex="-1">预防措施： <a class="header-anchor" href="#预防措施" aria-label="Permalink to “预防措施：”">&#8203;</a></h3>
<ul>
<li><strong>显式清理</strong>：在使用 <code>ThreadLocal</code> 的地方，确保在完成操作后调用 <code>remove()</code> 方法。</li>
<li><strong>使用 <code>WeakReference</code></strong>：如果可能，考虑将 <code>ThreadLocal</code> 中存储的对象封装在 <code>WeakReference</code> 中，以便在没有强引用时可以被垃圾回收。</li>
<li><strong>短生命周期的线程</strong>：尽量避免在长生命周期线程中使用 <code>ThreadLocal</code>，或者定期清理其值。</li>
</ul>
<p>通过适当管理 <code>ThreadLocal</code>，可以有效降低内存泄漏的风险。是的，<code>ThreadLocal</code> 可能存在内存泄漏的风险，特别是在以下情况下：</p>
<h3 id="_1-长生命周期的线程-1" tabindex="-1">1. <strong>长生命周期的线程</strong> <a class="header-anchor" href="#_1-长生命周期的线程-1" aria-label="Permalink to “1. 长生命周期的线程”">&#8203;</a></h3>
<p>当一个 <code>ThreadLocal</code> 对象与长生命周期的线程（如线程池中的线程）关联时，如果没有及时清理 <code>ThreadLocal</code> 变量，这些变量可能会保持对外部对象的引用，从而导致这些对象无法被垃圾回收，造成内存泄漏。</p>
<h3 id="_2-static-变量-1" tabindex="-1">2. <strong>Static 变量</strong> <a class="header-anchor" href="#_2-static-变量-1" aria-label="Permalink to “2. Static 变量”">&#8203;</a></h3>
<p>如果 <code>ThreadLocal</code> 被声明为 <code>static</code>，它将对整个应用程序的生命周期存在，且会在同一线程中保留其值。此时，如果 <code>ThreadLocal</code> 变量持有的对象的生命周期超过了线程的生命周期，也会导致内存泄漏。</p>
<h3 id="_3-忘记清理-1" tabindex="-1">3. <strong>忘记清理</strong> <a class="header-anchor" href="#_3-忘记清理-1" aria-label="Permalink to “3. 忘记清理”">&#8203;</a></h3>
<p>在使用 <code>ThreadLocal</code> 后，应该在不再需要时调用 <code>remove()</code> 方法来清除其值，以避免引用保留在 <code>ThreadLocal</code> 中，尤其是在处理线程池时。</p>
<h3 id="预防措施-1" tabindex="-1">预防措施： <a class="header-anchor" href="#预防措施-1" aria-label="Permalink to “预防措施：”">&#8203;</a></h3>
<ul>
<li><strong>显式清理</strong>：在使用 <code>ThreadLocal</code> 的地方，确保在完成操作后调用 <code>remove()</code> 方法。</li>
<li><strong>使用 <code>WeakReference</code></strong>：如果可能，考虑将 <code>ThreadLocal</code> 中存储的对象封装在 <code>WeakReference</code> 中，以便在没有强引用时可以被垃圾回收。</li>
<li><strong>短生命周期的线程</strong>：尽量避免在长生命周期线程中使用 <code>ThreadLocal</code>，或者定期清理其值。</li>
</ul>
<p>通过适当管理 <code>ThreadLocal</code>，可以有效降低内存泄漏的风险。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
        <item>
            <title><![CDATA[Trace日志简介]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Trace%E6%97%A5%E5%BF%97.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Trace日志.html</guid>
            <pubDate>Sun, 27 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[在ANR日志中，重点关注主线程的堆栈跟踪。通常，主线程被阻塞的原因可能包括但不限于：]]></description>
            <content:encoded><![CDATA[<h1 id="trace日志简介" tabindex="-1">Trace日志简介 <a class="header-anchor" href="#trace日志简介" aria-label="Permalink to “Trace日志简介”">&#8203;</a></h1>
<h2 id="_1-日志关键信息解释" tabindex="-1">1. 日志关键信息解释 <a class="header-anchor" href="#_1-日志关键信息解释" aria-label="Permalink to “1. 日志关键信息解释”">&#8203;</a></h2>
<div class="language-txt line-numbers-mode" data-lang="txt"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre><!--::markdown-it-async::e0soe584w8dmntyll2j7t::--><code>// 发生anr的进程id，时间和进程名称。
----- pid 2023 at 2018-07-24 19:33:17 -----

// 发生ANR的包名
Cmd line: com.rui.android.poc

// 线程的基本信息
// CheckJNI is off: 表示Java Native Interface（JNI）检查功能被关闭。
// JNI调用不会进行额外的错误检查，可以提高性能但是会忽略部分错误。

// workarounds are off: 表示所有的临时解决方案被禁用，
// 这可能是为了追求性能或专注于应用的正常行为。

// pins=0 表示当前没有JNI引用被固定在内存中，
// 也就是没有活动的JNI引用指向Java对象

// global=348 表示当前存在348个全局引用。
// 在JNI中，全局引用是指由C/C++代码持有的对象，
// 这些对象不会被垃圾回收
JNI: CheckJNI is off; workarounds are off; pins=0; globals=348

// tll=0 当前线程锁的数量(Thread Local Locks)
// tsl=0 当前持有的可重入锁的数量(Thread-Safe Locks)
// tscl=0 当前持有的可重入锁中被其他线程锁定的数量(Thread-Safe Contention Locks)
// ghl=0 全局锁的数量(Global Locks)
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)

// 线程名称、daemon是守护线程，线程的优先级（默认为5）
// 线程锁id和线程状态
// 一个线程名称，如果是自己手动创建的线程，一般会命名为
// Thread-xxx 的格式，其中xxx线程id，只增不减不会复用
// 这其中的tid并不是线程的id，它是一个在Java虚拟机中
// 用来实现线程锁的变量，随着线程的增减，这个变量的值
// 是可能被复用的
&quot;main&quot; prio=5 tid=1 NATIVE // 线程状态

// group 线程组的名称
// sCount 线程被挂起的次数，当一个线程被调试后，sCount会
// 重置为0，调试完毕后sCount会根据是否被正常挂起增长，
// dsCount 线程被调试器挂起的次数，dsCount不会被重置为0，
// 所以dsCount也可以用来判断这个线程是否被调试过
// obj 这个线程的Java对象的地址
// self 这个线程Native的地址
| group=&quot;main&quot; sCount=1 dsCount=0 obj=0x4164dcf0 self=0x41565628

// 线程的调度信息
// sysTid linux下的内核线程id
// nice 线程的调度优先级
// sched 标志了线程的调度策略和优先级
// cgrp 调度属组
// handle 线程的处理函数地址
  | sysTid=2023 nice=-1 sched=0/0 cgrp=apps handle=1074626900

// 线程当前上下文信息
// state 调度状态
// schedstat 从/proc/[pid]/task/[tid]/schedstat 读出
// 三个值分别代表线程在cpu上执行的时间、线程的等待时间
// 和线程执行的时间片长度，有的android内核版本不支持这项
// 三个值都是0
// utm 线程用户态下使用的时间值(jiffies)
// stm 内核态下的调度时间值
// core 最后执行这个线程的cpu核的符号
 | state=S schedstat=( 0 0 0 ) utm=49 stm=21 core=0

// 线程的调用栈信息（这里可以查看到导致ANR的代码调用流程）
  (native backtrace unavailable)  
  at libcore.io.Posix.open(Native Method)  
  at libcore.io.BlockGuardOs.open(BlockGuardOs.java:110)  
  at libcore.io.IoBridge.open(IoBridge.java:430)  
  at java.io.FileInputStream.&lt;init&gt;(FileInputStream.java:78)  
  at com.ruiven.android.Peripheral.util.kingFiles.write(kingFiles.java:58)  
  at com.ruiven.android.Peripheral.util.kingFiles.write(kingFiles.java:51)  
  at com.ruiven.android.Peripheral.util.kingLog.writeToFiles(kingLog.java:71)  
  at com.ruiven.android.Peripheral.util.kingLog.f(kingLog.java:47)  
  at com.ruiven.android.poc.receiver.ReceiverBatteryChanged.onReceive(ReceiverBatteryChanged.java:114)  
  at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:767)  
  at android.os.Handler.handleCallback(Handler.java:769)  
  at android.os.Handler.dispatchMessage(Handler.java:97)  
  at android.os.Looper.loop(Looper.java:136)  
  at com.ruiven.android.Peripheral.util.Cockroach$1.run(Cockroach.java:38)  
  at android.os.Handler.handleCallback(Handler.java:769)  
  at android.os.Handler.dispatchMessage(Handler.java:97)  
  at android.os.Looper.loop(Looper.java:136)  
  at android.app.ActivityThread.main(ActivityThread.java:5375)  
  at java.lang.reflect.Method.invokeNative(Native Method)  
  at java.lang.reflect.Method.invoke(Method.java:515)  
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:976)  
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:792)  
  at dalvik.system.NativeStart.main(Native Method)

// binder线程是进程的线程池中用来处理binder请求的线程
&quot;Binder_3&quot; prio=5 tid=23 NATIVE  
| group=&quot;main&quot; sCount=1 dsCount=0 obj=0x41b765f8 self=0x4f213f18  
| sysTid=1347 nice=0 sched=0/0 cgrp=apps handle=1327603952  
| state=S schedstat=( 0 0 0 ) utm=0 stm=3 core=1

#00  pc 000205d0  /system/lib/libc.so (__ioctl+8) 
#01  pc 0002d01f  /system/lib/libc.so (ioctl+14)  
#02  pc 0001e519  /system/lib/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+140)  
#03  pc 0001ec67  /system/lib/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+6)  
#04  pc 0001ecfd  /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+48)  
#05  pc 000236cd  /system/lib/libbinder.so  
#06  pc 0000ea19  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+216)  
#07  pc 0004e769  /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+68)  
#08  pc 0000e54b  /system/lib/libutils.so  
#09  pc 0000d240  /system/lib/libc.so (__thread_entry+72)  
#10  pc 0000d3dc  /system/lib/libc.so (pthread_create+240)  at dalvik.system.NativeStart.run(Native Method)

// JDWP线程是支持虚拟机调试的线程，
// daemon表示守护进程，不需要关心&quot;JDWP&quot; 
daemon prio=5 tid=4 VMWAIT  
| group=&quot;system&quot; sCount=1 dsCount=0 obj=0x41a21868 self=0x4cc821b0  
| sysTid=785 nice=0 sched=0/0 cgrp=apps handle=1257589656  
| state=S schedstat=( 0 0 0 ) utm=0 stm=0 core=1  
#00  pc 00021420  /system/lib/libc.so (recvmsg+8)  
#01  pc 000668ab  /system/lib/libdvm.so  
#02  pc 00066adf  /system/lib/libdvm.so  
#03  pc 000697af  /system/lib/libdvm.so  
#04  pc 00059fdd  /system/lib/libdvm.so  
#05  pc 0000d240  /system/lib/libc.so (__thread_entry+72)  
#06  pc 0000d3dc  /system/lib/libc.so (pthread_create+240)  at dalvik.system.NativeStart.run(Native Method)

// “Signal Catcher”负责接收和处理kernel发送的各种信号，
// 例如SIGNAL_QUIT、SIGNAL_USR1等就是被该线程
// 接收到并处理的，traces.txt 文件中的内容就是由该线程负责输出的，
// 可以看到它的状态是RUNNABLE.
&quot;Signal Catcher&quot; daemon prio=5 tid=3 RUNNABLE  
| group=&quot;system&quot; sCount=0 dsCount=0 obj=0x41a21770 self=0x4af3e5e0  
| sysTid=784 nice=0 sched=0/0 cgrp=apps handle=1257677168  
| state=R schedstat=( 0 0 0 ) utm=2 stm=2 core=1  at dalvik.system.NativeStart.run(Native Method)
----- end 2023 -----</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br><span class="line-number">85</span><br><span class="line-number">86</span><br><span class="line-number">87</span><br><span class="line-number">88</span><br><span class="line-number">89</span><br><span class="line-number">90</span><br><span class="line-number">91</span><br><span class="line-number">92</span><br><span class="line-number">93</span><br><span class="line-number">94</span><br><span class="line-number">95</span><br><span class="line-number">96</span><br><span class="line-number">97</span><br><span class="line-number">98</span><br><span class="line-number">99</span><br><span class="line-number">100</span><br><span class="line-number">101</span><br><span class="line-number">102</span><br><span class="line-number">103</span><br><span class="line-number">104</span><br><span class="line-number">105</span><br><span class="line-number">106</span><br><span class="line-number">107</span><br><span class="line-number">108</span><br><span class="line-number">109</span><br><span class="line-number">110</span><br><span class="line-number">111</span><br><span class="line-number">112</span><br><span class="line-number">113</span><br><span class="line-number">114</span><br><span class="line-number">115</span><br><span class="line-number">116</span><br><span class="line-number">117</span><br><span class="line-number">118</span><br><span class="line-number">119</span><br><span class="line-number">120</span><br><span class="line-number">121</span><br><span class="line-number">122</span><br><span class="line-number">123</span><br><span class="line-number">124</span><br><span class="line-number">125</span><br><span class="line-number">126</span><br><span class="line-number">127</span><br><span class="line-number">128</span><br><span class="line-number">129</span><br><span class="line-number">130</span><br><span class="line-number">131</span><br></div></div><p>在ANR日志中，重点关注主线程的堆栈跟踪。通常，主线程被阻塞的原因可能包括但不限于：</p>
<ol>
<li>长时间的计算任务。</li>
<li>数据库查询。</li>
<li>文件读写。</li>
<li>网络请求。</li>
<li>锁竞争。</li>
</ol>
<h2 id="_2-线程状态" tabindex="-1">2. 线程状态 <a class="header-anchor" href="#_2-线程状态" aria-label="Permalink to “2. 线程状态”">&#8203;</a></h2>
<table tabindex="0">
<thead>
<tr>
<th>Thread.java中定义的状态</th>
<th>Thread.cpp中定义的状态</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>TERMINATED</td>
<td>ZOMBIE</td>
<td>线程死亡，终止运行</td>
</tr>
<tr>
<td>RUNNABLE</td>
<td>RUNNING/RUNNABLE</td>
<td>线程可运行或正在运行</td>
</tr>
<tr>
<td>TIMED_WAITING</td>
<td>TIMED_WAIT</td>
<td>执行了带有超时参数的wait、sleep或join函数</td>
</tr>
<tr>
<td>BLOCKED</td>
<td>MONITOR</td>
<td>线程阻塞，等待获取对象锁</td>
</tr>
<tr>
<td>WAITING</td>
<td>WAIT</td>
<td>执行了无超时参数的wait函数</td>
</tr>
<tr>
<td>NEW</td>
<td>INITIALIZING</td>
<td>新建，正在初始化，为其分配资源</td>
</tr>
<tr>
<td>NEW</td>
<td>STARTING</td>
<td>新建，正在启动</td>
</tr>
<tr>
<td>RUNNABLE</td>
<td>NATIVE</td>
<td>正在执行JNI本地函数</td>
</tr>
<tr>
<td>WAITING</td>
<td>VMWAIT</td>
<td>正在等待VM资源</td>
</tr>
<tr>
<td>RUNNABLE</td>
<td>SUSPENDED</td>
<td>线程暂停，通常是由于GC或debug被暂停</td>
</tr>
<tr>
<td></td>
<td>UNKNOWN</td>
<td>未知状态</td>
</tr>
</tbody>
</table>
<blockquote>
<p>特别说明一下 <code>MONITOR状态</code> 和 <code>SUSPEND状态</code>，<strong>MONITOR状态一般是类的同步块或者同步方法造成的，SUSPENDED状态在debugger的时候会出现，可以用来区别是不是真的是用户正常操作跑出了ANR。</strong></p>
</blockquote>
<div class="language-txt line-numbers-mode" data-lang="txt"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre><!--::markdown-it-async::qcpmurnedzvm6qwzzh99j::--><code>mutexes(互斥)的选项简写对应含义：

tll--thread list lock
tsl-- thread suspend lock
tscl-- thread suspend count lock
ghl--gc heap lock
hwl--heap worker lock
hwll--heap worker list lock</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><blockquote>
<p>在<code>trace</code>信息中，通过 <code>Cmd line: packagename</code> 可以找到要分析的进程这里要说一下，<strong>并不是trace文件包含的应用就一定是造成ANR根本原因。</strong></p>
</blockquote>
<h2 id="_3-线程状态实例" tabindex="-1">3. 线程状态实例 <a class="header-anchor" href="#_3-线程状态实例" aria-label="Permalink to “3. 线程状态实例”">&#8203;</a></h2>
<ol>
<li>TIMED_WAIT状态</li>
</ol>
<div class="language-txt line-numbers-mode" data-lang="txt"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre><!--::markdown-it-async::1xktkl6w4st4dt7v7mm6g::--><code>DALVIK THREADS:&quot;main&quot; prio=5 tid=3 TIMED_WAIT  
| group=&quot;main&quot; sCount=1 dsCount=0 s=0 obj=0x400143a8  
| sysTid=691 nice=0 sched=0/0 handle=-1091117924  
at java.lang.Object.wait(Native Method)  - waiting on &lt;0x1cd570&gt; (a android.os.MessageQueue)  
at java.lang.Object.wait(Object.java:195)  
at android.os.MessageQueue.next(MessageQueue.java:144)  
at android.os.Looper.loop(Looper.java:110)  
at android.app.ActivityThread.main(ActivityThread.java:3742) 
at java.lang.reflect.Method.invokeNative(Native Method)  
at java.lang.reflect.Method.invoke(Method.java:515)  
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)  
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)  
at dalvik.system.NativeStart.main(Native Method)

&quot;Binder Thread #3&quot; prio=5 tid=15 NATIVE  
| group=&quot;main&quot; sCount=1 dsCount=0 s=0 obj=0x434e7758  
| sysTid=734 nice=0 sched=0/0 handle=1733632  at dalvik.system.NativeStart.run(Native Method)
&quot;Binder Thread #2&quot; prio=5 tid=13 NATIVE  
| group=&quot;main&quot; sCount=1 dsCount=0 s=0 obj=0x1cd570  
| sysTid=696 nice=0 sched=0/0 handle=1369840  at dalvik.system.NativeStart.run(Native Method)
&quot;Binder Thread #1&quot; prio=5 tid=11 NATIVE  
| group=&quot;main&quot; sCount=1 dsCount=0 s=0 obj=0x433aca10  
| sysTid=695 nice=0 sched=0/0 handle=1367448  at dalvik.system.NativeStart.run(Native Method)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br></div></div><ol start="2">
<li>SUSPENDED状态</li>
</ol>
<div class="language-txt line-numbers-mode" data-lang="txt"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre><!--::markdown-it-async::r6kfkcww8l8gz94m7p79k6::--><code>&quot;main&quot; prio=5 tid=1 Suspended  
| group=&quot;main&quot; sCount=1 dsCount=0 cgrp=bg_non_interactive handle=0x7fa2a39000  
| sysTid=16770 nice=-4 sched=0/0 cgrp=bg_non_interactive handle=0x7fa2a39000  
| state=S schedstat=( 2661049558440 288674775480 3568435 ) utm=226454 stm=39650 core=1 HZ=100  
| heldMutexes= at android.os.MessageQueue.removeMessages(MessageQueue.java:702) 
at android.os.Handler.removeCallbacks(Handler.java:487) 
at me.ele.android.lmagex.b$3.println(SourceFile:103) 
at android.os.Looper.loop(Looper.java:153) 
at android.app.ActivityThread.main(ActivityThread.java:5665) 
at java.lang.reflect.Method.invoke!(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:822) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:712)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><ol start="3">
<li>WAITING状态</li>
</ol>
<div class="language-txt line-numbers-mode" data-lang="txt"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre><!--::markdown-it-async::06x52641ix3aem6ykpigyz9::--><code>&quot;main&quot; prio=5 tid=1 Waiting  
| group=&quot;main&quot; sCount=0 dsCount=0 flags=0 obj=0x731a0ec8 self=0xb40000753e8b6c00  
| sysTid=28146 nice=-10 cgrp=default sched=0/0 handle=0x753fe804f8  
| state=? schedstat=( 0 0 0 ) utm=0 stm=0 core=0 HZ=100  
| stack=0x7fd0403000-0x7fd0405000 stackSize=8192KB  
| held mutexes=  at java.lang.Object.wait(Native method)  - waiting on &lt;0x025d68dd&gt; (a android.opengl.GLSurfaceView$GLThreadManager)  
at java.lang.Object.wait(Object.java:442)  
at java.lang.Object.wait(Object.java:568)  
at android.opengl.GLSurfaceView$GLThread.onPause(GLSurfaceView.java:1731)  
at android.opengl.GLSurfaceView.onPause(GLSurfaceView.java:579)  
at com.amap.api.mapcore.util.e.onPause(SourceFile:117)  
at com.amap.api.mapcore.util.e.onDetachedGLThread(SourceFile:73)  
at com.amap.api.mapcore.util.c.destroy(SourceFile:5750)  
at com.amap.api.mapcore.util.t.onDestroy(SourceFile:207)  
at com.amap.api.maps.MapView.onDestroy(SourceFile:165)</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br></div></div><h2 id="_4-log-main-分析" tabindex="-1">4. log_main 分析 <a class="header-anchor" href="#_4-log-main-分析" aria-label="Permalink to “4. log_main 分析”">&#8203;</a></h2>
<p>ANR分析时也需要注意分析 <code>Looper</code> 信息， <code>AnrManager</code> 信息， <code>WindowManager</code> 信息等等。</p>
<p><code>AnrManager</code> 信息</p>
<div class="language-txt line-numbers-mode" data-lang="txt"><button title="Copy Code" class="copy"></button><span class="lang">txt</span><pre><!--::markdown-it-async::1dotpwuvivipur82zpvqzh::--><code>// 行号后面：时间，用户ID（UID，1000通常为系统进程），进程ID（PID），线程ID（TID），日志级别，模块，内容
行  10719: 07-02 10:13:34.102  1000  1674  1674 I AnrManager: startAnrManagerService
行  10721: 07-02 10:13:34.262  1000  1674  1674 I AnrManager: prepareStackTraceFile: 

// 这里开始记录ANR dump信息
行  15333: 07-02 10:13:56.743  1000  1674  2963 I AnrManager: startAnrDump
行  15334: 07-02 10:13:56.743  1000  1674  2963 I AnrManager: isANRFlowSkipped() AnrFlow = 0
行  15335: 07-02 10:13:56.744  1000  1674  2963 I AnrManager: enableTraceLog: false

// ANR事件发生，应用 com.android.launcher 触发 ANR，原因是 Input dispatching timed out 
// (Application does not have a focused window)（输入分发超时，应用程序没有聚焦的窗口）。
// 开始收集ANR调试信息。
行  15336: 07-02 10:13:56.750  1000  1674  2963 I AnrManager: dumpAnrDebugInfo begin: AnrDumpRecord{ Input dispatching timed out (Application does not have a focused window) ProcessRecord{6ce010b 2378:com.android.launcher/u0a63} IsCompleted:false IsCancelled:false }, onlyDumpSelf = false, isSilentANR = false
行  15337: 07-02 10:13:56.750  1000  1674  2963 I AnrManager: dumpAnrDebugInfoLocked: AnrDumpRecord{ Input dispatching timed out (Application does not have a focused window) ProcessRecord{6ce010b 2378:com.android.launcher/u0a63} IsCompleted:false IsCancelled:false }, onlyDumpSelf = false, isSilentANR = false
行  15338: 07-02 10:13:56.758  1000  1674  2964 I AnrManager: /dev/binderfs/binder_logs/timeout_log isn&#039;t exist
行  15339: 07-02 10:13:56.790  1000  1674  2963 I AnrManager: dumpStackTraces begin!

// 堆栈信息转储结束。
行  15414: 07-02 10:14:00.878  1000  1674  2963 I AnrManager: dumpStackTraces end!
行  15415: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: ANR in com.android.launcher (com.android.launcher/.uioverrides.QuickstepLauncher), time=41755
行  15416: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: Reason: Input dispatching timed out (Application does not have a focused window)

// 这是系统的负载信息，分别代表1分钟、5分钟和15分钟的系统平均负载。这里表示系统的负载较高，特别是
// 在过去1分钟内。
行  15417: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: Load: 10.76 / 2.71 / 0.91

// 这是从 /proc/pressure/memory 文件中提取的内存压力信息。avg10, avg60, avg300 
// 分别是过去10秒、60秒和300秒的内存压力统计值。这里显示内存压力为0，说明在该时间段内系统并没有
// 内存压力。
行  15418: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: ----- Output from /proc/pressure/memory -----
行  15419: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: some avg10=0.00 avg60=0.00 avg300=0.00 total=0
行  15420: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: full avg10=0.00 avg60=0.00 avg300=0.00 total=0
行  15421: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: ----- End output from /proc/pressure/memory -----
行  15422: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: 
行  15423: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: Android time :[2024-07-02 10:14:00.87] [45.992]

// 这是CPU使用情况报告，显示在过去6.4秒内各个进程的CPU使用率。
行  15424: 07-02 10:14:00.882  1000  1674  2963 I AnrManager: CPU usage from 6404ms to 0ms ago (2024-07-02 10:13:50.238 to 2024-07-02 10:13:56.642):

// 1674/system_server 使用了45%的CPU时间，其中28%用于用户态，17%用于内核态，另外有2881次轻微
// 页面错误（minor faults），9次重大页面错误（major faults）。
行  15425: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   45% 1674/system_server: 28% user + 17% kernel / faults: 2881 minor 9 major

// 691/surfaceflinger 和其他进程的CPU使用情况也被详细列出
行  15426: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   36% 691/surfaceflinger: 24% user + 12% kernel / faults: 1263 minor
行  15427: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   32% 1992/com.android.systemui: 20% user + 12% kernel / faults: 379 minor
行  15428: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   27% 2790/android.process.acore: 25% user + 1.7% kernel / faults: 3484 minor 31 major
行  15429: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   21% 676/android.hardware.graphics.composer@2.1-service: 10% user + 10% kernel / faults: 851 minor
行  15430: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   20% 669/android.hardware.audio.service.mediatek: 9.9% user + 10% kernel
行  15431: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   12% 688/audioserver: 7.6% user + 4.5% kernel / faults: 119 minor 1 major
行  15432: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   9% 326/logd: 2% user + 7% kernel / faults: 557 minor
行  15433: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   7.4% 2126/com.android.networkstack.process: 5.7% user + 1.7% kernel / faults: 1353 minor 7 major
行  15434: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   4.2% 893/mobile_log_d: 1.8% user + 2.3% kernel / faults: 210 minor
行  15435: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   3.2% 518/kfps: 0% user + 3.2% kernel
行  15436: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   2.9% 684/android.hardware.memtrack-service.mediatek: 0.6% user + 2.3% kernel / faults: 824 minor
行  15437: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   2.6% 2144/com.android.permissioncontroller: 1.7% user + 0.9% kernel / faults: 234 minor
行  15438: 07-02 10:14:00.882  1000  1674  2963 I AnrManager:   2.6% 2293/com.android.phone: 2% user + 0.6% kernel / faults: 1004 minor
...

// 整个系统的CPU使用情况，总计36%的CPU使用率，其中19%是用户态，14%是内核态，IRQ（中断）占1.6%。
行  15506: 07-02 10:14:00.883  1000  1674  2963 I AnrManager: 36% TOTAL: 19% user + 14% kernel + 0% iowait + 1.6% irq + 0.4% softirq

// 这是在短时间内（从10:13:56.791到10:13:57.539之间）更详细的CPU使用情况。
// 1674/system_server 使用了74%的CPU，2963/AnrConsumer占用了43%的CPU（15%用户态，
// 28%内核态），以及其他子线程的使用情况。
行  15507: 07-02 10:14:00.883  1000  1674  2963 I AnrManager: CPU usage from 148ms to 897ms later (2024-07-02 10:13:56.791 to 2024-07-02 10:13:57.539):
行  15508: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:   74% 1674/system_server: 33% user + 41% kernel / faults: 240 minor
行  15509: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     43% 2963/AnrConsumer: 15% user + 28% kernel
行  15510: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     5.1% 1711/binder:1674_1: 5.1% user + 0% kernel
行  15511: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     5.1% 1720/android.display: 0% user + 5.1% kernel
行  15512: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     2.5% 1718/android.ui: 2.5% user + 0% kernel
行  15513: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     2.5% 1733/batterystats-ha: 2.5% user + 0% kernel
行  15514: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     2.5% 1821/InputDispatcher: 2.5% user + 0% kernel
行  15515: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     2.5% 1822/system_server: 0% user + 2.5% kernel
行  15516: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     2.5% 1823/InputReader: 2.5% user + 0% kernel
行  15517: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     2.5% 1901/RenderThread: 2.5% user + 0% kernel
行  15518: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     2.5% 2332/binder:1674_9: 2.5% user + 0% kernel
行  15519: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:   35% 691/surfaceflinger: 25% user + 10% kernel / faults: 130 minor
行  15520: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     20% 691/surfaceflinger: 17% user + 3.4% kernel
行  15521: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     5.1% 1062/surfaceflinger: 5.1% user + 0% kernel
行  15522: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     5.1% 1785/binder:691_3: 1.7% user + 3.4% kernel
行  15523: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     3.4% 1053/TimerDispatch: 1.7% user + 1.7% kernel
行  15524: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     1.7% 772/binder:691_2: 0% user + 1.7% kernel
行  15525: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:     1.7% 1054/app: 0% user + 1.7% kernel
行  15526: 07-02 10:14:00.883  1000  1674  2963 I AnrManager:   47% 1992/com.android.systemui: 27% user + 19% kernel / faults: 6 minor
...

// 这行日志记录了系统整体的CPU使用情况：
//	•	总使用率为32%。
//	•	其中 15% 是用户态（user mode），即应用程序或用户空间的进程使用的CPU时间。
//	•	14% 是内核态（kernel mode），即操作系统内核或设备驱动程序占用的CPU时间。
//	•	1.7% 是硬件中断（irq），表示系统响应外部硬件设备的中断请求所占用的CPU时间。
//	•	0.5% 是软中断（softirq），这通常与处理一些高优先级任务如网络包等相关。
行  15559: 07-02 10:14:00.883  1000  1674  2963 I AnrManager: 32% TOTAL: 15% user + 14% kernel + 1.7% irq + 0.5% softirq
行  15560: 07-02 10:14:00.883  1000  1674  2963 I AnrManager: dumpAnrDebugInfo end: AnrDumpRecord{ Input dispatching timed out (Application does not have a focused window) ProcessRecord{6ce010b 2378:com.android.launcher/u0a63} IsCompleted:true IsCancelled:false }, onlyDumpSelf = false , isSilentANR = false
行  15561: 07-02 10:14:00.884  1000  1674  2963 I AnrManager: addErrorToDropBox app = ProcessRecord{6ce010b 2378:com.android.launcher/u0a63} processName = com.android.launcher activityShortComponentName = com.android.launcher/.uioverrides.QuickstepLauncher parentShortComponentName = com.android.launcher/.uioverrides.QuickstepLauncher parentProcess = ProcessRecord{6ce010b 2378:com.android.launcher/u0a63} annotation = Input dispatching timed out (Application does not have a focused window) mTracesFile = /data/anr/anr_2024-07-02-10-13-57-639
行  15562: 07-02 10:14:00.886  1000  1674  2963 I AnrManager:  controller = null</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br><span class="line-number">38</span><br><span class="line-number">39</span><br><span class="line-number">40</span><br><span class="line-number">41</span><br><span class="line-number">42</span><br><span class="line-number">43</span><br><span class="line-number">44</span><br><span class="line-number">45</span><br><span class="line-number">46</span><br><span class="line-number">47</span><br><span class="line-number">48</span><br><span class="line-number">49</span><br><span class="line-number">50</span><br><span class="line-number">51</span><br><span class="line-number">52</span><br><span class="line-number">53</span><br><span class="line-number">54</span><br><span class="line-number">55</span><br><span class="line-number">56</span><br><span class="line-number">57</span><br><span class="line-number">58</span><br><span class="line-number">59</span><br><span class="line-number">60</span><br><span class="line-number">61</span><br><span class="line-number">62</span><br><span class="line-number">63</span><br><span class="line-number">64</span><br><span class="line-number">65</span><br><span class="line-number">66</span><br><span class="line-number">67</span><br><span class="line-number">68</span><br><span class="line-number">69</span><br><span class="line-number">70</span><br><span class="line-number">71</span><br><span class="line-number">72</span><br><span class="line-number">73</span><br><span class="line-number">74</span><br><span class="line-number">75</span><br><span class="line-number">76</span><br><span class="line-number">77</span><br><span class="line-number">78</span><br><span class="line-number">79</span><br><span class="line-number">80</span><br><span class="line-number">81</span><br><span class="line-number">82</span><br><span class="line-number">83</span><br><span class="line-number">84</span><br><span class="line-number">85</span><br><span class="line-number">86</span><br><span class="line-number">87</span><br><span class="line-number">88</span><br><span class="line-number">89</span><br><span class="line-number">90</span><br><span class="line-number">91</span><br><span class="line-number">92</span><br><span class="line-number">93</span><br><span class="line-number">94</span><br><span class="line-number">95</span><br><span class="line-number">96</span><br><span class="line-number">97</span><br></div></div>]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[异或运算交换两数]]></title>
            <link>https://endlessyoung.github.io/Blog_/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%BC%82%E6%88%96%E8%BF%90%E7%AE%97.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/数据结构和算法/异或运算.html</guid>
            <pubDate>Wed, 16 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[异或的基本运算法则：一个数和自己异或的结果是0，和0异或的结果是其本身，并且满足交换律和结合律。异或也可以看作是没有进位的加法。例如： a ^ a = 0 和 a ^ 0 = a]]></description>
            <content:encoded><![CDATA[<h1 id="异或运算交换两数" tabindex="-1">异或运算交换两数 <a class="header-anchor" href="#异或运算交换两数" aria-label="Permalink to “异或运算交换两数”">&#8203;</a></h1>
<p>异或的基本运算法则：一个数和自己异或的结果是0，和0异或的结果是其本身，并且满足交换律和结合律。异或也可以看作是没有进位的加法。例如： a ^ a = 0 和 a ^ 0 = a</p>
<h2 id="_1-交换两个数" tabindex="-1">1. 交换两个数 <a class="header-anchor" href="#_1-交换两个数" aria-label="Permalink to “1. 交换两个数”">&#8203;</a></h2>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::r6bs2twm17dtln3v3eodn::--><code>int a = 10;
int b = 20;

a = a ^ b; // a = a ^ b
b = a ^ b; // b = a ^ b ^ b = a
a = a ^ b; // a = a ^ b ^ a = b</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><h2 id="_2-寻找数组内只有一个数出现奇数次的数-数组内只有两个数出现奇数次的数" tabindex="-1">2. 寻找数组内只有一个数出现奇数次的数，数组内只有两个数出现奇数次的数 <a class="header-anchor" href="#_2-寻找数组内只有一个数出现奇数次的数-数组内只有两个数出现奇数次的数" aria-label="Permalink to “2. 寻找数组内只有一个数出现奇数次的数，数组内只有两个数出现奇数次的数”">&#8203;</a></h2>
<div class="language-java line-numbers-mode" data-lang="java"><button title="Copy Code" class="copy"></button><span class="lang">Java</span><pre><!--::markdown-it-async::wczjqzc31z02169vn7nbz4::--><code>public class eor {
    public static void main(String[] args) {
        int[] arr1 = new int[]{1, 1, 2, 2, 3, 3, 3, 4, 4, 4};
        int[] arr2 = new int[]{1, 2, 3, 3, 4, 4};

        System.out.println(findOnlyOneOddNumber(arr1));
        System.out.println(Arrays.toString(findTwoOddNumber(arr2)));
    }

    // 一个数组中只有一个数出现奇数次，找到这个数
    public static int findOnlyOneOddNumber(int[] arr) {
        int eor = 0;

        for (int j : arr) {
            eor ^= j;
        }

        return eor;
    }

    // 一个数组中两个数出现奇数次，找到这个数
    public static int[] findTwoOddNumber(int[] arr) {
        int eor = 0;
        for (int j : arr) {
            eor ^= j;
        }
        int rightOne = eor &amp; (~eor + 1);

        int onlyOne = 0;
        for (int cur : arr) {
            if ((cur &amp; rightOne) == 1) {
                onlyOne ^= cur;
            }
        }
        return new int[]{onlyOne, eor ^ onlyOne};
    }
}</code></pre>
<div class="line-numbers-wrapper" aria-hidden="true"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br><span class="line-number">19</span><br><span class="line-number">20</span><br><span class="line-number">21</span><br><span class="line-number">22</span><br><span class="line-number">23</span><br><span class="line-number">24</span><br><span class="line-number">25</span><br><span class="line-number">26</span><br><span class="line-number">27</span><br><span class="line-number">28</span><br><span class="line-number">29</span><br><span class="line-number">30</span><br><span class="line-number">31</span><br><span class="line-number">32</span><br><span class="line-number">33</span><br><span class="line-number">34</span><br><span class="line-number">35</span><br><span class="line-number">36</span><br><span class="line-number">37</span><br></div></div><ul>
<li>详细解释：</li>
</ul>
<p><code>cur &amp; rightOne == 1</code> 这一条件的作用，是通过 <code>rightOne</code> 的值，将数组中的数按位区分开来，以便找到其中一个只出现一次的数。这个条件并不是在检查某个数是否是 <code>1</code>，而是在根据 <code>rightOne</code> 确定的位，对数组中的数进行分组。下面是详细解释：</p>
<ul>
<li><code>rightOne</code> 是通过 <code>eor &amp; (~eor + 1)</code> 计算出来的，它的作用是提取出 <code>eor</code> 二进制表示中<strong>最右侧的 <code>1</code></strong>。</li>
<li><code>eor</code> 是两个只出现一次的数的异或结果，这意味着在 <code>eor</code> 的二进制表示中，至少有一位是 <code>1</code>，这表示这两个数在这一位上是不同的（一个是 <code>0</code>，一个是 <code>1</code>）。</li>
</ul>
<h3 id="为什么要用-cur-rightone" tabindex="-1">为什么要用 <code>cur &amp; rightOne</code>？ <a class="header-anchor" href="#为什么要用-cur-rightone" aria-label="Permalink to “为什么要用 cur &amp; rightOne？”">&#8203;</a></h3>
<ul>
<li><code>rightOne</code> 只在某一位上为 <code>1</code>（其他位都是 <code>0</code>），这代表两个只出现一次的数在这一位上有所不同。</li>
<li>通过 <code>cur &amp; rightOne</code>，我们可以把数组中的数分成两组：
<ul>
<li><strong>第一组</strong>：<code>cur &amp; rightOne == 1</code>，即这一位上是 <code>1</code> 的数。</li>
<li><strong>第二组</strong>：<code>cur &amp; rightOne == 0</code>，即这一位上是 <code>0</code> 的数。</li>
</ul>
</li>
</ul>
<p>这两组中的数分别包含了：</p>
<ul>
<li>一个只出现一次的数在第一组中。</li>
<li>另一个只出现一次的数在第二组中。</li>
</ul>
<p>因为其他数都是出现两次的，异或操作会把这些数相互抵消掉，只剩下那两个只出现一次的数之一。</p>
<ul>
<li><code>onlyOne</code> 通过在第一组中异或，最终会得到那一组中的那个只出现一次的数，因为其他数都出现两次会相互抵消。</li>
<li>而 <code>eor ^ onlyOne</code> 则通过将 <code>eor</code> 中的异或结果去掉 <code>onlyOne</code>，得到另一个只出现一次的数。</li>
</ul>
<h3 id="举例说明" tabindex="-1">举例说明 <a class="header-anchor" href="#举例说明" aria-label="Permalink to “举例说明”">&#8203;</a></h3>
<p>假设 <code>arr</code> 为 <code>{1, 2, 3, 3, 4, 4}</code>：</p>
<ol>
<li>
<p><strong>计算 <code>eor</code></strong>:</p>
<ul>
<li><code>eor = 1 ^ 2 ^ 3 ^ 3 ^ 4 ^ 4 = 1 ^ 2</code>（因为相同的数 <code>3</code> 和 <code>4</code> 异或后为 <code>0</code>，被抵消）。</li>
<li>结果 <code>eor = 1 ^ 2 = 3</code>，即二进制是 <code>11</code>。</li>
</ul>
</li>
<li>
<p><strong>计算 <code>rightOne</code></strong>:</p>
<ul>
<li><code>rightOne = eor &amp; (~eor + 1)</code>，二进制是 <code>11 &amp; 01 = 01</code>，表示最右侧的 <code>1</code>。</li>
</ul>
</li>
<li>
<p><strong>根据 <code>rightOne</code> 分组并异或</strong>:</p>
<ul>
<li>对于 <code>cur</code> 是 <code>1</code> 和 <code>2</code>：
<ul>
<li><code>1 &amp; rightOne == 1</code>（因为 <code>1</code> 的二进制是 <code>01</code>，最右位是 <code>1</code>）。</li>
<li><code>2 &amp; rightOne == 0</code>（因为 <code>2</code> 的二进制是 <code>10</code>，最右位是 <code>0</code>）。</li>
</ul>
</li>
<li>所以 <code>1</code> 被放入一个组，<code>2</code> 被放入另一个组。</li>
</ul>
</li>
<li>
<p><strong>找到 <code>onlyOne</code></strong>:</p>
<ul>
<li>由于 <code>1</code> 被放在了满足 <code>cur &amp; rightOne == 1</code> 的组里，<code>onlyOne</code> 就是 <code>1</code>。</li>
<li>而 <code>eor ^ onlyOne = 3 ^ 1 = 2</code>，所以另一个数是 <code>2</code>。</li>
</ul>
</li>
</ol>
<p>通过这一方式，<code>cur &amp; rightOne == 1</code> 的检查把不同的数分组，从而让异或操作能够分别得到这两个只出现一次的数。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>数据结构和算法</category>
        </item>
        <item>
            <title><![CDATA[Android一个APP里面最少有几个线程]]></title>
            <link>https://endlessyoung.github.io/Blog_/Android/Android%E4%B8%80%E4%B8%AAAPP%E9%87%8C%E9%9D%A2%E6%9C%80%E5%B0%91%E6%9C%89%E5%87%A0%E4%B8%AA%E7%BA%BF%E7%A8%8B.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Android/Android一个APP里面最少有几个线程.html</guid>
            <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Android一个进程里面最少包含5个线程，分别为：]]></description>
            <content:encoded><![CDATA[<h1 id="android一个app里面最少有几个线程" tabindex="-1">Android一个APP里面最少有几个线程 <a class="header-anchor" href="#android一个app里面最少有几个线程" aria-label="Permalink to “Android一个APP里面最少有几个线程”">&#8203;</a></h1>
<p>Android一个进程里面最少包含5个线程，分别为：</p>
<ol>
<li><strong>main线程（主线程）</strong>：</li>
<li><strong>FinalizerDaemon线程</strong>：
终结者守护线程。对于重写了成员函数finalize的对象，它们被GC决定回收时，并没有马上被回收，而是被放入到一个队列中，等待
FinalizerDaemon守护线程去调用它们的成员函数finalize，然后再被回收。</li>
<li><strong>FinalizerWatchdogDaemon线程</strong>：
监控终结者守护线程。用来监控FinalizerDaemon线程的执行。一旦检测那些重定了成员函数finalize的对象在执行成员函数finalize时超出一定的时候，那么就会退出VM。</li>
<li><strong>HeapTaskDaemon线程</strong>：
堆栈守护线程。用来执行堆栈的操作，也就是用来将那些空闲的堆内存归还给系统。</li>
<li><strong>ReferenceQueueDaemon线程</strong>：
引用队列守护线程。我们知道，在创建引用对象的时候，可以关联一个队列。当被引用对象引用的对象被GC回收的时候，被引用对象就会被加入到其创建时关联的队列去。这个加入队列的操作就是由ReferenceQueueDaemon守护线程来完成的。这样应用程序就可以知道哪些被引用对象引用的对象已经被回收了。</li>
</ol>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Android</category>
        </item>
        <item>
            <title><![CDATA[GC算法]]></title>
            <link>https://endlessyoung.github.io/Blog_/Java/GC%E7%AE%97%E6%B3%95.html</link>
            <guid isPermaLink="false">https://endlessyoung.github.io/Blog_/Java/GC算法.html</guid>
            <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[讲内存分为两个大小相同的空间，每次只使用其中一个空间。当内存快被用完时，GC会将存活的对象复制到另一个空闲空间上，然后一次性清空当前空间。]]></description>
            <content:encoded><![CDATA[<h1 id="gc算法" tabindex="-1">GC算法 <a class="header-anchor" href="#gc算法" aria-label="Permalink to “GC算法”">&#8203;</a></h1>
<h2 id="一、-标记-清除算法-mark-sweep" tabindex="-1">一、 标记-清除算法 (Mark-Sweep) <a class="header-anchor" href="#一、-标记-清除算法-mark-sweep" aria-label="Permalink to “一、 标记-清除算法 (Mark-Sweep)”">&#8203;</a></h2>
<ul>
<li><strong>原理：</strong></li>
</ul>
<ol>
<li><strong>标记阶段</strong>：从GC Roots（一般是程序的入口，比如：栈、静态变量 等）出发，遍历所有可达的对象并标记。</li>
<li><strong>清除阶段</strong>：遍历堆，回收没有标记的对象。</li>
</ol>
<ul>
<li>优点：</li>
</ul>
<ol>
<li>简单直观：整个过程的逻辑清晰</li>
<li>不需要移动对象：直接回收不需要的对象。</li>
</ol>
<ul>
<li>缺点：</li>
</ul>
<ol>
<li>内存碎片：对象清除后内存不做整理，会产生内存碎片，导致分配大对象时可能没有足够连续的内存。</li>
<li>效率低下：标记和清除过程中需要遍历整个堆，回收速度比较慢。</li>
</ol>
<ul>
<li>使用场景：常见于老年代的垃圾回收策略，因为对象生命周期较长，不需要频繁的内存整理。</li>
</ul>
<h2 id="二、-复制算法-copying" tabindex="-1">二、 复制算法 (Copying) <a class="header-anchor" href="#二、-复制算法-copying" aria-label="Permalink to “二、 复制算法 (Copying)”">&#8203;</a></h2>
<ul>
<li>
<p>原理：
讲内存分为两个大小相同的空间，每次只使用其中一个空间。当内存快被用完时，GC会将存活的对象复制到另一个空闲空间上，然后一次性清空当前空间。</p>
</li>
<li>
<p>优点：</p>
</li>
</ul>
<ol>
<li>没有碎片问题：通过将存活对象复制到新空间，直接回收掉不需要的对象，没有内存碎片。</li>
<li>分配速度快：对象始终分配在一段连续的内存区域上，指针推进分配速度快。</li>
</ol>
<ul>
<li>缺点：</li>
</ul>
<ol>
<li>空间浪费：需要预留两个空间，总体内存使用效率比较低。</li>
<li>对象复制开销：存活对象需要不断地复制，复制操作在对象多时可能会影响性能。</li>
</ol>
<ul>
<li>使用场景：
适用于新生代，因为新生代对象生命周期短，存活的对象相对较少，复制代价低。</li>
</ul>
<h2 id="三、-标记-整理算法-mark-compact" tabindex="-1">三、 标记-整理算法 (Mark-Compact) <a class="header-anchor" href="#三、-标记-整理算法-mark-compact" aria-label="Permalink to “三、 标记-整理算法 (Mark-Compact)”">&#8203;</a></h2>
<ul>
<li>
<p>原理：
先标记所有存活的对象，然后将存活对象向一端移动，最后清除边界以外的对象，整理后保证内存空间的连续性。</p>
</li>
<li>
<p>优点：</p>
</li>
</ul>
<ol>
<li>没有碎片问题：通过对象的移动整理内存，避免了内存碎片的产生。</li>
<li>效率适中：相比于标记-清除，移动对象后不需要过多的内存整理。</li>
</ol>
<ul>
<li>缺点：</li>
</ul>
<ol>
<li>对象移动开销：需要对所有存活对象进行移动，在对象较多时开销较大。</li>
<li>实时性差：在标记和整理过程中，程序可能会暂停，对实时性要求高的程序不友好。</li>
</ol>
<ul>
<li>使用场景：
适用于老年代，需要减少内存碎片且对象的生命周期较长的场景。</li>
</ul>
<h2 id="四、-分代收集算法-generational-garbage-collection" tabindex="-1">四、 分代收集算法 (Generational Garbage Collection) <a class="header-anchor" href="#四、-分代收集算法-generational-garbage-collection" aria-label="Permalink to “四、 分代收集算法 (Generational Garbage Collection)”">&#8203;</a></h2>
<ul>
<li>
<p>原理：
根据对象的生命周期将内存分为不同区域，通常分为新生代（Young Generation）和老年代（Old Generation）。新生代对象容易“早死”，老年代对象生命周期较长。新生代采用复制算法回收，老年代采用标记-清除或标记-整理。</p>
</li>
<li>
<p>优点：
提高回收效率：通过分代，可以对不同生命周期的对象使用不同的算法，新生代回收效率高，老年代减少回收频率。
减少暂停时间：新生代回收频率高但对象少，回收速度快，减少了程序停顿的时间。</p>
</li>
<li>
<p>缺点：
复杂度高：需要管理不同代的对象，且存在不同代之间对象引用的跨代处理，增加了实现的复杂性。
内存分配压力：当新生代对象晋升到老年代时，老年代可能会面临更大的回收压力。</p>
</li>
<li>
<p>使用场景：
JVM等现代虚拟机普遍采用分代收集算法，适用于各种应用场景。</p>
</li>
</ul>
<h2 id="五、-增量式垃圾回收-incremental-gc" tabindex="-1">五、 增量式垃圾回收 (Incremental GC) <a class="header-anchor" href="#五、-增量式垃圾回收-incremental-gc" aria-label="Permalink to “五、 增量式垃圾回收 (Incremental GC)”">&#8203;</a></h2>
<ul>
<li>原理：
将一次性完成的垃圾回收过程拆分成多个小步骤，在程序执行过程中逐步执行，减少程序长时间停顿。</li>
<li>优点：
减少暂停时间：将一次性全堆回收拆解为多个小步骤执行，避免程序长时间的停顿。</li>
<li>缺点：
整体效率降低：增量回收可能需要在GC时进行多次暂停，总体回收效率不如传统GC。</li>
<li>使用场景：
适用于实时性要求高的应用场景，如交互式系统、游戏等。</li>
</ul>
<h2 id="六、-并行和并发gc-parallel-and-concurrent-gc" tabindex="-1">六、 并行和并发GC (Parallel and Concurrent GC) <a class="header-anchor" href="#六、-并行和并发gc-parallel-and-concurrent-gc" aria-label="Permalink to “六、 并行和并发GC (Parallel and Concurrent GC)”">&#8203;</a></h2>
<ul>
<li>原理：
并行GC：多条线程同时执行GC过程，以提高回收效率。
并发GC：垃圾回收和应用线程同时执行，减少GC对应用的影响。</li>
<li>优点：
提高效率：利用多线程并行执行GC，提升回收速度。
减少停顿：并发GC可以在应用线程运行的同时进行垃圾回收，降低应用停顿时间。</li>
<li>缺点：
资源竞争：GC和应用线程争夺CPU资源，可能影响应用性能。
复杂性增加：并发GC需要处理对象被回收线程修改的问题，算法复杂度较高。</li>
<li>使用场景：
大规模、高并发的服务端系统，如数据库、Web服务器等，通常采用并发或并行GC来提升性能。</li>
</ul>
<h2 id="_7-总结" tabindex="-1">7. 总结： <a class="header-anchor" href="#_7-总结" aria-label="Permalink to “7. 总结：”">&#8203;</a></h2>
<p>不同的垃圾回收算法各有优缺点，选择时需要根据应用场景的具体需求来综合考量。现代GC常用分代收集策略结合不同算法，最大化效率并减少应用停顿时间：</p>
<ol>
<li><strong>新生代</strong>：生命周期短，使用复制算法。</li>
<li><strong>老年代</strong>：生命周期长，使用标记-清除或标记-整理算法。</li>
<li><strong>并发或并行GC</strong>：适用于高并发、大规模应用场景。</li>
</ol>
<p>这些算法相辅相成，帮助系统自动高效地管理内存，提高应用的稳定性和性能。</p>
]]></content:encoded>
            <author>Endless Young</author>
            <category>Java</category>
        </item>
    </channel>
</rss>