<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>IO01 Blog</title><link>https://blog.io01.xyz/</link><description>Recent content on IO01 Blog</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 09 Jun 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.io01.xyz/feed.xml" rel="self" type="application/rss+xml"/><item><title>从源码解析一个 SparseArray 线程不安全的崩溃</title><link>https://blog.io01.xyz/posts/2e2d/</link><pubDate>Wed, 12 Apr 2023 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/2e2d/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>工作中遇到一个崩溃，将无关逻辑去掉，最后的崩溃部分是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1&lt;/span>&lt;span>&lt;span style="color:#ff79c6">if&lt;/span> (sparseArray.&lt;span style="color:#50fa7b">get&lt;/span>(1) &lt;span style="color:#ff79c6">!=&lt;/span> &lt;span style="color:#ff79c6">null&lt;/span>) { 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2&lt;/span>&lt;span> Log.&lt;span style="color:#50fa7b">i&lt;/span>(&lt;span style="color:#f1fa8c">&amp;#34;SparseArray&amp;#34;&lt;/span>, sparseArray.&lt;span style="color:#50fa7b">get&lt;/span>(1).&lt;span style="color:#50fa7b">toString&lt;/span>()); 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3&lt;/span>&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>抛出的异常是空指针：&lt;code>java.lang.NullPointerException&lt;/code>，其实根据经验也知道是线程安全问题，但是排查的过程中却越来越觉得我们的应用场景中不应该出现 null 的存在。&lt;/p></description></item><item><title>自建NAS方案选型-基建篇</title><link>https://blog.io01.xyz/posts/6op1/</link><pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/6op1/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>上一篇讲了在 2022 年底到 2023 年初一台性能还不错并且兼顾了兼容性的 NAS 硬件选型思路，算下来价格并不便宜，但是从兼容性和零件可替换性上还是不错的。本篇更多的是关心整体系统基建而非应用层软件。&lt;strong>如果对 Linux 不够熟悉，直接无脑 Windows Server 什么都不用折腾。&lt;/strong>&lt;/p></description></item><item><title>自建NAS方案选型-硬件篇</title><link>https://blog.io01.xyz/posts/0j3l/</link><pubDate>Fri, 13 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/0j3l/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>之前使用的是多年前很火的威联通 453Bmini，2000 块钱 4 盘位的成品 NAS，性价比很高，开箱即用。虽然很多人认为可用性不如群晖，但是我个人的使用不喜欢依赖某一个品牌的生态，大多是开源组装的，所以对我来说威联通和群晖没什么本质区别。慢慢的用途越来越多，这款 NAS 选用的 J3455 性能低下和硬盘无法休眠实在太吵成了我的困扰，特别在理清了我本身不需要任何成品 NAS 生态的需求后，自己组装一台 NAS 成了唯一选择。&lt;/p></description></item><item><title>子线程更新 UI 全解</title><link>https://blog.io01.xyz/posts/9qg5/</link><pubDate>Thu, 06 Jan 2022 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/9qg5/</guid><description>&lt;h2 id="子线程更新-ui-异常设计理念及简单源码解析">子线程更新 UI 异常设计理念及简单源码解析&lt;/h2>
&lt;p>初学者可能会犯在子线程更新 UI 的错误，例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1&lt;/span>&lt;span>thread { imageView.setBackgroundColor(&lt;span style="color:#50fa7b">Color&lt;/span>.RED) }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>一旦运行，应用会直接崩溃并抛出异常，这也是我们 Android 开发的一条铁律：&lt;em>在子线程中不能更新 UI&lt;/em>。&lt;/p>
&lt;p>那么为什么 Android 不让子线程更新 UI 呢？原因在于现在屏幕刷新率最低是 60Hz，意味着最多每 16ms 就会刷新一次屏幕，所以 UI 更新要尽可能快速，否则会丢帧导致卡顿。那么 UI 更新操作就不能加锁，频繁的加锁释放锁可能会延长 UI 渲染时间，但是不加锁如果允许子线程更新 UI 会导致多个线程对 UI 同时更新，造成线程不安全而导致 UI 最终效果无法想象，所以 Android 直接限制了子线程更新 UI，实际上不只是 Android 有这种限制，常见的 UI 框架基本都是单线程模型。&lt;/p></description></item><item><title>使用阿里云效同步 Git 仓库到 Github</title><link>https://blog.io01.xyz/posts/hq29/</link><pubDate>Mon, 29 Nov 2021 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/hq29/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>有时 Github 网络的不稳定性实在令人头痛，push 总是失败，而且需要把主仓库放在 Github 而非 Gitee 等国内平台，虽然可以通过配置代理进行，不过也可以曲线救国，选择通过 push 到国内代码管理平台，然后通过 CI 平台自动推送到 Github。&lt;/p>
&lt;p>阿里云效综合了代码仓库和 CI 平台，私有代码仓库免费，免费构建时间足够支撑我们这点小事，并且执行环境可以选香港保证了能 push 到 GitHub，所以选择了阿里云效。&lt;/p>
&lt;p>理论上所有能通过 &lt;code>git push&lt;/code> 触发 CI（CI 的基本操作了）并且基础环境存在 &lt;code>git&lt;/code> 而且网络良好的平台都可以按照本文方法进行，甚至有更简单的比如 Gitlab 直接支持推送到 Github，至于本文为何没有选用 Gitlab，纯属个人偏好。&lt;/p></description></item><item><title>深入源码排查 FLAG_ACTIVITY_NEW_TASK 导致的 Activity 无法正常启动</title><link>https://blog.io01.xyz/posts/6fn7/</link><pubDate>Thu, 16 Sep 2021 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/6fn7/</guid><description>&lt;h2 id="引言">引言&lt;/h2>
&lt;p>众所周知 Android 中 Activity 有四种启动模式，决定了是否创建新的 Activity 实例或复用当前实例。但是实际上 Activity 的启动十分复杂，不单单是 &lt;code>android:launchMode&lt;/code> 就能决定的，多个 Intent Flag 都能改变启动的行为模式，除此之外还会根据 &lt;code>android:taskAffinity&lt;/code> 的值来判断对应任务栈是否存在。&lt;/p></description></item><item><title>遵循 Google 应用指南的 Retrofit + Coroutine 封装</title><link>https://blog.io01.xyz/posts/2aa2/</link><pubDate>Fri, 18 Jun 2021 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/2aa2/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>Retrofit 从 2.6.0 以后对 Kotlin 协程提供了原生支持，使得异步请求从写法上更像同步调用，配合 Google 出的 Jetpack 全家桶可以使得网络请求更加优雅简单，而且可以完全遵循 Google 给出的应用架构指南。&lt;/p>
&lt;p>另外我们有一个更加多样的需求：我们的应用使用了多套服务接口，每套接口返回的参数模板不同，baseUrl 则更多样化，因此带来了更复杂的封装逻辑。&lt;/p></description></item><item><title>时间格式化的趣事：YYYY 示年</title><link>https://blog.io01.xyz/posts/53aj/</link><pubDate>Thu, 27 Feb 2020 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/53aj/</guid><description>&lt;h2 id="引言">引言&lt;/h2>
&lt;p>2019 年 12 月 29 到 31 日在格式化时间时出现了一个有趣的小 bug：比如 2019 年 12 月 31 日时间戳有可能会被格式化成：2020-12-31。&lt;/p></description></item><item><title>MD5 计算前导 0 丢失问题</title><link>https://blog.io01.xyz/posts/33dn/</link><pubDate>Mon, 20 Jan 2020 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/33dn/</guid><description>&lt;h2 id="现象">现象&lt;/h2>
&lt;p>我们使用 Java 生成 MD5 时常用的写法如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1&lt;/span>&lt;span>&lt;span style="color:#8be9fd;font-style:italic">private&lt;/span> &lt;span style="color:#8be9fd;font-style:italic">static&lt;/span> String &lt;span style="color:#50fa7b">md5&lt;/span>(String data) &lt;span style="color:#8be9fd;font-style:italic">throws&lt;/span> NoSuchAlgorithmException {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2&lt;/span>&lt;span> MessageDigest md &lt;span style="color:#ff79c6">=&lt;/span> MessageDigest.&lt;span style="color:#50fa7b">getInstance&lt;/span>(&lt;span style="color:#f1fa8c">&amp;#34;MD5&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3&lt;/span>&lt;span> md.&lt;span style="color:#50fa7b">update&lt;/span>(data.&lt;span style="color:#50fa7b">getBytes&lt;/span>());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4&lt;/span>&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> &lt;span style="color:#ff79c6">new&lt;/span> BigInteger(1, md.&lt;span style="color:#50fa7b">digest&lt;/span>()).&lt;span style="color:#50fa7b">toString&lt;/span>(16);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5&lt;/span>&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果计算 &lt;code>a&lt;/code> 的 MD5 会发现结果为 &lt;code>cc175b9c0f1b6a831c399e269772661&lt;/code>，只有 31 位，正确的值应该是 &lt;code>0cc175b9c0f1b6a831c399e269772661&lt;/code>，仔细观察可以发现我们的计算方法把第一位 &lt;code>0&lt;/code> 丢失了。&lt;/p></description></item><item><title>从阿里规约谈起 - 包装类的值比较注意事项</title><link>https://blog.io01.xyz/posts/57fb/</link><pubDate>Fri, 29 Nov 2019 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/57fb/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>&lt;code>int&lt;/code>、&lt;code>long&lt;/code> 等基本数据类型值比较可以直接使用 &lt;code>==&lt;/code> 比较，其包装类型：&lt;code>Integer&lt;/code>、&lt;code>Long&lt;/code> 如果直接使用 &lt;code>==&lt;/code> 比较大小我们会发现一个神奇的现象。当然，阿里规约也对此做出强制规定：&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>【强制】&lt;/strong> 所有的相同类型的包装类对象之间值的比较，全部使用 &lt;code>equals&lt;/code> 方法比较。&lt;/p>
&lt;/blockquote></description></item><item><title>有趣的算法 - 布隆过滤器</title><link>https://blog.io01.xyz/posts/27fg/</link><pubDate>Wed, 30 Oct 2019 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/27fg/</guid><description>&lt;h2 id="引言">引言&lt;/h2>
&lt;p>现在假设一个需求：设计一个 url 黑名单系统，需求是 1 亿个 url 黑名单，每个 url 平均长度 30 字节，判断当前的 url 是否在黑名单中。&lt;/p>
&lt;p>我们最先想到的可能时 HashSet，如果少量的 url，HashSet 有着 O(1) 的查询效率是首选的方案。但是面对 1 亿个 url，单单存储 value 就需要 2861MB 内存，显然不可取。而如果放到硬盘上进行数据库查询，面对近 3GB 的数据库，每次匹配都要查询的话，IO 操作本身就是瓶颈。&lt;/p>
&lt;p>所以这时候引入了布隆过滤器。&lt;/p></description></item><item><title>从阿里规约谈起 - Arrays.asList 三坑</title><link>https://blog.io01.xyz/posts/23af/</link><pubDate>Tue, 24 Sep 2019 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/23af/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>将数组转换成 List 是日常开发十分常见的操作，对此 JDK 提供了一个非常好用的工具类：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1&lt;/span>&lt;span>List list &lt;span style="color:#ff79c6">=&lt;/span> Arrays.&lt;span style="color:#50fa7b">asList&lt;/span>(array);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但是如果操纵 List 的内容的话，阿里规约会给出一个提示：&lt;/p>
&lt;p>

 
 
 
 
 

&lt;img alt="使用 asList 的问题" title="" src="https://static.fori.fun/blog/posts/static/p3c_aslist_error.png" />&lt;/p>
&lt;p>于是深入看了下，发现 &lt;code>Arrays.asList&lt;/code> 有三个日常开发中容易坑人的地方。&lt;/p></description></item><item><title>从阿里规约谈起 - HashMap 初始化和扩容相关</title><link>https://blog.io01.xyz/posts/6tf9/</link><pubDate>Fri, 30 Aug 2019 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/6tf9/</guid><description>&lt;h2 id="引言">引言&lt;/h2>
&lt;p>日常开发中使用 HashMap 方法如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-java" data-lang="java">&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1&lt;/span>&lt;span>Map&lt;span style="color:#ff79c6">&amp;lt;&lt;/span>String, Object&lt;span style="color:#ff79c6">&amp;gt;&lt;/span> HashMap &lt;span style="color:#ff79c6">=&lt;/span> &lt;span style="color:#ff79c6">new&lt;/span> HashMap&lt;span style="color:#ff79c6">&amp;lt;&amp;gt;&lt;/span>();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但是阿里规约会提示有问题：&lt;/p>
&lt;p>

 
 
 
 
 

&lt;img alt="未指定初始化容量" title="" src="https://static.fori.fun/blog/posts/static/p3c_hashmap_error.png" />&lt;/p>
&lt;p>由此我们可以看一下 HashMap 初始化和扩容相关。&lt;/p>
&lt;p>注：本文涉及了少许 HashMap 基础，由于与主线无关，不详讲。源码均加了些注释，注释无法解释清楚的地方放在源码之后论述。&lt;/p></description></item><item><title>从阿里规约谈起 - 禁用 Executors 创建线程池</title><link>https://blog.io01.xyz/posts/28aj/</link><pubDate>Fri, 30 Aug 2019 00:00:00 +0000</pubDate><guid>https://blog.io01.xyz/posts/28aj/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>Android 开发中耗时任务应该放在子线程中进行，否则会阻塞 UI 造成 ANR。但是如果直接创建子线程，阿里规约会提示：&lt;/p>
&lt;p>

 
 
 
 
 

&lt;img alt="直接使用子线程报错" title="" src="https://static.fori.fun/blog/posts/static/p3c_new_thread_error.png" />&lt;/p>
&lt;p>关于禁止直接创建线程的原因如图，不再赘述。&lt;/p></description></item></channel></rss>