<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>梦断代码</title>
  
  <subtitle>乐天知命</subtitle>
  <link href="https://blog.zhanghai.me/atom.xml" rel="self"/>
  
  <link href="https://blog.zhanghai.me/"/>
  <updated>2020-11-22T09:12:59.985Z</updated>
  <id>https://blog.zhanghai.me/</id>
  
  <author>
    <name>张海</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Fixing Line Editing on Android 8.0+</title>
    <link href="https://blog.zhanghai.me/fixing-line-editing-on-android-8-0/"/>
    <id>https://blog.zhanghai.me/fixing-line-editing-on-android-8-0/</id>
    <published>2020-11-22T04:24:25.000Z</published>
    <updated>2020-11-22T09:12:59.985Z</updated>
    
    <content type="html"><![CDATA[<p>Recently I’ve been working on a toy that launches an REPL in <code>adb shell</code>, which would be more comfortable to use if it has the line editing functionality similar to <code>bash</code>. After some research, I found that actually there are a number of line editing libraries available, so it should be a nice and easy addition. However, after some integration work, I was surprised to find that all these libraries (e.g. <a href="https://github.com/jline/jline3">jline3</a>, <a href="https://github.com/antirez/linenoise">linenoise</a> (which was also once <a href="https://android.googlesource.com/platform/system/core/+/refs/tags/android-4.4.4_r2.0.1/liblinenoise/">imported into AOSP</a>)) only worked on older versions of Android, that is, until Android 8.0.</p><span id="more"></span><h2 id="Identifying-the-issue"><a href="#Identifying-the-issue" class="headerlink" title="Identifying the issue"></a>Identifying the issue</h2><p>So I created a <a href="https://github.com/zhanghai/linenoise-android">small sample app</a> with <code>linenoise</code> and debugged it to pinpoint the issue. Strangely, I found that it was <a href="https://github.com/antirez/linenoise/blob/97d2850af13c339369093b78abe5265845d78220/linenoise.c#L258">the following line</a> that failed:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* put terminal in raw mode after flushing */</span></span><br><span class="line"><span class="keyword">if</span> (tcsetattr(fd,TCSAFLUSH,&amp;raw) &lt; <span class="number">0</span>) <span class="keyword">goto</span> fatal;</span><br></pre></td></tr></table></figure><p>Which returned <code>-1</code> with <code>errno</code> set to <code>EACCES</code> (<code>13</code>). But why would this terminal configuration fail anyway?</p><p>So I took a look at <a href="https://linux.die.net/man/3/tcsetattr"><code>man tcsetattr</code></a>:</p><blockquote><p>tcsetattr() sets the parameters associated with the terminal (unless support is required from the underlying hardware that is not available) from the termios structure referred to by termios_p. optional_actions specifies when the changes take effect:</p><p>TCSANOW</p><p>the change occurs immediately.</p><p>TCSADRAIN</p><p>the change occurs after all output written to fd has been transmitted. This function should be used when changing parameters that affect output.</p><p>TCSAFLUSH</p><p>the change occurs after all output written to the object referred by fd has been transmitted, and all input that has been received but not read will be discarded before the change is made.</p></blockquote><p>The manual page didn’t contain any information for specific error codes, so it wasn’t very helpful for understanding why we’ve got <code>EACCES</code>. Maybe we’ve lost some kind of access to the TTY device since Android 8.0+? However, the default Android shell <code>mksh</code> (used by <code>adb shell</code>) still has working line editing, and how that was done became another mystery to me.</p><p>So I began searching for <code>tcsetattr Android</code>, and found that the Termux people also noticed <a href="https://github.com/termux/termux-packages/issues/1359">the same issue</a> years ago — the <code>EACCES</code> was actually a SELinux denial. And as a workaround, they were redefining <code>TCSAFLUSH</code> to be <code>TCSANOW</code>, but that isn’t functionally equivalent and may be subject to subtle breakages. So I decided to find out what was actually going wrong in SELinux.</p><p>My next step was to figure out why line editing is still working for <code>mksh</code> — is there any similar patch in AOSP, and how did AOSP do it? After some code search, I found out that <code>mksh</code> was actually <a href="https://cs.android.com/android/platform/superproject/+/master:external/mksh/src/sh.h;l=530;drc=a5d35a625a246b18a75388646d6b9b861c6dda4b">using <code>TCSADRAIN</code> instead of <code>TCSAFLUSH</code></a> since a long time ago, so it seems both <code>TCSANOW</code> and <code>TCSADRAIN</code> are working, and it’s just <code>TCSAFLUSH</code> became broken on Android 8.0+. However, <code>TCSAFLUSH</code> still sounds more robust and is still <a href="https://cs.android.com/search?q=TCSAFLUSH%20-f:.*%5C.(h%7Cgo%7Cpy%7Crs%7Ctxt)">being used in a number of places in Android</a>, notably <code>toybox</code>‘s <code>stty</code> implementation (which broke <code>jline3</code>). So I think it’s still worth fixing <code>TCSAFLUSH</code>.</p><h2 id="Debugging-the-SELinux-denial"><a href="#Debugging-the-SELinux-denial" class="headerlink" title="Debugging the SELinux denial"></a>Debugging the SELinux denial</h2><p>To debug the SELinux denial, I looked at the SELinux logcat message on an Android 11 emulator:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">type=1400 audit(0.0:763): avc: denied &#123; ioctl &#125; for path=&quot;/dev/pts/0&quot; dev=&quot;devpts&quot; ino=3 ioctlcmd=0x5404 scontext=u:r:shell:s0 tcontext=u:object_r:devpts:s0 tclass=chr_file permissive=0</span><br></pre></td></tr></table></figure><p>It is clear that <code>tcsetattr</code> is actually a wrapper over an <code>ioctl</code> operation on the TTY device, and Android do have SELinux rules over <code>ioctl</code>. So I looked for <code>devpts</code> and <code>ioctl</code> inside <a href="https://cs.android.com/android/platform/superproject/+/master:system/sepolicy/">system&#x2F;sepolicy</a>, and found the macro <code>unpriv_tty_ioctls</code> in <a href="https://cs.android.com/android/platform/superproject/+/master:system/sepolicy/public/ioctl_macros;l=51;drc=5fe69e082ac44aba637469fc59fee9f311c8d20b"><code>ioctl_macros</code></a>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># commonly used TTY ioctls</span><br><span class="line"># merge with unpriv_unix_sock_ioctls?</span><br><span class="line">define(`unpriv_tty_ioctls&#x27;, `&#123;</span><br><span class="line">  TIOCOUTQ FIOCLEX FIONCLEX TCGETS TCSETS TIOCGWINSZ TIOCSWINSZ TIOCSCTTY</span><br><span class="line">  TCSETSW TCFLSH TIOCSPGRP TIOCGPGRP</span><br><span class="line">&#125;&#x27;)</span><br></pre></td></tr></table></figure><p>Which is used in <a href="https://cs.android.com/android/platform/superproject/+/master:system/sepolicy/public/domain.te;l=293;drc=12376168b44904799936cad2ea1de10dfad456f6"><code>domain.te</code></a>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># Restrict PTYs to only allowed ioctls.</span><br><span class="line"># Note that granting this allowlist to domain does</span><br><span class="line"># not grant the wider ioctl permission. That must be granted</span><br><span class="line"># separately.</span><br><span class="line">allowxperm domain devpts:chr_file ioctl unpriv_tty_ioctls;</span><br></pre></td></tr></table></figure><p>So I took a look at <a href="https://linux.die.net/man/4/tty_ioctl"><code>man tty_ioctl</code></a>:</p><blockquote><p>TCSETS</p><p>const struct termios *argp</p><p>Equivalent to tcsetattr(fd, TCSANOW, argp).</p><p>Set the current serial port settings.</p><p>TCSETSW</p><p>const struct termios *argp</p><p>Equivalent to tcsetattr(fd, TCSADRAIN, argp).</p><p>Allow the output buffer to drain, and set the current serial port settings.</p><p>TCSETSF</p><p>const struct termios *argp</p><p>Equivalent to tcsetattr(fd, TCSAFLUSH, argp).</p><p>Allow the output buffer to drain, discard pending input, and set the current serial port settings.</p></blockquote><p>Notably, the macro <code>unpriv_tty_ioctls</code> included <code>TCSETS</code> and <code>TCSETSW</code>, but not <code>TCSETSF</code>. So it seems it’s indeed the missing <code>TCSETSF</code> that caused this SELinux denial.</p><h2 id="Fixing-the-SELinux-policy"><a href="#Fixing-the-SELinux-policy" class="headerlink" title="Fixing the SELinux policy"></a>Fixing the SELinux policy</h2><p>Now that we have the root cause, can we fix it by simply adding <code>TCSETSF</code> to the macro <code>unpriv_tty_ioctls</code>, or was there any other concerns about it so that it was deliberately left out?</p><p>So I looked into git history of the file. Actually, the variant <code>TCSETSW</code> was the initial one added into <code>unpriv_tty_ioctls</code> in <a href="https://android-review.googlesource.com/c/platform/system/sepolicy/+/306278/">aosp&#x2F;306278</a>, and the base <code>TCSETS</code> was added later by <a href="https://android-review.googlesource.com/c/platform/system/sepolicy/+/310920/">aosp&#x2F;310920</a>. Looking at the initial change <a href="https://android-review.googlesource.com/c/platform/system/sepolicy/+/306278/">aosp&#x2F;306278</a>, the primary reason to restrict ioctl for TTY devices was to mitigate the security exploit of <code>TIOCSTI</code>, so it indeed seems that adding <code>TCSETSF</code> as another variant of <code>TCSETS</code> and <code>TCSETSW</code> would be fine, and should fix all the code relying on it.</p><h2 id="Testing-and-submitting-the-fix"><a href="#Testing-and-submitting-the-fix" class="headerlink" title="Testing and submitting the fix"></a>Testing and submitting the fix</h2><p>Then I started testing my fix locally. As I’m working on Android already, I naturally starting tested on an eng build, however soon I was surprised to find that my sample app runs perfectly even without my fix. Was it somehow patched recently? However since I’ve been looking at the latest version of the source code anyway, was my entire theory going in the wrong direction? I became deeply confused, until I suddenly realized the special thing about eng build — <code>adb shell</code> has root access.</p><p>So I immediately checked the SELinux context of the shell with <code>ps -AZ | grep sh</code>, and the output was <code>u:r:su:s0</code>, so actually this wouldn’t be the usual SELinux context on consumer devices because the shell process can’t be in the <code>su</code> domain. The <code>su</code> domain has a lot of privileges, and no wonder my sample app was working fine inside it. I became relieved that I’m not dealing with something crazy, and began testing on a user build instead. This time the sample app failed without the fix, and worked when my fix was applied, so this was finally confirmed to be the correct fix. Yay!</p><p>The remaining work was uploading the patch as <a href="https://android-review.googlesource.com/c/platform/system/sepolicy/+/1491378/">aosp&#x2F;1491378</a>, getting it persubmit verified and code reviewed, and finally submitting the change. This concluded my small weekend journey into SELinux, and the <code>TCSAFLUSH</code> issue will become fixed with the Android S release. However since there’s no way to backport the fix, we still have to stick with <code>TCSADRAIN</code> for Android 8.0–11 compatibility for some years.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently I’ve been working on a toy that launches an REPL in &lt;code&gt;adb shell&lt;/code&gt;, which would be more comfortable to use if it has the line editing functionality similar to &lt;code&gt;bash&lt;/code&gt;. After some research, I found that actually there are a number of line editing libraries available, so it should be a nice and easy addition. However, after some integration work, I was surprised to find that all these libraries (e.g. &lt;a href=&quot;https://github.com/jline/jline3&quot;&gt;jline3&lt;/a&gt;, &lt;a href=&quot;https://github.com/antirez/linenoise&quot;&gt;linenoise&lt;/a&gt; (which was also once &lt;a href=&quot;https://android.googlesource.com/platform/system/core/+/refs/tags/android-4.4.4_r2.0.1/liblinenoise/&quot;&gt;imported into AOSP&lt;/a&gt;)) only worked on older versions of Android, that is, until Android 8.0.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Android" scheme="https://blog.zhanghai.me/tags/android/"/>
    
    <category term="Linux" scheme="https://blog.zhanghai.me/tags/linux/"/>
    
    <category term="Software" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>Remapping ThinkPad Keys with udev hwdb</title>
    <link href="https://blog.zhanghai.me/remapping-thinkpad-keys-with-udev-hwdb/"/>
    <id>https://blog.zhanghai.me/remapping-thinkpad-keys-with-udev-hwdb/</id>
    <published>2019-02-11T01:28:33.000Z</published>
    <updated>2023-09-23T05:28:16.784Z</updated>
    
    <content type="html"><![CDATA[<p>ThinkPad T series (also X1 Carbon) laptops have a great keyboard that I’ve been using delightfully for years. However, there’s a minor issue with its keyboard layout: they replaced the <code>Menu</code> key with a <code>PrtSc</code>. In my day to day work, I almost always accidentally hit that key while using my lovely <code>Ctrl</code> and <code>Alt</code> keys, upon which my laptop happily plays a shatter sound, flashes my screen white for a split second and spawns a screenshot file under my <code>Pictures/</code> (thank you, GNOME). Whereas when I wanted to use my <code>Menu</code> key, it’s nowhere to be found.</p><p>However, there’s still an <code>Insert</code> key lying quietly in the top-right corner, which I never used (except for checking if some app even supports it). So why not make my old <code>Insert</code> <code>PrtSc</code> and my old <code>PrtSc</code> the new <code>Menu</code>?</p><p>Moreover, there are also 4 special keys (<code>Fn</code> + <code>F9</code>–<code>F12</code>) that could have been my media keys, but are by default strange things like <code>Settings</code> and <code>Search</code>. Why not map them to media keys as well?</p><img src="/remapping-thinkpad-keys-with-udev-hwdb/thinkpad-keyboard.webp" class=""title="ThinkPad keyboard (credit: Lenovo)"><span id="more"></span><h2 id="Things-I-tried"><a href="#Things-I-tried" class="headerlink" title="Things I tried"></a>Things I tried</h2><p><a href="https://wiki.archlinux.org/index.php/xmodmap"><code>xmodmap</code></a> was the go-to tool for remapping keys, and it works on the X11 server level so the only thing you need to care about is the X11 key symbols (no key codes, scan codes and other nightmares). However, naturally this tool won’t work under Wayland (which supports fractional scaling etc), and it cannot get automatically loaded by my GNOME 3 (even with autostart). Some say it’s been deprecated, and people should use <code>xkb</code> instead, so no luck here.</p><p>The other way is to modify the <code>xkb</code> key symbol database. Although it doesn’t provide any means of overriding in <code>/etc/</code>, you can directly edit the files under <code>/usr/share/X11/xkb/symbols/</code>. The interesting files are <code>pc</code> for the standard keys, and <code>inet</code> for the special keys, and here’s the patch I’ve been using for two years:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">diff --git a/pc b/pc.zhnew</span></span><br><span class="line"><span class="comment">index 0199713..33631cb 100644</span></span><br><span class="line"><span class="comment">--- a/pc</span></span><br><span class="line"><span class="comment">+++ b/pc.zhnew</span></span><br><span class="line"><span class="meta">@@ -29,7 +29,7 @@</span> xkb_symbols &quot;pc105&quot; &#123;</span><br><span class="line">     key &lt;RTSH&gt; &#123;[Shift_R]&#125;;</span><br><span class="line">     key &lt;RCTL&gt; &#123;[Control_R]&#125;;</span><br><span class="line">     key &lt;RWIN&gt; &#123;[Super_R]&#125;;</span><br><span class="line"><span class="deletion">-    key &lt;MENU&gt; &#123;[Menu]&#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;PRSC&gt; &#123;[Menu]&#125;;</span></span><br><span class="line"></span><br><span class="line">     // Beginning of modifier mappings.</span><br><span class="line">     modifier_map Shift  &#123; Shift_L, Shift_R &#125;;</span><br><span class="line"><span class="meta">@@ -64,7 +64,7 @@</span> xkb_symbols &quot;pc105&quot; &#123;</span><br><span class="line"></span><br><span class="line"> hidden partial alphanumeric_keys</span><br><span class="line"> xkb_symbols &quot;editing&quot; &#123;</span><br><span class="line"><span class="deletion">-    key &lt;PRSC&gt; &#123;</span></span><br><span class="line"><span class="addition">+    key  &lt;INS&gt; &#123;</span></span><br><span class="line"> type= &quot;PC_ALT_LEVEL2&quot;,</span><br><span class="line"> symbols[Group1]= [Print, Sys_Req]</span><br><span class="line">     &#125;;</span><br><span class="line"><span class="meta">@@ -73,7 +73,7 @@</span> xkb_symbols &quot;editing&quot; &#123;</span><br><span class="line"> type= &quot;PC_CONTROL_LEVEL2&quot;,</span><br><span class="line"> symbols[Group1]= [Pause, Break]</span><br><span class="line">     &#125;;</span><br><span class="line"><span class="deletion">-    key  &lt;INS&gt; &#123;[Insert]&#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;MENU&gt; &#123;[Insert]&#125;;</span></span><br><span class="line">     key &lt;HOME&gt; &#123;[Home]&#125;;</span><br><span class="line">     key &lt;PGUP&gt; &#123;[Prior]&#125;;</span><br><span class="line">     key &lt;DELE&gt; &#123;[Delete]&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">diff --git a/inet b/inet.zhnew</span></span><br><span class="line"><span class="comment">index 755597e..f4b86d6 100644</span></span><br><span class="line"><span class="comment">--- a/inet</span></span><br><span class="line"><span class="comment">+++ b/inet.zhnew</span></span><br><span class="line"><span class="meta">@@ -121,13 +121,13 @@</span> xkb_symbols &quot;evdev&quot; &#123;</span><br><span class="line"></span><br><span class="line"> //  key &lt;I120&gt;   &#123;      []       &#125;; // KEY_MACRO</span><br><span class="line">     key &lt;I126&gt;   &#123;      [plusminus]       &#125;;</span><br><span class="line"><span class="deletion">-    key &lt;I128&gt;   &#123;      [XF86LaunchA]       &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I173&gt;   &#123;      [XF86LaunchA]       &#125;;</span></span><br><span class="line">     key &lt;I147&gt;   &#123;      [XF86MenuKB]       &#125;;</span><br><span class="line">     key &lt;I148&gt;   &#123;      [XF86Calculator]       &#125;;</span><br><span class="line"> //  key &lt;I149&gt;   &#123;      []       &#125;; // KEY_SETUP</span><br><span class="line">     key &lt;I150&gt;   &#123;      [XF86Sleep]       &#125;;</span><br><span class="line">     key &lt;I151&gt;   &#123;      [XF86WakeUp]       &#125;;</span><br><span class="line"><span class="deletion">-    key &lt;I152&gt;   &#123;      [XF86Explorer]       &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I171&gt;   &#123;      [XF86Explorer]       &#125;;</span></span><br><span class="line">     key &lt;I153&gt;   &#123;      [XF86Send]       &#125;;</span><br><span class="line"> //  key &lt;I154&gt;   &#123;      []       &#125;; // KEY_DELETEFILE</span><br><span class="line">     key &lt;I155&gt;   &#123;      [XF86Xfer]       &#125;;</span><br><span class="line"><span class="meta">@@ -146,15 +146,15 @@</span> xkb_symbols &quot;evdev&quot; &#123;</span><br><span class="line"> //  key &lt;I168&gt;   &#123;      []       &#125;; // KEY_CLOSECD (opposite of eject)</span><br><span class="line">     key &lt;I169&gt;   &#123;      [XF86Eject]       &#125;;</span><br><span class="line">     key &lt;I170&gt;   &#123;      [XF86Eject, XF86Eject]       &#125;;</span><br><span class="line"><span class="deletion">-    key &lt;I171&gt;   &#123;      [XF86AudioNext]       &#125;;</span></span><br><span class="line"><span class="deletion">-    key &lt;I172&gt;   &#123;      [XF86AudioPlay, XF86AudioPause] &#125;;</span></span><br><span class="line"><span class="deletion">-    key &lt;I173&gt;   &#123;      [XF86AudioPrev]       &#125;;</span></span><br><span class="line"><span class="deletion">-    key &lt;I174&gt;   &#123;      [XF86AudioStop, XF86Eject]    &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I152&gt;   &#123;      [XF86AudioNext]       &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I179&gt;   &#123;      [XF86AudioPlay, XF86AudioPause] &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I128&gt;   &#123;      [XF86AudioPrev]       &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I225&gt;   &#123;      [XF86AudioStop, XF86Eject]    &#125;;</span></span><br><span class="line">     key &lt;I175&gt;   &#123;      [XF86AudioRecord]       &#125;;</span><br><span class="line">     key &lt;I176&gt;   &#123;      [XF86AudioRewind]       &#125;;</span><br><span class="line">     key &lt;I177&gt;   &#123;      [XF86Phone]       &#125;;</span><br><span class="line"> //  key &lt;I178&gt;   &#123;      []       &#125;; // KEY_ISO</span><br><span class="line"><span class="deletion">-    key &lt;I179&gt;   &#123;      [XF86Tools]       &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I172&gt;   &#123;      [XF86Tools]       &#125;;</span></span><br><span class="line">     key &lt;I180&gt;   &#123;      [XF86HomePage]       &#125;;</span><br><span class="line">     key &lt;I181&gt;   &#123;      [XF86Reload]       &#125;;</span><br><span class="line">     key &lt;I182&gt;   &#123;      [XF86Close]       &#125;;</span><br><span class="line"><span class="meta">@@ -188,7 +188,7 @@</span> xkb_symbols &quot;evdev&quot; &#123;</span><br><span class="line"> //  key &lt;I222&gt;   &#123;      []       &#125;; // KEY_QUESTION</span><br><span class="line">     key &lt;I223&gt;   &#123;      [XF86Mail]       &#125;;</span><br><span class="line">     key &lt;I224&gt;   &#123;      [XF86Messenger]       &#125;; // KEY_CHAT</span><br><span class="line"><span class="deletion">-    key &lt;I225&gt;   &#123;      [XF86Search]       &#125;;</span></span><br><span class="line"><span class="addition">+    key &lt;I174&gt;   &#123;      [XF86Search]       &#125;;</span></span><br><span class="line">     key &lt;I226&gt;   &#123;      [XF86Go]       &#125;; // KEY_CONNECT</span><br><span class="line">     key &lt;I227&gt;   &#123;      [XF86Finance]       &#125;;</span><br><span class="line">     key &lt;I228&gt;   &#123;      [XF86Game]       &#125;; // KEY_SPORT</span><br></pre></td></tr></table></figure><p>It works perfectly, without any overhead. The only problem with it, is that because these things live inside <code>/usr/</code>, which is managed by <code>pacman</code>, it is reverted to packaged version every time <code>xkeyboard-config</code> is updated, and it actually does get updated sometimes. In that case I’ll find me suddenly making screenshots again, and need to patch those files and reboot for things to work.</p><h2 id="Using-udev-hwdb"><a href="#Using-udev-hwdb" class="headerlink" title="Using udev hwdb"></a>Using udev hwdb</h2><p>Recently when I was doing some brief research about new ThinkPads, I came across the <a href="https://wiki.archlinux.org/index.php/Lenovo_ThinkPad_T480#Special_buttons">ArchWiki for ThinkPad T480</a>, which mentioned using something called hwdb to add support for its two special buttons. It looked promising, and I finally took some hours today to figure it out for my own remapping.</p><p>The hwdb in udev works on a much lower level: it maps the scan codes from your keyboard to standard key codes, and <code>/etc/udev/hwdb.d/</code> provides a means of customization, which allows overriding the way scan codes are mapped. Some more detail can be found out on <a href="https://wiki.archlinux.org/index.php/map_scancodes_to_keycodes">Arch Wiki</a>.</p><p>And here is the final hwdb file I came up with:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># /etc/udev/hwdb.d/90-zh-thinkpad.hwdb</span><br><span class="line"></span><br><span class="line">evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*</span><br><span class="line"> KEYBOARD_KEY_b7=compose</span><br><span class="line"> KEYBOARD_KEY_d2=sysrq</span><br><span class="line"></span><br><span class="line">evdev:name:ThinkPad Extra Buttons:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*:pvrThinkPad*</span><br><span class="line"> KEYBOARD_KEY_1c=playpause</span><br><span class="line"> KEYBOARD_KEY_1d=stopcd</span><br><span class="line"> KEYBOARD_KEY_1e=previoussong</span><br><span class="line"> KEYBOARD_KEY_1f=nextsong</span><br></pre></td></tr></table></figure><p>The hwdb rules we need to write consists of two parts: matching and mapping. The matching expression is a shell glob that matches the device, where as the mapping maps the scan code (in hex) to key code macro names in kernel’s <a href="https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h"><code>include/uapi/linux/input-event-codes.h</code></a>. <code>man hwdb</code> provided some simple example, but actually comments in the <a href="https://github.com/systemd/systemd/blob/master/hwdb/60-keyboard.hwdb">built-in hwdb file</a> provides much more details about this file.</p><p>In order to find out the scan codes for my keys, I tried both methods in <a href="https://wiki.archlinux.org/index.php/Keyboard_input#Identifying_scancodes">Arch Wiki</a>. The traditional <code>showkey --scancodes</code> didn’t work well for me, requiring switching to a tty, and was printing multiple bytes of hex for a single keystroke of mine. In contrast, <code>evtest</code> was just the right tool. Just execute <code>sudo evtest</code> in a terminal and select something like <code>AT Translated Set 2 keyboard</code> for the builtin keyboard (mine is <code>3</code>), and you can test your keystrokes to find out its scan code in output like <code>(MSC_SCAN), value &lt;SCAN CODE HERE&gt;</code>.</p><img src="/remapping-thinkpad-keys-with-udev-hwdb/evtest-devices.png" class=""title="evtest listing devices"><img src="/remapping-thinkpad-keys-with-udev-hwdb/evtest-events.png" class=""title="evtest printing events"><p>Another caveat is that, for the special keys on ThinkPad keyboard, unlike regular keys they are not listed under the <code>AT Translated Set 2 keyboard</code>, but actually under another input device named <code>ThinkPad Extra Buttons</code>. It took me some time to realize this, and I also tried <code>showkey</code> which disappointed me again.</p><p>After we’ve got the scan codes from <code>evtest</code> and key codes from <code>input-event-codes.h</code>, it’s time to write the rule. We need the matching part for the two devices, whose format specification is available in the comment inside systemd’s bulitin hwdb file. The exact info for your current machine can be obtained from <code>cat /sys/class/dmi/id/modalias</code>, and combining with other existing ThinkPad rules in <code>/usr/lib/udev/hwdb.d/60-keyboard.hwdb</code> I derived mine successfully.</p><p>The actual rules read by udev upon boot is a compiled binary file called <code>hwdb.bin</code>, so one will need to compile the configuration files into binary with <code>sudo systemd-hwdb update</code>. To make the changes take effect immediately, run <code>sudo udevadm trigger</code>, and finally, try out the new key mapping!</p><h2 id="One-last-thing…-the-Menu-key"><a href="#One-last-thing…-the-Menu-key" class="headerlink" title="One last thing… the Menu key"></a>One last thing… the Menu key</h2><p>Most of my key mapping worked, except for my new <code>Menu</code> key. I double checked the scan code and the key code name – they both seemed correct. In <code>input-event-codes.h</code>, I also found <code>KEY_OPITON</code> and <code>KEY_CONTEXT_MENU</code>, but neither of them worked as <code>Menu</code> key as well.</p><p>So I tried <code>xev</code>. Interestingly, it printed <code>XF86MenuKB</code> as the key symbol on the X11 level, instead of <code>Menu</code>. This must be something with the X11 key symbol database. I did some grep, played around for around half an hour, and finally found out the answer when I expanded my search into <code>/usr/share/X11/xkb/keycodes/evdev</code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">alias &lt;MENU&gt; = &lt;COMP&gt;;</span><br><span class="line">...</span><br><span class="line">&lt;I147&gt; = 147;// #define KEY_MENU                139</span><br></pre></td></tr></table></figure><p>Combined with the output from <code>xmodmap -pke | grep Menu</code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">keycode 135 = Menu NoSymbol Menu</span><br><span class="line">keycode 147 = XF86MenuKB NoSymbol XF86MenuKB</span><br></pre></td></tr></table></figure><p>I surprisingly found out that my key code <code>KEY_MENU</code> was mapped to key symbol <code>XF86MenuKB</code>. And to map to key symbol <code>Menu</code>, I actually need to map my scan code to key code <code>KEY_COMP</code>.</p><p>So one last change, save, and <code>sudo systemd-hwdb update &amp;&amp; sudo udevadm trigger</code>. Hooray! All my keys are working flawlessly now, and I don’t need to worry about package updates anymore (just forget about <code>xkeyboard-config</code>). Although the obscure and scattered documentation put me through these tedious trial-and-error attempts, it still kinda excited me when I finally got my keys remapped correctly, after all these years.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;ThinkPad T series (also X1 Carbon) laptops have a great keyboard that I’ve been using delightfully for years. However, there’s a minor issue with its keyboard layout: they replaced the &lt;code&gt;Menu&lt;/code&gt; key with a &lt;code&gt;PrtSc&lt;/code&gt;. In my day to day work, I almost always accidentally hit that key while using my lovely &lt;code&gt;Ctrl&lt;/code&gt; and &lt;code&gt;Alt&lt;/code&gt; keys, upon which my laptop happily plays a shatter sound, flashes my screen white for a split second and spawns a screenshot file under my &lt;code&gt;Pictures/&lt;/code&gt; (thank you, GNOME). Whereas when I wanted to use my &lt;code&gt;Menu&lt;/code&gt; key, it’s nowhere to be found.&lt;/p&gt;
&lt;p&gt;However, there’s still an &lt;code&gt;Insert&lt;/code&gt; key lying quietly in the top-right corner, which I never used (except for checking if some app even supports it). So why not make my old &lt;code&gt;Insert&lt;/code&gt; &lt;code&gt;PrtSc&lt;/code&gt; and my old &lt;code&gt;PrtSc&lt;/code&gt; the new &lt;code&gt;Menu&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Moreover, there are also 4 special keys (&lt;code&gt;Fn&lt;/code&gt; + &lt;code&gt;F9&lt;/code&gt;–&lt;code&gt;F12&lt;/code&gt;) that could have been my media keys, but are by default strange things like &lt;code&gt;Settings&lt;/code&gt; and &lt;code&gt;Search&lt;/code&gt;. Why not map them to media keys as well?&lt;/p&gt;
&lt;img src=&quot;/remapping-thinkpad-keys-with-udev-hwdb/thinkpad-keyboard.webp&quot; class=&quot;&quot; title=&quot;ThinkPad keyboard (credit: Lenovo)&quot;&gt;</summary>
    
    
    
    
    <category term="Linux" scheme="https://blog.zhanghai.me/tags/linux/"/>
    
    <category term="Software" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>Writing a Comparator for Natural Sort Order</title>
    <link href="https://blog.zhanghai.me/writing-a-comparator-for-natural-sort-order/"/>
    <id>https://blog.zhanghai.me/writing-a-comparator-for-natural-sort-order/</id>
    <published>2018-06-16T21:03:48.000Z</published>
    <updated>2023-09-23T06:55:14.107Z</updated>
    
    <content type="html"><![CDATA[<p>Natural sort order is what we usually see in most file managers when browsing a list of numbered files, that it sorts the numerical part of the file name as a number instead of comparing it character by character. For instance:</p><table><thead><tr><th>Lexicographical sort</th><th>Natural sort</th></tr></thead><tbody><tr><td>1.txt</td><td>1.txt</td></tr><tr><td>10.txt</td><td>2.txt</td></tr><tr><td>100.txt</td><td>3.txt</td></tr><tr><td>101.txt</td><td>4.txt</td></tr><tr><td>102.txt</td><td>5.txt</td></tr><tr><td>103.txt</td><td>10.txt</td></tr><tr><td>104.txt</td><td>11.txt</td></tr><tr><td>105.txt</td><td>12.txt</td></tr><tr><td>11.txt</td><td>13.txt</td></tr><tr><td>12.txt</td><td>14.txt</td></tr><tr><td>13.txt</td><td>15.txt</td></tr><tr><td>14.txt</td><td>100.txt</td></tr><tr><td>15.txt</td><td>101.txt</td></tr><tr><td>2.txt</td><td>102.txt</td></tr><tr><td>3.txt</td><td>103.txt</td></tr><tr><td>4.txt</td><td>104.txt</td></tr><tr><td>5.txt</td><td>105.txt</td></tr></tbody></table><p>Lexicographical sort can be easily implemented using <code>String::compareToIgnoreCase</code>, but it is not very acceptable for end users. However for natural sort, things is actually a little more complicated.</p><span id="more"></span><h2 id="The-Definition"><a href="#The-Definition" class="headerlink" title="The Definition"></a>The Definition</h2><p>There is a small <a href="https://www.wikiwand.com/en/Natural_sort_order">Wikipedia article on Natural Sort Order</a>, which mentioned the following definition for natural sort order:</p><blockquote><p>Natural sort order is an ordering of strings in alphabetical order, except that multi-digit numbers are ordered as a single character.</p></blockquote><p>However, there’s still some undefined behavior about this sort order when case-insensitivity is involved alongside treating numbers as a whole. In <a href="https://jjacky.com/2013-03-22-natural-sort-order-in-c/">an article discussing natural sort order in C</a>, the author mentioned the following case:</p><table><thead><tr><th>Name</th></tr></thead><tbody><tr><td>Test1</td></tr><tr><td>tesT1</td></tr><tr><td>test1</td></tr><tr><td>tEst2</td></tr><tr><td>test2</td></tr><tr><td>test3</td></tr></tbody></table><p>These string is listed in a reasonable way, however if you simply define the natural sorting to be sorting chunks of characters and numbers individually, the <code>Test</code> string have to be equal to <code>test</code> so that the ordering won’t be deterministic.</p><p>And there’s another case involving leading zeros:</p><table><thead><tr><th>Name</th></tr></thead><tbody><tr><td>test1</td></tr><tr><td>test01</td></tr><tr><td>test001</td></tr><tr><td>test01a</td></tr></tbody></table><p>Intuitively, things that are shorter should come before those longer ones, so equal numbers with less leading zeros should come before those with more. Furthermore, if there’s more text following the number, it should come after those without, thus the aforementioned leading zero comparison has a lower priority.</p><p>I didn’t find any standard for these corner cases, so I’m merely deciding on the most reasonable approach.</p><h2 id="Various-Implementations"><a href="#Various-Implementations" class="headerlink" title="Various Implementations"></a>Various Implementations</h2><p>There’s <a href="https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/">an article on CodingHorror</a> and <a href="https://stackoverflow.com/questions/1262239/natural-sort-order-string-comparison-in-java-is-one-built-in">a StackOverflow question</a> that referenced some existing implementations.</p><p>And here are some additional implementations that I found inside some battle-tested applications:</p><ul><li><a href="https://github.com/eneim/cabinet/blob/master/app/src/main/java/com/afollestad/cabinet/utils/AlphanumComparator.java">Cabinet&#x2F;AlphanumComparator</a>: Adapted from DaveKoelle’s <code>AlphanumComparator</code>.</li><li><a href="https://github.com/GNOME/glib/blob/master/glib/gunicollate.c">glib&#x2F;g_utf8_collate_key_for_filename</a>: Used by Nautilus, generates a new string that can be used for comparison.</li><li><a href="https://github.com/coreutils/gnulib/blob/master/lib/filevercmp.c">gnulib&#x2F;filevercmp</a>: Used by GNU coreutils’ <code>ls -v</code>.</li><li><a href="https://github.com/coreutils/coreutils/blob/master/src/sort.c">coreutils&#x2F;sort</a>: Used by GNU coreutils’ <code>sort -V</code>.</li></ul><p>But there’s some common issues in existing implementations:</p><ul><li>Use of regular expression: The comparator is frequently evaluated during the sort, so performance is our concern and splitting on regular expressions is not what we expect under such conditions.</li><li>Memory allocation: Also due to the frequent invocation, we should avoid memory allocation (e.g. actually building those “chunks”), and this can be done by iteration over the two strings.</li><li>Unicode awareness: Unicode code points are not necessarily single <code>char</code>s (think of surrogates), so those code point related methods should be used instead of simple increment to advance the index of current “character”.</li><li>Locale awareness: Instead of <code>c &gt;= &#39;0&#39; &amp;&amp; c &lt;= &#39;9&#39;</code>, one should use <code>Character.isDigit()</code> and <code>Character.digit()</code> for getting numbers out of a character.</li><li>Correctness: Some implementations cannot handle the two aforementioned corner cases properly.</li><li>Coding style: Some implementations are simply not so readable.</li></ul><h2 id="My-Implementation"><a href="#My-Implementation" class="headerlink" title="My Implementation"></a>My Implementation</h2><p>So I wrote my own implementation with those issues in mind, and it basically iterates over the two string and handles consecutive digits together. Most of the code is self-explanatory and the comments can also describe the algorithm:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NaturalOrderComparator</span> <span class="keyword">implements</span> <span class="title class_">Comparator</span>&lt;String&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">DIGIT_RADIX</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compare</span><span class="params">(String string1, String string2)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">start1</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">start2</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">leadingZeroCompareResult</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (start1 &lt; string1.length() &amp;&amp; start2 &lt; string2.length()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">codePoint1</span> <span class="operator">=</span> string1.codePointAt(start1);</span><br><span class="line">            <span class="type">int</span> <span class="variable">codePoint2</span> <span class="operator">=</span> string2.codePointAt(start2);</span><br><span class="line">            <span class="comment">// Check if both code points are digits.</span></span><br><span class="line">            <span class="keyword">if</span> (!Character.isDigit(codePoint1) || !Character.isDigit(codePoint2)) &#123;</span><br><span class="line">                <span class="keyword">if</span> (!codePointEqualsIgnoreCase(codePoint1, codePoint2)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> codePointCompareToIgnoreCase(codePoint1, codePoint2);</span><br><span class="line">                &#125;</span><br><span class="line">                start1 = string1.offsetByCodePoints(start1, <span class="number">1</span>);</span><br><span class="line">                start2 = string2.offsetByCodePoints(start2, <span class="number">1</span>);</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// Get end of current number.</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">end1</span> <span class="operator">=</span> start1;</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                end1 = string1.offsetByCodePoints(end1, <span class="number">1</span>);</span><br><span class="line">            &#125; <span class="keyword">while</span> (end1 &lt; string1.length() &amp;&amp; Character.isDigit(string1.codePointAt(end1)));</span><br><span class="line">            <span class="type">int</span> <span class="variable">end2</span> <span class="operator">=</span> start2;</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                end2 = string2.offsetByCodePoints(end2, <span class="number">1</span>);</span><br><span class="line">            &#125; <span class="keyword">while</span> (end2 &lt; string2.length() &amp;&amp; Character.isDigit(string2.codePointAt(end2)));</span><br><span class="line">            <span class="comment">// Get start of current number without leading zeros.</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">noLeadingZeroStart1</span> <span class="operator">=</span> start1;</span><br><span class="line">            <span class="keyword">while</span> (noLeadingZeroStart1 &lt; end1 &amp;&amp; Character.digit(string1.codePointAt(</span><br><span class="line">                    noLeadingZeroStart1), DIGIT_RADIX) == <span class="number">0</span>) &#123;</span><br><span class="line">                noLeadingZeroStart1 = string1.offsetByCodePoints(noLeadingZeroStart1, <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">int</span> <span class="variable">noLeadingZeroStart2</span> <span class="operator">=</span> start2;</span><br><span class="line">            <span class="keyword">while</span> (noLeadingZeroStart2 &lt; end2 &amp;&amp; Character.digit(string2.codePointAt(</span><br><span class="line">                    noLeadingZeroStart2), DIGIT_RADIX) == <span class="number">0</span>) &#123;</span><br><span class="line">                noLeadingZeroStart2 = string2.offsetByCodePoints(noLeadingZeroStart2, <span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// If the two lengths of numbers (without leading zeros) differ, the shorter one comes</span></span><br><span class="line">            <span class="comment">// first.</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">noLeadingZeroLength1</span> <span class="operator">=</span> string1.codePointCount(noLeadingZeroStart1, end1);</span><br><span class="line">            <span class="type">int</span> <span class="variable">noLeadingZeroLength2</span> <span class="operator">=</span> string2.codePointCount(noLeadingZeroStart2, end2);</span><br><span class="line">            <span class="keyword">if</span> (noLeadingZeroLength1 != noLeadingZeroLength2) &#123;</span><br><span class="line">                <span class="keyword">return</span> noLeadingZeroLength1 - noLeadingZeroLength2;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// If any two digits starting from the first non-zero ones differs, the less one comes</span></span><br><span class="line">            <span class="comment">// first.</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">digitIndex1</span> <span class="operator">=</span> noLeadingZeroStart1, digitIndex2 = noLeadingZeroStart2;</span><br><span class="line">                 digitIndex1 &lt; end1; digitIndex1 = string1.offsetByCodePoints(digitIndex1, <span class="number">1</span>),</span><br><span class="line">                         digitIndex2 = string2.offsetByCodePoints(digitIndex2, <span class="number">1</span>)) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">digit1</span> <span class="operator">=</span> Character.digit(string1.codePointAt(digitIndex1), DIGIT_RADIX);</span><br><span class="line">                <span class="type">int</span> <span class="variable">digit2</span> <span class="operator">=</span> Character.digit(string2.codePointAt(digitIndex2), DIGIT_RADIX);</span><br><span class="line">                <span class="keyword">if</span> (digit1 != digit2) &#123;</span><br><span class="line">                    <span class="keyword">return</span> digit1 - digit2;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// If the two numbers are the same, the one with less leading zeros (shorter) comes</span></span><br><span class="line">            <span class="comment">// first.</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">leadingZeroLength1</span> <span class="operator">=</span> string1.codePointCount(start1, noLeadingZeroStart1);</span><br><span class="line">            <span class="type">int</span> <span class="variable">leadingZeroLength2</span> <span class="operator">=</span> string2.codePointCount(start2, noLeadingZeroStart2);</span><br><span class="line">            <span class="keyword">if</span> (leadingZeroLength1 != leadingZeroLength2) &#123;</span><br><span class="line">                <span class="keyword">if</span> (leadingZeroCompareResult == <span class="number">0</span>) &#123;</span><br><span class="line">                    leadingZeroCompareResult = leadingZeroLength1 - leadingZeroLength2;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            start1 = end1;</span><br><span class="line">            start2 = end2;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// If one of the two strings is exhausted first, it comes first.</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">remainingLength1</span> <span class="operator">=</span> string1.codePointCount(start1, string1.length());</span><br><span class="line">        <span class="type">int</span> <span class="variable">remainingLength2</span> <span class="operator">=</span> string2.codePointCount(start2, string2.length());</span><br><span class="line">        <span class="keyword">if</span> (remainingLength1 != remainingLength2) &#123;</span><br><span class="line">            <span class="keyword">return</span> remainingLength1 - remainingLength2;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// The one with less leading zeros (shorter) comes first if others are the same.</span></span><br><span class="line">        <span class="keyword">if</span> (leadingZeroCompareResult != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> leadingZeroCompareResult;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// Fall back to plain comparison.</span></span><br><span class="line">        <span class="keyword">return</span> string1.compareTo(string2);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// @see String#regionMatches(boolean, int, String, int, int)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">codePointEqualsIgnoreCase</span><span class="params">(<span class="type">int</span> codePoint1, <span class="type">int</span> codePoint2)</span> &#123;</span><br><span class="line">        codePoint1 = Character.toUpperCase(codePoint1);</span><br><span class="line">        codePoint2 = Character.toUpperCase(codePoint2);</span><br><span class="line">        <span class="keyword">if</span> (codePoint1 == codePoint2) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        codePoint1 = Character.toLowerCase(codePoint1);</span><br><span class="line">        codePoint2 = Character.toLowerCase(codePoint2);</span><br><span class="line">        <span class="keyword">return</span> codePoint1 == codePoint2;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// @see String.CaseInsensitiveComparator#compare(String, String)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">codePointCompareToIgnoreCase</span><span class="params">(<span class="type">int</span> codePoint1, <span class="type">int</span> codePoint2)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (codePoint1 != codePoint2) &#123;</span><br><span class="line">            codePoint1 = Character.toUpperCase(codePoint1);</span><br><span class="line">            codePoint2 = Character.toUpperCase(codePoint2);</span><br><span class="line">            <span class="keyword">if</span> (codePoint1 != codePoint2) &#123;</span><br><span class="line">                codePoint1 = Character.toUpperCase(codePoint1);</span><br><span class="line">                codePoint2 = Character.toUpperCase(codePoint2);</span><br><span class="line">                <span class="keyword">if</span> (codePoint1 != codePoint2) &#123;</span><br><span class="line">                    <span class="keyword">return</span> codePoint1 - codePoint2;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The code is also available as <a href="https://gist.github.com/zhanghai/57ab1232405435fb1ef6e6af06b5dc36">a GitHub gist</a> and is licensed under the Apache 2.0 license.</p><h2 id="Further-Considerations"><a href="#Further-Considerations" class="headerlink" title="Further Considerations"></a>Further Considerations</h2><p>In fact, to account for Unicode’s compatibility decomposition, canonical composition and <a href="https://www.w3.org/International/wiki/Case_folding">case folding</a>, an ICU <code>Normalizer</code> should be used with mode <code>NFKC_Casefold</code>. However, since the ICU package is not available on Android until Android Oreo, and the JDK implementation didn’t account for such level of correctness, I’m currently leaving it out and you can trivially add it by applying the normalization before comparison.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Natural sort order is what we usually see in most file managers when browsing a list of numbered files, that it sorts the numerical part of the file name as a number instead of comparing it character by character. For instance:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lexicographical sort&lt;/th&gt;
&lt;th&gt;Natural sort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1.txt&lt;/td&gt;
&lt;td&gt;1.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10.txt&lt;/td&gt;
&lt;td&gt;2.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100.txt&lt;/td&gt;
&lt;td&gt;3.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101.txt&lt;/td&gt;
&lt;td&gt;4.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102.txt&lt;/td&gt;
&lt;td&gt;5.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103.txt&lt;/td&gt;
&lt;td&gt;10.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104.txt&lt;/td&gt;
&lt;td&gt;11.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;105.txt&lt;/td&gt;
&lt;td&gt;12.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11.txt&lt;/td&gt;
&lt;td&gt;13.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12.txt&lt;/td&gt;
&lt;td&gt;14.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13.txt&lt;/td&gt;
&lt;td&gt;15.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.txt&lt;/td&gt;
&lt;td&gt;100.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.txt&lt;/td&gt;
&lt;td&gt;101.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.txt&lt;/td&gt;
&lt;td&gt;102.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.txt&lt;/td&gt;
&lt;td&gt;103.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4.txt&lt;/td&gt;
&lt;td&gt;104.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.txt&lt;/td&gt;
&lt;td&gt;105.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Lexicographical sort can be easily implemented using &lt;code&gt;String::compareToIgnoreCase&lt;/code&gt;, but it is not very acceptable for end users. However for natural sort, things is actually a little more complicated.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Programming" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
  </entry>
  
  <entry>
    <title>Fixing My Missing Degree Celsius Symbol</title>
    <link href="https://blog.zhanghai.me/fixing-my-missing-degree-celsius-symbol/"/>
    <id>https://blog.zhanghai.me/fixing-my-missing-degree-celsius-symbol/</id>
    <published>2018-04-11T04:01:59.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<p>For years <a href="https://extensions.gnome.org/extension/750/openweather/">OpenWeather</a> has been my favorite GNOME extension for quick and beautiful weather information. However today when I looked at my top bar again for weather, I had the impression as if something is missing:</p><img src="/fixing-my-missing-degree-celsius-symbol/open-weather-without-degree-celsius.png" class=""title="Now"><p>And this is what it used to be like:</p><img src="/fixing-my-missing-degree-celsius-symbol/open-weather-with-degree-celsius.png" class=""title="Used to be"><p>My degree Celsius symbol is missing!</p><span id="more"></span><h2 id="Finding-the-culprit"><a href="#Finding-the-culprit" class="headerlink" title="Finding the culprit"></a>Finding the culprit</h2><p>The first thing I thought was, is this specific to OpenWeather? And I was surprised to find out that when I googled “degree Celsius symbol”, the <code>°C</code> as <code>U+00B0 ° degree sign</code> plus <code>U+0043 C latin capital letter c</code> is present, while ℃ as <code>U+2103 ℃ degree celsius</code> is simply empty. However a <code>&lt;span&gt;</code> with <code>font-family: serif</code> on Wikipedia showed the symbol correctly, so this must be a font&#x2F;fontconfig issue.</p><p>And I <code>less</code>ed my own fontconfig configuration with <code>/etc/fonts/local.conf</code>:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=<span class="string">&quot;1.0&quot;</span>?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">fontconfig</span> <span class="keyword">SYSTEM</span> <span class="string">&quot;fonts.dtd&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">fontconfig</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">match</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">test</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">string</span>&gt;</span>sans-serif<span class="tag">&lt;/<span class="name">string</span>&gt;</span><span class="tag">&lt;/<span class="name">test</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;prepend&quot;</span> <span class="attr">binding</span>=<span class="string">&quot;strong&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Micro Hei<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Zen Hei<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Bitmap Song<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>DejaVu Sans<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>Noto Color Emoji<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">match</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">match</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">test</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">string</span>&gt;</span>serif<span class="tag">&lt;/<span class="name">string</span>&gt;</span><span class="tag">&lt;/<span class="name">test</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;prepend&quot;</span> <span class="attr">binding</span>=<span class="string">&quot;strong&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>DejaVu Serif<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Bitmap Song<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Zen Hei Sharp<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>AR PL UMing CN<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>AR PL UMing TW<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>AR PL New Sung<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>Noto Color Emoji<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">match</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">match</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">test</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">string</span>&gt;</span>monospace<span class="tag">&lt;/<span class="name">string</span>&gt;</span><span class="tag">&lt;/<span class="name">test</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;prepend&quot;</span> <span class="attr">binding</span>=<span class="string">&quot;strong&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Micro Hei Mono<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Zen Hei Mono<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Bitmap Song<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>DejaVu Sans Mono<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>Noto Color Emoji<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">match</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">fontconfig</span>&gt;</span></span><br></pre></td></tr></table></figure><p>So the different order of font families in <code>sans-serif</code> and <code>serif</code> might be the issue. I googled a while for finding out the font selected by <code>fontconfig</code> for a glyph, and found <a href="https://repolinux.wordpress.com/2013/03/10/find-out-fallback-font-used-by-fontconfig-for-a-certain-character/">an article</a> suggesting the use of <code>FC_DEBUG</code> and <code>pango-view</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FC_DEBUG=4 pango-view -q --font=<span class="string">&#x27;&lt;YOUR_FONT_FAMILY&gt;&#x27;</span> -t <span class="string">&#x27;&lt;YOUR_CHARACTER&gt;&#x27;</span> 2&gt;&amp;1 | grep -o <span class="string">&#x27;family: &quot;[^&quot;]\+&#x27;</span> | <span class="built_in">cut</span> -c 10- | <span class="built_in">tail</span> -n 1</span><br></pre></td></tr></table></figure><p>The latter part basically filters out the last printed family name from the tedious debug output. And when I run this for my degree Celsius symbol (<code>℃</code>), I found:</p><img src="/fixing-my-missing-degree-celsius-symbol/pango-view-wqy-micro-hei.png" class=""title="Result of pango-view"><p>What’s wrong with <code>WenQuanYi Micro Hei</code>? I’ve been using it for years, and now it’s giving me nothing for degree Celsius symbol? And why did fontconfig ever choose it seemed to have no such glyph?</p><p>Confused by this I opened my <code>wqy-microhei.ttc</code> with FontForge, and was again surprised to find out that my <code>WenQuanYi Micro Hei</code> is providing an empty glyph (instead of nothing) for certain codepoints!</p><img src="/fixing-my-missing-degree-celsius-symbol/font-forge-wqy-micro-hei.png" class=""title="Fontforge with highlight on empty glyphs"><p>This is crazy, and I’m left wondering how my degree Celsius symbol ever worked in the past few years.</p><h2 id="Finding-the-way-to-fix-it"><a href="#Finding-the-way-to-fix-it" class="headerlink" title="Finding the way to fix it"></a>Finding the way to fix it</h2><p>There must be some configuration that allow me to blacklist certain glyphs in a font. And after another round of digging around, I found some interesting results.</p><p>The first one is <a href="https://bugs.freedesktop.org/show_bug.cgi?id=7528">a bug on fontconfig Bugzilla</a> which requested such support back in 2006 and was marked fixed in 2011. Feeling excited, I scrolled down and found a single line of comment from the dev:</p><blockquote><p>This is fixed in master with target&#x3D;scan charset editing.</p></blockquote><p>After all the hard work, why didn’t he or she celebrate the moment of closing this bug with a more detailed explanation, or at least a helpful pointer? Even after reading though <code>man fonts-conf</code>, I found nothing describing how I can edit the charset of a font family. After all, the <code>&lt;charset&gt;</code> element says it must contain one or more integer, however the whole charset normally contains a huge amount of hexadecimal integers.</p><p>So I searched harder and found <a href="https://stackoverflow.com/a/47614324">another answer on StackOverflow</a>. It suggested an undocumented way of configuration that’s mentioned somewhere in a RedHat bug thread, which was its only appearance on the whole Internet:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">match</span> <span class="attr">target</span>=<span class="string">&quot;scan&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">test</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span> <span class="attr">compare</span>=<span class="string">&quot;not_eq&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">string</span>&gt;</span>VL Gothic<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">test</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;charset&quot;</span> <span class="attr">mode</span>=<span class="string">&quot;assign&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">minus</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">name</span>&gt;</span>charset<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">range</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">int</span>&gt;</span>0x0021<span class="tag">&lt;/<span class="name">int</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">int</span>&gt;</span>0x00FF<span class="tag">&lt;/<span class="name">int</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">range</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">minus</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">match</span>&gt;</span></span><br></pre></td></tr></table></figure><p>This is just what i wanted! So I happily applied this to my mischievous <code>WenQuanYi Micro Hei</code>, changing the <code>compare</code> to <code>&quot;eq&quot;</code> and setting the range to include <code>0x2103</code> (My precious degree Celsius symbol). Finally, <code>sudo fc-cache -f &amp;&amp; fc-cache -f</code>! [sigh of relief]</p><img src="/fixing-my-missing-degree-celsius-symbol/saw-range-expected-charset.png" class=""title="Result of pango-view"><p>Nope.</p><p>This could have worked, in some time of the history, but is not working for now, on my latest Arch Linux installation.</p><p>No.</p><p>But wait. I did saw something mentioning, we have a <code>&lt;charset&gt;</code> element and it must contain some integers? So I started to coin my own solution (You should also do the same for <code>WenQuanYi Micro Hei Mono</code>):</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">match</span> <span class="attr">target</span>=<span class="string">&quot;scan&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">test</span> <span class="attr">name</span>=<span class="string">&quot;family&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>WenQuanYi Micro Hei<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">test</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">edit</span> <span class="attr">name</span>=<span class="string">&quot;charset&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">minus</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">name</span>&gt;</span>charset<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">charset</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">int</span>&gt;</span>0x2103<span class="tag">&lt;/<span class="name">int</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">int</span>&gt;</span>0x2109<span class="tag">&lt;/<span class="name">int</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">int</span>&gt;</span>0x212B<span class="tag">&lt;/<span class="name">int</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">int</span>&gt;</span>0x2164<span class="tag">&lt;/<span class="name">int</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">int</span>&gt;</span>0x2169<span class="tag">&lt;/<span class="name">int</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">charset</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">minus</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">edit</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">match</span>&gt;</span></span><br></pre></td></tr></table></figure><p><code>sudo fc-cache -f &amp;&amp; fc-cache -f</code>, <code>pango-view</code> again to confirm, and <code>Alt+F2 r</code> to restart my GNOME shell. It worked! After all these hours, my degree Celsius symbol is back and alive again!</p><h2 id="A-final-note"><a href="#A-final-note" class="headerlink" title="A final note"></a>A final note</h2><p>The above solution is only a workaround, and to actually fix this, The <code>Wenquanyi Micro Hei</code> font itself should be patched. In fact this was what I first tried when I surprisingly found out those empty glyphs.</p><p>However, when I went to <a href="https://wenq.org/">wenq.org</a>, there latest update was posted in late 2011 (We are in 2018 now), and their forum is giving me a blank page with certain resources returning 404. Their SourceForge issue tracker is also unattended for years with that <a href="https://sourceforge.net/p/wqy/bugs/42/">notorious hangul advance bug</a> still open. Sadly, the whole project seems dead. So I decided to work around instead of fixing this.</p><p>And one sentence I encountered during my search kept lingering in my mind:</p><p><em>When you gaze long into fontconfig, fontconfig also gazes into you.</em></p><p><strong>Update</strong>: With another link on their official website, I was able to find their real bug tracker and <a href="https://wenq.org/dev/index.cgi?MicroHei_Bug/0012">this bug</a>, and <a href="https://wenq.org/dev/index.cgi?MicroHei_Bug/0013">a similar bug</a>. The dev said it’s fixed, but maybe some recent <code>fontconfig</code> or <code>freetype</code> changes brought it back. Maybe some day I should fork it and try to actually fix these bugs.</p><p>And according to the dev’s comment, those glyphs existed in <code>Droid Sans Fallback</code>, so a proper fix to my configuration should also include adding <code>Droid Sans Fallback</code> to the <code>sans-serif</code> and <code>monospace</code> families, right below the <code>WenQuanYi Micro Hei</code> entry.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;For years &lt;a href=&quot;https://extensions.gnome.org/extension/750/openweather/&quot;&gt;OpenWeather&lt;/a&gt; has been my favorite GNOME extension for quick and beautiful weather information. However today when I looked at my top bar again for weather, I had the impression as if something is missing:&lt;/p&gt;
&lt;img src=&quot;/fixing-my-missing-degree-celsius-symbol/open-weather-without-degree-celsius.png&quot; class=&quot;&quot; title=&quot;Now&quot;&gt;

&lt;p&gt;And this is what it used to be like:&lt;/p&gt;
&lt;img src=&quot;/fixing-my-missing-degree-celsius-symbol/open-weather-with-degree-celsius.png&quot; class=&quot;&quot; title=&quot;Used to be&quot;&gt;

&lt;p&gt;My degree Celsius symbol is missing!&lt;/p&gt;</summary>
    
    
    
    
    <category term="Linux" scheme="https://blog.zhanghai.me/tags/linux/"/>
    
    <category term="Software" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>使用 JavaScript 编写 GTK+ 3 应用程序</title>
    <link href="https://blog.zhanghai.me/buliding-gtk-3-application-with-javascript/"/>
    <id>https://blog.zhanghai.me/buliding-gtk-3-application-with-javascript/</id>
    <published>2017-04-07T17:23:35.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言</h2><p>JavaScript 是一门灵活的动态语言，在实现应用功能时十分易用。最近有写作图形界面程序的需要，又比较喜爱 GTK+ 3 的设计，因此想到可以尝试使用 JavaScript 来完成任务。</p><span id="more"></span><h2 id="选择运行时"><a href="# 选择运行时" class="headerlink" title="选择运行时"></a>选择运行时 </h2><p> 首选的运行时当然是 node，因为它有着良好的 ES2015 特性支持，并且可以方便地使用 npm 管理的模块。在经过一番搜索之后，我找到了 <a href="https://github.com/WebReflection/node-gtk">WebReflection&#x2F;node-gtk</a> 这个项目，却发现它无法使用 node-gyp 在我的 Arch Linux 上进行编译。另一个 <a href="https://github.com/creationix/node-gir">creationix&#x2F;node-gir</a> 提供了对 GObject Introspection 的绑定，但是在它的 README 中写明了有 bug 和内存管理等问题，看上去也难以令人满意。</p><p>因此，我选择了使用 GJS 作为运行时。虽然没有 node 的 ES2015 支持和使用不同模块的便利，但对于使用 GObject Introspection 的绑定而言足够可靠。</p><h2 id="编写应用程序"><a href="# 编写应用程序" class="headerlink" title="编写应用程序"></a>编写应用程序 </h2><p>GNOME 官方提供了<a href="https://developer.gnome.org/gnome-devel-demos/stable/hellognome.js.html"> 一个简单的 GJS 程序示例</a>，可以按照它的架构进行编程。</p><h2 id="文档"><a href="# 文档" class="headerlink" title="文档"></a>文档</h2><ul><li><a href="https://wiki.gnome.org/Projects/GTK%2B">GTK+ Workspace</a>：GTK+ 项目主页，有各种链接。</li><li><a href="https://developer.gnome.org/gtk3/stable/">GTK+ 3 Reference Manual</a>：GTK+ C API 文档，最为基础。</li><li><a href="https://wiki.gnome.org/Projects/Gjs">Project Gjs</a>：Gjs 项目主页，有各种链接。</li><li><a href="https://people.gnome.org/~gcampagna/docs/">Gjs Generated Documentation</a>：可以查看部分命名不同。</li><li><a href="https://python-gtk-3-tutorial.readthedocs.io/">The Python GTK+ 3 Tutorial</a>：可以作为 GTK+ 3 教程进行参考。</li></ul><h2 id="技巧"><a href="# 技巧" class="headerlink" title="技巧"></a>技巧 </h2><h3 id="通过构造器设置属性"><a href="# 通过构造器设置属性" class="headerlink" title="通过构造器设置属性"></a> 通过构造器设置属性</h3><p><code>Gtk.Widget</code> 的所有属性均可以通过向构造器传入的 <code>Object</code> 进行设置，而可用的属性可以通过运行 <code>gtk3-widget-factory</code>，然后启用 GtkInspector 来查看和实验。</p><h3 id="布局"><a href="# 布局" class="headerlink" title="布局"></a>布局 </h3><p> 可以一直使用 <code>Gtk.Grid</code> 进行布局，它的功能类似于 Android 中的 <code>LinearLayout</code>。可以设置它的 <code>border_width</code>、<code>row_spacing</code> 和 <code>column_spacing</code> 来控制留白，以及设置 <code>expand</code>、<code>hexpand</code> 和 <code>vexpand</code> 控制大小。通过调用（继承自 <code>Gtk.Container</code> 并经过覆盖）<code>add()</code> 方法即可按顺序添加子控件，不需要使用 <code>attach()</code> 那样复杂的功能。</p><h3 id="设置 -CSS-Class"><a href="# 设置 -CSS-Class" class="headerlink" title="设置 CSS Class"></a>设置 CSS Class</h3><p>例如给 <code>Gtk.Button</code> 添加 <code>suggested-action</code> CSS 类使其变蓝：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">button.<span class="title function_">get_style_context</span>().<span class="title function_">add_class</span>(<span class="string">&#x27;suggested-action&#x27;</span>);</span><br></pre></td></tr></table></figure><h3 id="控件杂项"><a href="# 控件杂项" class="headerlink" title="控件杂项"></a>控件杂项</h3><p><code>Gtk.Entry</code> 相当于 Android 中的 <code>EditText</code>。可以通过设置 <code>width_char</code> 来修改 <code>Gtk.Entry</code> 的最小大小。</p><p><code>Gtk.Widiget</code> 的 <code>sensitive</code> 属性相当于 Android 中的 <code>android:enabled</code>。</p><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语</h2><p>Gjs 使得我们可以使用灵活的 JavaScript 编写美观的原生程序，总体来说体验是很好的。</p><p>我把自己编写的一个小程序放在了 <a href="https://gist.github.com/zhanghai/4ae88f5afae4c353c0a30c0c6595efe5">GitHub Gist</a>，可以作为参考。</p><img src="/buliding-gtk-3-application-with-javascript/optical-system.png" class=""title="Gjs 应用程序 OpticalSystem"><p>最后，在编写这篇文章时，我找到了另一个 <a href="https://github.com/jo/hello-gjs-npm"> 结合了 Gjs、NPM 和 Babel 的示例项目</a>，读者也可以参考它进行编程。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;JavaScript 是一门灵活的动态语言，在实现应用功能时十分易用。最近有写作图形界面程序的需要，又比较喜爱 GTK+ 3 的设计，因此想到可以尝试使用 JavaScript 来完成任务。&lt;/p&gt;</summary>
    
    
    
    
    <category term="教程" scheme="https://blog.zhanghai.me/tags/tutorial/"/>
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
    <category term="学业" scheme="https://blog.zhanghai.me/tags/schoolwork/"/>
    
  </entry>
  
  <entry>
    <title>使用 LaTeX 编写论文</title>
    <link href="https://blog.zhanghai.me/writing-thesis-with-latex/"/>
    <id>https://blog.zhanghai.me/writing-thesis-with-latex/</id>
    <published>2017-03-29T22:16:36.000Z</published>
    <updated>2019-05-16T05:05:51.868Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 临近毕设，学院里提供了毕业设计报告的 LaTeX 模板，于是决定离开简单的 Markdown ，开始使用 LaTeX 进行写作。以下就是我使用过程中的一些记录。</p><span id="more"></span><h2 id="样例项目"><a href="# 样例项目" class="headerlink" title="样例项目"></a>样例项目 </h2><p> 我对学院提供的旧 LaTeX 模板进行了较多的修改，使其符合了学院方面的最新格式要求，并且修复了设置字号时行间距不正确等等错误，可以直接使用或者用于参考学习：</p><p><a href="https://github.com/zhanghai/zju-csse-undergraduate-design-proposal-latex-template">浙江大学计算机科学与技术、软件工程专业本科毕业设计开题报告 LaTeX 模板</a></p><p>其中的 <code>install-fonts.sh</code> 可以用于从 Windows 分区获取需要的字体并进行安装。</p><h2 id="安装 -LaTeX"><a href="# 安装 -LaTeX" class="headerlink" title="安装 LaTeX"></a>安装 LaTeX</h2><p>在 Arch Linux 上安装 LaTeX（TeXLive）较为简单，可以参考 <a href="https://wiki.archlinux.org/index.php/TeX_Live">Arch Wiki</a> 安装对应的软件包。</p><p>也可以直接执行以下命令进行安装，这样基本不会在使用时遇到无法找到常用宏包的情况。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S texlive-most texlive-langchinese</span><br></pre></td></tr></table></figure><h2 id="构建文档"><a href="# 构建文档" class="headerlink" title="构建文档"></a>构建文档</h2><p>xeCJK 是提供 LaTeX 中文支持的宏包，并且依赖于 XeLaTeX，因此，我们需要使用 <code>xelatex</code> 命令进行构建。</p><p>LaTeX 在构建交叉索引时需要多次运行，才能最终解析所有的引用，并且期间需要 BibTeX 对参考文献数据库进行处理。因此，一般的手动构建命令是：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">xelatex main</span><br><span class="line">bibtex main</span><br><span class="line">xelatex main</span><br><span class="line">xelatex main</span><br></pre></td></tr></table></figure><p>不同于部分网页上给出的示例，这些命令都可以接受不带后缀名的参数，并且有时写出后缀名可能会阻碍多文件等情况下的正确构建。</p><p>对于子目录和多文件使用引用的情况，在主文件中应该使用 <code>\include</code> 而非 <code>\input</code>，否则会需要其他处理。</p><p>为了简化构建步骤，实际上应当使用 <code>latexmk</code>，它会根据需要自动调用各种命令。因此之前的手动构建步骤可以被以下命令替代：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">latexmk -xelatex main</span><br></pre></td></tr></table></figure><h2 id="了解 -LaTeX- 和解决问题"><a href="# 了解 -LaTeX- 和解决问题" class="headerlink" title="了解 LaTeX 和解决问题"></a>了解 LaTeX 和解决问题</h2><p>LaTeX 有很多历史问题带来的不同，也有多重相似但不同的工具选择，因此网上提供的解决方案不一定可以工作，需要自己试验。</p><p>可以通过 <a href="https://www.sharelatex.com/learn">ShareLaTeX 提供的文档</a> 获得对 LaTeX 的初步了解。但是，这份文档中提到的引用等高级特性的使用可能与我们的方式不同。</p><p>推荐使用 <a href="https://tex.stackexchange.com/">TeX - LaTeX Stack Exchange</a> 作为解决问题时的主要信息来源。</p><p>可以通过 <code>texdef</code> 命令查看 LaTeX 中命令的定义。例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">texdef -t xelatex -s -c csbachelor \thebibliography</span><br></pre></td></tr></table></figure><h2 id="零碎知识"><a href="# 零碎知识" class="headerlink" title="零碎知识"></a>零碎知识</h2><ul><li><code>\\</code>：换行，不新建段落。可选参数可以控制空白长度。</li><li><code>\par</code>：创建段落。<code>\parskip</code> 和 <code>\parindent</code> 将生效。<em>部分命令需要对段落才能生效，因此可能需要在最后一段文字最后加上 <code>\par</code>。</em></li><li><code>\ </code>：一个空格。</li><li><code>~</code>：一个 NBSP，不会被分行打断，常用于类似 <code>图~1</code> 这样的场景。</li><li><code>\hspace&#123;1em&#125;</code>：一个 1 em 长度的空格。</li><li><code>\renewcommand</code>：修改命令定义。</li><li><code>\setlength</code>：修改长度变量。</li><li><code>\setcounter</code>：修改计数器。</li></ul><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语</h2><p>LaTeX 十分强大，并且作为纯文本格式依然比 Word 文档等更加清晰、明确、可靠。但是，整个 LaTeX 环境也正因为它的高自由度和复杂，在许多方面缺乏一致性，这给使用者带来了不少困难。</p><p>总体来说，在经过大量的学习和尝试之后，我依然认为 LaTeX 是一个优秀的排版工具。它可以让使用者在一次配置之后获得强大的功能，并且在写作过程中始终保持工作在纯文本层面上。</p><p>关于各种命令和功能的常见用法，读者可以继续参考我在文章开头提到过的 <a href="https://github.com/zhanghai/zju-csse-undergraduate-design-proposal-latex-template"> 报告模板</a>，我也会在写作毕业设计论文时继续更新。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;临近毕设，学院里提供了毕业设计报告的 LaTeX 模板，于是决定离开简单的 Markdown ，开始使用 LaTeX 进行写作。以下就是我使用过程中的一些记录。&lt;/p&gt;</summary>
    
    
    
    
    <category term="教程" scheme="https://blog.zhanghai.me/tags/tutorial/"/>
    
    <category term="学业" scheme="https://blog.zhanghai.me/tags/schoolwork/"/>
    
    <category term="软件" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>WebGL 入门笔记</title>
    <link href="https://blog.zhanghai.me/webgl-notes/"/>
    <id>https://blog.zhanghai.me/webgl-notes/</id>
    <published>2016-10-11T03:01:43.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 因为计算机图形学课程作业的需要，我在使用过 OpenGL 的基础上学习了使用 WebGL 进行二维图形渲染。</p><p>WebGL 拥有与 OpenGL ES 相似的 API（前者基于后者），但与 OpenGL 相比，两者缺少基础图形的渲染管线，而是需要手写 shader 进行渲染。</p><span id="more"></span><h2 id="基础"><a href="# 基础" class="headerlink" title="基础"></a>基础 </h2><p> 请阅读 <a href="https://www.html5rocks.com/en/tutorials/webgl/webgl_fundamentals/">WebGL Fundamentals</a> 以获得对于 WebGL 的大体了解。</p><h2 id="Shader"><a href="#Shader" class="headerlink" title="Shader"></a>Shader</h2><p>在 WebGL 中需要两种 shader 进行渲染。第一种是 vertex shader，负责对顶点进行变换，例如将像素坐标映射到 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection">clip space</a>。而第二种是 fragment shader，负责对像素点进行着色。</p><p>Shader 可以有两种参数。第一种是 attribute，在每次调用中不同，例如当前顶点的位置；第二种是 uniform，在渲染过程中共享，例如渲染的变换矩阵。</p><p>以下是两个实用的简单 2D shader：</p><p>Vertex shader：</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">attribute</span> <span class="type">vec2</span> a_position;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">mat3</span> u_transformation;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main() &#123;</span><br><span class="line">    <span class="built_in">gl_Position</span> = <span class="type">vec4</span>((u_transformation * <span class="type">vec3</span>(a_position, <span class="number">1</span>)).xy, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Fragment shader：</p><figure class="highlight glsl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">precision</span> <span class="keyword">mediump</span> <span class="type">float</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">uniform</span> <span class="type">vec4</span> u_color;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> main() &#123;</span><br><span class="line">   <span class="built_in">gl_FragColor</span> = u_color;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="初始化"><a href="# 初始化" class="headerlink" title="初始化"></a>初始化 </h2><p> 以下是我的一些工具函数，参考了 <a href="http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html">WebGL Boilerplate</a> 和一部分 <a href="http://webglfundamentals.org/webgl/lessons/webgl-less-code-more-fun.html">WebGL - Less Code, More Fun</a>：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createAndCompileShader</span>(<span class="params">gl, id</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> element = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(id);</span><br><span class="line">    <span class="keyword">var</span> type = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">switch</span> (element.<span class="property">type</span>) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;x-shader/x-vertex&#x27;</span>:</span><br><span class="line">            type = gl.<span class="property">VERTEX_SHADER</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;x-shader/x-fragment&#x27;</span>:</span><br><span class="line">            type = gl.<span class="property">FRAGMENT_SHADER</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="attr">default</span>:</span><br><span class="line">            <span class="keyword">throw</span> <span class="string">&#x27;Unknown shader type: &#x27;</span> + element.<span class="property">type</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">var</span> source = element.<span class="property">textContent</span>;</span><br><span class="line">    <span class="keyword">var</span> shader = gl.<span class="title function_">createShader</span>(type);</span><br><span class="line">    gl.<span class="title function_">shaderSource</span>(shader, source);</span><br><span class="line">    gl.<span class="title function_">compileShader</span>(shader);</span><br><span class="line">    <span class="keyword">if</span> (!gl.<span class="title function_">getShaderParameter</span>(shader, gl.<span class="property">COMPILE_STATUS</span>)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="string">&#x27;Error compiling shader: &#x27;</span> + gl.<span class="title function_">getShaderInfoLog</span>(shader);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> shader;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createAndLinkProgram</span>(<span class="params">gl, vertexShaderId, fragmentShaderId</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> program = gl.<span class="title function_">createProgram</span>();</span><br><span class="line">    gl.<span class="title function_">attachShader</span>(program, <span class="title function_">createAndCompileShader</span>(gl, vertexShaderId));</span><br><span class="line">    gl.<span class="title function_">attachShader</span>(program, <span class="title function_">createAndCompileShader</span>(gl, fragmentShaderId));</span><br><span class="line">    gl.<span class="title function_">linkProgram</span>(program);</span><br><span class="line">    <span class="keyword">if</span> (!gl.<span class="title function_">getProgramParameter</span>(program, gl.<span class="property">LINK_STATUS</span>)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="string">&#x27;Error linking program: &#x27;</span> + gl.<span class="title function_">getProgramInfoLog</span>(program);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> program;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="提供数据"><a href="# 提供数据" class="headerlink" title="提供数据"></a>提供数据 </h2><p> 以下是我自己写作的一些工具函数：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">[float]</span>&#125; <span class="variable">value</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setUniformFv</span>(<span class="params">gl, program, name, value</span>) &#123;</span><br><span class="line">    gl[<span class="string">&#x27;uniform&#x27;</span> + value.<span class="property">length</span> + <span class="string">&#x27;fv&#x27;</span>](gl.<span class="title function_">getUniformLocation</span>(program, name), value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">string</span>&#125; color &#x27;#RRGGBB&#x27;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">float=</span>&#125; alpha \in [0, 1]</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setUniformColor</span>(<span class="params">gl, program, name, color, alpha</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="regexp">/^#[0-9A-Fa-f]&#123;6&#125;$/</span>.<span class="title function_">test</span>(color)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="string">&#x27;Invalid color: &#x27;</span> + color;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">setUniformFv</span>(gl, program, name, [</span><br><span class="line">        <span class="built_in">parseInt</span>(color.<span class="title function_">substr</span>(<span class="number">1</span>, <span class="number">2</span>), <span class="number">16</span>) / <span class="number">0xff</span>,</span><br><span class="line">        <span class="built_in">parseInt</span>(color.<span class="title function_">substr</span>(<span class="number">3</span>, <span class="number">2</span>), <span class="number">16</span>) / <span class="number">0xff</span>,</span><br><span class="line">        <span class="built_in">parseInt</span>(color.<span class="title function_">substr</span>(<span class="number">5</span>, <span class="number">2</span>), <span class="number">16</span>) / <span class="number">0xff</span>,</span><br><span class="line">        alpha || <span class="number">1</span></span><br><span class="line">    ]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setUniformMatrix</span>(<span class="params">gl, program, name, value</span>) &#123;</span><br><span class="line">    gl[<span class="string">&#x27;uniformMatrix&#x27;</span> + <span class="title class_">Math</span>.<span class="title function_">sqrt</span>(value.<span class="property">length</span>) + <span class="string">&#x27;fv&#x27;</span>](gl.<span class="title function_">getUniformLocation</span>(program, name), <span class="literal">false</span>, value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">[[float]]</span>&#125; <span class="variable">value</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setAttributeArrayFva</span>(<span class="params">gl, program, name, value</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> buffer = gl.<span class="title function_">createBuffer</span>();</span><br><span class="line">    gl.<span class="title function_">bindBuffer</span>(gl.<span class="property">ARRAY_BUFFER</span>, buffer);</span><br><span class="line">    <span class="keyword">var</span> data = <span class="keyword">new</span> <span class="title class_">Float32Array</span>(<span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">concat</span>.<span class="title function_">apply</span>([], value));</span><br><span class="line">    gl.<span class="title function_">bufferData</span>(gl.<span class="property">ARRAY_BUFFER</span>, data, gl.<span class="property">STATIC_DRAW</span>);</span><br><span class="line">    <span class="keyword">var</span> location = gl.<span class="title function_">getAttribLocation</span>(program, name);</span><br><span class="line">    gl.<span class="title function_">enableVertexAttribArray</span>(location);</span><br><span class="line">    <span class="keyword">var</span> size = value[<span class="number">0</span>].<span class="property">length</span>;</span><br><span class="line">    gl.<span class="title function_">vertexAttribPointer</span>(location, size, gl.<span class="property">FLOAT</span>, <span class="literal">false</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="变换矩阵"><a href="# 变换矩阵" class="headerlink" title="变换矩阵"></a>变换矩阵 </h2><p> 以下代码参考了 <a href="https://www.html5rocks.com/en/tutorials/webgl/webgl_transforms/">WebGL 2D Translation</a> 和 <a href="https://www.html5rocks.com/en/tutorials/webgl/webgl_orthographic_3d/">WebGL Orthographic 3D</a>。需要注意的是，原教程的代码中有部分矩阵错误地以行优先而非列有限的方式进行表示或运算。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">makeIdentityMatrix</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span></span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">makeTranslationMatrix</span>(<span class="params">tx, ty</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>,</span><br><span class="line">        tx, ty, <span class="number">1</span></span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">makeScalingMatrix</span>(<span class="params">sx, sy</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        sx, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, sy, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span></span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">degreeToRadian</span>(<span class="params">angle</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> angle / <span class="number">180</span> * <span class="title class_">Math</span>.<span class="property">PI</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">makeRotationMatrix</span>(<span class="params">angle</span>) &#123;</span><br><span class="line">    angle = <span class="title function_">degreeToRadian</span>(angle);</span><br><span class="line">    <span class="keyword">var</span> cosa = <span class="title class_">Math</span>.<span class="title function_">cos</span>(angle);</span><br><span class="line">    <span class="keyword">var</span> sina = <span class="title class_">Math</span>.<span class="title function_">sin</span>(angle);</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        cosa, sina, <span class="number">0</span>,</span><br><span class="line">        -sina, cosa, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span></span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">makeSkewXMatrix</span>(<span class="params">angle</span>) &#123;</span><br><span class="line">    angle = <span class="title function_">degreeToRadian</span>(angle);</span><br><span class="line">    <span class="keyword">var</span> tana = <span class="title class_">Math</span>.<span class="title function_">tan</span>(angle);</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line">        tana, <span class="number">1</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span></span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">makeSkewYMatrix</span>(<span class="params">angle</span>) &#123;</span><br><span class="line">    angle = <span class="title function_">degreeToRadian</span>(angle);</span><br><span class="line">    <span class="keyword">var</span> tana = <span class="title class_">Math</span>.<span class="title function_">tan</span>(angle);</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        <span class="number">1</span>, tana, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span></span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">makeProjectionMatrix</span>(<span class="params">width, height</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        <span class="number">2</span> / width, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line">        <span class="number">0</span>, -<span class="number">2</span> / height, <span class="number">0</span>,</span><br><span class="line">        -<span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span></span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">multiplyMatrix</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">    <span class="comment">// A \cdot B = (B^T \cdot A^T)^T</span></span><br><span class="line">    <span class="keyword">var</span> a00 = a[<span class="number">0</span>], a01 = a[<span class="number">1</span>], a02 = a[<span class="number">2</span>];</span><br><span class="line">    <span class="keyword">var</span> a10 = a[<span class="number">3</span>], a11 = a[<span class="number">4</span>], a12 = a[<span class="number">5</span>];</span><br><span class="line">    <span class="keyword">var</span> a20 = a[<span class="number">6</span>], a21 = a[<span class="number">7</span>], a22 = a[<span class="number">8</span>];</span><br><span class="line">    <span class="keyword">var</span> b00 = b[<span class="number">0</span>], b01 = b[<span class="number">1</span>], b02 = b[<span class="number">2</span>];</span><br><span class="line">    <span class="keyword">var</span> b10 = b[<span class="number">3</span>], b11 = b[<span class="number">4</span>], b12 = b[<span class="number">5</span>];</span><br><span class="line">    <span class="keyword">var</span> b20 = b[<span class="number">6</span>], b21 = b[<span class="number">7</span>], b22 = b[<span class="number">8</span>];</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        b00 * a00 + b01 * a10 + b02 * a20,</span><br><span class="line">        b00 * a01 + b01 * a11 + b02 * a21,</span><br><span class="line">        b00 * a02 + b01 * a12 + b02 * a22,</span><br><span class="line">        b10 * a00 + b11 * a10 + b12 * a20,</span><br><span class="line">        b10 * a01 + b11 * a11 + b12 * a21,</span><br><span class="line">        b10 * a02 + b11 * a12 + b12 * a22,</span><br><span class="line">        b20 * a00 + b21 * a10 + b22 * a20,</span><br><span class="line">        b20 * a01 + b21 * a11 + b22 * a21,</span><br><span class="line">        b20 * a02 + b21 * a12 + b22 * a22</span><br><span class="line">    ];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="近似于 -OpenGL- 的 -API- 封装"><a href="# 近似于 -OpenGL- 的 -API- 封装" class="headerlink" title="近似于 OpenGL 的 API 封装"></a>近似于 OpenGL 的 API 封装</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> _gl = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">var</span> _program = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">initialize</span>(<span class="params">gl, program</span>) &#123;</span><br><span class="line">    _gl = gl;</span><br><span class="line">    _program = program;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setColor</span>(<span class="params">color, alpha</span>) &#123;</span><br><span class="line">    <span class="title function_">setUniformColor</span>(_gl, _program, <span class="string">&#x27;u_color&#x27;</span>, color, alpha);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> _transformations = [];</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getTransformation</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> _transformations[_transformations.<span class="property">length</span> - <span class="number">1</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">_setTransformation</span>(<span class="params">transformation</span>) &#123;</span><br><span class="line">    <span class="title function_">setUniformMatrix</span>(_gl, _program, <span class="string">&#x27;u_transformation&#x27;</span>, transformation);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setTransformation</span>(<span class="params">transformation</span>) &#123;</span><br><span class="line">    <span class="title function_">_setTransformation</span>(transformation);</span><br><span class="line">    _transformations = [transformation];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">pushTransformation</span>(<span class="params">transformation</span>) &#123;</span><br><span class="line">    transformation = <span class="title function_">multiplyMatrix</span>(<span class="title function_">getTransformation</span>(), transformation);</span><br><span class="line">    <span class="title function_">_setTransformation</span>(transformation);</span><br><span class="line">    _transformations.<span class="title function_">push</span>(transformation);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">popTransformation</span>(<span class="params"></span>) &#123;</span><br><span class="line">    _transformations.<span class="title function_">pop</span>();</span><br><span class="line">    <span class="title function_">_setTransformation</span>(<span class="title function_">getTransformation</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">drawTriangles</span>(<span class="params">positions</span>) &#123;</span><br><span class="line">    <span class="title function_">setAttributeArrayFva</span>(_gl, _program, <span class="string">&#x27;a_position&#x27;</span>, positions);</span><br><span class="line">    _gl.<span class="title function_">drawArrays</span>(_gl.<span class="property">TRIANGLES</span>, <span class="number">0</span>, positions.<span class="property">length</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">drawRectangle</span>(<span class="params">x, y, width, height</span>) &#123;</span><br><span class="line">    <span class="keyword">var</span> x1 = x;</span><br><span class="line">    <span class="keyword">var</span> y1= y;</span><br><span class="line">    <span class="keyword">var</span> x2 = x + width;</span><br><span class="line">    <span class="keyword">var</span> y2 = y + height;</span><br><span class="line">    <span class="title function_">drawTriangles</span>([</span><br><span class="line">        [x1, y1],</span><br><span class="line">        [x2, y1],</span><br><span class="line">        [x1, y2],</span><br><span class="line">        [x1, y2],</span><br><span class="line">        [x2, y1],</span><br><span class="line">        [x2, y2]</span><br><span class="line">    ]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="绘制和应对大小改变"><a href="# 绘制和应对大小改变" class="headerlink" title="绘制和应对大小改变"></a>绘制和应对大小改变 </h2><p> 以下代码参考了 <a href="http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html">WebGL Resizing the Canvas</a> 和 <a href="http://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html">WebGL Anti-Patterns</a>。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">draw</span>(<span class="params">gl</span>) &#123;</span><br><span class="line"></span><br><span class="line">    gl.<span class="title function_">viewport</span>(<span class="number">0</span>, <span class="number">0</span>, gl.<span class="property">drawingBufferWidth</span>, gl.<span class="property">drawingBufferHeight</span>);</span><br><span class="line"></span><br><span class="line">    gl.<span class="title function_">clearColor</span>(<span class="number">0xde</span> / <span class="number">0xff</span>, <span class="number">0x29</span> / <span class="number">0xff</span>, <span class="number">0x10</span> / <span class="number">0xff</span>, <span class="number">0xff</span> / <span class="number">0xff</span>);</span><br><span class="line">    gl.<span class="title function_">clear</span>(gl.<span class="property">COLOR_BUFFER_BIT</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> program = <span class="title function_">createAndLinkProgram</span>(gl, <span class="string">&#x27;vertex-shader&#x27;</span>, <span class="string">&#x27;fragment-shader&#x27;</span>);</span><br><span class="line">    gl.<span class="title function_">useProgram</span>(program);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">initialize</span>(gl, program);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">setColor</span>(<span class="string">&#x27;#ffdd00&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">setTransformation</span>(<span class="title function_">makeProjectionMatrix</span>(<span class="number">1000</span>, <span class="number">800</span>));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Draw here.</span></span><br><span class="line"></span><br><span class="line">    gl.<span class="title function_">flush</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">main</span>(<span class="params"></span>) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> canvas = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;canvas&#x27;</span>);</span><br><span class="line">    <span class="keyword">var</span> webgl = canvas.<span class="title function_">getContext</span>(<span class="string">&#x27;webgl&#x27;</span>) || canvas.<span class="title function_">getContext</span>(<span class="string">&#x27;experimental-webgl&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (!webgl) &#123;</span><br><span class="line">        <span class="title function_">alert</span>(<span class="string">&quot;Error initializing WebGL. Your browser may not support it.&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> needToDraw = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">resizeAndDraw</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">var</span> resized = canvas.<span class="property">width</span> !== canvas.<span class="property">clientWidth</span> || canvas.<span class="property">height</span> !== canvas.<span class="property">clientWidth</span> * <span class="number">0.8</span>;</span><br><span class="line">        <span class="keyword">if</span> (resized) &#123;</span><br><span class="line">            canvas.<span class="property">width</span> = canvas.<span class="property">clientWidth</span>;</span><br><span class="line">            canvas.<span class="property">height</span> = canvas.<span class="property">clientWidth</span> * <span class="number">0.8</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (resized || needToDraw) &#123;</span><br><span class="line">            needToDraw = <span class="literal">false</span>;</span><br><span class="line">            <span class="title function_">draw</span>(webgl);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="title function_">requestAnimationFrame</span>(resizeAndDraw);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">requestAnimationFrame</span>(resizeAndDraw);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">main</span>();</span><br></pre></td></tr></table></figure><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语 </h2><p> 作为一个复杂度适中的示例，我的作业代码可以在 <a href="/webgl-notes/flag.html" title="这里">这里</a> 看到。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;因为计算机图形学课程作业的需要，我在使用过 OpenGL 的基础上学习了使用 WebGL 进行二维图形渲染。&lt;/p&gt;
&lt;p&gt;WebGL 拥有与 OpenGL ES 相似的 API（前者基于后者），但与 OpenGL 相比，两者缺少基础图形的渲染管线，而是需要手写 shader 进行渲染。&lt;/p&gt;</summary>
    
    
    
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
    <category term="学业" scheme="https://blog.zhanghai.me/tags/schoolwork/"/>
    
    <category term="笔记" scheme="https://blog.zhanghai.me/tags/note/"/>
    
  </entry>
  
  <entry>
    <title>将 Reveal.js 演示文稿部署至 GitHub Pages</title>
    <link href="https://blog.zhanghai.me/publish-reveal-js-presentation-to-github-pages/"/>
    <id>https://blog.zhanghai.me/publish-reveal-js-presentation-to-github-pages/</id>
    <published>2016-08-14T05:08:21.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言</h2><p><a href="https://github.com/hakimel/reveal.js">Reveal.js</a> 是一个美观实用 HTML 演示文稿框架，只需要你决定内容，就可以方便地产出外观优雅的演示文稿。你可以在线查看 <a href="http://lab.hakim.se/reveal-js/">Reveal.js 的 Demo</a>。</p><p>为了分享已经制作好的演示文稿，可以使用 GitHub Pages 进行部署，同时也能够不用安装依赖地开启演讲者视图。以下是我建立 <a href="http://slides.zhanghai.me/">slides.zhanghai.me</a> 的过程。</p><span id="more"></span><h2 id="基本"><a href="# 基本" class="headerlink" title="基本"></a>基本 </h2><p> 为了建立一个 GitHub Pages 站点，我们需要准备以下一些文件：</p><ul><li><code>404.html</code>：GitHub Pages 将使用此页面作为默认 404 页面的替代，一般可以换成一个符合自己站点风格的页面。</li><li><code>CNAME</code>：对于绑定自定义域名的 GitHub Pages，可以使用这个文件指定自己的自定义域名。</li><li><code>.nojekyll</code>：我们的站点不需要 Jekyll 的特性干预，因此将它关闭来避免可能的问题。</li></ul><p>然后在 GitHub 上建立仓库，执行以下的命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">git init</span><br><span class="line"><span class="comment"># 将默认的分支名 master 改为 gh-pages</span></span><br><span class="line">git symbolic-ref HEAD refs/heads/gh-pages</span><br><span class="line">git commit -am <span class="string">&#x27;Initial commit.&#x27;</span></span><br><span class="line"><span class="comment"># 将 your/repo/origin 替换为你的仓库地址</span></span><br><span class="line">git remote add origin your/repo/origin</span><br><span class="line">git push --set-upstream origin gh-pages</span><br></pre></td></tr></table></figure><p>当然，使用自定义域名的话还要配置一下 DNS 记录。</p><h2 id="演示文稿"><a href="# 演示文稿" class="headerlink" title="演示文稿"></a>演示文稿 </h2><p> 为了部署 Reveal.js 的演示文稿，可以利用 Git 的 Submodule 来完成对 Reveal.js 项目本身的引用。如此以来，我们的站点项目的 Git 仓库就不用跟踪 Reveal.js 中各个文件的状态，又可以记住所引用的 Reveal.js 仓库当时的版本（Commit SHA1）。</p><p>比较幸运的是，GitHub Pages 的构建过程是可以支持 Submodule 的。需要注意的是，Submodule 的仓库地址必须采用 https 而非 ssh 协议（否则会收到构建失败的邮件通知）。以下是将 Reveal.js 作为 Submodule 引入的命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git submodule add https://github.com/hakimel/reveal.js.git</span><br></pre></td></tr></table></figure><p>之后将你的 <code>index.html</code> 中对于 Reveal.js 文件的应用都加上 <code>reveal.js/</code> 的目录前缀即可。</p><p>其他详情可以参考我的站点仓库 <a href="https://github.com/zhanghai/slides.zhanghai.me">zhanghai&#x2F;slides.zhanghai.me</a>。</p><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语</h2><p>Reveal.js 是一个易用而优雅的 HTML 演示文稿框架，借助于它，我已经多年没有用过 PowerPoint 了。至于利用 GitHub Pages 将它放置在网页端，则可以在免运维的情况下可靠地分享自己的演示文稿，同时也可以不用安装大量 <code>npm</code> 依赖来开启演讲者视图，算是一个简单又有用的小技巧。</p><p>因此，将我的配置过程和结果记录在此，希望能对他人有所帮助。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/hakimel/reveal.js&quot;&gt;Reveal.js&lt;/a&gt; 是一个美观实用 HTML 演示文稿框架，只需要你决定内容，就可以方便地产出外观优雅的演示文稿。你可以在线查看 &lt;a href=&quot;http://lab.hakim.se/reveal-js/&quot;&gt;Reveal.js 的 Demo&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;为了分享已经制作好的演示文稿，可以使用 GitHub Pages 进行部署，同时也能够不用安装依赖地开启演讲者视图。以下是我建立 &lt;a href=&quot;http://slides.zhanghai.me/&quot;&gt;slides.zhanghai.me&lt;/a&gt; 的过程。&lt;/p&gt;</summary>
    
    
    
    
    <category term="教程" scheme="https://blog.zhanghai.me/tags/tutorial/"/>
    
    <category term="软件" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>为 Android 项目使用 Travis CI 并发布持续集成版本</title>
    <link href="https://blog.zhanghai.me/use-travis-ci-on-android-project-and-publish-ci-builds/"/>
    <id>https://blog.zhanghai.me/use-travis-ci-on-android-project-and-publish-ci-builds/</id>
    <published>2016-08-06T00:50:15.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p>Travis CI 是 GitHub 上开源项目采用持续集成的常见选择。为了给<a href="https://github.com/zhanghai/Douya"> 豆芽 </a> 提供持续集成版本用于公开测试，我配置了 Travis CI，并自己编写了脚本将构建结果发布到另一个空项目的 Release 中，并将其间的过程和遇到的问题记录于此。</p><p>Travis CI 构建 Android 项目的时间较长，因此调试配置时十分耗时。希望我的经验能对他人有所帮助。</p><span id="more"></span><h2 id="Travis-CI"><a href="#Travis-CI" class="headerlink" title="Travis CI"></a>Travis CI</h2><p>Travis CI 分为免费版（<a href="https://travis-ci.org/">travis-ci.org</a>）和付费版（<a href="https://travis-ci.com/">travis-ci.com</a>），两者之间没有相互的链接或说明，第一次配置时容易混淆。开源项目选择免费版即可。配置过程可以参考官方的 <a href="https://docs.travis-ci.com/user/getting-started/">Getting Started</a> 和 <a href="https://docs.travis-ci.com/user/languages/android">Android 项目配置</a>。</p><p>关于 Android 项目有一些较为微妙的配置问题，我自己调试并查阅了一些 Issue 方才解决。</p><ol><li>为了能够找到并下载最新的 Build Tools，需要启用最新版本的 Tools（<code>- tools</code>）。</li><li>Lint 过程中如果 Platform Tools 版本低于 SDK 版本则会报错，需要启用最新版本的 Platform Tools（<code>-platform-tools</code>）。</li><li>为了能够找到并下载最新的 Platform Tools，需要已有最新的 Tools，因此与官方给出的样例不同，需要将 <code>- tools</code> 放置在 <code>- platform-tools</code> 之前。</li></ol><p>因此我的 Android 部分最终配置如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">android:</span></span><br><span class="line">  <span class="attr">components:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">tools</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">platform-tools</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">build-tools-24.0.1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">android-24</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">extra-android-m2repository</span></span><br></pre></td></tr></table></figure><p>详细配置可以参考我的 <a href="https://github.com/zhanghai/Douya/blob/master/.travis.yml">.travis.yml</a>。</p><h2 id="启用构建缓存"><a href="# 启用构建缓存" class="headerlink" title="启用构建缓存"></a>启用构建缓存 </h2><p>Gradle 是一个为缓存优化过的工具，因此官方也提供了相应的<a href="https://docs.travis-ci.com/user/languages/android#Caching"> 开启缓存的方法</a>。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">before_cache:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">rm</span> <span class="string">-f</span> <span class="string">$HOME/.gradle/caches/modules-2/modules-2.lock</span></span><br><span class="line"><span class="attr">cache:</span></span><br><span class="line">  <span class="attr">directories:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">$HOME/.gradle/caches/</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">$HOME/.gradle/wrapper/</span></span><br></pre></td></tr></table></figure><h2 id="应用签名"><a href="# 应用签名" class="headerlink" title="应用签名"></a>应用签名 </h2><p> 为了给 CI 版本的 APK 签名，需要提供相应的 keystore 和密码。Travis CI 提供了 <a href="https://docs.travis-ci.com/user/environment-variables/#Defining-Variables-in-Repository-Settings"> 在设置中定义环境变量 </a> 的方式来传递低敏感级的信息。</p><p>基于以上文档和一些搜索结果，并参考过 <a href="https://github.com/shadowsocks/shadowsocks-android">Shadowsocks-Android 的配置方式</a>，我将我的签名配置编写为了从 <code>signing.properties</code>、环境变量、终端输入三个层级进行获取的方式。</p><p>以下是我的 <code>signing.gradle</code>：</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">android &#123;</span><br><span class="line">    signingConfigs &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            Properties signingProperties = <span class="keyword">new</span> Properties()</span><br><span class="line">            signingProperties.load(rootProject.<span class="keyword">file</span>(<span class="string">&quot;signing.properties&quot;</span>).newDataInputStream())</span><br><span class="line">            storeFile rootProject.<span class="keyword">file</span>(signingProperties.get(<span class="string">&quot;storeFile&quot;</span>))</span><br><span class="line">            storePassword signingProperties.get(<span class="string">&quot;storePassword&quot;</span>) ?: System.getenv(<span class="string">&quot;STORE_PASSWORD&quot;</span>) ?: System.console()?.readLine(<span class="string">&quot;\nStore password: &quot;</span>)</span><br><span class="line">            keyAlias signingProperties.get(<span class="string">&quot;keyAlias&quot;</span>)</span><br><span class="line">            keyPassword signingProperties.get(<span class="string">&quot;keyPassword&quot;</span>) ?: System.getenv(<span class="string">&quot;KEY_PASSWORD&quot;</span>) ?: System.console()?.readLine(<span class="string">&quot;\nKey password: &quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 Android Studio 中进行 Gradle 同步时，<code>System.console()</code> 返回 <code>null</code>，因而密码均为 <code>null</code>，不会中断同步过程，也不影响调试版本的构建。</p><p>我创建了 <code>signing.properties</code> 和 <code>signing.properties.travis</code> 两个文件，在前者中填写 keystore 的路径并加入 <code>.gitignore</code>，而将后者在 <code>.travis.yml</code> 的 <code>before_script</code> 中复制为 <code>signing.properties</code>。</p><p>而在 Travis CI 的设置中，添加 <code>STORE_PASSWORD</code> 和 <code>KEY_PASSWORD</code> 两个环境变量即可。建议在环境变量值的两端加上单引号以避免特殊字符被 shell 错误处理。</p><h2 id="获取版本信息"><a href="# 获取版本信息" class="headerlink" title="获取版本信息"></a>获取版本信息 </h2><p> 直接在 <code>.travis.yml</code> 中调用 <code>git describe</code> 或 <code>git log</code> 等命令是无法成功的，因为 Travis CI 采用了 <code>git clone --depth=50</code> 来进行 clone。相应地，需要先执行 <code>git fetch --unshallow</code> 来完成 clone。</p><p>我采用了 <a href="http://semver.org/lang/zh-CN/"> 语义化版本</a>（Semver）来命名版本。因此，我的版本名称采用了如下方式获取：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">version=<span class="string">&quot;<span class="subst">$(git describe --long --tags | sed &#x27;s/^v//;s/-\([0-9]\+\)-g\([0-9a-f]\+\)/+\1.\2/&#x27;)</span>&quot;</span></span><br></pre></td></tr></table></figure><p>例如，在名为 <code>1.0.0-alpha</code> 的 tag 后第 227 次短哈希值为 <code>886f8ce</code> 的 commit，对应的版本名即为 <code>1.0.0-alpha.1+227.886f8ce</code>。</p><p>然后使用 <code>sed -i &#39;s/versionName .*/versionName &quot;&#39;&quot;$&#123;version&#125;&quot;&#39;&quot;/&#39; app/build.gradle</code> 即可更新 <code>build.gradle</code> 中的 <code>versionName</code> 字段。</p><h2 id="上传至 -Release"><a href="# 上传至 -Release" class="headerlink" title="上传至 Release"></a>上传至 Release</h2><p>GitHub 提供了在 Release 中上传二进制构建输出的功能。然而，如果直接在项目仓库中为每次 commit 添加 Release（和相应的 tag）则未免过于杂乱，因此我选择了新建 <a href="https://github.com/zhanghai/DouyaCiBuilds"> 一个只有 README 的仓库</a>，并将所有持续集成版本的 Release 创建在此仓库中。</p><p>为了实现此功能，我选择了通过 curl 调用 GitHub API 的方式来完成。经过查阅文档和大量的调试，我的脚本最终是如下编写的：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line">repo=<span class="string">&quot;<span class="variable">$1</span>&quot;</span></span><br><span class="line"><span class="built_in">shift</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Repo: <span class="variable">$&#123;repo&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line"></span><br><span class="line">version=<span class="string">&quot;<span class="variable">$1</span>&quot;</span></span><br><span class="line"><span class="built_in">shift</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Version: <span class="variable">$&#123;version&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line"></span><br><span class="line">tag=<span class="string">&quot;v<span class="variable">$&#123;version&#125;</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Tag: <span class="variable">$&#123;tag&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line"></span><br><span class="line">body=<span class="string">&quot;<span class="variable">$1</span>&quot;</span></span><br><span class="line"><span class="built_in">shift</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Body: <span class="variable">$&#123;body&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line"></span><br><span class="line"><span class="comment"># Get old release by tag</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Getting old release by tag...&quot;</span> &gt;&amp;2</span><br><span class="line">response=<span class="string">&quot;<span class="subst">$(curl -H <span class="string">&quot;Authorization: token <span class="variable">$&#123;GITHUB_ACCESS_TOKEN&#125;</span>&quot;</span> <span class="string">&quot;https://api.github.com/repos/<span class="variable">$&#123;repo&#125;</span>/releases/tags/<span class="variable">$&#123;tag&#125;</span>&quot;</span>)</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$&#123;response&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line">old_release_id=<span class="string">&quot;<span class="subst">$(echo <span class="string">&quot;<span class="variable">$&#123;response&#125;</span>&quot;</span> | jq -r &#x27;.id&#x27;)</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [[<span class="string">&quot;<span class="variable">$&#123;old_release_id&#125;</span>&quot;</span> != <span class="string">&quot;null&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Delete old release</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;Deleting old release...&quot;</span> &gt;&amp;2</span><br><span class="line">    response=<span class="string">&quot;<span class="subst">$(curl -X &#x27;DELETE&#x27; -H <span class="string">&quot;Authorization: token <span class="variable">$&#123;GITHUB_ACCESS_TOKEN&#125;</span>&quot;</span> <span class="string">&quot;https://api.github.com/repos/<span class="variable">$&#123;repo&#125;</span>/releases/<span class="variable">$&#123;old_release_id&#125;</span>&quot;</span>)</span>&quot;</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$&#123;response&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Create release</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Creating release...&quot;</span> &gt;&amp;2</span><br><span class="line">response=<span class="string">&quot;<span class="subst">$(curl -H <span class="string">&quot;Authorization: token <span class="variable">$&#123;GITHUB_ACCESS_TOKEN&#125;</span>&quot;</span> -H &#x27;Content-Type: application/json&#x27; --data <span class="string">&quot;&#123; \&quot;tag_name\&quot;: <span class="subst">$(echo -n <span class="string">&quot;<span class="variable">$&#123;tag&#125;</span>&quot;</span> | jq -s -R -r @json)</span>, \&quot;name\&quot;: <span class="subst">$(echo -n <span class="string">&quot;<span class="variable">$&#123;tag&#125;</span>&quot;</span> | jq -s -R -r @json)</span>, \&quot;body\&quot;: <span class="subst">$(echo -n <span class="string">&quot;<span class="variable">$&#123;body&#125;</span>&quot;</span> | jq -s -R -r @json)</span> &#125;&quot;</span> <span class="string">&quot;https://api.github.com/repos/<span class="variable">$&#123;repo&#125;</span>/releases&quot;</span>)</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$&#123;response&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line">upload_url=<span class="string">&quot;<span class="subst">$(echo <span class="string">&quot;<span class="variable">$&#123;response&#125;</span>&quot;</span> | jq -r &#x27;.upload_url&#x27; | sed &#x27;s/&#123;?name,label&#125;$//g&#x27;)</span>&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Upload url: <span class="variable">$&#123;upload_url&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> file <span class="keyword">in</span> <span class="string">&quot;<span class="variable">$@</span>&quot;</span>; <span class="keyword">do</span></span><br><span class="line">    <span class="comment"># Upload file</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;Uploading file: <span class="variable">$&#123;file&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line">    name=<span class="string">&quot;<span class="subst">$(basename <span class="string">&quot;<span class="variable">$&#123;file&#125;</span>&quot;</span>)</span>&quot;</span></span><br><span class="line">    response=<span class="string">&quot;<span class="subst">$(curl -H <span class="string">&quot;Authorization: token <span class="variable">$&#123;GITHUB_ACCESS_TOKEN&#125;</span>&quot;</span> -H <span class="string">&quot;Content-Type: <span class="subst">$(file -b --mime-type <span class="string">&quot;<span class="variable">$&#123;file&#125;</span>&quot;</span>)</span>&quot;</span> --data-binary <span class="string">&quot;@<span class="variable">$&#123;file&#125;</span>&quot;</span> <span class="string">&quot;<span class="variable">$&#123;upload_url&#125;</span>?name=<span class="subst">$(echo -n <span class="string">&quot;<span class="variable">$&#123;name&#125;</span>&quot;</span> | jq -s -R -r @uri)</span>&quot;</span>)</span>&quot;</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$&#123;response&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>而设置 <code>GITHUB_ACCESS_TOKEN</code> 环境变量后的用法则如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./upload-to-releases.sh <span class="string">&#x27;zhanghai/DouyaCiBuilds&#x27;</span> <span class="string">&quot;<span class="variable">$&#123;version&#125;</span>&quot;</span> <span class="string">&quot;<span class="variable">$&#123;description&#125;</span>&quot;</span> <span class="string">&quot;douya-ci-<span class="variable">$&#123;version&#125;</span>.apk&quot;</span></span><br></pre></td></tr></table></figure><h2 id="其他"><a href="# 其他" class="headerlink" title="其他"></a>其他 </h2><p> 如果无法确定错误原因，就多加些 <code>echo</code> 或者 <code>cat</code> 吧。</p><p>如果希望在 Lint 失败时查看输出，可以在 <code>after_failure</code> 中加入 <code>cat app/build/outputs/lint-results-*.html</code>。</p><p>我所采用的配置都可以在 <a href="https://github.com/zhanghai/Douya"> 豆芽 </a> 中找到。</p><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语 </h2><p> 为 Android 项目使用 Travis CI 的过程还算简单，但是也有一些微妙的问题需要解决，这令我花费了不少时间。而将每次的构建输出上传至另一个仓库的 Release 则是我考虑了一段时间后得出的方案，之前没见到过这种方式，用 curl 调用 GitHub API 也是第一次，同时再次感受到了 bash 的得心应手，总体上是一次十分有趣的体验。</p><p>因此，将我的配置过程和结果记录在此，希望对其他开发者有所帮助。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;Travis CI 是 GitHub 上开源项目采用持续集成的常见选择。为了给&lt;a href=&quot;https://github.com/zhanghai/Douya&quot;&gt;豆芽&lt;/a&gt;提供持续集成版本用于公开测试，我配置了 Travis CI，并自己编写了脚本将构建结果发布到另一个空项目的 Release 中，并将其间的过程和遇到的问题记录于此。&lt;/p&gt;
&lt;p&gt;Travis CI 构建 Android 项目的时间较长，因此调试配置时十分耗时。希望我的经验能对他人有所帮助。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Android" scheme="https://blog.zhanghai.me/tags/android/"/>
    
    <category term="教程" scheme="https://blog.zhanghai.me/tags/tutorial/"/>
    
    <category term="软件" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>Verilog 编程笔记</title>
    <link href="https://blog.zhanghai.me/verilog-notes/"/>
    <id>https://blog.zhanghai.me/verilog-notes/</id>
    <published>2016-03-24T02:27:26.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 这是我在大学修习逻辑与计算机设计基础、计算机组成和计算机体系结构三门硬件课的过程中积累的 Verilog 笔记。</p><p>Verilog 是一门主要用于逻辑电路设计的硬件描述语言。语言标准主要有 <a href="http://standards.ieee.org/findstds/standard/1364-1995.html">Verilog-1995</a> 与 <a href="http://standards.ieee.org/findstds/standard/1364-2001.html">Verilog-2001</a> 两个版本，建议在创建工程时选择 Verilog-2001 标准以支持更多实用的语法。</p><p>虽然 Verilog 的语法与 C 相似，但是二者是面向各自的目标硬件设计的。Verilog 是一门面向逻辑电路的具体实现而设计的语言（正如 C 在某种程度上是面向汇编等底层实现的），因此写作 Verilog 时不可以将 C 等编程语言的思维方式代入，而是要始终清晰地思考正在编写的代码将能够综合成怎样的逻辑电路实现——如果可以，那么大多能够写出在字面和实现上都优雅的代码；如果不行，那么综合时大概也会报错或消耗大量资源，此时则应该考虑调整思路。</p><span id="more"></span><h2 id="手册"><a href="# 手册" class="headerlink" title="手册"></a>手册</h2><p>Verilog 的标准文档 IEEE 1364-2001 在网络上可以找到下载，但相比之下，这份标准还是稍显冗长或不够友好。而在标准文档之外，Xilinx ISE 的 XST 综合器也提供了实现文档，并且其中包含了许多 Verilog 语法的实用描述，因此可以作为一本更加友好的手册进行查阅。</p><p><a href="http://www.xilinx.com/support/documentation/sw_manuals/xilinx14_7/xst_v6s6.pdf">XST User Guide for Virtex-6, Spartan-6, and 7 Series Devices</a></p><p>以及这里还有一份简单的非官方 Verilog-2001 手册：</p><p><a href="http://sutherland-hdl.com/pdfs/verilog_2001_ref_guide.pdf">Verilog HDL Quick Reference Guide</a></p><h2 id="指南"><a href="# 指南" class="headerlink" title="指南"></a>指南 </h2><p> 在本文草稿的同时，我的某位同学也完成了一份 Verilog 指南。指南十分详尽而实用，因此希望读者先行阅读。</p><p><a href="https://github.com/ZJUArch/verilog-general-guide">Verilog General Guide</a></p><h2 id="重复（Replication）"><a href="# 重复（Replication）" class="headerlink" title="重复（Replication）"></a>重复（Replication）</h2><p>在 Verilog 中，你可以使用 <code>&#123;COUNT&#123;singal&#125;&#125;</code> 将指定的信号重复数次后连接。</p><p>例如，想要写一个 32 位 0&#x2F;1 相见的字面量，不需要写出 <code>32&#39;b01010101010101010101010101010101</code>，而是写 <code>&#123;16&#123;2&#39;b01&#125;&#125;</code> 即可简洁而直观地完成。</p><p>另一个例子是 16 位到 32 位的符号扩展，也可以用 <code>&#123;16&#123;signal[15]&#125;, signal[15:0]&#125;</code> 来完成。</p><h2 id="参数定义：parameter-localparam-define"><a href="# 参数定义：parameter-localparam-define" class="headerlink" title="参数定义：parameter, localparam,  `define"></a>参数定义：<code>parameter</code>, <code>localparam</code>, <code> `define</code></h2><p>Verilog 提供了三种定义“常量”的方式。</p><p><code>parameter</code> 是可以由模块外部改变的参数。除了在模块内部定义的语法，我更加推荐采用 Verilog-2001 中在模块接口声明中定义的语法：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">module</span> module_name</span><br><span class="line">#(</span><br><span class="line"><span class="keyword">parameter</span> parameter_name = parameter_default_value</span><br><span class="line">) (</span><br><span class="line">...</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"><span class="keyword">endmodule</span></span><br><span class="line"></span><br><span class="line">module_name #(</span><br><span class="line"><span class="variable">.parameter_name</span>(parameter_value)</span><br><span class="line">) (</span><br><span class="line">...</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><code>localparam</code> 是模块内部定义的参数，语法如 <code>localparam localparam_name = localparam_value</code>。</p><p><code> `define</code> 是 Verilog 的宏定义，与 C 宏的文本替换相似。宏除了用于定义常量，还可以用来简化代码的编写，例如：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">`<span class="keyword">define</span> packed_wires = &#123;wire_1, wire_2, wire_3&#125;</span></span><br><span class="line">packed_wires = <span class="number">3&#x27;b101</span></span><br></pre></td></tr></table></figure><h2 id="变基部分选择（Indexed-part-select）"><a href="# 变基部分选择（Indexed-part-select）" class="headerlink" title="变基部分选择（Indexed part-select）"></a>变基部分选择（Indexed part-select）</h2><p>可以使用 <code>wire_name[index_wire_name]</code> 的方式来实现一位宽度的多路选择器。相应地，Verilog-2001 也提供了 <code>wire_name[WIDTH * index_wire_name +: WIDTH]</code> 的方式来实现多位宽度的多路选择器。详情可以参考这个 StackOverflow 问题：</p><p><a href="http://electronics.stackexchange.com/questions/74277">What is this operator called as “+:” in verilog - Electrical Engineering Stack Exchange</a>。</p><p>然而这个语法的综合有一些怪异的地方，有时会导致综合成移位器而非多路选择器，消耗较多的资源，使用时需要留意一下综合报告。</p><h2 id="对表达式进行选择"><a href="# 对表达式进行选择" class="headerlink" title="对表达式进行选择"></a>对表达式进行选择 </h2><p> 在 Verilog 中，无法直接对 <code>wire_name + 1&#39;b1</code> 这样的表达式选择某些位，但可以其实通过加上花括号的方式进行选择，例如 <code>&#123;wire_name + 1&#39;b1&#125;[3:0]</code>。这个形式可以用于显式地截短运算结果并且不触发警告。</p><p>然而这个 trick 对于变基部分选择无效。以及似乎在某些旧型号的硬件上 XST 无法识别这个语法。</p><h2 id="代码简化"><a href="# 代码简化" class="headerlink" title="代码简化"></a>代码简化 </h2><p> 可以采用 <code>generate</code>、<code>task</code> 或 <code>function</code> 简化代码逻辑的编写，详细的使用方法在 Verilog General Guide 中已经说明，故不再赘述。</p><h2 id="信号宽度警告"><a href="# 信号宽度警告" class="headerlink" title="信号宽度警告"></a>信号宽度警告 </h2><p> 信号宽度警告是一种十分有用的警告，因为许多低级错误都是由于错误地（或忘记）指定信号宽度导致的。请务必仔细检查综合报告。</p><p>由于裸写的非零十进制整数字面量默认为 32 位，容易增加多余的警告，因此建议在书写字面量时总是使用 <code>2&#39;b</code>、<code>10&#39;d</code>、<code>16&#39;h</code> 这样的前缀形式。</p><p>至于对信号进行递增并且在溢出后自动归零，可以采用 <code>reg_name = reg_name + 1&#39;b1</code> 的形式，综合器不会将此判断为溢出而发出警告。</p><h2 id="组合逻辑"><a href="# 组合逻辑" class="headerlink" title="组合逻辑"></a>组合逻辑 </h2><p> 在 Verilog 中组合逻辑可以通过在 <code>always @*</code> 块中使用 <code>if</code>、<code>switch</code> 和阻塞赋值 <code>=</code> 实现，也可以直接使用 <code>assign</code> 和 <code>?:</code> 这个条件运算符实现。</p><p>我个人的建议是避免使用 <code>always</code> 块实现组合逻辑，因为在用 <code>if</code> 和 <code>switch</code> 实现逻辑时，很容易忘记书写某些情况下的默认值，导致综合结果为 FF&#x2F;Latch 的时序电路而非组合电路，并且这件事情很难在综合报告中发现。</p><p>我目前采取的实践是仅将 <code>always</code> 块用于实现时序电路，而使用条件运算符实现组合逻辑。</p><p>顺带提及，<code>always @*</code> 用于实现组合电路，应该只使用阻塞赋值 <code>=</code>；<code>always @(posedge clock)</code> 用于实现时序电路，应该只使用非阻塞赋值 <code>&lt;=</code>；两者混用依然可以成功综合，但是意义不明，十分容易出错。</p><h2 id="Lint"><a href="#Lint" class="headerlink" title="Lint"></a>Lint</h2><p>对于长时间调试难以找出的错误，可以尝试使用 Lint 工具对代码进行静态检查，它比 XST 综合器的检查更加严格，也常常能指出代码中埋藏的许多低级错误（我曾经用它在十分钟内找出了一个调试了一整天未能找出的 Bug）。</p><p>Verilator 是一个免费的 Verilog 模拟器，其中也包含了 Lint 功能。安装 <code>verilator</code>后，执行 <code>verilator --lint-only top.v</code> 即可开始检查。</p><h2 id="其他"><a href="# 其他" class="headerlink" title="其他"></a>其他 </h2><p> 综合过程中输出的 Simulation mismatch 警告不能无视，否则可能会导致不稳定的综合结果和奇怪的错误。</p><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语 </h2><p> 除了某些语法检查不够严格，Verilog 其实是一门设计得较为直觉的语言，语法结构大多可以映射到电路实现，有着 C 一般的简洁和直观。如果遵循一定的编码规范和最佳实践，使用硬件而非软件的思维进行编码，还是可以较为简单地达成目标的。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;这是我在大学修习逻辑与计算机设计基础、计算机组成和计算机体系结构三门硬件课的过程中积累的 Verilog 笔记。&lt;/p&gt;
&lt;p&gt;Verilog 是一门主要用于逻辑电路设计的硬件描述语言。语言标准主要有 &lt;a href=&quot;http://standards.ieee.org/findstds/standard/1364-1995.html&quot;&gt;Verilog-1995&lt;/a&gt; 与 &lt;a href=&quot;http://standards.ieee.org/findstds/standard/1364-2001.html&quot;&gt;Verilog-2001&lt;/a&gt; 两个版本，建议在创建工程时选择 Verilog-2001 标准以支持更多实用的语法。&lt;/p&gt;
&lt;p&gt;虽然 Verilog 的语法与 C 相似，但是二者是面向各自的目标硬件设计的。Verilog 是一门面向逻辑电路的具体实现而设计的语言（正如 C 在某种程度上是面向汇编等底层实现的），因此写作 Verilog 时不可以将 C 等编程语言的思维方式代入，而是要始终清晰地思考正在编写的代码将能够综合成怎样的逻辑电路实现——如果可以，那么大多能够写出在字面和实现上都优雅的代码；如果不行，那么综合时大概也会报错或消耗大量资源，此时则应该考虑调整思路。&lt;/p&gt;</summary>
    
    
    
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
    <category term="学业" scheme="https://blog.zhanghai.me/tags/schoolwork/"/>
    
    <category term="笔记" scheme="https://blog.zhanghai.me/tags/note/"/>
    
  </entry>
  
  <entry>
    <title>Plan 9 操作系统介绍</title>
    <link href="https://blog.zhanghai.me/introduction-to-plan-9-the-os/"/>
    <id>https://blog.zhanghai.me/introduction-to-plan-9-the-os/</id>
    <published>2015-10-21T22:32:24.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 这篇文章是我在操作系统原理课程的作业。作业选题本身很宽，大意是只要描述操作系统的发展即可。因为早就对 Plan 9 这个旨在取代 Unix 的操作系统有兴趣，又可以借此花时间仔细理解和实际使用一次这个系统，于是毫不犹豫地选择了这个题目。</p><p>以下就是我自己对 Plan 9 这个操作系统在设计上的理解和概括。</p><span id="more"></span><h2 id="简介"><a href="# 简介" class="headerlink" title="简介"></a>简介</h2><p>Plan 9 是一款分布式操作系统，由贝尔实验室计算机科学研究中心在 19 世纪 80 年代中期开始开发，旨在成为 Unix 的后继者。Plan 9 的团队正是曾开发 Unix 和 C 语言的团队，开发过程中参与者包括 Ken Thompson、Dennis Ritchie、Bjarne Stroustrup 等人。</p><p>本文将主要介绍 Plan 9 的部分设计理念。</p><h2 id="设计"><a href="# 设计" class="headerlink" title="设计"></a>设计 </h2><h3 id="文件"><a href="# 文件" class="headerlink" title="文件"></a> 文件 </h3><p>Plan 9 继承了 Unix 中“<a href="https://en.wikipedia.org/wiki/Everything_is_a_file"> 任何事物都是文件</a>”的哲学，并且对其进一步扩展，将所有计算资源都视为文件进行管理，使用读取和写入操作作为与资源交互的统一方式。</p><p>例如，在 Unix 中对许多具体硬件控制需要使用 <code>ioctl</code> 系统调用进行，以及 X Window 等系统中对于资源也使用函数调用控制而非用文件代表，但在 Plan 9 中，无论是 CPU、外围设备、网络，还是图形界面本身，设计者始终采用文件来代表资源并与之交互。</p><p>在这个意义下，其实文件的意义并非传统的磁盘存储的单元，而是一个指代一般意义上计算资源的名字。</p><h3 id="文件系统"><a href="# 文件系统" class="headerlink" title="文件系统"></a>文件系统</h3><p>Plan 9 与 Unix 同样采用的是层次性的文件系统。由于文件实际上就是资源，所以文件的路径实际上也与 URI 的意义类似，即计算资源可以通过特定的路径访问。</p><p>在文件的组织方式上，Plan 9 使用了与 Unix 类似约定的命名方式，例如也可以使用 <code>ls /proc</code> 查看所有进程等。</p><p>Plan 9 的文件系统并非传统意义上局限于磁盘的文件系统。在 Plan 9 中，由于资源即是文件，资源本身来自本地设备还是需要通过网络访问其实只是实现细节，所以两者在文件系统中具有统一的表示。Plan 9 采用一种名为 9P 的协议对资源的访问过程进行封装。经过这种抽象，系统中对远端和本机资源进行访问的接口达成了一致，由此通过分布式的文件系统从系统设计上实现了分布式计算的基础，例如即使是 CPU 资源也可以通过文件方便地共享。</p><h3 id="命名空间"><a href="# 命名空间" class="headerlink" title="命名空间"></a>命名空间 </h3><p> 文件系统只是资源的组织形式，而允许不同程序对资源采取不同的组织形式可以带来很大的可扩展性，因此 Plan 9 对每个进程使用使用了不同的文件系统，称为命名空间，并由此实现了资源的进程隔离。</p><p>此外，父进程还可以以 9P 协议向子进程的命名空间提供虚拟文件（以文件作为服务），这样实现了一种低成本而原生的跨进程资源访问，也使得提供给子进程资源可以被自由地替换。由此，实现 VPN 连接只需要将 <code>/net</code> 目录代理，实现窗口系统只需要将 <code>bitblt</code> 文件代理，等等。</p><h3 id="合并目录"><a href="# 合并目录" class="headerlink" title="合并目录"></a>合并目录 </h3><p> 因为要替换子进程文件系统中的目录，但目录中的内容又并非全部都需要重新提供，合并目录就成为了一个良好的解决方案。</p><p>Plan 9 中的合并目录是一种类似 Unix 中挂载和搜索路径相结合的做法。通过将目录甲合并至目录乙，访问目录乙时将会先在目录甲中进行查找，未找到时再在目录乙本身中查询。将这个设计与网络结合，则可以产生将远端的 <code>/bin</code> 目录合并至本地即可直接使用远端程序这样的使用方法。</p><h3 id="UTF-8"><a href="#UTF-8" class="headerlink" title="UTF-8"></a>UTF-8</h3><p>Plan 9 更加为人知晓的一个组件则是由 Ken Thompson 提出的 UTF-8 编码。通过这种编码方式，Unicode 自动兼容于原先的 ASCII 字符，不会出现需要编码转换的问题，因此时至今日已经被广泛采用。而 UTF-8 本身则早已是 Plan 9 的原生编码。</p><h2 id="发展"><a href="# 发展" class="headerlink" title="发展"></a>发展 </h2><p> 虽然 Plan 9 有着相对于 Unix 更加普适和可扩展的设计，但在实际使用中它并没有赢得太多的用户。</p><p>Plan 9 在 1992 年向大学公开，并在 1995 年由 AT&amp;T 商业发布。然而 1996 年，AT&amp;T 已将重心转移至 Inferno，一个基于 Plan 9 思想设计并且旨在与 Java 竞争的系统。到了 2000 年，掌管贝尔实验室的朗讯决定停止 Plan 9 的商业支持，并在几年之后将 Plan 9 的代码以自由软件许可的形式向大众公开。</p><h2 id="评价"><a href="# 评价" class="headerlink" title="评价"></a>评价</h2><p>Plan 9 对 Unix 中的许多理念做了进一步的提升和扩展，对资源管理和分布式计算进行了优秀而统一的实现，在操作系统架构的历史上具有不可磨灭的意义；来自其中的 UTF-8、rc shell 等组件，也被其他操作系统所吸纳接受；并且，Plan 9 的后继者 Inferno 和 Plan B 还在继续发展。</p><p>但是，仅有优雅的设计，却缺乏相应的生态，这导致了 Plan 9 在实际应用中的结果不成功，令人惋惜。这一切正如 Eric Raymond 所说，足以让目标高远的软件工程师们时时警醒：</p><blockquote><p>Plan 9 会失败，纯粹只是因为它的改进程度没有大到能取代 Unix。与 Plan 9 相比，Unix 虽然破破烂烂带有明显的缺陷，但还是能够把工作良好地完成，而这就足以保住它的地位了。</p><p>这件事情教给了那些怀有雄心壮志的系统架构师一个道理：一个更优解决方案所面临的最危险的敌人，其实是那些能把事情刚好完成的程序。</p></blockquote><h2 id="致谢"><a href="# 致谢" class="headerlink" title="致谢"></a>致谢 </h2><p> 本文写作过程中参考了 <a href="https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs">Plan 9 from Bell Labs - Wikipedia, the free encyclopedia</a> 和 <a href="https://zh.wikipedia.org/wiki/%E8%B2%9D%E7%88%BE%E5%AF%A6%E9%A9%97%E5%AE%A4%E4%B9%9D%E8%99%9F%E8%A8%88%E7%95%AB">贝尔实验室九号计划 - 维基百科，自由的百科全书</a> 等页面的内容，在此表示感谢。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;这篇文章是我在操作系统原理课程的作业。作业选题本身很宽，大意是只要描述操作系统的发展即可。因为早就对 Plan 9 这个旨在取代 Unix 的操作系统有兴趣，又可以借此花时间仔细理解和实际使用一次这个系统，于是毫不犹豫地选择了这个题目。&lt;/p&gt;
&lt;p&gt;以下就是我自己对 Plan 9 这个操作系统在设计上的理解和概括。&lt;/p&gt;</summary>
    
    
    
    
    <category term="学业" scheme="https://blog.zhanghai.me/tags/schoolwork/"/>
    
    <category term="计算机科学" scheme="https://blog.zhanghai.me/tags/computer-science/"/>
    
    <category term="软件" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>Thoughts on Content Handling in Android Application</title>
    <link href="https://blog.zhanghai.me/thoughts-on-content-handling-in-android-application/"/>
    <id>https://blog.zhanghai.me/thoughts-on-content-handling-in-android-application/</id>
    <published>2015-10-05T04:44:12.000Z</published>
    <updated>2020-11-22T06:08:03.407Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>For content-oriented Android application, how and where to store the content to display is a issue every developer will be concerned with. The Android framework provided a comprehensive solution with <code>ContentProvider</code> (and a lot more) which suits quite well with a SQLite database; but in a lot of other scenarios, the app only need to have some information cached, while the majority of content is directly retrieved from network, thus eliminating the need for a database (and a complicated content scheme).</p><p>But when we look into the core of this issue, we will soon find out that it is in fact a problem of whether to have a central storage, and how to notify different components about a change.</p><span id="more"></span><h2 id="Initial-Scenario"><a href="#Initial-Scenario" class="headerlink" title="Initial Scenario"></a>Initial Scenario</h2><p>Let’s look at the naive solution first, retrieving content from network and only keeping them in memory. In this case, no central storage is needed, and every component fetches its content independently, simple enough. But this approach immediately becomes flawed when it comes to content updating such as user modification. For instance, we have a list activity and a detail activity, and the user can often modified something in the detail activity, say, they clicked ‘like’, and the like button shall look activated not only in the detail activity, but also in the list activity now in background.</p><p>Content is fetched independently, so every component have no knowledge of whether someone else is holding the same content as itself, and that content will become stale when shown to user if the content this component newly retrieved is an updated version. Ideally speaking, any piece content is a single resource that should have only one state at point of time, and keeping it represented by one entity in memory is the best way to achieve this — yes, a central storage (whether in memory or also backed by disk), and the DRY (don’t repeat yourself) principle, a different story of the solution framework provided.</p><p>Anyway, we always need a notifying mechanism, instead of having some holder of a unique entity shared among clients, because clients may need to transform the content into something else instead of using the unique one directly, or they need notify others when content is changed.</p><h2 id="ContentProvider-Mechanism"><a href="#ContentProvider-Mechanism" class="headerlink" title="ContentProvider Mechanism"></a>ContentProvider Mechanism</h2><p>The idea behind <code>ContentProvider</code> is that, <code>ContentProvider</code> is the only central storage and all its content is the only real entity that will always be up-to-date, while the content its clients hold is only a copy that will easily get out-of-date. So when a change happened, the central storage is refreshed, and <code>ContentObserver</code>s are notified of the change and then they will query the central <code>ContentProvider</code> for an updated copy of content.</p><p>In this case, only the <code>ContentProvider</code> (or a delegated <code>SyncAdapter</code>) can fetch data from network; the clients can only request a fetch and wait for the change callback just as any other client. The central <code>ContentProvider</code> acts as a middle man between clients and network, to ensure the uniqueness of any content entity.</p><h2 id="Go-centralized"><a href="#Go-centralized" class="headerlink" title="Go centralized?"></a>Go centralized?</h2><p>But a principally-correct and comprehensive solution may not be the best solution for a specific type of scenario. We will face at least the following difficulties:</p><ol><li><p>Identifier: In order to ensure only one central entity per resource, We need URIs to uniquely identify resources just as its name suggests, so we must defining a content URI scheme. But things go complex quickly considering the complex logical hierarchy an interlinked content system will need.</p></li><li><p>Action: We’ll lose the flexibility on handling changes for special cases. Think of infinite loading, liking and post deleting, we’ll need a generic mechanism to notify all the observers on an URI if some items are newly loaded, modified or removed, while framework’s <code>ContentObserver</code> only offers an <code>onChanged()</code> method. However if we handle this only for specific cases, it can be much more easier to implement.</p></li><li><p>Releasing memory: Because most of the content is dynamic, there is little point in data persistence on disk. So we’ll store retrieved items only in memory, and then, we need to release them once nobody needs them. This can be tricky when, for instance, we have an observer on a collection, and another observer on a detail of this collection is removed, now whether the detailed content should be released depends on whether the collection observer actually observes on this detail, which cannot be inferred by the central content manager from URI scheme.</p></li><li><p>JSON interoperability: The framework <code>ContentProvider</code> mechanism uses <code>Cursor</code>, where only basic types and blob are valid column type, making it uninteroperable with the widely used JSON approach. We’ll have to roll our own.</p></li></ol><p>Implementing and using such a framework is a complex and heavy task, and complexity is error-prone, while it brings little advantage over the decentralized solution we are going to talk about.</p><h2 id="Stay-decentralized"><a href="#Stay-decentralized" class="headerlink" title="Stay decentralized"></a>Stay decentralized</h2><p>So we want to stay on the track of not having any central storage, and allow duplicated (so possibly inconsistent) entities of content in memory.</p><p>This way, we are to do a sync among components once we any content is newly fetched from network in this case. The solution is much more specific to each scenario in this case: we can utilize some already present event bus system to get notified of updates, listen to specific event code of content change, and then respond accordingly.</p><p>On the first sight, this solution may seem not generic enough, lacking in the beauty of unity. However, considering the overall cost of using a centralized storage with URI scheme, I believe this decentralized-and-syncing mechanism is the way to go for applications in this scenario.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Different mechanisms apply to different scenarios. Android’s centralized <code>ContentProvider</code> mechanism fits for content that should be persisted and synced, while the decentralized mechanism works well with application with highly-dynamic content.</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;Introduction&quot;&gt;&lt;a href=&quot;#Introduction&quot; class=&quot;headerlink&quot; title=&quot;Introduction&quot;&gt;&lt;/a&gt;Introduction&lt;/h2&gt;&lt;p&gt;For content-oriented Android application, how and where to store the content to display is a issue every developer will be concerned with. The Android framework provided a comprehensive solution with &lt;code&gt;ContentProvider&lt;/code&gt; (and a lot more) which suits quite well with a SQLite database; but in a lot of other scenarios, the app only need to have some information cached, while the majority of content is directly retrieved from network, thus eliminating the need for a database (and a complicated content scheme).&lt;/p&gt;
&lt;p&gt;But when we look into the core of this issue, we will soon find out that it is in fact a problem of whether to have a central storage, and how to notify different components about a change.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Android" scheme="https://blog.zhanghai.me/tags/android/"/>
    
    <category term="Programming" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
  </entry>
  
  <entry>
    <title>Android 开发入门指南</title>
    <link href="https://blog.zhanghai.me/android-developer-guide/"/>
    <id>https://blog.zhanghai.me/android-developer-guide/</id>
    <published>2015-09-29T04:13:29.000Z</published>
    <updated>2019-05-16T05:05:51.861Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 从我入门 Android 开发至今已经两年有余，过程中有了一些经验和积累，所以在此将我对于入门者的建议记录下来。</p><span id="more"></span><h2 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h2><p>学习 Android 开发，首先要有扎实的 Java 基础。如果不理解一门语言的设计思想，编写代码时将常常感觉思维与语法发生冲突，同时也无法针对这门语言扬长避短，最终代码将会难以实现和维护。</p><p>对于尚无 Java 基础的同学，我推荐 <a href="http://book.douban.com/subject/1474824/">《Thinking in Java》</a> 这本书（中文译本为<a href="http://book.douban.com/subject/2130190/">《Java 编程思想》</a>），因为它对 Java 语法和设计的讲解较为精确详细，同时对设计模式和最佳实践也有合适的涉猎。这本书除了最后一章关于图形界面的部分可以跳过，之前所有的章节，都应当仔细地阅读和理解一遍。我个人在阅读时使用了两周大约 40 小时的时间。</p><p>此外，初学者请格外重视代码风格，始终遵循 Java 的编码规范，因为这可以使代码风格统一、结构有序、逻辑清晰和易于维护。IntelliJ Idea（Android Studio）的默认代码风格正与之符合。</p><h2 id="框架入门"><a href="# 框架入门" class="headerlink" title="框架入门"></a>框架入门 </h2><p> 在拥有了 Java 语法基础之后，就可以开始学习 Android 的框架了。</p><p>Android API 和最佳实践的更新较为迅速，所以几乎没有可靠的 Android 开发书籍，不推荐购买书籍入门。而 <a href="https://developer.android.com/develop">Android 官方网站的文档</a> 是保持与框架同步的，同时也有很多关于最佳实践的介绍。</p><p>所以我的建议是，从官方文档的 <a href="https://developer.android.com/training/index.html">Training</a> 开始，读完最基础的部分后，自己想好要做的一个小型项目，再按照需求查找 <a href="https://developer.android.com/guide/index.html">Guide</a> 和 <a href="https://developer.android.com/reference/packages.html">Reference</a>，结合 <a href="https://stackoverflow.com/">StackOverflow</a> 等资源，在实践中完成第一个自己的 Android 应用，掌握一些 Android 开发的基础知识。这个过程我使用了另外两周的时间。</p><p>此外作为补充，<a href="http://guides.codepath.com/android">Code Path 的教程</a> 也可以参考，虽然并非必须；大多数最佳实践和陷阱都需要开发者自己通过搜索多方资料和阅读源代码积累。</p><h2 id="开发环境"><a href="# 开发环境" class="headerlink" title="开发环境"></a>开发环境 </h2><p> 关于开发时使用的环境，Eclipse 早已经是日薄西山，而构建于 <a href="https://www.jetbrains.com/idea/">IntelliJ Idea</a> 之上的 <a href="https://developer.android.com/tools/studio/index.html">Android Studio</a> 才是 Google 和社区支持的重心，也是在官方文档中建议使用的开发环境。</p><p>同时，Android Studio 的 Refactor，Generate，Lint Check 等功能也十分强大，能够显著地提高开发体验和效率。此外 Emacs 键绑定在调节个别设置之后就完全可用。</p><p>Android Studio 的连击 Shift 全局搜索功能在想要打开特定文件时也十分好用，默认在工程范围内搜索，再次连击可以将框架代码也纳入搜索范围。</p><p>在工具的选择上还是应该以功能性和主流选择为依据，因此我建议即使之前熟悉 Eclipse 的同学，也开始使用 Android Studio 进行 Android 开发。</p><p>官方文档中有关于 Android Studio 和 Android SDK 配置的详细说明。Android Studio 的更新动态可以在 <a href="http://tools.android.com/recent">Anroid Tools Project Site</a> 上查看，也提供了 RSS 订阅。</p><h2 id="问题解决"><a href="# 问题解决" class="headerlink" title="问题解决"></a>问题解决 </h2><p> 在开发的过程中如果遇到问题，首先可以尝试 <a href="https://www.google.com/">Google 搜索</a>。搜索结果中，又以 Android 官方的教程结果为好，<a href="https://stackoverflow.com/">StackOverflow</a> 辅之，某些编写精良的国外博客可以参考。</p><p>此外，如果是框架或者支持库的 Bug，<a href="https://code.google.com/p/android/issues/">Google Code Issues</a> 和 StackOverflow 上也可能有人贴出 Workaround；如果暂时没有，自己在 Google Code 上提交 issue 也有一定可能收到关注<del>（和 Chris Banes 搭话玩）</del>。</p><p>对于 CSDN、博客园等国内博客，则应该尽量避免，即使读时，应抱有将信将疑的态度，因为国内博客的总体质量偏低，常常代码风格混乱、不遵循最佳实践、只以达成效果为目的，甚至可能有明显的错误。当然少数排版良好、编写细致的博客除外。</p><p>至于 Android 开发 QQ 群、微信群等，由于质量和氛围，在总体上个人极其不建议新手加入。</p><h1 id="阅读框架代码"><a href="# 阅读框架代码" class="headerlink" title="阅读框架代码"></a>阅读框架代码 </h1><p> 如果问题没有直接的解答，则可以尝试查看 Android 的 JavaDoc 和源代码，来理解框架的具体行为。</p><p>在这个过程中，Android Studio 是很好的工具。通过按住 Ctrl + 单击，可以跳转到源代码所在的位置，或者资源文件的定义；点击类或方法左侧行号旁边的标记，可以访问继承或重载当前片段的代码，查看框架的细节行为，或是内部的使用范例；这些功能使得查看框架代码十分方便快捷。</p><p>此外，<a href="http://grepcode.com/">GrepCode</a>、<a href="https://github.com/android/">GitHub</a> 和 <a href="https://android.googlesource.com/">Google Source</a> 可以在线查看框架代码的变更，以及未进入 SDK 的代码，使用时 Google“类名 网站名”即可。三家各有千秋。GrepCode 类名或方法名旁的向下箭头，类似 Android Studio，可以点击查看使用情况或者继承、覆盖者等，便于快速定位问题、理解架构；GitHub 和 Google Source 则有完整的 git 记录，可以查看 diff 和 blame（GoogleSource 是真正的版本库所在地，GitHub 会自动拉取 GoogleSource 上的更新）；个人感觉 GitHub 的界面和功能相对更加友好一些。</p><p>顺带一提，涉及到 IPC 的 <code>IBinder</code> 之类代码，具体实现可以在 <code>com.android.server</code> 这个包下找到。</p><h2 id="向下兼容"><a href="# 向下兼容" class="headerlink" title="向下兼容"></a>向下兼容 </h2><p> 虽然 Android 的 Training 和 Guide 中大部分是以原生框架 API 来教学的，但实际上有一些功能是在后续版本的 Android 中才引入的，直接使用这些功能会导致在旧的 Android 上找不到相应方法而发生崩溃。</p><p>在 Android 开发的早期，问题不是如此明显，开发者经常自己条件执行与版本相关的代码。久而久之，也有人将部分方面的兼容性代码封装成库提供给开发者使用，直到后来 Android 官方终于发布了 <a href="https://developer.android.com/tools/support-library/index.html">Support Library</a> 来全面地完成这项工作。</p><p>我曾经对支持库有所保留，有时还是偏向于使用原生实现而非支持库实现。但后来我慢慢感受到，Android 官方的支持重点还是使用支持库的方式，并且这样也能够不断带来更好的兼容性。因此，我还是建议新手在使用新 API 时，选择支持库而非原生实现。</p><p>目前支持库主要向下支持至 V7，而官方建议开发者向下支持至 V14（Android 4.0） 就可以了，这样覆盖了 95% 左右的用户。关于 Android 版本的统计数据可以在 <a href="https://developer.android.com/about/dashboards/index.html">这里</a> 查看。</p><h2 id="开源第三方库"><a href="# 开源第三方库" class="headerlink" title="开源第三方库"></a>开源第三方库</h2><p>Android 中界面、网络、数据存储等许多高级功能的实现可能较为复杂，这时开源库就变得十分方便。现在大多数 Android 的开源库都托管在 GitHub 上，并支持在 gradle 中一行引入。但是，GitHub 并非专注于 Android，搜索功能也还有待提升，因此以下介绍其他一些常用的技巧。</p><p>建议：</p><ul><li><a href="https://www.google.com/">Google</a>：直接搜索你的关键字即可。</li><li><a href="https://android-arsenal.com/">Android Arsenal</a>：目前最好用的 Android 开源库索引，提供搜索和分类查询。</li></ul><p>了解：</p><ul><li><a href="https://github.com/Trinea/android-open-project">Android Open Project</a>：收录 Android 开源库的 Repo，较全，但不如以上方法。</li><li><a href="https://github.com/wasabeef/awesome-android-ui">Awesome Android UI</a>：内容同上，一般全。</li><li><a href="https://github.com/snowdream/awesome-android">Awesome Android</a>：同上，一般全。</li></ul><p>在选择开源库时，建议考察它的 Star&#x2F;Fork 数、文档、代码风格、作者回复 Issue 的活跃程度，谨慎作出决定。一旦使用，建议 Watch 此项目，关注更新。</p><p>建议使用的部分开源第三方库：</p><ul><li><a href="https://github.com/JakeWharton/butterknife">ButterKnife</a>：自动视图注入，替代冗长的 <code>findViewById()</code>。</li><li><a href="https://github.com/mcxiaoke/android-volley">Volley</a>：短时网络请求。已进入 AOSP，链接中是第三方附加了一些 Tweak 的镜像。</li><li><a href="https://github.com/google/gson">Gson</a>：JSON 与 Java 对象自动转换，在进行数据模型复杂的应用开发时极其有用，与 Volley 搭配和谐。</li><li><a href="https://github.com/bumptech/glide">Glide</a>：为可回收视图容器优化的图片加载，可与 Volley 整合。被 Chris Banes 在 <a href="https://github.com/chrisbanes/cheesesquare/">cheesesquare</a> 中使用。</li><li><a href="https://github.com/JakeWharton/ThreeTenABP">ThreetenABP</a>：Java 8 日期时间 API，优异的设计和实现，从此日期时间编程不再困难。</li></ul><p>另外提一下其他几个项目，且并非针对作者个人。在我个人看来，虽然很多很全，但 <a href="https://github.com/Trinea/android-common">android-common</a> 和 <a href="https://github.com/Trinea/android-demo">android-demo</a> 两个项目的代码质量并不算很高，也无法确定是否始终遵循着 Android 最新的变更和最佳实践。因此，我个人极其反对直接复制粘贴其中的代码（或者其他任何代码），也不推荐直接参考其中的代码而不查阅其他来源；相应地，应该始终使用 Google 搜索解决方案，这样才能保持最新。</p><h2 id="界面设计"><a href="# 界面设计" class="headerlink" title="界面设计"></a>界面设计 </h2><p> 作为一名合格的 Android 开发者，也应该拥有 Android 相关的设计素养，而不是设计师给了一个不标准（或者像 iOS）的设计，却不知道提出意见或修正。</p><p>Android 现有的设计规范是 <a href="http://www.google.com/design/spec/">Material Design</a>，已经初步成熟，但有时还是会有小的补充，因此建议始终阅读这个文档。在入门 Android 开发后，建议抽出时间，将这份文档通读一遍，以了解符合 Android 平台的设计。另外，我见过的几个中文版设计文档翻译质量一般，排版较差，还有一些时效性问题，因此不是十分建议阅读。</p><p>Material Design 的实现主要依靠支持库中的 AppCompat 和 Design 库，目前文档不算十分完善，可以参考 <a href="http://android-developers.blogspot.jp/2015/05/android-design-support-library.html">BlogSpot 上的相关介绍</a> 和 <a href="https://github.com/chrisbanes/cheesesquare/">Chris Banes 的 CheeseSquare</a>，辅助以 StackOverflow、Google Code Issues 和部分国外博客。</p><p>为了精确地实现界面布局，可以使用开发者选项中的“显示视图边界”来确定各个视图的大小和位置，此外 <a href="https://play.google.com/store/apps/details?id=com.faizmalkani.keylines">KeylinePushing</a> 是一款十分好用的工具应用，可以在屏幕上显示关键线和网格，帮助检视视图布局。</p><p>关于设计资源。<a href="https://www.google.com/design/icons/">Material Icons</a> 是 Google 的开源 Material Design 图标库，内容十分丰富，也符合设计规范。<a href="http://romannurik.github.io/AndroidAssetStudio/index.html">Android Asset Studio</a> 曾经十分流行，但由于 Material Design 的引入开始有些过时，不过启动器图标和设备预览图的生成器依然可以使用。</p><h2 id="信息来源"><a href="# 信息来源" class="headerlink" title="信息来源"></a>信息来源</h2><p>Android 的更新发展较为快速，为了跟进 Android 最新的动态，可以了解、关注或订阅以下的人或内容。其中，Google Plus 可以作为一个干净的技术社交平台（屏蔽几个热门推荐就好），有很多活跃的 Android 开发者和社群，十分建议加入。</p><p>建议：</p><ul><li><a href="https://plus.google.com/+AndroidDevelopers/">+Android Developers</a>：Android 官方帐号，新闻、ProTip、工具更新等，十分值得关注。</li><li><a href="https://plus.google.com/+ChrisBanes/">+Chris Banes</a>：Andorid Design 支持库开发者，Android 相关动态更新频繁。</li></ul><p>推荐：</p><ul><li><a href="https://plus.google.com/+RomainGuy/">+Romain Guy</a>：Android 视图组件开发者，同时也是一位优秀的摄影师，你或许不经意间也见过他的作品。</li><li><a href="https://plus.google.com/+RomanNurik/">+Roman Nurik</a>：就职于 Google 的设计师，Muzei 的开发者。</li><li><a href="https://plus.google.com/105051985738280261832/">+Dannie Hackborn</a>：Android 框架开发者。</li><li><a href="https://plus.google.com/+ChetHaase/">+Chet Haase</a>：Android 框架开发者，写作此文时正在开发 Data Binding 支持库。</li><li><a href="http://android-developers.blogspot.com/">Android Developers Blog</a>：Android Developers 的博客，可 RSS 订阅，不过 +Android Developers 在它更新时一般会发动态推荐。</li><li><a href="http://tools.android.com/recent">Android Tools Project Site - Recent Changes</a>：Android Studio、Gradle 插件、SDK 等工具更新，可 RSS 订阅，不过 +Android Developers 在它更新时一般会发动态推荐。</li><li><a href="https://www.youtube.com/user/androiddevelopers">Android Developers on YouTube</a>：Android 开发相关视频，可关注，不过 +Android Developers 在它更新时一般会发动态推荐。</li><li><a href="http://androidbackstage.blogspot.jp/">Android Developers Backstage</a>：Android 开发相关 PodCast，可选择关注。</li><li><a href="http://jakewharton.com/blog/">Jake Wharton’s Blog</a>，ButterKnife、ActionBarSherlock，Andorid-PullToRefresh 等知名开源项目作者，就职于 Square，博客更新已不太频繁，但有深度。</li><li><a href="https://commonsware.com/blog/">CommonsWare’s Blog</a>：StackOverflow 知名答主，CWAC 系列组件作者，某本 Android 开发书籍的作者，博客内容大多优质，但十分喜欢卖书，在 Google+ 上主要推销他的书……</li></ul><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语 </h2><p> 学习和开发是一个循序渐进的过程，通过一个实际项目，不断接触 Android 的方方面面，例如网络、数据库、视图、资源系统、组件、支持库等等，最终才可以对 Android 开发拥有深入的了解。祝各位学习顺利。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;从我入门 Android 开发至今已经两年有余，过程中有了一些经验和积累，所以在此将我对于入门者的建议记录下来。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Android" scheme="https://blog.zhanghai.me/tags/android/"/>
    
    <category term="教程" scheme="https://blog.zhanghai.me/tags/tutorial/"/>
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
  </entry>
  
  <entry>
    <title>关于 C 语言的面试问题</title>
    <link href="https://blog.zhanghai.me/c-interview-questions/"/>
    <id>https://blog.zhanghai.me/c-interview-questions/</id>
    <published>2015-09-15T16:32:43.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="由来"><a href="# 由来" class="headerlink" title="由来"></a>由来 </h2><p> 这是我在作为学生组织的面试官时，对熟练掌握 C&#x2F;C++ 的面试者提出几个的问题，以及对于这几个问题我自己的答案。</p><p>语法规则和特定技巧很大程度上只是记忆的问题，而在几个事实上知晓与否并不能断定面试者的高下。C 语言是一门十分接近底层实现的语言，许多的设计决定与实现直接相关，如果想要合适地运用，也要求程序员对于 C 的实现有透彻的理解。因此，这几个问题主要考察的是面试者在 C 原理与设计层面上的理解。</p><span id="more"></span><h2 id="问题"><a href="# 问题" class="headerlink" title="问题"></a>问题</h2><ol><li><p>为什么以下的语句不会导致非法内存访问？</p> <!-- Unable to wrap a backtick code block in a list item. --> <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="type">float</span> f;</span><br><span class="line">&#125; *s = <span class="literal">NULL</span>;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;%p&quot;</span>, &amp;(*s).f);</span><br></pre></td></tr></table></figure></li><li><p>C++ 中的类实现了继承。请简单阐述如何在 C 中实现 <code>struct</code> 的继承。</p></li><li><p>在 C 中 <code>void *</code> 类型可以自动转换成其他指针类型，但在 C++ 中需要显式转换。请谈一谈 C 和 C++ 为什么采取了这两种设计。</p></li><li><p>（请简述 C 语言中变量自动初始化的规则。）</p><p> 在 C 语言中，全局变量和静态局部变量可以自动初始化为全零，但局部变量不会进行自动初始化。请谈一谈 C 为什么采取了这样的设计。</p></li></ol><h2 id="我的答案"><a href="# 我的答案" class="headerlink" title="我的答案"></a>我的答案</h2><ol><li><p>C 语言中类型只存在于编译前，<code>struct</code>只是对偏移量计算等操作的简化而已，因此，计算 <code>&amp;(*s).f</code>，不过是计算<code>s + sizeof(s.i)</code> 而已，并不会像其他语言那样导致空指针异常。</p></li><li><p>因为 C 中的 <code>struct</code> 不过是对偏移量计算的简化，可以通过在子类的头部直接放置一个父类成员，之后进行类型转换，就能实现数据成员的继承，示例如下：</p> <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">&#125; <span class="type">base_t</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">base_t</span> base;</span><br><span class="line">    <span class="type">float</span> f;</span><br><span class="line">&#125; <span class="type">derived_t</span></span><br><span class="line"></span><br><span class="line"><span class="type">derived_t</span> *derived = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="type">derived_t</span>));</span><br><span class="line"><span class="type">base_t</span> *base = (<span class="type">base_t</span> *) derived;</span><br></pre></td></tr></table></figure><p> 为了实现函数的继承和多态，则需要函数指针，<code>this</code>指针参数，构造器等实现，此处不再赘述。</p></li><li><p>在 C 中，<code>void *</code>指向的是未知类型的内存区域，无论按照什么类型理解，都是合法的；并且 malloc 这样的常用调用返回类型只能是<code>void *</code>，允许自动转换可以大大减少麻烦。</p><p> 在 C++ 中，类型可能是类，而为了实现面向对象编程的特性，在类中保存了一些元信息，因此将一块未知的内存区域作为一个类来理解，这个操作不一定合法；为了保持类和非类指针在语法上的统一，选择了禁止 <code>void *</code> 的自动转换。</p></li><li><p>由于很多全局变量的初始值都需要是零，而在可执行文件中存储这么多零并没有意义，因此 C 语言规定全局变量初值为零，由此可以在生成的可执行文件中只记录全局变量所需空间的大小，而省略为零的值。至于静态局部变量，它的生命周期要求与全局变量相同，只是在编译时赋予了不同作用域的限制而已。为了实现这个特性，在大多数操作系统中，全局变量和静态局部变量被存放在一个全局的数据存储位置（BSS 段），在程序开始执行前由系统进行分配和清零。</p><p> 局部变量与函数调用相关，在系统调用栈上动态分配，自动初始化将会在运行时不断带来额外负担，因此 C 语言将初始化交给程序员处理，不进行自动初始化。</p></li></ol><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语 </h2><p> 理解了 C 的实现与设计之后，使用 C 就得心应手了－－你了解语言这样设计的原因和目的，也知道自己的一行代码会被翻译成怎样的指令，由此，豁然开朗。</p><p>当然，C 有适合的任务，也有不适合的任务，任何语言都是如此，所以复杂性上的欠缺并不影响我对 C 语言的评价。</p><p>大道至简，C 的许多设计与 Unix 哲学若合一契。也是因此我对 C&#x2F;C++ 这种说法不太喜欢。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;由来&quot;&gt;&lt;a href=&quot;#由来&quot; class=&quot;headerlink&quot; title=&quot;由来&quot;&gt;&lt;/a&gt;由来&lt;/h2&gt;&lt;p&gt;这是我在作为学生组织的面试官时，对熟练掌握 C&amp;#x2F;C++ 的面试者提出几个的问题，以及对于这几个问题我自己的答案。&lt;/p&gt;
&lt;p&gt;语法规则和特定技巧很大程度上只是记忆的问题，而在几个事实上知晓与否并不能断定面试者的高下。C 语言是一门十分接近底层实现的语言，许多的设计决定与实现直接相关，如果想要合适地运用，也要求程序员对于 C 的实现有透彻的理解。因此，这几个问题主要考察的是面试者在 C 原理与设计层面上的理解。&lt;/p&gt;</summary>
    
    
    
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
    <category term="笔记" scheme="https://blog.zhanghai.me/tags/note/"/>
    
    <category term="职业" scheme="https://blog.zhanghai.me/tags/career/"/>
    
  </entry>
  
  <entry>
    <title>使用 Gradle 发布项目至 Maven Central</title>
    <link href="https://blog.zhanghai.me/publish-project-to-maven-central-with-gradle/"/>
    <id>https://blog.zhanghai.me/publish-project-to-maven-central-with-gradle/</id>
    <published>2015-06-11T19:32:45.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 将项目发布至 Maven Central，可以使开发者在使用时十分方便地进行整合和依赖管理。然而发布过程并不简单，因此将我发布的过程和遇到的问题记录于此。</p><span id="more"></span><h2 id="Gradle"><a href="#Gradle" class="headerlink" title="Gradle"></a>Gradle</h2><p>作为一个 Android 开发者，一般会使用 Gradle 进行构建与发布。广为使用的脚本是 Chris Banes 所写的 <code>gradle-mvn-push</code>，项目中的 <code>README.md</code> 有详细的步骤说明。</p><blockquote class="callout"><div class="title">外部文章</div><a href="https://github.com/chrisbanes/gradle-mvn-push/blob/master/README.md">gradle-mvn-push 的 README</a></blockquote><p>我不喜欢将密码明文写在配置文件中，而是习惯从终端读入密码，因此我对 <code>gradle-mvn-push.gradle</code> 进行了一些修改：</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> getReleaseRepositoryUrl() &#123;</span><br><span class="line">    <span class="keyword">return</span> hasProperty(<span class="string">&#x27;RELEASE_REPOSITORY_URL&#x27;</span>) ? RELEASE_REPOSITORY_URL</span><br><span class="line">            : <span class="string">&quot;https://oss.sonatype.org/service/local/staging/deploy/maven2/&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> getSnapshotRepositoryUrl() &#123;</span><br><span class="line">    <span class="keyword">return</span> hasProperty(<span class="string">&#x27;SNAPSHOT_REPOSITORY_URL&#x27;</span>) ? SNAPSHOT_REPOSITORY_URL</span><br><span class="line">            : <span class="string">&quot;https://oss.sonatype.org/content/repositories/snapshots/&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">    gradle.taskGraph.whenReady &#123; taskGraph -&gt;</span><br><span class="line">        <span class="keyword">if</span> (taskGraph.allTasks.<span class="keyword">any</span> &#123; it <span class="keyword">instanceof</span> Sign &#125;) &#123;</span><br><span class="line">            <span class="keyword">allprojects</span> &#123;</span><br><span class="line">                 ext.<span class="string">&quot;signing.password&quot;</span> = System.console() != <span class="keyword">null</span> ? System.console().readLine(<span class="string">&quot;\nPGP private key password: &quot;</span>) : <span class="string">&quot;&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>此外，这个脚本在构建 <code>JavaDoc</code> 时会使用系统的语言设置，导致生成的文档可能是中文版本。通过将 <code>LANG=&quot;en_US.UTF-8&quot; </code> 附加在 <code>gradle</code> 命令之前可以将语言设置为英文。</p><p>关于我自己采用的配置，可以参考我 <a href="https://github.com/zhanghai/MaterialProgressBar"> 在 Github 上的项目</a>。</p><h2 id="OSSRH"><a href="#OSSRH" class="headerlink" title="OSSRH"></a>OSSRH</h2><p>为了在 Maven Central 上发布项目，一般会选择在 Sonatype OSSRH (OSS Repository Hosting) 上注册账户进行操作。最顶层的帮助是以下这篇文章，从其中的链接可以进入其他子页面查看详细步骤。</p><blockquote class="callout"><div class="title">外部文章</div><a href="http://central.sonatype.org/pages/ossrh-guide.html">OSSRH Guide</a></blockquote><p>同时，这篇中文文章也较为详尽，对我的发布过程有很大帮助。</p><blockquote class="callout"><div class="title">外部文章 </div><a href="http://blog.csdn.net/qiujuer/article/details/44195131"> 使用 Gradle 发布 AAR 到 Maven 仓库</a></blockquote><p>以下是我的步骤：</p><ol><li><p><a href="https://issues.sonatype.org/secure/Signup!default.jspa">注册 JIRA 账户</a>。</p></li><li><p><a href="https://issues.sonatype.org/secure/CreateIssue.jspa?issuetype=21&pid=10134">创建工程工单</a>。</p><p> Group Id 请填写顶级域名的反转，因为每个用户只需要创建一次工单，之后会将整个 Group Id 下的权限开放给用户。我填写了项目的 Group Id，之后被处理者修正。</p><p> SCM URL 可以直接填写 GitHub 仓库的地址。我开始时一直尝试使用 SSH 克隆地址，但总是不能通过验证。</p></li><li><p>发布项目至暂存区。</p><p> 使用 <code>gradle-mvn-push</code>，执行 <code>LANG=&quot;en_US.UTF-8&quot; gradle clean build uploadArchives</code>。这需要正确的 PGP 签名设置，将在这篇文章的稍后进行说明。</p></li><li><p>公开发布项目。</p><p> 前往 <a href="https://oss.sonatype.org/">Sonatype OSS</a>，登录，点击左侧边栏中的 Staging Repositories，找到并选择自己的项目，点击 Close，刷新并等待操作完成，之后点击 Release，即完成公开发布。</p></li><li><p>回复工单</p><p> 在处理者对工单的回复中，会要求你在公开发布第一个项目后回复此工单，照做即可。</p></li></ol><h2 id="GnuPG（2-1）"><a href="#GnuPG（2-1）" class="headerlink" title="GnuPG（2.1）"></a>GnuPG（2.1）</h2><p>GnuPG 2.1 的使用是我在这次发布过程中遇到的最为棘手的问题，因为网络上大部分的教程还是基于之前的版本，导致许多已经更改的操作方式不再可用；并且 Gradle 也尚未支持这个版本，尽管 2.1 已在去年 11 月发布。因此在这里简单记录一些我遇到的问题和解决方案。</p><ol><li><p>无法选定密钥长度。</p><p> 在 GnuPG 2.1 中，需要使用 <code>--full-gen-key</code> 以获取之前在 <code>--gen-key</code> 中可用的选项。</p></li><li><p>无法使用命令行输入密码。</p><p> GnuPG 默认在有图形界面时使用会 <code>XGrabKeyboard</code> 的窗口进行密码输入，并且无法从剪贴板粘贴文本，给我使用随机密码带来了很多不便，因此我希望从命令行输入密码。</p><p> 在 GnuPG 2.1 中，一切相关的操作被交由 <code>gpg-agent</code> 完成，同时，<code>--pinentry-mode loopback</code> 被提示为不支持，即使在 <code>gpg-agent.conf</code> 中添加 <code>allow-loopback-pinentry</code> 也没有作用。</p><p> 最终我的解决方案是在执行 <code>gpg</code> 前 <code>DISPLAY=&quot;&quot;</code> 使其使用一个基于 <code>curses</code> 的命令行界面，然后对终端进行粘贴文本。</p></li><li><p><code>secring.gpg</code> 中找不到私钥。</p><p> GnuPG 2.1 <a href="https://www.gnupg.org/faq/whats-new-in-2.1.html#nosecring">已经不再使用 <code>secring.gpg</code></a>，因此新创建的私钥不会向其中写入，而是会由 <code>gpg-agent</code> 写入至 <code>private-keys-v1.d</code> 目录。为了兼容 Gradle 签名插件，我将私钥导出，模拟了一个 <code>secring.gpg</code>。</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DISPLAY=<span class="string">&quot;&quot;</span> gpg --output secring.gpg --export-secret-keys &lt;YOUR_KEY_ID&gt;</span><br></pre></td></tr></table></figure></li><li><p>需要将公钥发布至公钥服务器才能通过审核。</p><p> 在对项目进行 Close 时，会对签名进行验证，因此需要将你的公钥上传以使其可以被公开获取。我选择了发布至 MIT 公钥服务器。</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --keyserver pgp.mit.edu --send-key &lt;YOUR_KEY_ID&gt;</span><br></pre></td></tr></table></figure></li></ol><h2 id="附加：为示例应用生成签名密钥"><a href="# 附加：为示例应用生成签名密钥" class="headerlink" title="附加：为示例应用生成签名密钥"></a>附加：为示例应用生成签名密钥 </h2><p> 可以使用 Java 的 <code>keytool</code> 工具进行生成，但 Android Studio 提供了自带推荐参数并且简单易用的界面。</p><p>在 Android Studio 中，点击“Build &gt; Generate Signed APK…”，如果已有密钥存储则点击“Choose existing…”并选择，之后点击“Create new…”填写信息并创建密钥即可。</p><p>这种方法并不会覆盖密钥存储中已有的密钥。但还是建议在操作之前，进行备份。</p><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语 </h2><p> 发布项目到 Maven Central 的过程确实十分复杂，同时 GnuPG 进行了不兼容升级并且缺乏文档，所以令我花费了不少时间。</p><p>相比之下，通过 BinTray 发布则更为简单和自动化一些，然而它要求你将私钥上传至它的服务器，这令我不太舒服，因此并没有采用。</p><p>虽然麻烦，但是对于开发者来说，发布之后的使用还是十分舒服的，因此这些努力也算没有白费。记录在此，希望能够在其他开发者发布自己的项目时有所帮助。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;将项目发布至 Maven Central，可以使开发者在使用时十分方便地进行整合和依赖管理。然而发布过程并不简单，因此将我发布的过程和遇到的问题记录于此。&lt;/p&gt;</summary>
    
    
    
    
    <category term="教程" scheme="https://blog.zhanghai.me/tags/tutorial/"/>
    
    <category term="软件" scheme="https://blog.zhanghai.me/tags/software/"/>
    
  </entry>
  
  <entry>
    <title>使用 SWT 搭建跨平台桌面应用</title>
    <link href="https://blog.zhanghai.me/building-portable-desktop-application-with-swt/"/>
    <id>https://blog.zhanghai.me/building-portable-desktop-application-with-swt/</id>
    <published>2015-04-29T17:25:34.000Z</published>
    <updated>2019-05-16T05:05:51.861Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 在我的计算机组成课程上，教授要求编写一个 MIPS 汇编器并提供图形界面。由于我的工作环境并非 Windows，同时希望在各个平台的界面中使用原生控件，并且我偏好使用静态类型语言，所以决定使用 SWT 进行图形界面的编写。</p><img src="/building-portable-desktop-application-with-swt/mipsasm-windows.png" class=""title="MIPS 汇编集成开发环境 - Windows"><span id="more"></span><img src="/building-portable-desktop-application-with-swt/mipsasm-gtk3.png" class=""title="MIPS 汇编集成开发环境 - GTK+ 3"><h2 id="集成"><a href="# 集成" class="headerlink" title="集成"></a>集成 </h2><p> 鉴于 Maven 上的版本老旧，推荐直接前往 <a href="http://www.eclipse.org/swt/">SWT 官方网站 </a> 下载稳定发行版本。我使用的开发环境是 IntelliJ Idea，因此在 Project Structure 中的 Libraries 和 Artifacts 内添加相应的设置即可。</p><h2 id="架构"><a href="# 架构" class="headerlink" title="架构"></a>架构 </h2><p> 我是一名 Android 开发者，因此我在学习 SWT 时也会与 Android 框架进行对比。二者多有类似，也有不同。</p><p>在 SWT 中，<a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Display.html"><code>Display</code></a> 与 Android 中 <a href="http://developer.android.com/reference/android/content/Context.html"><code>Context</code></a> 和 <a href="http://developer.android.com/reference/android/app/Application.html"><code>Application</code></a> 的设计有所相近，具有系统资源管理和应用层面操作的功能。而 <code>Display</code> 的一个实用的扩展是 <code>Display.getCurrent()</code> 和 <code>Display.getDefault()</code>，即可以通过静态方法获取当前线程或默认的 <code>Display</code>。</p><p>而与 Android 中的 <a href="http://developer.android.com/reference/android/app/Activity.html"><code>Activity</code></a> 相近的则是 <a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Shell.html"><code>Shell</code></a>。<code>Shell</code> 代表一个平台无关的抽象窗口，由此可以进行窗口相关的操作，并建立视图结构。</p><p>与 Andorid 不同，SWT 中没有 XML 布局系统，所有视图的创建需要由应用程序的 Java 代码完成（但有自动将 XML 转换为 Java 代码的工具，如 <a href="https://code.google.com/p/swtxml/">SWTXML</a> 等）。<a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Control.html"><code>Control</code></a> 与 Android 中的 <a href="http://developer.android.com/reference/android/view/View.html"><code>View</code></a> 类似，是所有控件的基类，但不同的是，所有的 <code>Control</code> 在构造时都需要一个 <a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Composite.html"><code>Composite</code></a> 作为父对象，既是 Android 中的 <code>Context</code>，也是视图结构上的父容器。而之后如果需要更改控件的父容器，可以调用<code>Control.setParent()</code>，但是否成功则需要由平台支持决定。</p><p>一个窗口最初的 <code>Composite</code> 实例即是 <code>Shell</code>。</p><h2 id="使用"><a href="# 使用" class="headerlink" title="使用"></a>使用 </h2><h3 id="主循环"><a href="# 主循环" class="headerlink" title="主循环"></a> 主循环</h3><p>SWT 的主循环是由应用程序自己书写的。常见的范式如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">onCreateDisplay();</span><br><span class="line"></span><br><span class="line">onCreateShell();</span><br><span class="line"></span><br><span class="line">shell.open();</span><br><span class="line"><span class="keyword">while</span> (!shell.isDisposed()) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!display.readAndDispatch()) &#123;</span><br><span class="line">        display.sleep();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">display.dispose();</span><br></pre></td></tr></table></figure><h3 id="视图结构创建"><a href="# 视图结构创建" class="headerlink" title="视图结构创建"></a>视图结构创建 </h3><p> 在创建 <code>Shell</code> 实例之后，一般会添加界面元素和布局信息。示例代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">onCreateShell</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    shell = <span class="keyword">new</span> <span class="title class_">Shell</span>(display, SWT.SHELL_TRIM);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置图标</span></span><br><span class="line">    shell.setImages(icons);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置根视图布局</span></span><br><span class="line">    shell.setLayout(<span class="keyword">new</span> <span class="title class_">FillLayout</span>());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建一个 SashForm，作为根视图的子视图</span></span><br><span class="line">    sashForm = <span class="keyword">new</span> <span class="title class_">SashForm</span>(shell, SWT.VERTICAL);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建编辑器文本框</span></span><br><span class="line">    editText = <span class="keyword">new</span> <span class="title class_">StyledText</span>(sashForm, SWT.H_SCROLL | SWT.V_SCROLL);</span><br><span class="line">    editText.setAlwaysShowScrollBars(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建消息区文本框</span></span><br><span class="line">    messageText = <span class="keyword">new</span> <span class="title class_">StyledText</span>(sashForm, SWT.H_SCROLL | SWT.V_SCROLL);</span><br><span class="line">    messageText.setAlwaysShowScrollBars(<span class="literal">false</span>);</span><br><span class="line">    messageText.setEditable(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    sashForm.setWeights(<span class="keyword">new</span> <span class="title class_">int</span>[]&#123;<span class="number">8</span>, <span class="number">2</span>&#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 本程序菜单实现依赖于 editText</span></span><br><span class="line">    onCreateMenu();</span><br><span class="line">    shell.setMenuBar(menu);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 监听窗口关闭事件，提示保存</span></span><br><span class="line">    shell.addListener(SWT.Close, <span class="keyword">new</span> <span class="title class_">Listener</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleEvent</span><span class="params">(Event event)</span> &#123;</span><br><span class="line">            event.doit = onClose();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新窗口标题</span></span><br><span class="line">    updateTitle();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="视图布局"><a href="# 视图布局" class="headerlink" title="视图布局"></a>视图布局</h3><p>SWT 中的布局方式分为两步：使用 <code>Composite.setLayout()</code> 设置父元素布局子元素时使用的布局方式，使用 <code>Control.setLayoutData()</code> 提供子元素的布局参数。这与 Android 中的布局方式有些许不同，即 SWT 中的 <a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Layout.html"><code>Layout</code></a> 是父容器的参数而非父容器本身的类型。</p><p>SWT 中常用的 <code>Layout</code> 包括 <a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/layout/FillLayout.html"><code>FillLayout</code></a>、<a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/layout/RowLayout.html"><code>RowLayout</code></a>、<a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/layout/GridLayout.html"><code>GridLayout</code></a>、<a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/layout/FormLayout.html"><code>FormLayout</code></a> 和 <a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/custom/SashForm.html"><code>SashForm</code></a> 等。对于前三种基础布局，可以参考 <a href="http://www.eclipse.org/articles/article.php?file=Article-Understanding-Layouts/index.html">Eclipse 的这篇文章 </a> 进行学习。</p><h3 id="资源系统"><a href="# 资源系统" class="headerlink" title="资源系统"></a>资源系统 </h3><p> 与 Android 框架不同，SWT 只是一个图形界面库，因此不提供资源系统。</p><p>在写作一般规模的 SWT 应用时，可以使用 Java 自带的 <a href="http://docs.oracle.com/javase/7/docs/api/java/util/ResourceBundle.html"><code>ResourceBundle</code></a> 进行资源管理与国际化。</p><p>常见的范式如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ResourceBundle</span> <span class="variable">resourceBundle</span> <span class="operator">=</span> ResourceBundle.getBundle(<span class="string">&quot;res/string/mipside&quot;</span>, <span class="keyword">new</span> <span class="title class_">Utf8Control</span>());</span><br><span class="line">Display.setAppName(resourceBundle.getString(<span class="string">&quot;app_name&quot;</span>));</span><br></pre></td></tr></table></figure><p>其中 <code>Utf8Control</code> 是为了使得 ResourceBundle 支持直接读取 UTF-8 编码的资源文件的类，可以从之后我的项目中获得我编写的版本；ResourceBundle 默认只支持 ASCII 编码。</p><h3 id="设置存储"><a href="# 设置存储" class="headerlink" title="设置存储"></a>设置存储 </h3><p> 为了保存用户的设置，可以使用 Java 自带的 <a href="https://docs.oracle.com/javase/7/docs/api/java/util/prefs/Preferences.html"><code>Preferences</code></a> 进行键值对的存储。使用时可以通过 <code>Preferences.userNodeForPackage()</code> 获取一个节点的 <code>Preferences</code> 实例，而之后则与使用 Android 中的 <a href="http://developer.android.com/reference/android/content/SharedPreferences.html"><code>SharedPreferences</code></a> 类似。</p><h2 id="部署"><a href="# 部署" class="headerlink" title="部署"></a>部署 </h2><p> 在 Linux 上，一个 Shell 脚本执行 <code>java -jar</code> 已经足够。</p><p>在 Windows 上，经过一些比较，我决定使用 <a href="http://launch4j.sourceforge.net/">launch4j</a>，至今体验良好。</p><p>在 Mac OS X 上，JVM 需要特定参数才能正常启动应用，以及其他打包应用程序的细节，可以参考 <a href="https://www.eclipse.org/swt/macosx/"> 这篇 Eclipse 的文档</a>。</p><h2 id="评价"><a href="# 评价" class="headerlink" title="评价"></a>评价 </h2><p> 在使用 SWT 完成我的项目后，总体来说，感觉这是一个可用的图形库。</p><p>从接口方面看，SWT 遵循着一致的命名规范，但 Builder 模式的缺失使得构造控件的过程较为冗长，此外将样式、键码等大量常量放置在 <a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/SWT.html"><code>SWT</code></a> 类中也显得较为奇怪。</p><p>从功能方面看，SWT 跨平台调用原生控件的能力确实令人惊喜。众多部件在 Windows、Linux GTK+ 3 和 GTK+ 2、Mac OS X Cocoa 上不需要经过调试即表现良好，并且通过打包与原生应用几乎没有什么差异。美中不足的只是一些小的设计细节，例如标签中的文本不能使用光标选择复制、带有链接的多行标签需要使用另一个类却不能指定多行文本居中对齐。但总体来说，这些瑕疵要么可以绕过，要么相比于 SWT 本身带来的便捷来说，无伤大雅。</p><h2 id="结语"><a href="# 结语" class="headerlink" title="结语"></a>结语 </h2><p> 菜单、字体、图像、对话框、<a href="http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/custom/StyledText.html"><code>StyledText</code></a>，还有关于 SWT 的很多方面这篇文章没有覆盖到。但网络上相关的资源是十分丰富的，例如 Google、Eclipse 的 Javadoc 和 Java2s 上的代码示例都是有用的信息来源。</p><p>如果有兴趣，我的项目中也有一些按照我自己对 SWT 的理解写出的代码，可以 <a href="https://github.com/zhanghai/mipsasm"> 移步至 GitHub 浏览</a>。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;在我的计算机组成课程上，教授要求编写一个 MIPS 汇编器并提供图形界面。由于我的工作环境并非 Windows，同时希望在各个平台的界面中使用原生控件，并且我偏好使用静态类型语言，所以决定使用 SWT 进行图形界面的编写。&lt;/p&gt;
&lt;img src=&quot;/building-portable-desktop-application-with-swt/mipsasm-windows.png&quot; class=&quot;&quot; title=&quot;MIPS 汇编集成开发环境 - Windows&quot;&gt;</summary>
    
    
    
    
    <category term="教程" scheme="https://blog.zhanghai.me/tags/tutorial/"/>
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
    <category term="学业" scheme="https://blog.zhanghai.me/tags/schoolwork/"/>
    
  </entry>
  
  <entry>
    <title>LinearLayout 的拖放操作和动画</title>
    <link href="https://blog.zhanghai.me/drag-and-drop-with-animation-on-linearlayout/"/>
    <id>https://blog.zhanghai.me/drag-and-drop-with-animation-on-linearlayout/</id>
    <published>2015-02-25T21:04:38.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p>Android 中<code>ListView</code> 的拖放操作和动画实现已经被 <a href="https://www.youtube.com/watch?v=_BZIvjMgH-Q"> 这个 DevByte</a> 和 <a href="https://android.googlesource.com/platform/development/+/master/samples/devbytes/animation/ListViewDraggingAnimation"> 相关的样例 </a> 说明，并且也有 <a href="https://github.com/nhaarman/ListViewAnimations">ListViewAnimations</a> 这样强大的开源库进行了集成。但是，一番 Google 后，我发现基于 <code>LinearLayout</code> 的相关实现却不多。</p><p>然而，有时我们可能需要使用 <code>LinearLayout</code> 替代 <code>ListView</code> 来实现列表，例如不需要 <code>ListView</code> 的视图回收机制（比如使用 <code>Fragment</code> 作为列表项），或者我们需要把这个视图放在 <code>ScrollView</code> 中。</p><p>在使用 <code>LinearLayout</code> 实现拖放和动画时，实现代码相比于之前提到的 <code>ListView</code> 实现也需要一些变动。因为我在网络上没有找到相应的资料，所以写下这篇文章来记录这个过程。</p><span id="more"></span><h2 id="拖放"><a href="# 拖放" class="headerlink" title="拖放"></a>拖放 </h2><blockquote class="callout"><div class="title"> 前置阅读</div><a href="https://developer.android.com/guide/topics/ui/drag-drop.html">Drag and Drop | Android Developers</a><br></blockquote><p>为 <code>LinearLayout</code> 设定 <a href="http://developer.android.com/reference/android/view/View.OnDragListener.html"><code>View.OnDragListener</code></a> 很简单，其机制在官方教程中有详细说明，在此不再赘述。</p><p>但是，官方教程中给出的样例在释放被拖动条目后只会显示一条 <code>Toast</code>，而一般的需求则是拖放排序。所以在参考了网上的一些文章后，我给出了下面这个简单的实现。与官方样例相比，添加的主要是在<code>ViewGroup</code> 中交换子视图的实现，以及将被拖动的视图作为 <code>LocalState</code> 传递。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setupDragSort</span><span class="params">(View view)</span> &#123;</span><br><span class="line">    view.setOnDragListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnDragListener() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onDrag</span><span class="params">(<span class="keyword">final</span> View view, DragEvent event)</span> &#123;</span><br><span class="line">            <span class="type">ViewGroup</span> <span class="variable">viewGroup</span> <span class="operator">=</span> (ViewGroup)view.getParent();</span><br><span class="line">            <span class="type">View</span> <span class="variable">dragView</span> <span class="operator">=</span> (View)event.getLocalState();</span><br><span class="line">            <span class="keyword">switch</span> (event.getAction()) &#123;</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DROP:</span><br><span class="line">                    <span class="keyword">if</span> (view != dragView) &#123;</span><br><span class="line">                        swapViewGroupChildren(viewGroup, view, dragView);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    view.setOnLongClickListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnLongClickListener() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onLongClick</span><span class="params">(View view)</span> &#123;</span><br><span class="line">            view.startDrag(<span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">View</span>.DragShadowBuilder(view), view, <span class="number">0</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">swapViewGroupChildren</span><span class="params">(ViewGroup viewGroup, View firstView, View secondView)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">firstIndex</span> <span class="operator">=</span> viewGroup.indexOfChild(firstView);</span><br><span class="line">    <span class="type">int</span> <span class="variable">secondIndex</span> <span class="operator">=</span> viewGroup.indexOfChild(secondView);</span><br><span class="line">    <span class="keyword">if</span> (firstIndex &lt; secondIndex) &#123;</span><br><span class="line">        viewGroup.removeViewAt(secondIndex);</span><br><span class="line">        viewGroup.removeViewAt(firstIndex);</span><br><span class="line">        viewGroup.addView(secondView, firstIndex);</span><br><span class="line">        viewGroup.addView(firstView, secondIndex);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        viewGroup.removeViewAt(firstIndex);</span><br><span class="line">        viewGroup.removeViewAt(secondIndex);</span><br><span class="line">        viewGroup.addView(firstView, secondIndex);</span><br><span class="line">        viewGroup.addView(secondView, firstIndex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><video controls loop src="/drag-and-drop-with-animation-on-linearlayout/swap.mp4" onclick="this.paused ? this.play() : this.pause()"></video><p>这个实现已经可以完成拖动排序，然而界面效果却不理想：被拖动的条目没有消失，列表在拖动过程中也没有作出相应的改变。下面我们将使用 Android 的属性动画实现这种界面效果。</p><h2 id="在拖动过程中响应更改"><a href="# 在拖动过程中响应更改" class="headerlink" title="在拖动过程中响应更改"></a>在拖动过程中响应更改 </h2><p> 言归正传。为了实现拖放过程中的动画，我们的目标是使用 <code>LinearLayout</code> 的列表视图能够对用户的拖动实时作出相应，也就是每次当用户的拖动越过某个临界线的时候，就将列表展现为被拖动条目在这里放下时的预览。因此，需要完成的工作就是将被拖动视图的 <code>Visibility</code> 设置为<code>View.INVISIBLE</code>，此时被拖动视图参与布局计算，但不进行绘制（已经被用户拖起），再不断改变列表中各个条目的位置。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setupDragSort</span><span class="params">(View view)</span> &#123;</span><br><span class="line">    view.setOnDragListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnDragListener() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onDrag</span><span class="params">(<span class="keyword">final</span> View view, DragEvent event)</span> &#123;</span><br><span class="line">            <span class="type">ViewGroup</span> <span class="variable">viewGroup</span> <span class="operator">=</span> (ViewGroup)view.getParent();</span><br><span class="line">            <span class="type">DragState</span> <span class="variable">dragState</span> <span class="operator">=</span> (DragState)event.getLocalState();</span><br><span class="line">            <span class="keyword">switch</span> (event.getAction()) &#123;</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DRAG_STARTED:</span><br><span class="line">                    <span class="keyword">if</span> (view == dragState.view) &#123;</span><br><span class="line">                        view.setVisibility(View.INVISIBLE);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                ...</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DRAG_ENDED:</span><br><span class="line">                    <span class="keyword">if</span> (view == dragState.view) &#123;</span><br><span class="line">                        view.setVisibility(View.VISIBLE);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    view.setOnLongClickListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnLongClickListener() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onLongClick</span><span class="params">(View view)</span> &#123;</span><br><span class="line">            view.startDrag(<span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">View</span>.DragShadowBuilder(view), <span class="keyword">new</span> <span class="title class_">DragState</span>(view), <span class="number">0</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DragState</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> View view;</span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> index;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">DragState</span><span class="params">(View view)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.view = view;</span><br><span class="line">        index = ((ViewGroup)view.getParent()).indexOfChild(view);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一个很自然的想法是，在用户拖动条目经过某个其他条目超过一半高度时，就将这个条目在父视图中的位置与被拖动条目互换（而不是等到用户拖动完成时）。这样就基本实现了布局系统中的改变。然而，由于在用户快速拖动时，Android 可能来不及向每个经过的视图发送消息，这种方式可能导致列表顺序的改变的问题（我在自己测试时就遇到了）。</p><p>所以在实现视图交换时，我们需要使用递归的方式进行，直到两个视图达到相邻。实现代码如下。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setupDragSort</span><span class="params">(View view)</span> &#123;</span><br><span class="line">    view.setOnDragListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnDragListener() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onDrag</span><span class="params">(<span class="keyword">final</span> View view, DragEvent event)</span> &#123;</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">switch</span> (event.getAction()) &#123;</span><br><span class="line">                ...</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DRAG_LOCATION: &#123;</span><br><span class="line">                    <span class="keyword">if</span> (view == dragState.view)&#123;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> viewGroup.indexOfChild(view);</span><br><span class="line">                    <span class="keyword">if</span> ((index &gt; dragState.index &amp;&amp; event.getY() &gt; view.getHeight() / <span class="number">2</span>)</span><br><span class="line">                            || (index &lt; dragState.index &amp;&amp; event.getY() &lt; view.getHeight() / <span class="number">2</span>)) &#123;</span><br><span class="line">                        swapViews(viewGroup, view, index, dragState);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        swapViewsBetweenIfNeeded(viewGroup, index, dragState);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                ...</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">swapViewsBetweenIfNeeded</span><span class="params">(ViewGroup viewGroup, <span class="type">int</span> index,</span></span><br><span class="line"><span class="params">                                             DragState dragState)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (index - dragState.index &gt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">indexAbove</span> <span class="operator">=</span> index - <span class="number">1</span>;</span><br><span class="line">        swapViews(viewGroup, viewGroup.getChildAt(indexAbove), indexAbove, dragState);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (dragState.index - index &gt; <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">indexBelow</span> <span class="operator">=</span> index + <span class="number">1</span>;</span><br><span class="line">        swapViews(viewGroup, viewGroup.getChildAt(indexBelow), indexBelow, dragState);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">swapViews</span><span class="params">(ViewGroup viewGroup, <span class="keyword">final</span> View view, <span class="type">int</span> index,</span></span><br><span class="line"><span class="params">                              DragState dragState)</span> &#123;</span><br><span class="line">    swapViewsBetweenIfNeeded(viewGroup, index, dragState);</span><br><span class="line">    swapViewGroupChildren(viewGroup, view, dragState.view);</span><br><span class="line">    dragState.index = index;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><video controls loop src="/drag-and-drop-with-animation-on-linearlayout/realtime.mp4" onclick="this.paused ? this.play() : this.pause()"></video><h2 id="动画"><a href="# 动画" class="headerlink" title="动画"></a>动画 </h2><p> 接下来是交换过程中动画的实现。在实现过程中，我参考了 <a href="https://github.com/justasm/DragLinearLayout">justasm 的 DragLinearLayout</a> 中的代码，在此表示感谢。</p><blockquote class="callout"><div class="title">前置阅读</div><a href="https://developer.android.com/guide/topics/graphics/prop-animation.html">Property Animation | Android Developers</a></blockquote><p>在实现动画时，我们主要利用的是 Android 的属性动画机制，涉及到的是 <code>View</code> 的<a href="http://developer.android.com/reference/android/view/View.html#Y"><code>Y</code></a>这个属性。</p><p>在谈及实际实现之前，值得在此提及的是 <code>View</code> 的<code>Left</code>和 <code>Top</code> 与<code>X</code>和 <code>Y</code> 的关系。<code>Left</code>和 <code>Top</code> 是在视图树布局过程中按照视图层级和布局参数等得出的，表示特定视图在屏幕上被布局系统分配的位置；而 <code>X</code> 和<code>Y</code>则是用于在实际绘制视图时定位的依据。</p><p>这种实现的好处是，通过将实际绘制时与布局时的视图位置独立起来，可以实现动画过程中视图的位移、旋转等视觉变换，而不必受到布局系统中视图定位的拘束。顺带一提，<code>X</code>和 <code>Y</code> 其实是由 <code>Left</code> 和<code>Top</code>分别加上 <code>TRANSLATION_X</code> 和<code>TRANSLATION_Y</code>得到的，这是因为实际上视图还是要依赖于布局才能定位。</p><p>言归正传。为了让视图位置的变化更加平滑，需要让视图的绘制位置从上一个位置渐变到下一个位置。我们在需要改变视图位置时可以通过 <a href="http://developer.android.com/reference/android/view/View.html#getY%28%29"><code>View.getY()</code></a> 得到视图当前的绘制位置，但视图的下一个位置则需要经过下一次布局计算后才能获得。因此，我们使用一个常见的技巧，也就是利用<a href="http://developer.android.com/reference/android/view/ViewTreeObserver.OnPreDrawListener.html"><code>ViewTreeObserver.OnPreDrawListener</code></a>，在绘制之前获取已经计算完成的布局位置，在这时开始进行视图动画。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">swapViews</span><span class="params">(ViewGroup viewGroup, <span class="keyword">final</span> View view, <span class="type">int</span> index,</span></span><br><span class="line"><span class="params">                              DragState dragState)</span> &#123;</span><br><span class="line">    swapViewsBetweenIfNeeded(viewGroup, index, dragState);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">float</span> <span class="variable">viewY</span> <span class="operator">=</span> view.getY();</span><br><span class="line">    swapViewGroupChildren(viewGroup, view, dragState.view);</span><br><span class="line">    dragState.index = index;</span><br><span class="line">    postOnPreDraw(view, <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            ObjectAnimator</span><br><span class="line">                    .ofFloat(view, View.Y, viewY, view.getTop())</span><br><span class="line">                    .setDuration(getDuration(view))</span><br><span class="line">                    .start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">getDuration</span><span class="params">(View view)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> view.getResources().getInteger(android.R.integer.config_shortAnimTime);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">postOnPreDraw</span><span class="params">(View view, <span class="keyword">final</span> Runnable runnable)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">ViewTreeObserver</span> <span class="variable">observer</span> <span class="operator">=</span> view.getViewTreeObserver();</span><br><span class="line">    observer.addOnPreDrawListener(<span class="keyword">new</span> <span class="title class_">ViewTreeObserver</span>.OnPreDrawListener() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onPreDraw</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (observer.isAlive()) &#123;</span><br><span class="line">                observer.removeOnPreDrawListener(<span class="built_in">this</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            runnable.run();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如此，我们就基本完成了拖放操作和动画的实现。效果如下：</p><video controls loop src="/drag-and-drop-with-animation-on-linearlayout/animation.mp4" onclick="this.paused ? this.play() : this.pause()"></video><h2 id="附加：删除条目"><a href="# 附加：删除条目" class="headerlink" title="附加：删除条目"></a>附加：删除条目 </h2><p> 既然写了这么多，最后再顺带给出一个删除条目及相应动画的实现。其中的 <code>view</code> 参数是在 <code>viewGroup</code> 外的一个拖放目标，用于删除。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setupDragDelete</span><span class="params">(View view, <span class="keyword">final</span> ViewGroup viewGroup)</span> &#123;</span><br><span class="line">    view.setOnDragListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnDragListener() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onDrag</span><span class="params">(View view, DragEvent event)</span> &#123;</span><br><span class="line">            <span class="keyword">switch</span> (event.getAction()) &#123;</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DRAG_ENTERED:</span><br><span class="line">                    view.setActivated(<span class="literal">true</span>);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DRAG_EXITED:</span><br><span class="line">                    view.setActivated(<span class="literal">false</span>);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DROP:</span><br><span class="line">                    <span class="type">DragState</span> <span class="variable">dragState</span> <span class="operator">=</span> (DragState)event.getLocalState();</span><br><span class="line">                    removeView(viewGroup, dragState);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> DragEvent.ACTION_DRAG_ENDED:</span><br><span class="line">                    <span class="comment">// <span class="doctag">NOTE:</span> Needed because ACTION_DRAG_EXITED may not be sent when the drag</span></span><br><span class="line">                    <span class="comment">// ends within the view.</span></span><br><span class="line">                    view.setActivated(<span class="literal">false</span>);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">removeView</span><span class="params">(ViewGroup viewGroup, DragState dragState)</span> &#123;</span><br><span class="line">    viewGroup.removeView(dragState.view);</span><br><span class="line">    <span class="type">int</span> <span class="variable">childCount</span> <span class="operator">=</span> viewGroup.getChildCount();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> dragState.index; i &lt; childCount; ++i) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">View</span> <span class="variable">view</span> <span class="operator">=</span> viewGroup.getChildAt(i);</span><br><span class="line">        <span class="keyword">final</span> <span class="type">float</span> <span class="variable">viewY</span> <span class="operator">=</span> view.getY();</span><br><span class="line">        postOnPreDraw(view, <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                ObjectAnimator</span><br><span class="line">                        .ofFloat(view, View.Y, viewY, view.getTop())</span><br><span class="line">                        .setDuration(getDuration(view))</span><br><span class="line">                        .start();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要注意的是，如果 <code>LinearLayout</code> 的高度设置为 <code>wrap_content</code>，则为了避免动画被视图边界剪裁，以及在<code>ScrollView</code> 中高度正确变化，需要手动对 <code>LinearLayout</code> 的高度进行动画；这同时涉及到需要覆盖 <code>ScrollView</code> 中<code>measureChild()</code>方法来计算我们所请求的高度。我在下面的完整实现中完成了这个部分。</p><blockquote class="callout"><div class="title">完整实现 </div><a href="https://gist.github.com/zhanghai/b0c42a56e49ca907a9cb"> 在 GitHub 上浏览</blockquote>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;Android 中&lt;code&gt;ListView&lt;/code&gt;的拖放操作和动画实现已经被&lt;a href=&quot;https://www.youtube.com/watch?v=_BZIvjMgH-Q&quot;&gt;这个 DevByte&lt;/a&gt; 和&lt;a href=&quot;https://android.googlesource.com/platform/development/+/master/samples/devbytes/animation/ListViewDraggingAnimation&quot;&gt;相关的样例&lt;/a&gt;说明，并且也有 &lt;a href=&quot;https://github.com/nhaarman/ListViewAnimations&quot;&gt;ListViewAnimations&lt;/a&gt; 这样强大的开源库进行了集成。但是，一番 Google 后，我发现基于&lt;code&gt;LinearLayout&lt;/code&gt;的相关实现却不多。&lt;/p&gt;
&lt;p&gt;然而，有时我们可能需要使用&lt;code&gt;LinearLayout&lt;/code&gt;替代&lt;code&gt;ListView&lt;/code&gt;来实现列表，例如不需要&lt;code&gt;ListView&lt;/code&gt;的视图回收机制（比如使用&lt;code&gt;Fragment&lt;/code&gt;作为列表项），或者我们需要把这个视图放在&lt;code&gt;ScrollView&lt;/code&gt;中。&lt;/p&gt;
&lt;p&gt;在使用&lt;code&gt;LinearLayout&lt;/code&gt;实现拖放和动画时，实现代码相比于之前提到的&lt;code&gt;ListView&lt;/code&gt;实现也需要一些变动。因为我在网络上没有找到相应的资料，所以写下这篇文章来记录这个过程。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Android" scheme="https://blog.zhanghai.me/tags/android/"/>
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
  </entry>
  
  <entry>
    <title>Bash Scripting Notes</title>
    <link href="https://blog.zhanghai.me/bash-scripting-notes/"/>
    <id>https://blog.zhanghai.me/bash-scripting-notes/</id>
    <published>2015-02-16T05:08:11.000Z</published>
    <updated>2019-05-16T05:05:51.861Z</updated>
    
    <content type="html"><![CDATA[<p>This is my notebook for bash scripting.</p><h2 id="Styling"><a href="#Styling" class="headerlink" title="Styling"></a>Styling</h2><p>Use <a href="https://google.github.io/styleguide/shell.xml">Google style guide for shell</a>.</p><span id="more"></span><h2 id="Organization"><a href="#Organization" class="headerlink" title="Organization"></a>Organization</h2><p>Use functions; use a <code>main</code> function and call it by <code>main &quot;$@&quot;</code> at the end of the script.</p><h2 id="Boolean-variable"><a href="#Boolean-variable" class="headerlink" title="Boolean variable"></a>Boolean variable</h2><p>Use <code>1</code> or <code>0</code> literal for boolean; test by <code>[[$&#123;bool_val&#125; -eq 1]]</code>.</p><h2 id="Local-variable"><a href="#Local-variable" class="headerlink" title="Local variable"></a>Local variable</h2><p>Use <code>local</code> for function local variable; seprate declaration and assignment.</p><h2 id="String-operation"><a href="#String-operation" class="headerlink" title="String operation"></a>String operation</h2><p>See <a href="http://tldp.org/LDP/abs/html/refcards.html#AEN22828">this manual</a>.</p><h2 id="getopt"><a href="#getopt" class="headerlink" title="getopt"></a>getopt</h2><p>Always quote <code>&quot;$@&quot;</code> to preserve correct word splitting.</p><h2 id="Read-password"><a href="#Read-password" class="headerlink" title="Read password"></a>Read password</h2><p>Use <code>read -p &lt;prompt&gt; -s &lt;variable&gt;; echo</code> to read password.</p><h2 id="Use-sudo"><a href="#Use-sudo" class="headerlink" title="Use sudo"></a>Use sudo</h2><p>Include this function and call it before you’re going to use <code>sudo</code>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">prepare_sudo</span></span>() &#123;</span><br><span class="line">    <span class="built_in">sudo</span> -v</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Here-document"><a href="#Here-document" class="headerlink" title="Here document"></a>Here document</h2><p>Use Here document for usage(), etc.</p><p>You can utilize the following technique to write a here document as root</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">tee</span> <span class="string">&quot;<span class="variable">$&#123;profile_path&#125;</span>&quot;</span> &gt;/dev/null &lt;&lt;<span class="string">EOF</span></span><br></pre></td></tr></table></figure><h2 id="Script-name"><a href="#Script-name" class="headerlink" title="Script name"></a>Script name</h2><p>&#96;&#96;<br>name&#x3D;$(basename “$0”)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">## Report error</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>error() {<br>    echo “${FUNCNAME[1]}: $@” &gt;&amp;2<br>}</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">## Configuration file</span><br><span class="line"></span><br><span class="line">You can source an external configuration file as bash script. A bit unsafe though.</span><br><span class="line"></span><br><span class="line">```bash</span><br><span class="line">source example.conf</span><br></pre></td></tr></table></figure><h2 id="Wait-for-certain-condition"><a href="#Wait-for-certain-condition" class="headerlink" title="Wait for certain condition"></a>Wait for certain condition</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> $(<span class="built_in">seq</span> 0 <span class="string">&quot;<span class="variable">$&#123;timeout&#125;</span>&quot;</span>); <span class="keyword">do</span></span><br><span class="line">    check_something;</span><br><span class="line">    <span class="built_in">sleep</span> 1;</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><h2 id="Watch-file-changes"><a href="#Watch-file-changes" class="headerlink" title="Watch file changes"></a>Watch file changes</h2><p>Use <code>inotifywait</code>.</p><h1 id="Colored-makepkg-like-functions"><a href="#Colored-makepkg-like-functions" class="headerlink" title="Colored makepkg-like functions"></a>Colored makepkg-like functions</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">msg_blue()·&#123;</span><br><span class="line">    <span class="built_in">printf</span>·<span class="string">&quot;<span class="variable">$&#123;BLUE&#125;</span>==&gt;<span class="variable">$&#123;ALL_OFF&#125;</span><span class="variable">$&#123;BOLD&#125;</span>·$1<span class="variable">$&#123;ALL_OFF&#125;</span>\n&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">note()·&#123;</span><br><span class="line">    <span class="built_in">printf</span>·<span class="string">&quot;<span class="variable">$&#123;BLUE&#125;</span>==&gt;<span class="variable">$&#123;ALL_OFF&#125;</span><span class="variable">$&#123;YELLOW&#125;</span>·NOTE:<span class="variable">$&#123;ALL_OFF&#125;</span><span class="variable">$&#123;BOLD&#125;</span>·$1<span class="variable">$&#123;ALL_OFF&#125;</span>\n&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ALL_OFF=<span class="string">&quot;<span class="subst">$(tput·sgr0)</span>&quot;</span></span><br><span class="line">BOLD=<span class="string">&quot;<span class="subst">$(tput·bold)</span>&quot;</span></span><br><span class="line">BLUE=<span class="string">&quot;<span class="variable">$&#123;BOLD&#125;</span><span class="subst">$(tput·setaf·4)</span>&quot;</span></span><br><span class="line">YELLOW=<span class="string">&quot;<span class="variable">$&#123;BOLD&#125;</span><span class="subst">$(tput·setaf·3)</span>&quot;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;This is my notebook for bash scripting.&lt;/p&gt;
&lt;h2 id=&quot;Styling&quot;&gt;&lt;a href=&quot;#Styling&quot; class=&quot;headerlink&quot; title=&quot;Styling&quot;&gt;&lt;/a&gt;Styling&lt;/h2&gt;&lt;p&gt;Use &lt;a href=&quot;https://google.github.io/styleguide/shell.xml&quot;&gt;Google style guide for shell&lt;/a&gt;.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Note" scheme="https://blog.zhanghai.me/tags/note/"/>
    
    <category term="Linux" scheme="https://blog.zhanghai.me/tags/linux/"/>
    
    <category term="Programming" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
  </entry>
  
  <entry>
    <title>C 语言应试笔记</title>
    <link href="https://blog.zhanghai.me/c-exam-notes/"/>
    <id>https://blog.zhanghai.me/c-exam-notes/</id>
    <published>2015-02-16T02:38:12.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 这是我在大一时 C 语言课程考试前总结的坑们，每次相关考试前都会复习一次。如果你发现可以补充的地方，欢迎评论。</p><span id="more"></span><h2 id="正文"><a href="# 正文" class="headerlink" title="正文"></a>正文 </h2><blockquote class="callout"><div class="title"> 一言以概 </div> 任何看上去有简单答案的问题都藏有坑。</blockquote><p>看见定义变量，总是注意变量是否被初始化过。</p><p>注意 <code>if</code> 中的等号是 <code>=</code> 还是 <code>==</code>。</p><p><code>if</code> 语句没有花括号时只包含之后一行，如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (condition)</span><br><span class="line">    optionally_executed();</span><br><span class="line">    always_executed();</span><br></pre></td></tr></table></figure><p><code>if else</code>语句在没有花括号时，<code>else</code>总是匹配最近的<code>if</code>（Dangling Else），如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (a)</span><br><span class="line">    <span class="keyword">if</span> (b) do_b();</span><br><span class="line"><span class="keyword">else</span> do_c();</span><br></pre></td></tr></table></figure><p>实际上是：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (a) &#123;</span><br><span class="line">    <span class="keyword">if</span> (b) &#123;</span><br><span class="line">        do_b();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        do_c();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>switch</code>语句在缺少 <code>break</code> 时将 Fall through。</p><p>求值逻辑表达式时 <code> 真</code>为 <code>1</code>，<code> 假</code>为 <code>0</code>。 判断逻辑表达式时，<code> 非 0</code>为 <code> 真</code>，<code>0</code>为 <code> 假</code>。</p><p><code>true</code>、<code>false</code>、<code>TRUE</code>、<code>FALSE</code> 是合法的变量名，因为在 C 中它们不是关键字。</p><p><code>*p++</code>等价于 <code>*(p++)</code>，因后自增<code>++</code> 优先级高于解引用<code>*</code>。</p><p>注意整型除法结果被截断， <code>int n = 2; 1 / n == 0</code>。</p><p>注意本地变量需要初始化。</p><p>注意变量作用域屏蔽，如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> b;</span><br><span class="line"><span class="type">void</span> <span class="title function_">f</span><span class="params">()</span> &#123; <span class="type">int</span> a = <span class="number">0</span>, b = <span class="number">1</span>; &#125;</span><br></pre></td></tr></table></figure><p>注意函数为传值，指针也是值传递，例如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">swap</span><span class="params">(<span class="type">int</span> *a, <span class="type">int</span> *b)</span> &#123;</span><br><span class="line">    <span class="type">int</span> *temp = a;</span><br><span class="line">    a = b;</span><br><span class="line">    b = temp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>并不影响外部。</p><p>注意 <code>int</code> 溢出，若易发生溢出（如阶乘操作）应使用 <code>long</code> 或<code>double</code>代替。</p><p><code>char a = 255</code>，打印后值为 <code>-1</code>，因为 <code>char</code> 为有符号类型并采用补码表示，其范围为 <code>-128 ~ 127</code>。</p><p>负数的位移操作中符号位也参与位移；左操作数为负数的右移 <code>&gt;&gt;</code> 结果由实现定义，Turbo C 为补 1。</p><p><code>a/b</code>与 <code>a%b</code> 结果在 <code>a</code> 与<code>b</code>中有一个为负数时由实现定义，但保证 <code>a / b * b + a % b</code> 结果为<code>a</code>。</p><p>过滤回车结束的输入字符串：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">i = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">while</span> ((ch = getchar()) != <span class="string">&#x27;\n&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (ch ...) &#123;</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    str[i++] = ch;</span><br><span class="line">&#125;</span><br><span class="line">str[i] = <span class="string">&#x27;\0&#x27;</span>;</span><br></pre></td></tr></table></figure><p>C 语言二维数组存储采用 Row-major 方式，即在内存中存储为 row1, row2, row3…，初始化时必须给出列数以确定行中元素个数，静态变量中未初始化元素自动为 0，使用时可以将列数溢出到下一行，即偏移值计算为 row_count * row + column。</p><p>注意分号：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (...);</span><br><span class="line">            do_something();</span><br></pre></td></tr></table></figure><p><code>do_something</code>只执行一次。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="number">0</span>);</span><br><span class="line">    do_something();</span><br></pre></td></tr></table></figure><p><code>do_something</code>一定会被执行。</p><p><code>sizeof()</code>表达式的值在编译时确定。编译器不计算其中表达式的值，仅将其替换为对应类型。<code>sizeof</code>应用于数组时结果为 数组元素个数 * 元素大小，应用于指针时为指针变量长度（在 32 位机器上地址长度为 32，故值为 8；64 位机器上为 16）。</p><p>无论 <code>x</code> 是数组还是指针，在定义上编译器认为 <code>x[3]</code> 与<code>*(x+3)</code>是等价的。但根据 x 的类型是数组还是指针, 编译器将为 <code>x+3</code> 或<code>sizeof(x)</code>生成不同的代码。</p><p>类似 <code>char []</code> 类型的数组名被视为指向 <code>char</code> 的指针，<code>char[][]</code>也被视为指向 <code>char[]</code> 的指针。</p><p>注意 <code>&amp;&amp;</code> 或<code>||</code>具有 <code> 短路求值 </code> 特性，不会执行无必要求值的表达式。</p><p>注意含中文字符的文件应保存为 <code>GB*</code> 编码，例如<code>GB18030</code>。</p><p><code>strcpy(dst, src)</code>中目标在前，源在后。</p><p>注意 <code>int a, b, c</code>，<code>c = 2a + b</code> 非法（需要<code>*</code>）。</p><p>注意 <code>x&lt;=y&lt;=z</code> 意为<code>(x&lt;=y)&lt;=z</code>，意义非预期但合法。</p><p><code>scanf</code>遇到空白字符截止，为输入一行可使用 <code>scanf(&quot;[^\n]&quot;,str)</code> 或<code>gets()</code>。</p><p>注意：<code>scanf(&quot;%s&quot;, s)</code>输入 <code>&quot;How are you?&quot;</code> 遇到空格截断，只得到<code>How</code>。</p><p><code>strcpy(char *dst, char *src) &#123; while(*dst++ = *src++); &#125;;</code>中 <code>++</code> 优先级高于<code>*</code></p><p><code>[]</code>、<code>()</code>优先级高于 <code>*</code>，故<code>int *array[1]</code> 等价于 <code>(int *)array[1]</code>，为 int 指针的数组；<code>int (*p)[1]</code> 为指向 <code>int</code> 数组的指针。</p><p>解读方法：以 <code>int **a[1][2]</code> 为例。从 a 出发，优先向右解读。（<a href="http://unixwiz.net/techtips/reading-cdecl.html">来源</a>）</p><ol><li><code>a</code> is … <code>int</code></li><li><code>a</code> is <code>array</code> of 1 … <code>int</code></li><li><code>a</code> is <code>array</code> of 1 <code>array</code> of 2 … <code>int</code></li><li><code>a</code> is <code>array</code> of 1 <code>array</code> of 2 <code>pointer</code>s to … <code>int</code></li><li><code>a</code> is <code>array</code> of 1 <code>array</code> of 2 <code>pointer</code>s to <code>poiner</code> to <code>int</code></li></ol><p>优先级：<em>后置自增 &#x2F; 自减 </em>、函数调用、数组元素、结构成员 &gt; 前置自增 &#x2F; 自减、正负、类型装换、<em> 解引用 </em>、取地址、数据类型大小、内存操作、（逻辑、位）非 &gt; 结构成员解引用 &gt; 乘除、取余 &gt; 加减 &gt; <em> 位移</em> &gt; 比较 &gt; 等价 &gt; （逻辑、位）与、或、异或 &gt; 三元条件、（复合）赋值。</p><p>字符串字面值与字符常量中转义序列 <code>\ddd</code> 可以为 1、2、3 位 8 进制数（0<del>7），上限为 377；<code>\xhh</code>可以为 1、2 位十六进制数（0</del>F）。</p><p>转义序列列表：</p><table><thead><tr><th>转移序列</th><th>含义</th></tr></thead><tbody><tr><td><code>\a</code></td><td><code>BEL</code></td></tr><tr><td><code>\b</code></td><td><code>BS</code></td></tr><tr><td><code>\f</code></td><td><code>FF</code></td></tr><tr><td><code>\n</code></td><td><code>LF</code></td></tr><tr><td><code>\r</code></td><td><code>CR</code></td></tr><tr><td><code>\t</code></td><td><code>HT</code></td></tr><tr><td><code>\v</code></td><td><code>VT</code></td></tr><tr><td><code>\\</code></td><td><code>\</code></td></tr><tr><td><code>\&#39;</code></td><td><code>&#39;</code></td></tr><tr><td><code>\&quot;</code></td><td><code>&quot;</code></td></tr><tr><td><code>\0</code></td><td><code>NULL</code></td></tr><tr><td><code>\ddd</code></td><td>八进制</td></tr><tr><td><code>\xhh</code></td><td>十六进制</td></tr></tbody></table><p>0~255 的数字也可以作为有效的字符取值。</p><p><code>int i = -1; printf(&quot;%d&quot;, (unsigned int)i);</code>打印出 <code>-1</code>，因为<code>%d</code> 为有符号整型。</p><p>使用草稿纸记录变量取值以计算程序输出，或对某些类型可理解程序意图猜测程序输出，或两者结合。</p><p><code>10^-6</code>表示为<code>1e-6</code>。</p><p>通过 <code>memset(array,0,sizeof(array))</code> 可以实现数组重初始化。（需要<code>string.h</code>）</p><p>宏 <code>#</code> 代表将此后的文本变为字符串；<code>##</code>代表连接文本；含有 <code>#</code> 与<code>##</code>的宏不进行参数展开，可利用包装宏展开。（<a href="http://learn.akae.cn/media/ch21s02.html">参考</a>）</p><p>宏函数定义：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> func_name(var) \</span></span><br><span class="line"><span class="meta">do &#123; \</span></span><br><span class="line"><span class="meta">    do_something(); \</span></span><br><span class="line"><span class="meta">&#125; while (0)</span></span><br></pre></td></tr></table></figure><p>调用与函数调用一致：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">func_name(var);</span><br></pre></td></tr></table></figure><p><code>scanf</code>中 <code>f</code> 为<code>float</code>，<code>lf</code>为 <code>double</code>；<code>printf</code> 中<code>f</code>为 <code>double</code>；<code>lf</code> 未定义。</p><p><code>,</code>逗号表达式对左侧表达式求值并丢弃返回值，之后对右侧表达式求值并返回其返回值。</p><p><code>#define SWAP(a,b) (a)^=(b)^=(a)^=(b)</code>可实现无临时变量的交换。</p><table><thead><tr><th>字符</th><th>ASCII</th></tr></thead><tbody><tr><td>0</td><td>48</td></tr><tr><td>A</td><td>65</td></tr><tr><td>a</td><td>97</td></tr></tbody></table><p>变量命名法则：<code>[A-Za-z_][A-Za-z0-9_]*</code>，且不含关键字。</p><p><code>auto</code>，<code>register</code>，<code>volatile</code>均为 C 关键字。</p><p><code>007</code>为八进制数字，注意范围为 <em>0-7</em>，<code>0x0F</code> 为十六进制数字。</p><p><code>strcat</code>实现：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">strcat</span><span class="params">(<span class="type">char</span> *str1, <span class="type">char</span> *str2)</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (*str &amp;&amp; *++str);</span><br><span class="line">    <span class="keyword">while</span> (*str1++ = *str2++);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote class="callout"><div class="title">相关阅读</div><a href="http://blog.yzyzsun.me/c-traps-and-pitfalls/">《C 陷阱与缺陷》笔记 | 孙耀珠的博客</a></blockquote>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;这是我在大一时 C 语言课程考试前总结的坑们，每次相关考试前都会复习一次。如果你发现可以补充的地方，欢迎评论。&lt;/p&gt;</summary>
    
    
    
    
    <category term="编程" scheme="https://blog.zhanghai.me/tags/programming/"/>
    
    <category term="学业" scheme="https://blog.zhanghai.me/tags/schoolwork/"/>
    
    <category term="笔记" scheme="https://blog.zhanghai.me/tags/note/"/>
    
  </entry>
  
  <entry>
    <title>求是潮手机站 Android 客户端 V3</title>
    <link href="https://blog.zhanghai.me/qsc-mobile-android-v3/"/>
    <id>https://blog.zhanghai.me/qsc-mobile-android-v3/</id>
    <published>2014-12-17T03:33:35.000Z</published>
    <updated>2019-05-16T05:05:51.865Z</updated>
    
    <content type="html"><![CDATA[<img src="/qsc-mobile-android-v3/0.png" class=""title=" 零 "><span id="more"></span><img src="/qsc-mobile-android-v3/1.png" class=""title=" 一 "><img src="/qsc-mobile-android-v3/2.png" class=""title=" 二 "><img src="/qsc-mobile-android-v3/3.png" class=""title=" 三 "><img src="/qsc-mobile-android-v3/4.png" class=""title=" 四 ">]]></content>
    
    
    <summary type="html">&lt;img src=&quot;/qsc-mobile-android-v3/0.png&quot; class=&quot;&quot; title=&quot;零&quot;&gt;</summary>
    
    
    
    
    <category term="职业" scheme="https://blog.zhanghai.me/tags/career/"/>
    
  </entry>
  
</feed>
