Yrom's
2023-10-20T02:52:27.051Z
https://yrom.net/
Yrom
Hexo
通过 dyld-interposing 实现C/C++代码注入
https://yrom.net/blog/2023/10/19/dyld-interposing/
2023-10-19T06:22:54.000Z
2023-10-20T02:52:27.051Z
<p>苹果系统的链接器<code>/usr/lib/dyld</code> 提供了一个叫<code>dyld-interposing</code>的功能(从 Mac OS X 10.4 开始),可以在程序启动时替换掉某个函数的实现。这个功能可以用来实现代码注入(详见:<a href="https://atakua.org/p/books/Mac%20OS%20X%20Internals%20-%20A%20Systems%20Approach.pdf" target="_blank" rel="noopener">《Mac OS X Internals: A Systems Approach》</a>- Amit Singh - 第二章 2.6.3.4 <em>dyld</em> interposing)</p>
<h2 id="举个栗子"><a href="#举个栗子" class="headerlink" title="举个栗子"></a>举个栗子</h2><p>比如,我们可以在程序运行时,替换掉<code>malloc</code>函数的实现:</p>
<figure class="highlight c"><figcaption><span>malloc_trace.c</span></figcaption><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="comment">// malloc_trace.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><mach-o/dyld-interposing.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><memory.h> // memset</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><malloc/malloc.h> // malloc_printf</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">trace_malloc</span><span class="params">(<span class="keyword">size_t</span> size)</span> </span>{</span><br><span class="line"> <span class="keyword">char</span> *p = <span class="built_in">malloc</span>(size);</span><br><span class="line"> <span class="comment">// fills with '#'</span></span><br><span class="line"> <span class="built_in">memset</span>(p, <span class="string">'#'</span>, size);</span><br><span class="line"> malloc_printf(<span class="string">"malloc(%u) = %p\n"</span>, size, p);</span><br><span class="line"> <span class="keyword">return</span> (<span class="keyword">void</span> *)p;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">DYLD_INTERPOSE(trace_malloc, <span class="built_in">malloc</span>);</span><br></pre></td></tr></table></figure>
<figure class="highlight c"><figcaption><span>test.c</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// test.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdlib.h></span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">char</span> *p = (<span class="keyword">char</span>*)<span class="built_in">malloc</span>(<span class="number">10</span>);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"malloc return %p, %s\n"</span>, p, p);</span><br><span class="line"> <span class="built_in">free</span>(p);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">$ cc -dynamiclib -o libmalloctrace.dylib malloc_trace.c -install_name libmalloctrace.dylib</span><br><span class="line">$ cc -o test test.c</span><br><span class="line">$ DYLD_INSERT_LIBRARIES=libmalloctrace.dylib ./test</span><br><span class="line"></span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(1536) = 0x7febbc808200</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(32) = 0x7febbc704130</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(32) = 0x7febbc704170</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(20) = 0x7febbc705550</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(422) = 0x7febbc7055d0</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(50) = 0x7febbc7057e0</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(16) = 0x7febbc705880</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(52) = 0x7febbc705900</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(12) = 0x7febbc7059b0</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(10) = 0x7febbc705b00</span><br><span class="line">test(46555,0x11bdd3600) malloc: malloc(4096) = 0x7febbc808800</span><br><span class="line">malloc return 0x7febbc705b00, ##########</span><br></pre></td></tr></table></figure>
Graphics API Debuggers
https://yrom.net/blog/2023/09/27/graphics-api-debuggers/
2023-09-27T09:50:57.000Z
2023-10-07T03:23:19.601Z
<ul>
<li><a href="https://learn.microsoft.com/zh-cn/windows-hardware/drivers/display/using-gpuview" target="_blank"
Learning Render Graph
https://yrom.net/blog/2023/08/13/learning-render-graph/
2023-08-13T15:27:37.000Z
2023-09-27T09:52:15.832Z
<h2 id="什么是-Render-Graph?"><a href="#什么是-Render-Graph?" class="headerlink" title="什么是 Render Graph?"></a>什么是 Render Graph?</h2><p><strong>Render Graph</strong> 或者说 <strong>Frame graph</strong> 是对复杂渲染管线的一个高度抽象,以图(Graph)的形式呈现渲染过程中的各个步骤,不同的渲染任务之间的依赖关系,以及它们对资源(如纹理、缓冲区等)的使用。</p>
<blockquote>
<p><strong>Frame graphs</strong> are a design pattern for handling complex rendering pipelines, which are currently used in industry. Their usage is motivated by handling barriers, queue synchronization and memory aliasing in the background by abstracting the rendering pipeline of a frame on a higher level.<br> —— <a href="https://github.com/gfx-rs/gfx/wiki/Frame-graphs" target="_blank" rel="noopener">https://github.com/gfx-rs/gfx/wiki/Frame-graphs</a></p>
</blockquote>
<h2 id="解决什么问题?"><a href="#解决什么问题?" class="headerlink" title="解决什么问题?"></a>解决什么问题?</h2><p>在传统的渲染管线中,渲染过程通常被划分为多个阶段,如下图所示:<br><object type="image/svg+xml" data="/images/opengl-render-pipeline.svg"></object></p>
<p>这些阶段之间存在着输入和输出的依赖关系,其中<strong>一个阶段的输出作为下一个阶段的输入</strong>。</p>
<p>Render Graph 的主要思想是将渲染过程表示为一个<a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph" target="_blank" rel="noopener">有向无环图(DAG)</a>,其中节点表示渲染通道(Render pass),边表示依赖关系。每个渲染通道执行特定的渲染操作,可具有输入和输出资源,例如Texture、Frame Buffer和执行的 Shader/Program。例如,假设节点 A 的输出Texture是节点 B 的输入Texture,那么节点 B 就依赖于节点 A。</p>
<div align="center"><object type="image/svg+xml" data="/images/render-dag.svg"></object></div>
<p>通过概括渲染流程中的依赖关系,确保渲染阶段按正确的顺序执行,并且在需要时可基于一定的同步机制(Fence、Semaphore、Resource barriers)尽可能地<strong>并行</strong>执行渲染通道。</p>
<p>Render Graph 的目标是为了解决大型渲染引擎里复杂渲染管线中的一些问题。如资源生命周期管理、渲染效率、渲染过程的可视化调试等等。</p>
<p>Render Graph 不仅在游戏引擎中广泛应用:</p>
<ul>
<li>Unity 3D, <a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@16.0/manual/render-graph-system.html" target="_blank" rel="noopener">The render graph system</a></li>
<li>Unreal Engine, <a href="https://docs.unrealengine.com/5.2/en-US/render-dependency-graph-in-unreal-engine/" target="_blank" rel="noopener">Render Dependency Graph</a></li>
<li>Cocos Creator, <a href="https://blog.csdn.net/weixin_44053279/article/details/131199173" target="_blank" rel="noopener">面向数据的可定制渲染管线</a></li>
</ul>
<p>它的理念也在现代图形 API 中可窥一斑,如<code>Vulkan</code> 的 <a href="https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes" target="_blank" rel="noopener">Render Pass</a>、<code>DirectX 12</code> 的 <a href="https://learn.microsoft.com/en-us/windows/win32/direct3d12/recording-command-lists-and-bundles" target="_blank" rel="noopener">Command List</a>、<code>Metal</code> 的 <a href="https://developer.apple.com/documentation/metal/render_passes/customizing_render_pass_setup?language=objc" target="_blank" rel="noopener">Render Pass</a>等。</p>
从Android 原生库 (.so) 中里挖掘一些有用的信息
https://yrom.net/blog/2023/07/25/useful-info-of-android-so/
2023-07-25T09:20:18.000Z
2023-07-26T11:14:00.519Z
<p>当一个 Android APP 需要集成别的地方来的原生库(.so)时,你可能也会跟我一样会有那么几点疑惑:</p>
<ul>
<li>这个 so 用的什么 NDK 版本编译的?会不会跟项目里其它的so 冲突,尤其项目里使用<a href="https://developer.android.com/ndk/guides/cpp-support?hl=zh-cn#shared_runtimes" target="_blank" rel="noopener">共享 C++ STL</a>的情况下 <code>ANDROID_STL=c++_shared</code>,一个应用不能使用多个 C++ 运行时</li>
<li>这个 so 目标 Android API 等级是多少?会不会大于项目的<code>minSdkVersion</code>?</li>
<li>这个 so 依赖(链接)其它哪些 so?这些 so 有没有都放进项目里?</li>
<li>这个 so 有没有除了用文件哈希之外唯一编号,用来标识崩溃堆栈等?</li>
</ul>
<p>ps. 本文假定读者有一定 Android Native 开发经验,且理解一些基本的概念。</p>
<h2 id="查看-so-的-NDK-版本信息"><a href="#查看-so-的-NDK-版本信息" class="headerlink" title="查看 so 的 NDK 版本信息"></a>查看 so 的 NDK 版本信息</h2><p>通过 <a href="https://man7.org/linux/man-pages/man1/readelf.1.html" target="_blank" rel="noopener"><code>readelf</code></a> 工具查看 Android NDK 编译出来的 so 的 <a href="https://www.sco.com/developers/gabi/latest/ch4.sheader.html" target="_blank" rel="noopener"><code>Section headers</code></a> 里有什么 Android 特有的玩意。</p>
<p>ps. 可以用 <code>ndk-which</code> 找到 NDK 中预编译好的 <code>readelf</code>:</p>
<figure class="highlight sh"><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="variable">$ANDROID_NDK_HOME</span>/ndk-which --abi arm64-v8a readelf</span><br><span class="line">/~/ndk/21.4.7075529/prebuilt/darwin-x86_64/bin/../../../toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android-readelf</span><br></pre></td></tr></table></figure>
<p>定义一个名为 <code>readelf</code> 的 alias 方便在 Terminal 中调用 <code>aarch64-linux-android-readelf</code></p>
<figure class="highlight sh"><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="built_in">alias</span> readelf=`<span class="variable">$ANDROID_NDK_HOME</span>/ndk-which --abi arm64-v8a readelf`</span><br><span class="line">$ readelf -v</span><br><span class="line">GNU readelf (GNU Binutils) 2.27.0.20170315</span><br><span class="line">Copyright (C) 2016 Free Software Foundation, Inc.</span><br><span class="line">...</span><br></pre></td></tr></table></figure>
<p>以 NDK 中带的 <code>libc++_shared.so</code> 为例,在我本机上路径是<code>$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so</code>:</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ readelf -WS <span class="variable">$ANDROID_NDK_HOME</span>/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so</span><br></pre></td></tr></table></figure>
How to run Java standalone app (with JNI) on Android without creating an apk
https://yrom.net/blog/2023/07/07/run-java-with-jni-app-on-android/
2023-07-07T15:11:17.000Z
2023-07-10T06:29:08.240Z
<p>In this week, I found a great <a href="https://raccoon.onyxbits.de/blog/run-java-app-android/" target="_blank" rel="noopener">POC</a> to run a pure Java standalone app (command line tool, no apk) on Android. But what about running a standalone application using JNI (with .so files) on Android like this?</p>
<h2 id="Java-app-with-JNI"><a href="#Java-app-with-JNI" class="headerlink" title="Java app with JNI"></a>Java app with JNI</h2><p>Imagine there is a Java program that loads the JNI shared native library to run and use some Android APIs :</p>
<figure class="highlight java"><figcaption><span>HelloWorld.java</span></figcaption><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">package</span> com.example;</span><br><span class="line"><span class="keyword">import</span> android.os.Build;</span><br><span class="line"><span class="keyword">import</span> android.util.Log;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Helloworld</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">static</span> { System.loadLibrary(<span class="string">"hello"</span>); }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> String <span class="title">stringFromJNI</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> Log.i(<span class="string">"@@"</span>, <span class="string">"Hello world, "</span> + Build.MANUFACTURER + <span class="string">" "</span>+ Build.MODEL + <span class="string">"!"</span>);</span><br><span class="line"> Log.i(<span class="string">"@@"</span>, stringFromJNI());</span><br><span class="line"> System.out.println(stringFromJNI());</span><br><span class="line"> System.out.println(<span class="string">"DONE."</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getBuildVersion</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> Build.VERSION.RELEASE;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>…the JNI source would be like:</p>
<figure class="highlight c"><figcaption><span>hello-jni.c </span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ... emit codes</span></span><br><span class="line"></span><br><span class="line">JNIEXPORT jstring JNICALL</span><br><span class="line">Java_com_example_Helloworld_stringFromJNI(JNIEnv *env,</span><br><span class="line"> jobject thiz)</span><br><span class="line">{</span><br><span class="line"> <span class="comment">// ... emit codes</span></span><br><span class="line"> jmethodID versionFunc = (*env)->GetStaticMethodID(env, clz, <span class="string">"getBuildVersion"</span>, <span class="string">"()Ljava/lang/String;"</span>);</span><br><span class="line"></span><br><span class="line"> jstring buildVersion = (*env)->CallStaticObjectMethod(env, clz, versionFunc);</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">char</span> *version = (*env)->GetStringUTFChars(env, buildVersion, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!version)</span><br><span class="line"> {</span><br><span class="line"> LOGE(<span class="string">"Unable to get version string"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> LOGI(<span class="string">"Build Version - %s\n"</span>, version);</span><br><span class="line"> (*env)->ReleaseStringUTFChars(env, buildVersion, version);</span><br><span class="line"> }</span><br><span class="line"> (*env)->DeleteLocalRef(env, buildVersion);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> (*env)->NewStringUTF(env,</span><br><span class="line"> <span class="string">"Hello from JNI ! Compiled with ABI "</span> ABI <span class="string">"."</span>);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure>
<p>The working directory structure</p>
<figure class="highlight plain"><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">├── Helloworld.java</span><br><span class="line">└── hello-jni.c</span><br></pre></td></tr></table></figure>
<h2 id="Compile-and-deploy"><a href="#Compile-and-deploy" class="headerlink" title="Compile and deploy"></a>Compile and deploy</h2><p>Now we need to compile both the Java and C sources for Android.</p>
<p>Using <code>javac</code> and <code>dx</code> to compile for a jar file which Android can read:</p>
<figure class="highlight sh"><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="built_in">export</span> BUILD_DIR=<span class="variable">$PWD</span>/build</span><br><span class="line"><span class="built_in">export</span> JARFILE=helloworld.jar</span><br><span class="line"><span class="built_in">export</span> JAVAC_OPTS=-<span class="built_in">source</span> 1.8 -target 1.8 -cp .:<span class="variable">$ANDROID_HOME</span>/platforms/android-30/android.jar</span><br><span class="line"><span class="comment"># Compile .java to .class</span></span><br><span class="line">javac <span class="variable">$JAVAC_OPTS</span> -d <span class="variable">$BUILD_DIR</span>/classes Helloworld.java</span><br><span class="line"><span class="comment"># Convert .class file into a dex file and embedded in a jar file</span></span><br><span class="line"><span class="variable">$ANDROID_HOME</span>/build-tools/30.0.2/dx --output=<span class="variable">$BUILD_DIR</span>/<span class="variable">$JARFILE</span> --dex ./<span class="variable">$BUILD_DIR</span>/classes</span><br></pre></td></tr></table></figure>
<p>Cross-compile the C to Android shared native library via NDK:</p>
<figure class="highlight sh"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># Using the prebuilt toolchain diretly</span></span><br><span class="line"><span class="comment"># See https://developer.android.com/ndk/guides/other_build_systems</span></span><br><span class="line"><span class="built_in">export</span> ANDROID_NDK_STANDALONE=<span class="variable">$ANDROID_NDK_HOME</span>/toolchains/llvm/prebuilt/darwin-x86_64</span><br><span class="line"><span class="variable">$ANDROID_NDK_STANDALONE</span>/bin/clang \</span><br><span class="line"> --target=aarch64-none-linux-android21 \</span><br><span class="line"> --gcc-toolchain=<span class="variable">$ANDROID_NDK_STANDALONE</span> \</span><br><span class="line"> --sysroot <span class="variable">$ANDROID_NDK_STANDALONE</span>/sysroot \</span><br><span class="line"> -L<span class="variable">${ANDROID_NDK_STANDALONE}</span>/sysroot/usr/lib \</span><br><span class="line"> -shared -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables \</span><br><span class="line"> -fstack-protector-strong -no-canonical-prefixes -fno-addrsig -fPIC \</span><br><span class="line"> -Wl,-llog \</span><br><span class="line"> -Wl,-soname,libhello.so \</span><br><span class="line"> -o <span class="variable">$BUILD_DIR</span>/libhello.so hello-jni.c</span><br></pre></td></tr></table></figure>
<p>The build directory looks like: </p>
<figure class="highlight plain"><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">build</span><br><span class="line">├── classes</span><br><span class="line">│ └── com</span><br><span class="line">│ └── example</span><br><span class="line">│ └── Helloworld.class</span><br><span class="line">├── helloworld.jar</span><br><span class="line">└── libhello.so</span><br></pre></td></tr></table></figure>
<p>Using the <code>adb</code> tool to deploy the <code>helloworld.jar</code> and <code>libhello.so</code> to Android device:</p>
<figure class="highlight sh"><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">adb shell mkdir /data/<span class="built_in">local</span>/tmp/helloworld</span><br><span class="line">adb push <span class="variable">$BUILD_DIR</span>/helloworld.jar <span class="variable">$BUILD_DIR</span>/libhello.so /data/<span class="built_in">local</span>/tmp/helloworld/</span><br></pre></td></tr></table></figure>
<h2 id="Run-appliation-on-Android"><a href="#Run-appliation-on-Android" class="headerlink" title="Run appliation on Android"></a>Run appliation on Android</h2><p>Run <code>helloworld.jar</code> via <code>app_process</code> on Android:</p>
<figure class="highlight sh"><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">adb shell CLASSPATH=<span class="string">"/data/local/tmp/helloworld/helloworld.jar"</span> \</span><br><span class="line"> LD_LIBRARY_PATH=/data/<span class="built_in">local</span>/tmp/helloworld \</span><br><span class="line"> app_process \</span><br><span class="line"> /data/<span class="built_in">local</span>/tmp/helloworld \</span><br><span class="line"> com.example.Helloworld</span><br><span class="line"><span class="comment"># output</span></span><br><span class="line">Hello from JNI ! Compiled with ABI arm64-v8a.</span><br><span class="line">DONE.</span><br></pre></td></tr></table></figure>
How to detect memory leaks in C++ programs on macOS
https://yrom.net/blog/2023/07/03/detect-memory-leaks-of-cpp-program-on-macos/
2023-07-03T14:00:08.000Z
2023-07-04T08:55:14.980Z
<p>一段很典型的内存泄漏C++ 代码如下:</p>
<figure class="highlight cpp"><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"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">const</span> <span class="keyword">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">auto</span> *p = <span class="keyword">new</span> <span class="keyword">int</span>(<span class="number">10</span>);</span><br><span class="line"> <span class="comment">// other codes ...</span></span><br><span class="line"> p = <span class="literal">nullptr</span>; <span class="comment">// leaked</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如何在庞大的 C++ 项目代码中找出类似的问题呢?</p>
<h2 id="libgmalloc"><a href="#libgmalloc" class="headerlink" title="libgmalloc"></a>libgmalloc</h2><p>苹果提供了内存调试功能:<a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html" target="_blank" rel="noopener">Guard Malloc</a>,用于debug 内存问题。 <code>man libgmalloc</code> 可以查看更多使用信息。</p>
<p>开启<code>libgmalloc</code>的记录 malloc 调用日志的功能,在执行程序前设置环境变量<code>MallocStackLogging=1</code>,如:</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MallocStackLogging=1 ./my_tool</span><br></pre></td></tr></table></figure>
<p>日志会写到一个临时文件中:</p>
<blockquote>
<p>my_tool(38364) MallocStackLogging: stack logs being written to /private/tmp/stack-logs.38364.103f3a000.my_tool.19n2JH.index<br>my_tool(38364) MallocStackLogging: recording malloc and VM allocation stacks to disk using standard recorder</p>
</blockquote>
<p>需要注意的是,在程序退出时,这个调用日志文件会被自动删除:</p>
<blockquote>
<p>my_tool(38364) MallocStackLogging: stack logs deleted from /private/tmp/stack-logs.38364.103f3a000.my_tool.19n2JH.index</p>
</blockquote>
<p>所以需要将程序在结束时最好 block 住,以便分析日志文件:</p>
<figure class="highlight cpp"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">wait_for_input</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cout</span> << <span class="string">"Press Enter to exit."</span> << <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"> <span class="keyword">char</span> b[<span class="number">1</span>];</span><br><span class="line"> <span class="built_in">std</span>::<span class="built_in">cin</span>.read(b, <span class="number">1</span>);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">const</span> <span class="keyword">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">auto</span> *p = <span class="keyword">new</span> <span class="keyword">int</span>(<span class="number">10</span>);</span><br><span class="line"> <span class="comment">// other codes ...</span></span><br><span class="line"> p = <span class="literal">nullptr</span>; <span class="comment">// leaked</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// blocking program for analyzing malloc stack history</span></span><br><span class="line"> wait_for_input();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="The-leaks-Tools"><a href="#The-leaks-Tools" class="headerlink" title="The leaks Tools"></a>The leaks Tools</h2><p>另外,可以直接用 macOS 检测内存泄漏:<code>/usr/bin/leaks</code> (详见:<a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/FindingLeaks.html#//apple_ref/doc/uid/20001883-100980" target="_blank" rel="noopener">the leaks Tool</a> )</p>
<p>终端中执行 <code>man leaks</code> 查看使用手册。</p>
<p><code>leaks</code> 使用方式很简单,指定 <code>pid</code> 即可 attach 到执行中的程序:</p>
<figure class="highlight sh"><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="comment"># pid=38364</span></span><br><span class="line">leaks <span class="variable">$pid</span> --outputGraph=<span class="variable">$pid</span>.memgraph</span><br><span class="line"><span class="comment"># open memory graph file with Xcode</span></span><br><span class="line">open <span class="variable">$pid</span>.memgraph</span><br></pre></td></tr></table></figure>
<p><img src="/images/screenshots-xcode-memgraph.png" alt="Xcode Memory Graph Debugger"></p>
<p>结合起来就是:</p>
<ol>
<li>修改 C++ 程序 main 函数,使其在结束时 block,重编程序</li>
<li>设置环境变量<code>MallocStackLogging=1</code>,执行程序。</li>
<li>在程序执行结束时,亦即 block 时,执行 <code>leaks</code>,保存 memory graph文件</li>
<li><code>⌃+C</code> 结束程序</li>
<li>使用 <code>Xcode Memory Graph Debugger</code> 打开 memory graph文件,分析内存泄漏</li>
</ol>
Debug iOS app in Visual Studio Code
https://yrom.net/blog/2023/04/22/debug-ios-app-in-vscode/
2023-04-22T12:58:30.000Z
2023-07-04T08:40:51.628Z
Visual Studio Code 中使用 LLDB 远程调试 iOS app 和 XCTest
How to explicitly control exported symbols of dynamic shared libraries
https://yrom.net/blog/2023/04/19/how-to-explicitly-control-exported-symbols-of-dyamic-shared-libraries/
2023-04-19T14:03:37.000Z
2023-04-23T10:21:51.910Z
<h2 id="Benefits-of-exporting-symbols-on-demand"><a href="#Benefits-of-exporting-symbols-on-demand" class="headerlink" title="Benefits of exporting symbols on demand"></a>Benefits of exporting symbols on demand</h2><ol>
<li>Reduce the amount of time of loading the library.</li>
<li>Reduce the size of library.</li>
<li>Optimize the code generation.</li>
<li>Improve library maintainability and make the library easier to use.</li>
<li>Reduce the potential for symbol collision.</li>
</ol>
<h2 id="List-the-symbols-of-shared-library"><a href="#List-the-symbols-of-shared-library" class="headerlink" title="List the symbols of shared library"></a>List the symbols of shared library</h2><h3 id="1-nm"><a href="#1-nm" class="headerlink" title="1. nm"></a>1. nm</h3><p><a href="https://llvm.org/docs/CommandGuide/llvm-nm.html" target="_blank" rel="noopener">https://llvm.org/docs/CommandGuide/llvm-nm.html</a></p>
<p><code>nm -gD libxxx.so</code></p>
<h3 id="2-objdump"><a href="#2-objdump" class="headerlink" title="2. objdump"></a>2. objdump</h3><p><a href="https://llvm.org/docs/CommandGuide/llvm-objdump.html" target="_blank" rel="noopener">https://llvm.org/docs/CommandGuide/llvm-objdump.html</a></p>
<p><code>objdump -T libxxx.so</code></p>
<h3 id="3-readelf"><a href="#3-readelf" class="headerlink" title="3. readelf"></a>3. readelf</h3><p><a href="https://man7.org/linux/man-pages/man1/readelf.1.html" target="_blank" rel="noopener">https://man7.org/linux/man-pages/man1/readelf.1.html</a></p>
<p><code>readelf -Ws libxxx.so</code></p>
<h2 id="Option-1-the-visibility-attribute"><a href="#Option-1-the-visibility-attribute" class="headerlink" title="Option 1. the visibility attribute"></a>Option 1. the visibility attribute</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">if</span> HAVE_VISIBILITY && BUILDING_LIBFOO</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LIBFOO_EXPORTED __attribute__((__visibility__(<span class="meta-string">"default"</span>)))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> (defined _WIN32 && !defined __CYGWIN__) && BUILDING_SHARED && BUILDING_LIBFOO</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LIBFOO_EXPORTED __declspec(dllexport)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> (defined _WIN32 && !defined __CYGWIN__) && BUILDING_SHARED</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LIBFOO_EXPORTED __declspec(dllimport)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LIBFOO_EXPORTED</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure>
<p>Compile the code with <code>-fvisibility=hidden</code> flag. This option forces the default visibility of all symbols to be <code>hidden</code>.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c++ -I. -fvisibility=hidden -o a.o -c a.cpp</span><br></pre></td></tr></table></figure>
<p>On Windows, using the keyword __declspec(dllexport) to export symbols . See <a href="https://learn.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-declspec-dllexport?view=msvc-170" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-declspec-dllexport?view=msvc-170</a></p>
<p>See more details: <a href="https://gcc.gnu.org/wiki/Visibility" target="_blank" rel="noopener">https://gcc.gnu.org/wiki/Visibility</a> </p>
Android CMake 项目集成 Ccache
https://yrom.net/blog/2023/04/10/integrate-ccache-for-android-cmake-project/
2023-04-10T08:08:37.000Z
2023-04-23T08:52:49.466Z
<p><a href="https://ccache.dev/" target="_blank" rel="noopener">Ccache</a> is a compiler cache. 能极大的提升 clean build 编译效率。</p>
<p>github
沉迷 Midjourney
https://yrom.net/blog/2023/03/26/addicted-to-midjourney/
2023-03-26T03:00:20.000Z
2023-03-26T04:29:18.406Z
<p>近些日子沉迷 Midjourney,用不同咒语来回调教 AI,也算体验了一把炼丹的乐趣 —— 投喂一堆奇奇怪怪的东西,得一个匪夷所思的玩意。</p>
<p>推荐一个 Notion 笔记,总结了非常多的风格、建筑、艺术家、摄影技法等等关键字,用来辅助调教:<a href="https://marigoldguide.notion.site/marigoldguide/52ac9968a8da4003a825039022561a30?v=057d3669790c4dc28bd8d3ddf35e3a37" target="_blank" rel="noopener">https://marigoldguide.notion.site/marigoldguide/52ac9968a8da4003a825039022561a30?v=057d3669790c4dc28bd8d3ddf35e3a37</a></p>
<p>比如:<br>Castle Claude Monet –seed 9875<br><img src="https://marigoldguide.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F663fe48d-c6c6-4207-bf8d-ead6856af862%2FMarigold_Castle_Claude_Monet_1b66d349-a585-4fce-9662-a96c8d7c6550.png?id=5ea2e123-255d-4354-b3ae-d96aaa879fd0&table=block&spaceId=ae747c82-ce69-49df-9a48-f5c339c07ba7&width=2000&userId=&cache=v2" alt="Castle in the style of Claude Monet --seed 9875"></p>
<p>另外还有一个可视化组织咒语的工具网站,也很好用:<a href="https://prompt.noonshot.com" target="_blank" rel="noopener">MidJourney Prompt Helper</a></p>
<hr>
<p>下面是我用使用 Midjourney 画的图:</p>
<p>泉州蟳埔女,簪花围:</p>
<p><img src="https://cdn.midjourney.com/b5df3dbb-fac7-4ca5-a938-9b567bc41ed7/grid_0.png" alt="a pretty young woman smells flower on chair at sunset"></p>
<p><code>/imagine prompt: https://s.mj.run/t3qtGrXY96c, a pretty young woman smells flower on chair at sunset, --q 2 --ar 9:16</code></p>
<p>蓝眼泪:</p>
<p><img src="https://cdn.midjourney.com/d23c4037-5f1a-4b83-a139-fe3b8e1a82d7/grid_0.png" alt="In the midnight, the ocean beach"></p>
<p><code>/imagine prompt: https://s.mj.run/gGNA5xDRpCc In the midnight, the ocean beach, rippling light-blue waves, shining stars, weak lights, movie lights, HD, 4k, --ar 16:9 --no moon</code></p>
<p>宇航员月球种土豆:</p>
<p><img src="https://cdn.midjourney.com/bd01262d-8a30-4c3f-a30a-0fd6d6a0101c/grid_0.png" alt="astronaut planting potatoes on the moon"></p>
<p><code>/imagine prompt: astronaut planting potatoes on the moon, nebula shines through glass dome, high detail, edge lighting, movie lighting, oc render, 8K --ar 5:3</code></p>
Bazel run cc_test on Android
https://yrom.net/blog/2023/02/04/bazel-run-cc-test-on-android/
2023-02-04T09:40:20.000Z
2023-04-23T10:21:49.459Z
<p>See <a href="https://github.com/yrom/rules_android_cc_test" target="_blank"
如何快速定位 Flutter APP 内存泄漏
https://yrom.net/blog/2021/12/30/how-to-detecting-memory-leaks-in-flutter-app/
2021-12-30T11:10:20.000Z
2022-02-24T06:26:32.987Z
<p>当一个 APP 随着业务发展,<del>无用</del>功能越堆越多,参与开发的人员队伍越发壮大,代码大爆炸式膨胀,尽管有一系列的例如人工review、代码规范、静态lint检查之类流程上的克制手段,也止不住坍缩的小宇宙往深不可测无法捉摸的黑洞演进。其中,最为令人头疼的问题莫过于“<strong>内存泄漏</strong>”。</p>
<p>什么是<strong>内存泄漏</strong>?很常见却也很不起眼,你我随手写段代码就能轻松让一个对象被<strong>垃圾回收器</strong>(GC)视而不见。</p>
<p>如下面这一段:</p>
<figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">WidgetA</span> <span class="keyword">extends</span> <span class="title">StatefulWidget</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> _WidgetAState createState() => _WidgetAState();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_WidgetAState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">WidgetA</span>> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="keyword">return</span> GestureDetector(</span><br><span class="line"> onTap: () <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">var</span> r = <span class="keyword">await</span> fetchResult();</span><br><span class="line"> <span class="keyword">if</span> (mounted) {</span><br><span class="line"> setState(() {</span><br><span class="line"> <span class="keyword">this</span>.result = r;</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> child: ...</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>你能在阅读完这段代码的同时找到那个可能被泄漏的对象吗?</p>
<p>没错,<code>context</code> 可能会<strong>短时间泄漏</strong>。<br>但,得益于 Dart 积极高效的 GC 策略,这种泄漏已经算是<strong>内存泄漏</strong>问题中的可以忽略不计的轻症了。(扩展阅读:<a href="https://medium.com/flutter/flutter-dont-fear-the-garbage-collector-d69b3ff1ca30" target="_blank" rel="noopener">Flutter: Don’t Fear the Garbage Collector</a> )</p>
<p>又如这一段代码:</p>
<figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_WidgetAState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">WidgetA</span>> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> initState() {</span><br><span class="line"> <span class="keyword">super</span>.initState();</span><br><span class="line"> Provider.of<AuthStateBloc>(context).stream.listen(onAuthStateChanged);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">void</span> onAuthStateChanged(AuthState state) {</span><br><span class="line"> <span class="keyword">if</span> (mounted) {</span><br><span class="line"> setState(() {</span><br><span class="line"> <span class="keyword">this</span>.state = state;</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>有着丰富的 Flutter 开发经验的你,还是很轻松地就发现了,<code>stream.listen</code> 的返回对象<code>StreamSubscription</code>没有被正确地<code>cancel</code>,导致<code>_WidgetAState</code> 和它的 <code>Element</code> (context) 可能都<strong>泄漏</strong>了,直到祖节点的 <code>Provider<AuthStateBloc></code> 被卸载才可能被 GC 回收。</p>
<p>那么回到标题,有没有工具可以程序化的发现<strong>内存泄漏</strong>呢?</p>
<p>首先要解决的问题是怎么找到<strong>内存泄漏</strong>点?</p>
Flutter Raw Image Provider
https://yrom.net/blog/2021/06/20/flutter-raw-image-provider/
2021-06-20T14:15:53.000Z
2021-06-20T15:22:36.690Z
<p>Flutter 中的 Image Widget 内置支持 file、network、memory三种形式的文件。<br>但这几种都只支持常规的经过压缩后的图片文件或二进制数据,如jpg、png、webp文件等。并没有支持原始的<code>rgba</code> 二进制数据。<br>这里说的原始二进制数据是指图像的每个像素的色彩值所组成的字节数组。一张图有宽x高个像素点,一个像素点的色彩值用32bit来存储,分为4个通道,每个通道各占用8bit,分别为红、绿、蓝、透明度(RGBA),这个数组就是每个像素点色彩值的集合,dart 中一般用<code>Uint8List</code>。</p>
<p>一般情况下,考虑网络传输效率,会采用算法来压缩这个数据,故而你会看到有各种各样的图像压缩算法和文件格式。</p>
<p>你可能会问什么情况下会有需要直接去加载一张图的原始rgba数据?</p>
<p>这里举个简单例子:分块加载图片。将图片解码后,分割成一个个矩形区域,每个矩形就有一个 raw rgba 数据,将其交给Image渲染,这样做可以降低一定的GPU 内存压力,减少出现GPU OOM 或黑屏的概率。(可以参考我的试验项目:<a href="https://github.com/yrom/image_pieces)" target="_blank" rel="noopener">https://github.com/yrom/image_pieces)</a></p>
<p>要支持 raw rgba ,其实很简单,在 <code>dart:ui</code>包下有个方法<code>decodeImageFromPixels</code>可以直接使用,前提是需要有原始的二进制数据、宽、高。</p>
<figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'dart:ui'</span>;</span><br><span class="line"></span><br><span class="line">Future<Image> decodeRawRgba(ByteData bytes, <span class="built_in">int</span> width, <span class="built_in">int</span> height) {</span><br><span class="line"> <span class="keyword">final</span> Completer<Image> completer = Completer<Image>();</span><br><span class="line"> decodeImageFromPixels(</span><br><span class="line"> bytes.buffer.asUint8List(),</span><br><span class="line"> width,</span><br><span class="line"> height,</span><br><span class="line"> PixelFormat.rgba8888,</span><br><span class="line"> completer.complete,</span><br><span class="line"> );</span><br><span class="line"> <span class="keyword">return</span> completer.future;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>有了这个 <code>Image</code>(dart:ui)对象就可以交给 <code>RawImage</code> Widget 来加载了。但<code>RawImage</code>太过于底层了,能不能只用 <code>Image</code> Widget呢?因为需要复用 <code>LoadingBuilder</code>这些逻辑。</p>
<p>当然可以。查看一下 <code>Image</code> Widget 的构造函数就知道,我们需要一个 <code>ImageProvider</code>,那么问题进一步简化到如何写一个<code>ImageProvider</code> 支持 raw rgba 数据。</p>
<p>实现一个 <code>ImageProvider</code>,我们需要实现 <code>load</code>这个关键方法。以<code>MemoryImage</code>为例:</p>
<figure class="highlight dart"><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="class"><span class="keyword">class</span> <span class="title">MemoryImage</span> <span class="keyword">extends</span> <span class="title">ImageProvider</span><<span class="title">MemoryImage</span>> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) {</span><br><span class="line"> <span class="keyword">return</span> MultiFrameImageStreamCompleter(</span><br><span class="line"> codec: _loadAsync(key, decode),</span><br><span class="line"> scale: key.scale,</span><br><span class="line"> debugLabel: <span class="string">'MemoryImage(<span class="subst">${describeIdentity(key.bytes)}</span>)'</span>,</span><br><span class="line"> );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Future<ui.Codec> _loadAsync(MemoryImage key, DecoderCallback decode) {</span><br><span class="line"> <span class="keyword">return</span> decode(bytes);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>很显然,我们需要想一个方法构造出raw rgba 数据的 <code>Codec</code>。</p>
瞎拍 - 顾村寻花
https://yrom.net/blog/2021/03/14/shot-gucun-park/
2021-03-14T12:00:00.000Z
2021-03-18T12:47:07.950Z
<p>植树节刚过,上海也结束了细雨绵绵的天气,稍稍放晴,正好周末,前往顾村公园看看樱花开了没。</p>
<p>到了公园,花是没见着,人却许多。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC08550.jpg_m.webp" alt="紫叶李?"></p>
<p>往深处走,这种长着红叶的树,簇着花,密密麻麻,独占风头。百度识图告诉说是“紫叶李”,希望没认错。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC08552.jpg_m.webp" alt="紫叶李?"></p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC08708.jpg_m.webp" alt><br>粉色的摩天轮,在上面看樱花视野应该不错,想想而已。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC08558.jpg_m.webp" alt><br>还有这种白如凝脂的花,点缀公园,在一众光溜溜的树干里煞是好看。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC08619.jpg_m.webp" alt></p>
瞎拍 - 留沪过年
https://yrom.net/blog/2021/02/13/niu-year-in-shanghai/
2021-02-13T14:00:00.000Z
2021-03-18T12:07:30.980Z
<p>过年前疫情闹得凶,吓得我把回家票给退了……</p>
<p>谁知上海还是比较给力,没有扩散,却再买不着回去的票,只好一边上班一边摸鱼用盒马囤了许多年货。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC08385.jpg_m.webp" alt="一人一猫留沪过年"><br>就这样,一人一猫留沪过年。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/IMG_20210211_184017.jpg_m.webp" alt="年夜饭"><br>细雨声中,捣鼓好了一个人的年夜饭。</p>
http/2 over http tunnel proxy for Dart
https://yrom.net/blog/2020/12/22/http-2-over-http-tunnel-proxy-for-Dart/
2020-12-22T11:45:50.000Z
2020-12-22T11:57:30.191Z
<p>Flutter 项目里用到了 http2 包 (<a href="https://pub.dev/packages/http2" target="_blank" rel="noopener">https://pub.dev/packages/http2</a>) 。但官方似乎没有支持 http proxy ,转念一想,似乎也对,proxy 这层不属于 http2 协议该负责实现的事。</p>
<p>研究了一下 http/1.1 tunnel proxy 的协议(<a href="https://www.ietf.org/rfc/rfc2817.txt" target="_blank" rel="noopener">RFC 2817</a>)发现其实挺简单的。</p>
<p>这里直接放出示例了,仅供参考哦(需要登录的 proxy 就留给你自己实现了)。</p>
<figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'dart:async'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'dart:convert'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'dart:io'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'package:http2/http2.dart'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">var</span> uri = <span class="built_in">Uri</span>.parse(<span class="string">'https://cn.bing.com/search?q=http2'</span>);</span><br><span class="line"> <span class="keyword">var</span> transport = <span class="keyword">await</span> connectTunnel(<span class="string">'127.0.0.1'</span>, <span class="number">8888</span>, uri.host);</span><br><span class="line"> <span class="keyword">var</span> headers = [</span><br><span class="line"> Header.ascii(<span class="string">':method'</span>, <span class="string">'GET'</span>),</span><br><span class="line"> Header.ascii(<span class="string">':path'</span>, uri.hasQuery ? uri.path + <span class="string">'?'</span> + uri.query : uri.path),</span><br><span class="line"> Header.ascii(<span class="string">':scheme'</span>, uri.scheme),</span><br><span class="line"> Header.ascii(<span class="string">':authority'</span>, uri.host),</span><br><span class="line"> ];</span><br><span class="line"> <span class="keyword">var</span> stream = transport.makeRequest(headers, endStream: <span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">var</span> bodyStream = StreamController<<span class="built_in">List</span><<span class="built_in">int</span>>>();</span><br><span class="line"> utf8.decoder.bind(bodyStream.stream).listen((body) {</span><br><span class="line"> <span class="built_in">print</span>(body);</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">await</span> <span class="keyword">for</span> (<span class="keyword">var</span> message <span class="keyword">in</span> stream.incomingMessages) {</span><br><span class="line"> <span class="keyword">if</span> (message <span class="keyword">is</span> HeadersStreamMessage) {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">var</span> header <span class="keyword">in</span> message.headers) {</span><br><span class="line"> <span class="keyword">var</span> name = utf8.decode(header.name);</span><br><span class="line"> <span class="keyword">var</span> value = utf8.decode(header.value);</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Header: <span class="subst">$name</span>: <span class="subst">$value</span>'</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (message <span class="keyword">is</span> DataStreamMessage) {</span><br><span class="line"> bodyStream.add(message.bytes);</span><br><span class="line"> <span class="keyword">if</span> (message.endStream) {</span><br><span class="line"> bodyStream.close();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!bodyStream.isClosed) {</span><br><span class="line"> <span class="keyword">await</span> bodyStream.close();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">await</span> transport.finish();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Future<ClientTransportConnection> connectTunnel(<span class="built_in">String</span> proxyHost, <span class="built_in">int</span> proxyPort, <span class="built_in">String</span> targetHost,</span><br><span class="line"> [<span class="built_in">int</span> targetPort = <span class="number">443</span>]) <span class="keyword">async</span> {</span><br><span class="line"> <span class="keyword">var</span> proxy = <span class="keyword">await</span> Socket.connect(proxyHost, proxyPort, timeout: <span class="keyword">const</span> <span class="built_in">Duration</span>(seconds: <span class="number">1</span>));</span><br><span class="line"> <span class="keyword">const</span> CRLF = <span class="string">"\r\n"</span>;</span><br><span class="line"> proxy.write(<span class="string">"CONNECT <span class="subst">$targetHost</span>:<span class="subst">$targetPort</span> HTTP/1.1"</span>); <span class="comment">// request line</span></span><br><span class="line"> proxy.write(CRLF);</span><br><span class="line"> proxy.write(<span class="string">"Host: <span class="subst">$targetHost</span>:<span class="subst">$targetPort</span>"</span>); <span class="comment">// header</span></span><br><span class="line"> proxy.write(CRLF);</span><br><span class="line"> proxy.write(CRLF);</span><br><span class="line"> <span class="keyword">var</span> completer = Completer<<span class="built_in">bool</span>>.<span class="keyword">sync</span>();</span><br><span class="line"> <span class="keyword">var</span> sub = proxy.listen((event) {</span><br><span class="line"> <span class="keyword">var</span> response = ascii.decode(event);</span><br><span class="line"> <span class="keyword">var</span> lines = response.split(CRLF);</span><br><span class="line"> <span class="comment">// status line</span></span><br><span class="line"> <span class="keyword">var</span> statusLine = lines.first;</span><br><span class="line"> <span class="keyword">if</span> (statusLine.startsWith(<span class="string">"HTTP/1.1 200"</span>)) {</span><br><span class="line"> completer.complete();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> completer.completeError(statusLine);</span><br><span class="line"> }</span><br><span class="line"> }, onError: completer.completeError);</span><br><span class="line"> <span class="keyword">await</span> completer.future; <span class="comment">// established</span></span><br><span class="line"> <span class="keyword">await</span> sub.pause();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> socket = <span class="keyword">await</span> SecureSocket.secure(proxy, host: targetHost, supportedProtocols: <span class="keyword">const</span> [<span class="string">"h2"</span>]);</span><br><span class="line"> <span class="keyword">return</span> ClientTransportConnection.viaSocket(socket);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
用一个简单示例告诉你怎样提升 Flutter App 中的动画性能
https://yrom.net/blog/2020/11/16/optimize-animation-in-flutter/
2020-11-16T11:20:00.000Z
2020-11-16T13:12:51.967Z
<p><em>观前提醒:本文假设你已经有一定的 Flutter 开发经验,对Flutter 的 Widget,RenderObject 等概念有所了解,并且知道如何开启 DevTools。</em></p>
<p>现有一个简单的汽泡动画需要实现,如下图:</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/fluter-bubble-animation.gif" alt="bubble animation"></p>
<h2 id="直接通过-AnimationController-实现"><a href="#直接通过-AnimationController-实现" class="headerlink" title="直接通过 AnimationController 实现"></a>直接通过 AnimationController 实现</h2><p>当看到这个效果图的时候,很快啊,啪一下思路就来了。涉及到动画,有<strong>状态</strong>,用 <code>StatefulWidget</code> ,State 里创建一个 <code>AnimationController</code>,用两个 <code>Container</code> 对应两个圈,外圈的 <code>Container</code> 的宽高监听动画跟着更新就行。<br>代码如下:</p>
<figure class="highlight dart"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="built_in">double</span> size = <span class="number">56</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BubbleAnimationByAnimationController</span> <span class="keyword">extends</span> <span class="title">StatefulWidget</span> </span>{</span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> _BubbleAnimationByAnimationControllerState createState() => _BubbleAnimationByAnimationControllerState();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">_BubbleAnimationByAnimationControllerState</span> <span class="keyword">extends</span> <span class="title">State</span><<span class="title">BubbleAnimationByAnimationController</span>></span></span><br><span class="line"><span class="class"> <span class="title">with</span> <span class="title">SingleTickerProviderStateMixin</span> </span>{</span><br><span class="line"> AnimationController _controller;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> initState() {</span><br><span class="line"> <span class="keyword">super</span>.initState();</span><br><span class="line"> _controller = AnimationController(</span><br><span class="line"> duration: <span class="keyword">const</span> <span class="built_in">Duration</span>(seconds: <span class="number">1</span>),</span><br><span class="line"> vsync: <span class="keyword">this</span>,</span><br><span class="line"> )..addListener(() => setState(() {}));</span><br><span class="line"> _controller.repeat(reverse: <span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> <span class="keyword">void</span> dispose() {</span><br><span class="line"> _controller.dispose();</span><br><span class="line"> <span class="keyword">super</span>.dispose();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@override</span></span><br><span class="line"> Widget build(BuildContext context) {</span><br><span class="line"> <span class="comment">// 两个 `Container` 对应两个圈</span></span><br><span class="line"> <span class="keyword">return</span> Container( </span><br><span class="line"> alignment: Alignment.center,</span><br><span class="line"> constraints: BoxConstraints.tight(</span><br><span class="line"> Size.square((<span class="number">1</span> + _controller.value * <span class="number">0.2</span>) * size),</span><br><span class="line"> ),</span><br><span class="line"> decoration: BoxDecoration(</span><br><span class="line"> shape: BoxShape.circle,</span><br><span class="line"> color: Colors.blue[<span class="number">200</span>],</span><br><span class="line"> ),</span><br><span class="line"> child: Container(</span><br><span class="line"> alignment: Alignment.center,</span><br><span class="line"> padding: <span class="keyword">const</span> EdgeInsets.all(<span class="number">8.0</span>),</span><br><span class="line"> decoration: BoxDecoration(</span><br><span class="line"> shape: BoxShape.circle,</span><br><span class="line"> color: Colors.blue,</span><br><span class="line"> ),</span><br><span class="line"> width: size,</span><br><span class="line"> height: size,</span><br><span class="line"> child: Text(</span><br><span class="line"> <span class="string">'Hello world!'</span>,</span><br><span class="line"> style: TextStyle(color: Colors.white, fontSize: <span class="number">12</span>),</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></pre></td></tr></table></figure>
介绍一位新成员
https://yrom.net/blog/2020/09/20/a-new-family-member/
2020-09-20T15:50:00.000Z
2020-11-16T12:41:42.659Z
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/cat/DSC07723.jpg_m.webp" alt="雄赳赳气昂昂"><br>过年立的 Flag —— 拥有一只猫主子 —— 达成。撒花 ╮( ̄▽ ̄)╭</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/cat/DSC07724.jpg_m.webp" alt="不要抢我的玩具!"><br>一只原主人卖不出去的雄性<del>假</del>布偶。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/cat/DSC07778.jpg_m.webp" alt></p>
瞎拍 - 夏至厦门
https://yrom.net/blog/2020/08/16/shot-amoy/
2020-08-16T13:00:00.000Z
2020-11-16T12:41:56.288Z
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC07417-1.jpg_m.webp" alt="鼓浪屿"><br>算起来,厦门只去过几回,也没怎么仔细逛过,这次趁人少,顺便去了一回鼓浪屿。</p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC07423-2.jpg_m.webp" alt></p>
<p><img src="https://imgy.oss-cn-shanghai.aliyuncs.com/image/DSC07431-Pano-2.jpg_m.webp" alt></p>
<p>鼓浪屿月光岩上看厦门岛。</p>
Flutter Logging
https://yrom.net/blog/2020/07/08/flutter-logging/
2020-07-08T09:45:00.000Z
2023-04-23T10:21:46.371Z
<h3 id="1-Install-package-logging"><a href="#1-Install-package-logging" class="headerlink" title="1. Install package logging"></a>1. Install package <code>logging</code></h3><p> Add <code>logging</code> to <code>pubspec.yaml</code> file:<br> <figure class="highlight yaml"><figcaption><span>pubspec.yaml</span></figcaption><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="attr">dependencies:</span></span><br><span class="line"><span class="attr"> logging:</span> <span class="string">^0.11.4</span></span><br></pre></td></tr></table></figure></p>
<h3 id="2-Config-Logger"><a href="#2-Config-Logger" class="headerlink" title="2. Config Logger"></a>2. Config <code>Logger</code></h3><p> Initialize <code>Logger</code> before <code>runApp()</code> in <code>main.dart</code> file:</p>
<figure class="highlight dart"><figcaption><span>main.dart</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'logging.dart'</span>;</span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line"> <span class="comment">// config logger before runApp()</span></span><br><span class="line"> initLogging();</span><br><span class="line"> runApp(MyApp());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// log a debug message</span></span><br><span class="line"> log.fine(<span class="string">'Running my flutter app.'</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight dart"><figcaption><span>logging.dart</span></figcaption><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'package:logging/logging.dart'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// create Logger by name 'MyApp'</span></span><br><span class="line"><span class="keyword">final</span> log = Logger(<span class="string">'MyApp'</span>);</span><br><span class="line"><span class="keyword">void</span> initLogging() {</span><br><span class="line"> <span class="comment">// disable hierarchical logger</span></span><br><span class="line"> hierarchicalLoggingEnabled = <span class="keyword">false</span>;</span><br><span class="line"> <span class="comment">// change to another level as needed.</span></span><br><span class="line"> Logger.root.level = Level.INFO;</span><br><span class="line"> <span class="comment">// skip logging stactrace below the SEVERE level.</span></span><br><span class="line"> recordStackTraceAtLevel = Level.SEVERE;</span><br><span class="line"> <span class="keyword">assert</span>(() {</span><br><span class="line"> recordStackTraceAtLevel = Level.WARNING;</span><br><span class="line"> <span class="comment">// print all logs on debug build.</span></span><br><span class="line"> Logger.root.level = Level.ALL;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }());</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="3-Transmit-log-records-from-Logger-to-dart-developer"><a href="#3-Transmit-log-records-from-Logger-to-dart-developer" class="headerlink" title="3. Transmit log records from Logger to dart:developer"></a>3. Transmit log records from Logger to <code>dart:developer</code></h3><p> Logger is a <code>producer</code>, but it will not post any log records if no one is listening.</p>
<p> Print to console:<br> <figure class="highlight dart"><figcaption><span>logging.dart</span></figcaption><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">Logger.root.onRecord.listen((event) {</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"<span class="subst">${event.time}</span>: [<span class="subst">${event.level}</span>] [<span class="subst">${event.loggerName}</span>] <span class="subst">${event.message}</span>"</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p>
<p> Save to file:<br> <figure class="highlight dart"><figcaption><span>logging.dart</span></figcaption><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">File logFile = ...</span><br><span class="line">Logger.root.onRecord.listen((event) {</span><br><span class="line"> logFile.writeAsString(<span class="string">"<span class="subst">${event.time}</span>: [<span class="subst">${event.level}</span>] [<span class="subst">${event.loggerName}</span>] <span class="subst">${event.message}</span>"</span>,</span><br><span class="line"> mode: FileMode.append);</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p>
<p> It’s strongly recommended to use <code>dart:developer</code> for logging:</p>
<figure class="highlight dart"><figcaption><span>logging.dart</span></figcaption><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="keyword">import</span> <span class="string">'dart:developer'</span> <span class="keyword">as</span> developer;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> initLogging() {</span><br><span class="line"> ...</span><br><span class="line"> Logger.root.onRecord.listen((event) {</span><br><span class="line"> developer.log(</span><br><span class="line"> event.message,</span><br><span class="line"> time: event.time,</span><br><span class="line"> sequenceNumber: event.sequenceNumber,</span><br><span class="line"> level: event.level.value,</span><br><span class="line"> name: event.loggerName,</span><br><span class="line"> zone: event.zone,</span><br><span class="line"> error: event.error,</span><br><span class="line"> stackTrace: event.stackTrace,</span><br><span class="line"> );</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>