<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[P-Y's blog]]></title><description><![CDATA[P-Y's blog]]></description><link>https://blog.p-y.wtf</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 09:43:29 GMT</lastBuildDate><atom:link href="https://blog.p-y.wtf/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Parallelism with Android SQLite]]></title><description><![CDATA[The SQLDelight documentation provides this example:
val players: Flow<List<HockeyPlayer>> = 
  playerQueries.selectAll()
    .asFlow()
    .mapToList(Dispatchers.IO)

This looks reasonable, right? In the Square Point Of Sale application, we recently ...]]></description><link>https://blog.p-y.wtf/parallelism-with-android-sqlite</link><guid isPermaLink="true">https://blog.p-y.wtf/parallelism-with-android-sqlite</guid><category><![CDATA[Android]]></category><category><![CDATA[performance]]></category><category><![CDATA[parallelism]]></category><category><![CDATA[SQLite]]></category><category><![CDATA[connection pool]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Wed, 05 Feb 2025 05:35:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738733872150/b5ac6a91-e872-43f9-adf0-3de98583573d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <a target="_blank" href="https://sqldelight.github.io/sqldelight/2.0.2/android_sqlite/coroutines/">SQLDelight documentation</a> provides this example:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> players: Flow&lt;List&lt;HockeyPlayer&gt;&gt; = 
  playerQueries.selectAll()
    .asFlow()
    .mapToList(Dispatchers.IO)
</code></pre>
<p>This looks reasonable, right? In the Square Point Of Sale application, we recently discovered that using <code>Dispatchers.IO</code> to run SQLite queries could slow down other application tasks. Let’s dig into SQLite Android internals!</p>
<h1 id="heading-single-connection">Single connection</h1>
<p>On Android, <strong>by default</strong> SQLite can open <strong>at most one connection per database</strong> (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteConnectionPool.java;l=713;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteConnectionPool.java</a>).</p>
<p>This means that <strong>at most one thread at a time</strong> can run an SQL query.</p>
<p>If you run two SQLite queries on the same database in parallel using <code>Dispatchers.IO</code> , one <strong>thread will block</strong> while the other thread is running its query, and your application will consume two threads from the <code>Dispatchers.IO</code> pool. The first thread will be held for the duration of the first query. The second thread will be held for the <strong>duration of the first + the second query</strong>, as it will first wait for the first thread to release SQLite connection before it can use it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738722147770/88663283-abd4-4ba6-8839-ef5b10576e51.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-dispatchersio-parallelism"><code>Dispatchers.IO</code> parallelism</h1>
<p>What’s the big deal? After all, if there’s a single connection at most, then it makes sense that all queries have to run one after the other.</p>
<p><code>Dispatchers.IO</code> dispatches coroutines on at most 64 threads in parallel (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:external/kotlinx.coroutines/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt;l=67;drc=5a14ac176a9d56b989d046e46f3577db1edf8697">Dispatcher.kt</a>). As you increase the number of queries launched in parallel, the <code>Dispatchers.IO</code> threads will be held for longer &amp; longer, and spend most of their time blocking and waiting for the lock. Eventually, all 63 out of the 64 threads from the <code>Dispatchers.IO</code> pool will be blocked waiting for the connection lock, and <strong>new</strong> <code>Dispatchers.IO</code> <strong>tasks will wait in the dispatcher queue</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738722514497/ad97fd34-2fb0-4f36-9e11-2261c787ef47.png" alt class="image--center mx-auto" /></p>
<p>So, while it’s expected that all queries on the same database have to run one after another, if we spam <code>Dispatchers.IO</code> with queries we end up <strong>blocking other unrelated IO tasks that aren’t bound to the same constraint</strong>.</p>
<p>This is actually a common issue with <code>Dispatchers.IO</code> , and its <a target="_blank" href="https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html">kdoc</a> outlines how that can be worked around:</p>
<h3 id="heading-elasticity-for-limited-parallelism">Elasticity for limited parallelism</h3>
<blockquote>
<p><code>Dispatchers.IO</code> has a unique property of elasticity: its views obtained with <code>CoroutineDispatcher.limitedParallelism</code> are not restricted by the <code>Dispatchers.IO</code> parallelism. Conceptually, there is a dispatcher backed by an unlimited pool of threads, and both <code>Dispatchers.IO</code> and views of <code>Dispatchers.IO</code> are actually views of that dispatcher. In practice this means that, despite not abiding by <code>Dispatchers.IO</code>'s parallelism restrictions, its views share threads and resources with it. In the following example:</p>
</blockquote>
<pre><code class="lang-kotlin"><span class="hljs-comment">// 100 threads for MySQL connection</span>
<span class="hljs-keyword">val</span> myMysqlDbDispatcher = Dispatchers.IO.limitedParallelism(<span class="hljs-number">100</span>)
<span class="hljs-comment">// 60 threads for MongoDB connection</span>
<span class="hljs-keyword">val</span> myMongoDbDispatcher = Dispatchers.IO.limitedParallelism(<span class="hljs-number">60</span>)
</code></pre>
<blockquote>
<p>the system may have up to <code>64 + 100 + 60</code> threads dedicated to blocking tasks during peak loads, but during its steady state there is only a small number of threads shared among <code>Dispatchers.IO</code>, <code>myMysqlDbDispatcher</code> and <code>myMongoDbDispatcher</code>.</p>
</blockquote>
<p>So here, given that we know we can run at most one query at a time, we could use a distinct <code>Dispatchers.IO.limitedParallelism(1)</code> dispatcher instance for each database we query. If we had N databases, we’d end up using <strong>at most</strong> N + 64 threads for blocking operations and we’d never starve the <code>Dispatchers.IO</code> thread pool.</p>
<h1 id="heading-thread-hopping-amp-cpu-caches">Thread hopping &amp; CPU caches</h1>
<p>While <code>Dispatchers.IO.limitedParallelism(1)</code> guarantees that a single thread at a time will be running our SQLite database queries, it makes no guarantees on which thread. This means that each query could execute on a different thread. CPU governors try to ensure that threads stick to a single CPU core when possible, to avoid having to reload cpu caches constantly. So with <code>Dispatchers.IO.limitedParallelism(1)</code> we have high chances of blowing up cpu caches with every new query. We can instead use a <strong>single thread dispatcher per database</strong>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> playerDbDispatcher = newSingleThreadContext(<span class="hljs-string">"player-db"</span>)
</code></pre>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> players: Flow&lt;List&lt;HockeyPlayer&gt;&gt; = 
  playerQueries.selectAll()
    .asFlow()
    .mapToList(playerDbDispatcher)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738722920325/9fdb603a-f910-4c86-9420-81b4a8f06d17.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-wal-moar-sqlite-connections">WAL: moar SQLite connections</h1>
<p>SQLite has a <a target="_blank" href="https://sqlite.org/wal.html">Write-Ahead Log</a> (<strong>WAL</strong>) option, which can lead to a significant improvement in performance and was <a target="_blank" href="https://source.android.com/docs/core/perf/compatibility-wal">enabled by default in Android 9</a>. WAL also supports more concurrency, however that is <strong>not turned on by default</strong> on Android, to avoid breaking apps that rely on the previous behavior: a single connection leads to operations running serially, some apps might rely on that. To support additional concurrency, Android 11 added <a target="_blank" href="https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase#enableWriteAheadLogging\(\)">SQLiteDatabase#enableWriteAheadLogging</a>:</p>
<blockquote>
<p>This method enables <strong>parallel execution of queries</strong> from multiple threads on the same database. It does this by opening multiple connections to the database and using a different database connection for each query. […] It is a good idea to enable write-ahead logging whenever a database will be concurrently accessed and modified by multiple threads at the same time. However, write-ahead logging uses significantly more memory than ordinary journaling because there are multiple connections to the sa me database. So if a database will only be used by a single thread, or if optimizing concurrency is not very important, then write-ahead logging should be disabled.</p>
</blockquote>
<p>After calling <code>SQLiteDatabase.enableWriteAheadLogging(true)</code> the database will support one <strong>primary connection</strong> and several <strong>secondary connections</strong>.</p>
<h2 id="heading-the-primary-connection">The primary connection</h2>
<p>The <strong>primary</strong> connection is used for queries that <strong>write</strong> to the database, as well as for <strong>any query executed as part of a transaction</strong> (i.e. after <code>SQLiteDatabase.beginTransaction()</code> , source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java;l=828-833;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteDatabase.java</a>).</p>
<p>When you execute a query, the Android Framework automatically figures out if the query is a <code>SELECT</code> or a write query (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteProgram.java;l=60;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteProgram.java</a>). Apparently that custom query parsing had bugs in older versions of Android, so in the Room library all write queries are automatically wrapped in a transaction to ensure they use the primary connection. <strong>Consider wrapping all your write queries in a transaction</strong>.</p>
<p>Should you ever be using <code>SQLiteDatabase.beginTransaction()</code> for <strong>read only</strong> queries?</p>
<ul>
<li><p>Ideally, no, as starting a transaction means you’re now using the <strong>primary connection</strong>, so you can’t run concurrent queries.</p>
</li>
<li><p>However, the Android framework loads query results into a CursorWindow which has a max size defined by the private Android resource <code>R.integer.config_cursorWindowSize</code> (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/values/config.xml;l=2562;drc=26ab55038d82a6b9ff4d9080a3c8ab94eaa92e1c">config.xml</a>) and configurable by apps since API 28 (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/CursorWindow.java;l=163;drc=6266b5b1795fbf4a986dd01485f77120fee932a1">CursorWindow.java</a>).</p>
<ul>
<li><p>If you run a read query that loads a cursor window and starts reading from it, and meanwhile a write query runs, and then the cursor refreshes its window, the write might end up changing the number of rows. This could lead to inconsistent data or a crash: <code>java.lang.IllegalStateException: Couldn't read row 4247, col 0 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.</code></p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase#beginTransactionReadOnly\(\)">SQLiteDatabase#beginTransactionReadOnly</a> was added in API 35, presumably to support that use case and use secondary connections for read only transactions.</p>
</li>
<li><p>Before API 35, you can either use <code>SQLiteDatabase.beginTransaction()</code> and lose concurrency, or you can start paging your queries, loading small subsets of results that fit within the window. Paging correctly is really hard because rows can have a dynamic size (e.g. if including a blob or a string) so you can’t really predict the max number of rows to retrieve.</p>
</li>
<li><p>Read this to learn more: <a target="_blank" href="https://medium.com/androiddevelopers/large-database-queries-on-android-cb043ae626e8">Large Database Queries on Android</a></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-secondary-connections">Secondary connections</h2>
<p>The <strong>secondary</strong> connections are used for <code>SELECT</code> queries (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteProgram.java;l=60;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteProgram.java</a>).</p>
<ul>
<li><p>The <a target="_blank" href="https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase#enableWriteAheadLogging\(\)">documentation</a> claims that “the <strong>maximum number of connections</strong> used to execute queries in parallel is dependent upon the device memory and possibly other properties.”</p>
</li>
<li><p>In practice, the max number of connections is defined by the private Android resource <code>R.integer.db_connection_pool_size</code> (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteGlobal.java;l=143-148;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteGlobal.java</a>).</p>
</li>
<li><p>The default AOSP value for <code>R.integer.db_connection_pool_size</code> is <strong>4</strong> (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/values/config.xml;l=2485;drc=26ab55038d82a6b9ff4d9080a3c8ab94eaa92e1c">config.xml</a>)</p>
</li>
<li><p>For read only transactions, <code>SQLiteConnectionPool.waitForConnection()</code> will try to acquire one of the secondary connections first, and otherwise fallback to the primary connection if it’s available (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteConnectionPool.java;l=713;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteConnectionPool.java</a>)</p>
<ul>
<li><p>The max number of connections includes the primary connection, so we effectively get <strong>1</strong> primary connection and <strong>3</strong> secondary connection (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteConnectionPool.java;l=1018-1024;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteConnectionPool.java</a>).</p>
</li>
<li><p>That means we can either have 4 <code>SELECT</code> queries running in parallel, or 1 write query and 3 <code>SELECT</code> queries running in parallel.</p>
</li>
</ul>
</li>
</ul>
<p>You can read <code>R.integer.db_connection_pool_size</code> at runtime with the following helper method:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// Based on SQLiteGlobal.getWALConnectionPoolSize()</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getWALConnectionPoolSize</span><span class="hljs-params">()</span></span> {
  <span class="hljs-keyword">val</span> resources = Resources.getSystem()
  <span class="hljs-keyword">val</span> resId =
    resources.getIdentifier(<span class="hljs-string">"db_connection_pool_size"</span>, <span class="hljs-string">"integer"</span>, <span class="hljs-string">"android"</span>)
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (resId != <span class="hljs-number">0</span>) {
    max(<span class="hljs-number">2</span>, resources.getInteger(resId))
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-number">2</span>
  }
}
</code></pre>
<h2 id="heading-non-exclusive-transactions">Non exclusive transactions</h2>
<p>The <a target="_blank" href="https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase#enableWriteAheadLogging\(\)">SQLiteDatabase#enableWriteAheadLogging</a> documentation makes a surprising recommendation:</p>
<blockquote>
<p>Writers should use <code>beginTransactionNonExclusive()</code> to start a transaction. Non-exclusive mode allows database file to be in readable by other threads executing queries.</p>
</blockquote>
<p>In practice, this means <code>beginTransaction()</code> will execute <code>BEGIN EXCLUSIVE;</code> and <code>beginTransactionNonExclusive()</code> will execute <code>BEGIN IMMEDIATE;</code> (source: <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/database/sqlite/SQLiteSession.java;l=341-348;drc=05d9bfb652e9ec78502ba70515c49cf2eae3f988">SQLiteSession.java</a>).</p>
<p>However, the sqlite <a target="_blank" href="https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions">documentation on transactions</a> calls out:</p>
<blockquote>
<p>EXCLUSIVE and IMMEDIATE are the same in WAL mode, but in other journaling modes, EXCLUSIVE prevents other database connections from reading the database while the transaction is underway.</p>
</blockquote>
<p>I’m not sure why the documentation is recommending using <code>beginTransactionNonExclusive()</code> when WAL is enabled as it’s effectively the same as <code>beginTransaction()</code>, that seems like a mistake.</p>
<h2 id="heading-wal-amp-dispatchers">WAL &amp; Dispatchers</h2>
<p>We can now leverage concurrent reads &amp; write with WAL by using a serial dispatcher for writes and a concurrent dispatcher for reads with the appropriate elasticity, to increase concurrency without blocking more threads than necessary:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> dbWriteDispatcher = Dispatchers.IO.limitedParallelism(<span class="hljs-number">1</span>)

<span class="hljs-keyword">val</span> connectionPoolSize = getWALConnectionPoolSize()
<span class="hljs-keyword">val</span> dbReadDispatcher = Dispatchers.IO.limitedParallelism(connectionPoolSize)
</code></pre>
<p>Or, if you'd rather use dedicated thread pools:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> dbWriteDispatcher = newSingleThreadContext(<span class="hljs-string">"<span class="hljs-variable">$dbName</span>-writes"</span>)

<span class="hljs-keyword">val</span> connectionPoolSize = getWALConnectionPoolSize()
<span class="hljs-keyword">val</span> dbReadDispatcher = newFixedThreadPoolContext(connectionPoolSize, <span class="hljs-string">"<span class="hljs-variable">$dbName</span>-reads"</span>)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738724932706/486d56a0-3536-475c-b199-bd193ee015aa.png" alt class="image--center mx-auto" /></p>
<p>With both the previous approaches, we end up with <strong>5 threads dedicated to do work for a pool of 4 connections</strong>, so there will be times where 1 thread is blocked. Room took a different and more optimal approach: use a 4 thread pool for both reads and writes, but control the writes tasks to ensure we only try to run one at a time, in serial order (source: <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/room-runtime/src/androidMain/kotlin/androidx/room/TransactionExecutor.android.kt;drc=067ed409b9bc2dcc59b93c8e9ab8d092beeff3a5">TransactionExecutor.kt</a>).</p>
<h1 id="heading-the-elephant-in-the-room">The elephant in the Room</h1>
<p>How does all this apply to <a target="_blank" href="https://developer.android.com/jetpack/androidx/releases/room">Room</a>?</p>
<h2 id="heading-ballroom-wal-room-dance"><s>Ballroom</s> WAL Room dance</h2>
<p>When using Room, WAL with support for concurrent connections is enabled by default on API 16+, unless the device is a low ram device (source: <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt;l=800;drc=8bd971c6e30756ad97e8d3e4634f5a87167a256b">RoomDatabase.kt</a>).</p>
<p>Room can be configured to use custom executors (<code>RoomDatabase.Builder.setQueryExecutor()</code> &amp; <code>RoomDatabase.Builder.setTransactionExecutor()</code>, or <code>RoomDatabase.Builder.setQueryCoroutineContext()</code>).</p>
<p>The <code>RoomDatabase.Builder.setQueryExecutor</code> <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt;l=1138-1194;drc=8bd971c6e30756ad97e8d3e4634f5a87167a256b">kdoc</a> mentions that the query executor should use a max number of threads — without specifying how many — and that the <strong>transaction executor</strong> will execute <strong>at most one query at a time</strong>. The <code>@Transaction</code> <a target="_blank" href="https://developer.android.com/reference/androidx/room/Transaction">kdoc</a> mentions writes: “Insert, Update or Delete methods are always run inside a transaction”.</p>
<p>The doc also mentions that <em>"when both the transaction executor and query executor are unset, then a</em> <strong><em>default</em></strong> <code>Executor</code> <em>will be used that allocates and shares threads amongst Architecture Components libraries"</em>.</p>
<p>That default executor is actually <code>ArchTaskExecutor.getIOThreadExecutor()</code> (source: <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/room-runtime/src/androidMain/kotlin/androidx/room/RoomDatabase.android.kt;l=1604-1605;drc=8bd971c6e30756ad97e8d3e4634f5a87167a256b">RoomDatabase.android.kt</a>):</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RoomDatabase</span> </span>{
  <span class="hljs-keyword">open</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Builder</span>&lt;<span class="hljs-type">T : RoomDatabase</span>&gt; </span>{
    <span class="hljs-keyword">open</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">build</span><span class="hljs-params">()</span></span>: T {
      <span class="hljs-keyword">if</span> (queryExecutor == <span class="hljs-literal">null</span> &amp;&amp; transactionExecutor == <span class="hljs-literal">null</span>) {
        transactionExecutor = ArchTaskExecutor.getIOThreadExecutor()
        queryExecutor = transactionExecutor
      }
      <span class="hljs-comment">// ...</span>
</code></pre>
<p><code>ArchTaskExecutor.getIOThreadExecutor()</code> delegates to <code>DefaultTaskExecutor.mDiskIO</code> which is a <strong>fixed thread pool of size 4</strong> (source: <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:arch/core/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java;l=42;drc=5a10386f8cb40c82abb5bbee4556caaf64699571">DefaultTaskExecutor.java</a>). That thread pool is also used as the default fetch scheduler for Rx paging (source: <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:paging/paging-rxjava3/src/main/java/androidx/paging/rxjava3/RxPagedListBuilder.kt;l=299;drc=0ee887e405f72333e89394886bdf2d49de22f70f">RxPagedListBuilder.kt</a>) and the default executor for <code>ComputableLiveData</code> (source: <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/ComputableLiveData.kt;l=44;drc=18b3e6dc4422300577c60324286c1bbdcecab607">ComputableLiveData.kt</a>).</p>
<p>This design isn’t ideal as that thread pool ends up being shared for unrelated work. Even if it was only used by Room, it’s still not ideal as all databases end up sharing the same global thread pool and starving it. If you have more than one database managed by Room you should <strong>provide your own executors</strong>.</p>
<h2 id="heading-bundled-sqlite">Bundled SQLite</h2>
<p>It’s worth noting that Room now offers a <strong>bundled version of SQLite</strong> instead of the default Android one. This increases APK size (~1 MB) but means you get the latest SQLite, you get the exact same behavior on all Android versions, and you don’t end up using any of the Android Framework SQLite code. To learn more, I recommend watching <a target="_blank" href="https://www.droidcon.com/2024/10/17/bundle-up-and-save-powering-room-with-bundled-sqlite/">Powering Room with Bundled SQLite</a>.</p>
<p>Room with bundled SQLite also offers proper support for coroutines, i.e. no more thread blocking while we’re waiting for an SQLite connection thanks to a suspending connection pool (source: <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:room/room-runtime/src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt;l=93;drc=62aa237a71dbedd8c696f68d9e8961e185f2e159">ConnectionPoolImpl.kt</a>). This means threads only block when they’re actually running a query, and makes it less likely that you’ll end up starving a thread pool due to blocked threads waiting for connections.</p>
<h1 id="heading-take-aways">Take Aways</h1>
<ul>
<li><p><strong>Paginate queries</strong> to avoid returning results larger than the Cursor window size (that can be hard to tune right) or use <code>beginTransactionNonExclusive()</code> on API 35+ and otherwise fallback to <code>beginTransaction()</code>.</p>
</li>
<li><p>If you’re <strong>using Room</strong></p>
<ul>
<li><p>If you have <strong>more than one Room database</strong></p>
<ul>
<li><p>By default, all Room databases shared the same fixed thread pool of size 4, when instead you should be able to use up to 4 connections concurrently per database.</p>
</li>
<li><p>Call <code>RoomDatabase.Builder.setQueryCoroutineContext()</code> or <code>RoomDatabase.Builder.setQueryExecutor()</code> with a bounded executor to ensure each database can query on up to 4 different threads concurrently.</p>
</li>
</ul>
</li>
<li><p>Consider <strong>using Room with Bundled SQLite</strong> for perf wins, less maintenance pain and eliminating the risk of blocking threads while waiting for a connection.</p>
</li>
</ul>
</li>
<li><p>If you’re <strong>not using Room</strong></p>
<ul>
<li><p>Wrap all your write queries in a transaction.</p>
</li>
<li><p>Use bounded executors for your read and write queries, a different set for each database.</p>
</li>
<li><p>Consider using <strong>Bundled SQLite</strong> (I have not looked yet into the practical details of that).</p>
</li>
</ul>
</li>
</ul>
<p>I would be remiss not to mention that your Android apps should probably not try to execute a bajillion SQL queries in parallel. You might have a “<em>N + 1 query problem</em>” (look that up!), or simply too much async code firing up.</p>
<p>Huge thanks to Shane Tang for spelunking the SQLite docs, Yiğit Boyar and Dany Santiago for sharing a ton of insights on Room &amp; SQLite internals, and Zach Klippenstein &amp; Jesse Wilson for proof reading!</p>
]]></content:encoded></item><item><title><![CDATA[Rendering the Java heap as a Treemap]]></title><description><![CDATA[Exploring heap dumps
I have investigated many heap dumps over the years, and I usually switch back and forth between two tools:

YourKit Java Profiler to poke around the heap and look for interesting things (ask your company to buy a license!)

Shark...]]></description><link>https://blog.p-y.wtf/rendering-the-java-heap-as-a-treemap</link><guid isPermaLink="true">https://blog.p-y.wtf/rendering-the-java-heap-as-a-treemap</guid><category><![CDATA[Computer Science]]></category><category><![CDATA[graph theory]]></category><category><![CDATA[Java]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[Android]]></category><category><![CDATA[memory-management]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Wed, 25 Sep 2024 04:37:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727238961293/6b0fb63f-22a1-4134-9778-d34031711f16.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-exploring-heap-dumps">Exploring heap dumps</h1>
<p>I have investigated many heap dumps over the years, and I usually switch back and forth between two tools:</p>
<ul>
<li><p><a target="_blank" href="https://www.yourkit.com/">YourKit Java Profiler</a> to poke around the heap and look for interesting things (ask your company to buy a license!)</p>
</li>
<li><p><a target="_blank" href="https://square.github.io/leakcanary/shark/">Shark</a> when I'm trying to perform more complex aggregation questions or queries that YourKit can't answer easily.</p>
</li>
</ul>
<h1 id="heading-side-quest-shark-graphviz">Side quest: Shark + Graphviz</h1>
<p>I wanted to show off how the Shark APIs allow you to explore the heap graph in any way you want, so I wrote a Kotlin script that explores &amp; render the view hierarchy of any activity in the heap. It leverages Shark to locate activity instances, traverses all view &amp; view array references via a <a target="_blank" href="https://en.wikipedia.org/wiki/Breadth-first_search">Breadth First Traversal</a>, then generates a <a target="_blank" href="https://graphviz.org/">Graphviz</a> <code>.dot</code> file and executes a Graphviz command.</p>
<p>I shared it as a gist, it’s fairly short, go <a target="_blank" href="https://gist.github.com/pyricau/ecd450b73bfffe80a7e7b0f005351dfa">check it out</a>!</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> visitedObjectIds = traverseReferenceGraph(traversalRoots) { sourceObject -&gt;
  referenceReader.read(sourceObject).mapNotNull { reference -&gt;
    <span class="hljs-keyword">val</span> targetObject = graph.findObjectById(reference.valueObjectId)
    <span class="hljs-keyword">val</span> isView = targetObject <span class="hljs-keyword">is</span> HeapInstance &amp;&amp;
      targetObject instanceOf <span class="hljs-string">"android.view.View"</span>
    <span class="hljs-keyword">val</span> isViewArray = targetObject <span class="hljs-keyword">is</span> HeapObjectArray &amp;&amp;
      targetObject.arrayClassName == <span class="hljs-string">"android.view.View[]"</span>
    <span class="hljs-keyword">if</span> (isView || isViewArray) {
      visitedReferences += ObjectReference(
        sourceObjectId = sourceObject.objectId,
        targetObjectId = reference.valueObjectId,
        referenceName = reference.lazyDetailsResolver.resolve().name
      )
      targetObject
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-literal">null</span>
    }
  }
}
</code></pre>
<p>Here’s the result from a heap dump of the <a target="_blank" href="https://github.com/android/compose-samples/tree/main/JetNews">JetNews</a> app:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727200288642/3975cfd5-9606-4890-a248-a16ac0199f6e.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-whats-eating-my-java-heap">What's eating my Java heap?</h1>
<p>I frequently ask myself "why is the heap so large?" and "Are there some areas where we're using too much memory?". In a Java heap, we often talk about two types of object sizes, <strong>shallow size</strong> and <strong>retained size</strong>. Here’s a <a target="_blank" href="https://www.jetbrains.com/help/idea/read-the-memory-snapshot.html">definition from Jetbrains</a>:</p>
<ul>
<li><p><strong>Shallow size</strong>: the amount memory allocated to store the object itself. It does not include the sizes of the objects that are referenced by this object.</p>
</li>
<li><p><strong>Retained size</strong>: the sum of the object's shallow size and the shallow sizes of its retained objects (objects that are only referenced from this object). In other words, the retained size is <strong>the amount of memory that can be reclaimed by garbage-collecting this object</strong>.</p>
</li>
</ul>
<p>In YourKit, we can look at objects grouped by class to get a rough idea of what’s using memory.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727202473943/ed79b21c-b06e-40e1-a6a5-2abd1d958e6c.png" alt class="image--center mx-auto" /></p>
<p><strong>Shallow size</strong> is interesting but not terribly useful. We expect some big objects, but it’s more interesting to know what’s using them.</p>
<p>To talk about <strong>retained size</strong>, we need to talk about <strong>dominators</strong> first.</p>
<h1 id="heading-dominators">Dominators</h1>
<p>Let’s go over some definitions!</p>
<p>An object B is said to be <strong>reachable</strong> from another object A if there is a path of references from A to B, and otherwise B is <strong>unreachable</strong> from A.</p>
<p>A Garbage Collection root (<strong>gc root</strong>) is a reference to an object that tells the VM that object must not be garbage collected. There are many different kinds of GC Roots, but folks are usually familiar with system classes (loaded by the system class loader) and JNI references.</p>
<p>An object B is said to be <strong>reachable from GC roots</strong> is there is a path of references from at least one object referenced by a GC root, to B. Otherwise B is said to be <strong>unreacheable from GC roots.</strong> Objects that are unreachable from GC roots will be <strong>garbage collected</strong>. We often abbreviate this as “B is reachable / retained” or “B is unreachable / garbage collectable”.</p>
<p>If there is a path of references from an object A to an object B, and if removing a specific node C on that path makes B unreachable from A, then C is called a <strong>dominator</strong>, as it holds a position of control on any attempt at traversing the graph from A to B.</p>
<p>In the context of a heap dump, dominators are objects that are on the paths from GC roots to other objects. The dominator A of an object B, if removed, would make B unreachable. If you sum up the size of all the nodes that a dominator dominates, you get the amount of memory that would be freed if that dominator became unreachable. This is the <strong>retained size</strong>.</p>
<p>Let’s look at an example directed graph:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727216962334/7b42fef9-cd04-4def-9c50-92206f919ac8.png" alt class="image--center mx-auto" /></p>
<p>Here’s the resulting dominator graph:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727217048399/f4263957-3219-4f07-a39d-3b30454c4a3a.png" alt class="image--center mx-auto" /></p>
<p>Let’s look at what the dominator tree looks like for our previous heap dump of the JetNews app. Here we can see the activity is a dominator for all views, and every view group dominates all its descendant views.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727200288642/3975cfd5-9606-4890-a248-a16ac0199f6e.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-treemaps">Treemaps</h1>
<p>Wikipedia on <a target="_blank" href="https://en.wikipedia.org/wiki/Treemapping">Treemapping</a>:</p>
<blockquote>
<p>Treemapping is a method for displaying hierarchical data using nested figures, usually rectangles.</p>
<p>Treemaps display hierarchical (tree-structured) data as a set of nested rectangles. Each branch of the tree is given a rectangle, which is then tiled with smaller rectangles representing sub-branches. A leaf node's rectangle has an area proportional to a specified dimension of the data.[1] Often the leaf nodes are colored to show a separate dimension of the data.</p>
</blockquote>
<p>A classic usage is visualizing <strong>disk space usage</strong> with <a target="_blank" href="https://www.derlien.com/">Disk Inventory X</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727194929931/fea7abf4-d48c-407d-a2c6-a2f6fd1003ce.jpeg" alt class="image--center mx-auto" /></p>
<h1 id="heading-treemaps-amp-dominator-tree">Treemaps &amp; Dominator Tree</h1>
<p>If a treemap can be used to visualize disk space usage, we should be able to use it to visualize memory usage. However, a tree-map can only display tree-structured data, and the <strong>Java heap is a graph, not a tree</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727216962334/7b42fef9-cd04-4def-9c50-92206f919ac8.png" alt class="image--center mx-auto" /></p>
<p>As we saw earlier, Dominators form a tree: each node has a dominator, so each dominator has its own dominator, recursively until the root.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727217293904/b8158e2e-8db7-40ba-a0ce-dceb06501eaa.png" alt class="image--center mx-auto" /></p>
<p>Once you have the dominator tree, you can compute the shallow size of each object in the graph, then for each dominator you can compute its retained size as the sum of the shallow size of each object it dominates (including itself).</p>
<p>So you get a tree where each node has a "retained size" which represents how much memory could be reclaimed if that node became unreachable.</p>
<p>This seems to make a ton of sense! Can we build that? We’re going to need an algorithm for computing the dominator tree.</p>
<h1 id="heading-side-quest-leakcanarys-retained-size">Side quest: LeakCanary’s retained size</h1>
<p>LeakCanary shows leak traces, and to help figure out which leaks have the highest impact on memory it also displays the retained size from the references that are likely creating the leak. Which means I had to write code that computes the dominator tree. I read a couple papers, thought about my use case and how I didn’t need the entire dominator tree and took some shortcuts to make it run faster.</p>
<p>Well, as I read that code again for this exploration, I <a target="_blank" href="https://androiddev.social/@py/113176281321505098">realized I was very wrong</a> (<a target="_blank" href="https://github.com/square/leakcanary/issues/2715">square/leakcanary/issues#2715</a>). Until I reimplement this, retained size will often be over estimated.</p>
<h1 id="heading-linkevaldominators">LinkEvalDominators</h1>
<p>Google has engineers who are really good at reading computer science paper. Through a search on <a target="_blank" href="https://cs.android.com">cs.android.com</a>, I stumbled upon <a target="_blank" href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:perflib/src/main/java/com/android/tools/perflib/heap/analysis/LinkEvalDominators.kt;l=36;drc=499fa43009666c0f0a686d8e21722dbea8b2ecf0">LinkEvalDominators.kt</a>, which is an agnostic Kotlin implementation of a dominator algorithm. Copy pasta, fix a few things, and voila, I have a dominator tree based on a Shark graph.</p>
<p>Now I can connect that to my previous code that was traversing the view hierarchy:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> (sortedHeapNodes, immediateDominators) = with(LinkEvalDominators()) {
    computeDominators(traversalRoots) { (sourceObjectId) -&gt;
        <span class="hljs-keyword">val</span> sourceObject = graph.findObjectById(sourceObjectId)
        referenceReader.read(sourceObject).mapNotNull { reference -&gt;
            <span class="hljs-keyword">val</span> targetObject = graph.findObjectById(reference.valueObjectId)
            <span class="hljs-keyword">val</span> isView = targetObject <span class="hljs-keyword">is</span> HeapInstance &amp;&amp;
                    targetObject instanceOf <span class="hljs-string">"android.view.View"</span>
            <span class="hljs-keyword">val</span> isViewArray = targetObject <span class="hljs-keyword">is</span> HeapObjectArray &amp;&amp;
                    targetObject.arrayClassName == <span class="hljs-string">"android.view.View[]"</span>
            <span class="hljs-keyword">if</span> (isView || isViewArray) {
                HeapNode(targetObject.objectId)
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-literal">null</span>
            }
        }
    }
}
</code></pre>
<h1 id="heading-quick-treemap">Quick Treemap</h1>
<p>Did you know that Google Spreadsheet supports displaying sheets columns as a Treemap chart? All you need is 3 columns: child, parent and value (here, the retained size).</p>
<p>So let’s generate a CSV:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> csv = File(heapDumpFile.parent, <span class="hljs-string">"<span class="hljs-subst">${heapDumpFile.nameWithoutExtension}</span>.csv"</span>)
csv.printWriter().use { writer -&gt;
    with(writer) {
        println(<span class="hljs-string">"\"Child\",\"Parent\",\"Value\""</span>)
        rootDominators.forEach { rootDominator -&gt;
            printDominatorCsvRow(rootDominator)
        }
    }
}

<span class="hljs-function"><span class="hljs-keyword">fun</span> PrintWriter.<span class="hljs-title">printDominatorCsvRow</span><span class="hljs-params">(node: <span class="hljs-type">DominatorObject</span>)</span></span> {
    <span class="hljs-keyword">val</span> parent = node.parent
    <span class="hljs-keyword">if</span> (parent == <span class="hljs-literal">null</span>) {
        println(<span class="hljs-string">"\"<span class="hljs-subst">${node.name}</span>\",\"\",\"<span class="hljs-subst">${node.retainedSize}</span>\""</span>)
    } <span class="hljs-keyword">else</span> {
        println(<span class="hljs-string">"\"<span class="hljs-subst">${node.name}</span>\",\"<span class="hljs-subst">${parent.name}</span>\",\"<span class="hljs-subst">${node.retainedSize}</span>\""</span>)
    }
    <span class="hljs-keyword">for</span> (child <span class="hljs-keyword">in</span> node.dominatedNodes) {
        printDominatorCsvRow(child)
    }
}
</code></pre>
<p>Now we can import it into Google Spreadsheet:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727207229607/aff3575e-43e8-4606-b5fa-5185c1cc800d.png" alt class="image--center mx-auto" /></p>
<p>And we have our Treemap, configured to display all levels at once!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727207260071/b4fb527a-d126-4393-a865-a87c538906ee.png" alt class="image--center mx-auto" /></p>
<p>There’s a bit more code involved in that one, <a target="_blank" href="https://gist.github.com/pyricau/ffeebb7ee32aa8d3bdbe67a156f0bdd7">check out the gist!</a></p>
<h1 id="heading-side-quest-compose-treemap">Side-Quest: Compose Treemap</h1>
<p>Last year, I started exploring this as a LeakCanary feature. I didn’t get very far but I did port to Compose the TreeMap rendering implementation from <a target="_blank" href="https://github.com/d3/d3-hierarchy">d3-hierarchy</a> (see <a target="_blank" href="https://github.com/square/leakcanary/blob/02d0d8b6ebfe8de55c109b904d7b526063f3f852/leakcanary/leakcanary-app/src/main/java/org/leakcanary/screens/TreemapLayout.kt">TreemapLayout.kt</a> and <a target="_blank" href="https://github.com/square/leakcanary/blob/02d0d8b6ebfe8de55c109b904d7b526063f3f852/leakcanary/leakcanary-app/src/main/java/org/leakcanary/screens/TreeMapScreen.kt">TreeMapScreen.kt</a>). Let’s copy the contents of the previous gist straight into that code. Then we can see the results immediately with the Compose Preview!</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Composable</span>
<span class="hljs-meta">@Preview(device = Devices.FOLDABLE)</span>
<span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">RealHprofHeapTreemapPreview</span><span class="hljs-params">()</span></span> {
  <span class="hljs-keyword">val</span> heapDumpFile = File(<span class="hljs-string">"/Users/py/Desktop/memory-20240919T161101.hprof"</span>)

  <span class="hljs-keyword">val</span> root = heapDumpFile.openHeapGraph().use { graph -&gt;
    ... <span class="hljs-comment">// Same code as in previous gist.</span>

    <span class="hljs-function"><span class="hljs-keyword">fun</span> DominatorObject.<span class="hljs-title">mapToTreeMapNode</span><span class="hljs-params">()</span></span>: NodeValue&lt;String&gt; {
      <span class="hljs-keyword">val</span> children = dominatedNodes.map { it.mapToTreeMapNode() }
      <span class="hljs-keyword">return</span> NodeValue(retainedSize, name, children)
    }


    NodeValue(
      value = rootDominators.sumOf { it.retainedSize },
      content = <span class="hljs-string">"root"</span>,
      children = rootDominators.map { it.mapToTreeMapNode() }
    )
  }

  Treemap(root) { it }
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727210492820/fb057509-5537-4ca5-bf39-d93ef30b511b.png" alt class="image--center mx-auto" /></p>
<p>This is fairly ugly and probably buggy, but hey, a Compose Treemap, how cool is that?</p>
<h1 id="heading-entire-heap-as-a-treemap">Entire heap as a Treemap?</h1>
<p>So far we only looked at a small subset of objects, the view instances. My original goal was to visualize how memory is used for the entire heap. So, I removed the code that filters only on view instances:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> traversalRoots = graph.gcRoots
  <span class="hljs-comment">// Exclude Java local references</span>
  .filter { it !<span class="hljs-keyword">is</span> JavaFrame }
  .map { HeapNode(it.id) }.toSet()

<span class="hljs-keyword">val</span> (sortedHeapNodes, immediateDominators) = with(LinkEvalDominators()) {
  computeDominators(traversalRoots) { (sourceObjectId) -&gt;
    <span class="hljs-keyword">val</span> sourceObject = graph.findObjectById(sourceObjectId)
    referenceReader.read(sourceObject).map { reference -&gt;
      HeapNode(reference.valueObjectId)
    }
  }
}
</code></pre>
<p><a target="_blank" href="https://gist.github.com/pyricau/a67e4759cfc3a521205d6bed4a8a4c58">Check out the gist!</a></p>
<p>This generates a 29 MB CSV with 293K rows, which I imported in Google Spreadsheet to turn into the following Treemap:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727220069323/a70f98af-d441-4b90-bfb1-b5e185df9e12.png" alt class="image--center mx-auto" /></p>
<p>That’s unexpected! I configured the chart to only draw the top level, why am I seeing all these root nodes?</p>
<p>The heap dump has 25002 gc roots, and we end up with 56387 root dominators. How comes?</p>
<h1 id="heading-dominator-roots">Dominator roots</h1>
<p>Let’s look at a simple example, let’s say we have 2 classes that both have a reference to the <code>android.app.Application</code> singleton instance:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Context sContext = retrieveAppContext();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">B</span> </span>{
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Application sApplication = retrieveAppContext();
}
</code></pre>
<p>In the heap dump we will see two system class GC Roots:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727221944055/673d4cdb-a9b9-44d5-8e19-3264d94ec839.png" alt class="image--center mx-auto" /></p>
<p>Remember the dominator definition?</p>
<blockquote>
<p>A dominator node D of a node A is a node that if removed would make A unreachable.</p>
</blockquote>
<p>Here, there isn’t a single node that could be removed to make the <code>android.app.Application</code> instance unreachable, if we remove one GC root the other GC root will still reference the <code>android.app.Application</code> instance.</p>
<p>So here’s the corresponding dominator graph:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727222116283/e757fe1a-bab3-4bc5-934f-4e1afbdf3464.png" alt class="image--center mx-auto" /></p>
<p>Even though we had 2 gc roots, we ended up with 3 dominator roots.</p>
<p>Unfortunately, this also applies to instances that are deeper in the graph, without any additional GC roots. Let’s introduce a Dagger component:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727222878010/81937a4d-0278-41fb-8e42-db3ebf418ae4.png" alt class="image--center mx-auto" /></p>
<p>Once again, there is not a single node that could be removed to make the <code>com.example.DaggerMyComponent</code> instance unreachable, so our 2 GC roots now lead to 4 dominators:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727222884901/629b8c95-4c2a-492b-944f-55fc0a9f7e09.png" alt class="image--center mx-auto" /></p>
<p>Beyond dominator roots, this happens at every layer of the graph: an increase in graph breadth connectedness leads to lower depth in the dominator tree.</p>
<p>I captured the depth of every node in the dominator tree of the JetNews example heap dump, and generated a histogram of that depth:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727223376480/0b8efada-be69-447a-9693-cb42acd9cdd6.png" alt class="image--center mx-auto" /></p>
<p>This shows most nodes live pretty high in the dominator tree, a lot more than I originally anticipated.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>A Treemap generated from the dominator tree of a heap dump is less useful and intuitive than I expected.</p>
<p>In software engineering, we usually have a nice hierarchical mental model of our app components (e.g. an activity owns a view hierarchy, views own drawables, drawables own bitmaps, etc).</p>
<p>Unfortunately that nice hierarchy cannot be inferred automatically at runtime, because there are a lot of additional references that end up hiding what we expect to be the natural dominators.</p>
<p>Hope you enjoyed this ride!</p>
<p>Many thanks to <a target="_blank" href="https://blog.droidchef.dev/">Ishan Khanna</a> for mentioning <a target="_blank" href="https://github.com/jQAssistant">jQAssistant</a> and starting a conversation about heap dump visualization tools, and <a target="_blank" href="https://publicobject.com/">Jesse Wilson</a> for letting me spam his DMs and helping me think about this problem space differently.</p>
]]></content:encoded></item><item><title><![CDATA[Cutting some Slack, for leaks and giggles]]></title><description><![CDATA[In this article I run the new LeakCanary toolkit against the Slack Android app. Read on to learn a bunch!
A new LeakCanary toolkit
In two months, I will give a talk at Droidcon SF: Cutting Edges: universal heap trimming with LeakCanary 3

At Square, ...]]></description><link>https://blog.p-y.wtf/cutting-some-slack-for-leaks-and-giggles</link><guid isPermaLink="true">https://blog.p-y.wtf/cutting-some-slack-for-leaks-and-giggles</guid><category><![CDATA[Android]]></category><category><![CDATA[performance]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[slack]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Tue, 07 May 2024 00:47:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715042624291/88c20426-ecdf-4d1c-8ac7-426930b69bf2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article I run the new LeakCanary toolkit against the Slack Android app. Read on to learn a bunch!</p>
<h2 id="heading-a-new-leakcanary-toolkit">A new LeakCanary toolkit</h2>
<p>In two months, I will give a talk at Droidcon SF: <a target="_blank" href="https://sf.droidcon.com/pierre-yves-ricau/">Cutting Edges: universal heap trimming with LeakCanary 3</a></p>
<blockquote>
<p>At Square, we scaled our LeakCanary usage over the last nine years by running it on all UI tests on every pull request, uploading leaks detected in debug builds, and triaging leaks weekly. This works: we fixed thousands of leaks (in our apps, third-party libraries, and the Android Framework), and we're now finding fewer and fewer new leaks!</p>
<p>Unfortunately, we sometimes see the heap size grow over time without LeakCanary finding any issue. For example, constantly appending string logs to a collection would not trigger LeakCanary but would still lead to ANRs and OOMEs when the app eventually runs out of memory.</p>
<p>Inspired by the BLeak paper and the work of the Android Studio team, I built a new toolkit in LeakCanary that performs repeated heap dump diffs and detects objects with a constantly increasing number of outgoing edges (for example, a list that keeps growing).</p>
<p>Come learn how this works; together, we can fix all the leaks!</p>
</blockquote>
<p>I have two months to turn a prototype into a real tool! I just shipped a preview in <a target="_blank" href="https://square.github.io/leakcanary/changelog/#version-30-alpha-4-2024-05-10">LeakCanary 3.0 alpha 4</a>. I have been testing it on Square apps, and now I want to see if it's useful for other apps, especially complex apps. Please try it out!</p>
<p>In the meantime, I can do the work myself with other apps. Let's pick an app I use a lot... Slack Android!</p>
<h2 id="heading-installing-slack-android-on-an-emulator">Installing Slack Android on an emulator</h2>
<p>LeakCanary works with heap dumps. The new heap growth detection toolking can run as a UI Automator test, invoking <code>am dumpheap</code> to dump the heap of another app. Unfortunately, on normal Android OS builds, this only works for apps that are debuggable or profileable as shell, which obvious isn't the case of the Slack Android production app. Fortunately, these restrictions don't apply for Android <code>userdebug</code> OS builds. Non Play Store Emulator images are <code>userdebug</code> builds.</p>
<p>Let's download the APKs from my phone:</p>
<pre><code class="lang-bash">$ adb shell pm path com.Slack

package:/data/app/com.Slack/base.apk
package:/data/app/com.Slack/split_config.arm64_v8a.apk
package:/data/app/com.Slack/split_config.xxhdpi.apk

$ adb pull /data/app/com.Slack/base.apk
$ adb pull /data/app/com.Slack/split_config.arm64_v8a.apk
$ adb pull /data/app/com.Slack/split_config.xxhdpi.apk
</code></pre>
<p>I can then create an emulator similar to my phone and install them:</p>
<pre><code class="lang-bash">$ adb install-multiple base.apk split_config.arm64_v8a.apk split_config.xxhdpi.apk
</code></pre>
<h2 id="heading-ui-automator-test">UI Automator test</h2>
<p>First let's get our Gradle setup right:</p>
<pre><code class="lang-kotlin">dependencies {
  androidTestImplementation <span class="hljs-string">'com.squareup.leakcanary:leakcanary-android-uiautomator:3.0-alpha-4'</span>
  androidTestImplementation libs.assertjCore
  androidTestImplementation libs.junit
  androidTestImplementation libs.androidX.test.runner
}

android {
  defaultConfig {
    testInstrumentationRunner <span class="hljs-string">"androidx.test.runner.AndroidJUnitRunner"</span>
  }
}
</code></pre>
<p>Here's what the test looks like, repeatedly switching back and forth between two workspaces:</p>
<pre><code class="lang-kotlin">  <span class="hljs-meta">@Test</span>
  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">switching_workspaces_repeatedly_should_not_grow_heap</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> heapDiff = detector.findRepeatedlyGrowingObjects {
      device.openWorkspaceDrawer()
      device.selectWorkspace(<span class="hljs-string">"Android Study Group"</span>)
      device.openWorkspaceDrawer()
      device.selectWorkspace(<span class="hljs-string">"droidcon"</span>)
    }

    assertThat(heapDiff.growingObjects).isEmpty()
  }
</code></pre>
<p>I can then run the test:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715039106181/f85e8884-5e8f-4cdd-a8e5-a4d4ff9822f8.gif" alt class="image--center mx-auto" /></p>
<p>Here's the full test class with setup code and helper functions:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> androidx.test.platform.app.InstrumentationRegistry
<span class="hljs-keyword">import</span> androidx.test.uiautomator.By
<span class="hljs-keyword">import</span> androidx.test.uiautomator.UiDevice
<span class="hljs-keyword">import</span> androidx.test.uiautomator.Until
<span class="hljs-keyword">import</span> org.assertj.core.api.Assertions.assertThat
<span class="hljs-keyword">import</span> org.junit.Before
<span class="hljs-keyword">import</span> org.junit.Test
<span class="hljs-keyword">import</span> shark.ObjectGrowthDetector
<span class="hljs-keyword">import</span> shark.forAndroidHeap

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SlackTest</span> </span>{

  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())!!

  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> detector = ObjectGrowthDetector.forAndroidHeap().repeatingUiAutomatorScenario(
    dumpedAppPackageName = SLACK_PKG,
    maxHeapDumps = <span class="hljs-number">10</span>,
    scenarioLoopsPerDump = <span class="hljs-number">10</span>
  )

  <span class="hljs-meta">@Before</span>
  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">setUp</span><span class="hljs-params">()</span></span> {
    device.restartSlack()
  }

  <span class="hljs-meta">@Test</span>
  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">switching_workspaces_repeatedly_should_not_grow_heap</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> heapDiff = detector.findRepeatedlyGrowingObjects {
      device.openWorkspaceDrawer()
      device.selectWorkspace(<span class="hljs-string">"Android Study Group"</span>)
      device.openWorkspaceDrawer()
      device.selectWorkspace(<span class="hljs-string">"droidcon"</span>)
    }

    assertThat(heapDiff.growingObjects).isEmpty()
  }

  <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> UiDevice.<span class="hljs-title">restartSlack</span><span class="hljs-params">()</span></span> {
    executeShellCommand(<span class="hljs-string">"am force-stop <span class="hljs-variable">$SLACK_PKG</span>"</span>)
    wait(Until.gone(By.pkg(SLACK_PKG)), <span class="hljs-number">5_000</span>)
    executeShellCommand(<span class="hljs-string">"am start <span class="hljs-variable">$SLACK_PKG</span>"</span>)
    wait(Until.findObject(WORKSPACE_DRAWER_ICON_BUTTON), <span class="hljs-number">5_000</span>)
  }

  <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> UiDevice.<span class="hljs-title">openWorkspaceDrawer</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> teamAvatarButton = findObject(WORKSPACE_DRAWER_ICON_BUTTON)!!
    teamAvatarButton.click()
    wait(Until.findObject(WORKSPACE_NAME_ROW), <span class="hljs-number">5_000</span>)
  }

  <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> UiDevice.<span class="hljs-title">selectWorkspace</span><span class="hljs-params">(name: <span class="hljs-type">String</span>)</span></span> {
    <span class="hljs-keyword">val</span> group = findObject(By.text(name))!!
    group.click()
    wait(Until.gone(WORKSPACE_NAME_ROW), <span class="hljs-number">5_000</span>)
  }

  <span class="hljs-keyword">companion</span> <span class="hljs-keyword">object</span> {
    <span class="hljs-keyword">const</span> <span class="hljs-keyword">val</span> SLACK_PKG = <span class="hljs-string">"com.Slack"</span>
    <span class="hljs-keyword">val</span> WORKSPACE_NAME_ROW = By.res(SLACK_PKG, <span class="hljs-string">"workspace_name"</span>)!!
    <span class="hljs-keyword">val</span> WORKSPACE_DRAWER_ICON_BUTTON = By.res(SLACK_PKG, <span class="hljs-string">"team_avatar_button"</span>)!!
  }
}
</code></pre>
<p>If anyone wants to try running <code>findRepeatedlyGrowingObjects()</code> with <a target="_blank" href="https://maestro.mobile.dev/">Maestro</a>, be my guest!</p>
<h2 id="heading-results">Results</h2>
<p>I shared the results with the team at Slack. I want to showcase just one of the results, as it's interesting:</p>
<pre><code class="lang-bash">There was 1 failure:
1) switching_workspaces_repeatedly_should_not_grow_heap(SlackTest)
java.lang.AssertionError:
Expecting empty but was:&lt;[
┬───
│ GcRoot(ThreadObject) (372 objects)
    Retained size: 289 KB
    Retained objects: 7609
    Children:
    372 objects (20 new): INSTANCE_FIELD Thread.blockerLock -&gt; instance of java.lang.Object
    372 objects (20 new): INSTANCE_FIELD Thread.inheritedAccessControlContext -&gt; instance of java.security.AccessControlContext
    371 objects (20 new): INSTANCE_FIELD Thread.lock -&gt; instance of java.lang.Object
    201 objects (20 new): INSTANCE_FIELD Thread.target -&gt; instance of slack.app.SlackAppProdImpl$<span class="hljs-variable">$ExternalSyntheticLambda2</span>
,

...
</code></pre>
<p>Here I see an increase of 20 threads between 2 heap dumps. I ran the scenario 10 times in between heap dumps, and the scenario switched workspace twice, so that's 20 workspace switches. So one new thread per workspace switch.</p>
<p>Let's figure out what these new threads are. I can dump the heap from adb:</p>
<pre><code class="lang-bash">$ adb shell am dumpheap -g com.Slack /data/<span class="hljs-built_in">local</span>/tmp/slack.hprof
$ adb pull /data/<span class="hljs-built_in">local</span>/tmp/slack.hprof
</code></pre>
<p>Then I can write a Kotlin script that parses the heap dump, groups threads by name and counts them:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">#!/usr/bin/env kotlin</span>

<span class="hljs-meta">@file:DependsOn</span>(<span class="hljs-string">"com.squareup.leakcanary:shark:3.0-alpha-2"</span>)

<span class="hljs-keyword">import</span> java.io.File
<span class="hljs-keyword">import</span> shark.HprofHeapGraph.Companion.openHeapGraph

<span class="hljs-keyword">val</span> hprofFile = File(<span class="hljs-string">"./slack.hprof"</span>)

<span class="hljs-keyword">val</span> threadCounts = hprofFile.openHeapGraph().use { graph -&gt;
  graph.findClassByName(Thread::<span class="hljs-keyword">class</span>.java.name)!!.instances
    // group <span class="hljs-keyword">by</span> thread name
    .groupingBy { threadIntance -&gt;
      threadIntance[Thread::<span class="hljs-keyword">class</span>.java.name, <span class="hljs-string">"name"</span>]!!.value.readAsJavaString()
    }
    .eachCount()
    .toList()
    <span class="hljs-comment">// sort by count</span>
    .sortedBy { it.second }
}

println(threadCounts.joinToString(<span class="hljs-string">"\n"</span>) {
  <span class="hljs-string">"\"<span class="hljs-subst">${it.first}</span>\": <span class="hljs-subst">${it.second}</span>"</span>
})
</code></pre>
<pre><code class="lang-bash">...
<span class="hljs-string">"ms-event-dispatcher-1"</span>: 2
<span class="hljs-string">"OkHttp TaskRunner"</span>: 2
<span class="hljs-string">"NewSqlTransactionMonitor"</span>: 4
<span class="hljs-string">"file-upload-manager"</span>: 201
</code></pre>
<p>So there's a thread named <code>file-upload-manager</code> being created forever every time I switch workspaces. Not to worry though, I'm told this will be fixed in the near future.</p>
<p>I was really excited to show you how you can write a Kotlin script to analyze a heap dump, but in this case it would have been much easier to go with a thread dump:</p>
<pre><code class="lang-bash">$ adb shell ps -T | grep <span class="hljs-variable">$SLACK_PID</span> | awk <span class="hljs-string">'{print $10}'</span> | sort | 
uniq -c | sort

...
   2 ms-event-dispat
   2 OkHttp TaskRunn 
   4 NewSqlTransacti
 201 file-upload-man
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope this convinced you to try out the new <a target="_blank" href="https://square.github.io/leakcanary/changelog/#version-30-alpha-4-2024-05-10">heap growth detection toolkit</a> in LeakCanary 3. You can use it with JVM Unit tests, Espresso, UI Automator, and even directly from the command line. Let me know what you think!</p>
]]></content:encoded></item><item><title><![CDATA[A weirder HashMap]]></title><description><![CDATA[Today I was mob programming with Square's Mobile & Performance Reliability team and we toyed with an interesting idea.
Our codebase has classes that represent screens a user can navigate to. These classes are defined in modules, and these modules hav...]]></description><link>https://blog.p-y.wtf/a-weirder-hashmap</link><guid isPermaLink="true">https://blog.p-y.wtf/a-weirder-hashmap</guid><category><![CDATA[Android]]></category><category><![CDATA[performance]]></category><category><![CDATA[Internals]]></category><category><![CDATA[Java]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 15 Feb 2024 03:59:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707969524455/c4f665bd-0362-4712-aa30-b425007399d9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today I was mob programming with Square's Mobile &amp; Performance Reliability team and we toyed with an interesting idea.</p>
<p>Our codebase has <strong>classes that represent screens</strong> a user can navigate to. These classes are defined in modules, and these <strong>modules have an owner team</strong> defined.</p>
<p>When navigating to a screen, we wanted to have the owner team information available, at runtime. We created a build tool that looks at about 1000 Screen classes, determines the owner team, and generates a class to do the lookup at runtime.</p>
<p>The generated code looked like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">object</span> ScreenOwnership {
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> classNameToOwner = mapOf(
    <span class="hljs-string">"com.example.feature1.Screen1"</span> to <span class="hljs-string">"Team A"</span>,
    <span class="hljs-string">"com.example.feature2.Screen2"</span> to <span class="hljs-string">"Team A"</span>,
    <span class="hljs-string">"com.example.feature3.Screen3"</span> to <span class="hljs-string">"Team B"</span>,
    <span class="hljs-comment">// ... etc. for a thousand screens</span>
  )

  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ownerOf</span><span class="hljs-params">(screenClass: <span class="hljs-type">KClass</span>&lt;<span class="hljs-type">Screen</span>&gt;)</span></span>: String {
    <span class="hljs-keyword">return</span> classNameToOwner.getValue(screenClass.java.name)
  }
}
</code></pre>
<p>This works, but it feels a bit wasteful. Let's explore why.</p>
<blockquote>
<p>Yes, I'm aware, "Premature optimization is the root of all evil". Our goal here was to think through the impact of this implementation and come up with an alternative implementation, for fun. Don't be a killjoy.</p>
</blockquote>
<h1 id="heading-mapof-and-pairs">mapOf and pairs</h1>
<p><code>mapOf(vararg pairs: Pair&lt;K, V&gt;)</code> is a nice utility to create a map (more specifically, a <code>LinkedHashMap</code>) but using that syntax leads to the creation of a temporary vararg array of size 1000, as well as 1000 temporary <code>Pair</code> instances.</p>
<h1 id="heading-memory-hoarding">Memory hoarding</h1>
<p>Let's look at the retained size of the map we just created:</p>
<ul>
<li><p>~30 characters per class name * 2 bytes per character = 60 bytes per entry</p>
</li>
<li><p>Each entry is stored as a LinkedHashMapEntry which adds 2 references to HashMap.Node which itself holds 3 references and 1 int. On a 64bit</p>
<p>  VM that's 5 references * 8 bytes, plus 4 bytes for the int: 44 bytes per entry.</p>
</li>
<li><p>So for the entries alone we're at (60 + 44) * 1000 = 104 KB.</p>
</li>
<li><p>The default load factor is 75%, which means the size of the array backing the hashmap must always be at least 25% greater than the number of entries. And the array size has to be a factor of 2. So, for 1000 entries, that's an object array of size 2048: 2048 * 8 = 16,314 bytes.</p>
</li>
</ul>
<p>The total retained size of the map is <strong>~120 KB</strong>. Can we do better?</p>
<p>Could we make it... 0?</p>
<h1 id="heading-100-code-based-map">100% code-based map</h1>
<p>What if we generate code that returns the right team for a given screen, instead of creating a map?</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">object</span> ScreenOwnership {
  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ownerOf</span><span class="hljs-params">(screenClass: <span class="hljs-type">KClass</span>&lt;<span class="hljs-type">Screen</span>&gt;)</span></span>: String {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">when</span>(screenClass.java.name) {
      <span class="hljs-string">"com.example.feature1.Screen1"</span> -&gt; <span class="hljs-string">"Team A"</span>
      <span class="hljs-string">"com.example.feature2.Screen2"</span> -&gt; <span class="hljs-string">"Team B"</span>
      <span class="hljs-string">"com.example.feature2.Screen3"</span> -&gt; <span class="hljs-string">"Team C"</span>
      <span class="hljs-comment">// ... etc for a thousand screens</span>
      <span class="hljs-keyword">else</span> -&gt; error(<span class="hljs-string">"Unknown screen class <span class="hljs-subst">${screenClass.java.name}</span>"</span>)
    }
  }
}
</code></pre>
<p>The Kotlin compiler is smart and actually generates code that checks the string against its hashcode first, which looks like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">object</span> ScreenOwnership {
  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ownerOf</span><span class="hljs-params">(screenClass: <span class="hljs-type">KClass</span>&lt;<span class="hljs-type">Screen</span>&gt;)</span></span>: String {
    <span class="hljs-keyword">val</span> screenClassName = screenClass.java.name

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">when</span> (screenClassName.hashCode()) {
      <span class="hljs-number">1179818499</span> -&gt; <span class="hljs-keyword">if</span> (screenClassName == <span class="hljs-string">"com.example.feature1.Screen1"</span>)
        <span class="hljs-string">"Team A"</span>
      <span class="hljs-keyword">else</span>
        <span class="hljs-literal">null</span>

      -<span class="hljs-number">627635963</span> -&gt; <span class="hljs-keyword">if</span> (screenClassName == <span class="hljs-string">"com.example.feature2.Screen2"</span>)
        <span class="hljs-string">"Team B"</span>
      <span class="hljs-keyword">else</span>
        <span class="hljs-literal">null</span>

      -<span class="hljs-number">627635962</span> -&gt; <span class="hljs-keyword">if</span> (screenClassName == <span class="hljs-string">"com.example.feature3.Screen3"</span>)
        <span class="hljs-string">"Team C"</span>
      <span class="hljs-keyword">else</span>
        <span class="hljs-literal">null</span>
      <span class="hljs-comment">// ... etc for a thousand screens</span>
      <span class="hljs-keyword">else</span> -&gt; <span class="hljs-literal">null</span>
    } ?: error(<span class="hljs-string">"Unknown screen class <span class="hljs-variable">$screenClassName</span>"</span>)
  }
}
</code></pre>
<p>Since we know the full list of screen classes, we can check ahead of time whether there's any hashcode conflict, and if not, we can generate code that directly associates the hashcode of the screen class name to the corresponding team:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">object</span> ScreenOwnership {
  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">ownerOf</span><span class="hljs-params">(screenClass: <span class="hljs-type">KClass</span>&lt;<span class="hljs-type">Screen</span>&gt;)</span></span>: String {
    <span class="hljs-keyword">val</span> screenClassName = screenClass.java.name

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">when</span> (screenClassName.hashCode()) {
      <span class="hljs-number">1179818499</span> -&gt; <span class="hljs-string">"Team A"</span>
      -<span class="hljs-number">627635963</span> -&gt; <span class="hljs-string">"Team B"</span>
      -<span class="hljs-number">627635962</span> -&gt; <span class="hljs-string">"Team C"</span>
      <span class="hljs-comment">// ... etc for a thousand screens</span>
      <span class="hljs-keyword">else</span> -&gt; error(<span class="hljs-string">"Unknown screen class <span class="hljs-variable">$screenClass</span>"</span>)
    }
  }
}
</code></pre>
<h1 id="heading-linear-scan">Linear scan?</h1>
<p><code>HashMap.get(key)</code> calls <code>key.hashCode()</code> , wrangles that hashcode modulo the size of the backing array, which gives it an index to look up a linked list of entries in the backing array. The higher the load factor, the more hash collisions and the larger the linked lists. In other words, assuming a reasonable load factor, <code>HashMap.get(key)</code> has constant time performance - O(1) time complexity - to locate the entry, however it does need to follow references which might require loading a different page of memory.</p>
<p>Contrast this with our <code>when</code> based implementation:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">when</span> (screenClassName.hashCode()) {
  <span class="hljs-number">1179818499</span> -&gt; <span class="hljs-string">"Team A"</span>
  -<span class="hljs-number">627635963</span> -&gt; <span class="hljs-string">"Team B"</span>
  -<span class="hljs-number">627635962</span> -&gt; <span class="hljs-string">"Team C"</span>
}
</code></pre>
<p>This code seems to imply that we need to check the hashcode against every single value. That's a linear scan, O(n) time complexity. Oh no!</p>
<p>Fortunately for us, the Android ART runtime will transform a switch on Integers and sort the branches so that it can then perform a binary search at runtime (<a target="_blank" href="https://cs.android.com/android/platform/superproject/+/android-8.0.0_r1:art/runtime/interpreter/interpreter_common.h;l=455-464">source</a>) to jump to the correct instruction:</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-keyword">int32_t</span> <span class="hljs-title">DoSparseSwitch</span><span class="hljs-params">(<span class="hljs-keyword">const</span> Instruction* inst, <span class="hljs-keyword">const</span> ShadowFrame&amp; shadow_frame,
                                     <span class="hljs-keyword">uint16_t</span> inst_data)</span>
    </span>{
  <span class="hljs-keyword">const</span> <span class="hljs-keyword">uint16_t</span>* switch_data = <span class="hljs-keyword">reinterpret_cast</span>&lt;<span class="hljs-keyword">const</span> <span class="hljs-keyword">uint16_t</span>*&gt;(inst) + inst-&gt;VRegB_31t();
  <span class="hljs-keyword">int32_t</span> test_val = shadow_frame.GetVReg(inst-&gt;VRegA_31t(inst_data));
  <span class="hljs-keyword">uint16_t</span> size = switch_data[<span class="hljs-number">1</span>];
  <span class="hljs-comment">// Return length of SPARSE_SWITCH if size is 0.</span>
  <span class="hljs-keyword">if</span> (size == <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-number">3</span>;
  }
  <span class="hljs-keyword">const</span> <span class="hljs-keyword">int32_t</span>* keys = <span class="hljs-keyword">reinterpret_cast</span>&lt;<span class="hljs-keyword">const</span> <span class="hljs-keyword">int32_t</span>*&gt;(&amp;switch_data[<span class="hljs-number">2</span>]);
  <span class="hljs-keyword">const</span> <span class="hljs-keyword">int32_t</span>* entries = keys + size;
  <span class="hljs-keyword">int</span> lo = <span class="hljs-number">0</span>;
  <span class="hljs-keyword">int</span> hi = size - <span class="hljs-number">1</span>;
  <span class="hljs-keyword">while</span> (lo &lt;= hi) {
    <span class="hljs-keyword">int</span> mid = (lo + hi) / <span class="hljs-number">2</span>;
    <span class="hljs-keyword">int32_t</span> foundVal = keys[mid];
    <span class="hljs-keyword">if</span> (test_val &lt; foundVal) {
      hi = mid - <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (test_val &gt; foundVal) {
      lo = mid + <span class="hljs-number">1</span>;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> entries[mid];
    }
  }
  <span class="hljs-comment">// No corresponding value: move forward by 3 (size of SPARSE_SWITCH).</span>
  <span class="hljs-keyword">return</span> <span class="hljs-number">3</span>;
}
</code></pre>
<p>With a binary search, we're looking at O(log n) time complexity. Not bad!</p>
<h1 id="heading-scatter-map">Scatter Map</h1>
<p>I would be remiss if I did not mention Romain Guy's recent article, <a target="_blank" href="https://www.romainguy.dev/posts/2024/a-better-hashmap/">A Better Hash Map</a>, which inspired this article. <code>ScatterMap</code> significantly improves the memory footprint &amp; memory cache behavior over <code>HashMap</code>. It's very cool! Not as cool as generating a code-based map though 😜.</p>
]]></content:encoded></item><item><title><![CDATA[DIY: your own Dependency Injection library!]]></title><description><![CDATA[Dependency Injection libraries are powerful tools, but they're often also intimidating & confusing.
When that happens to me, I find that understanding how a tool works helps me get over the initial scare of the dark magic internals.
In this article, ...]]></description><link>https://blog.p-y.wtf/diy-your-own-dependency-injection-library</link><guid isPermaLink="true">https://blog.p-y.wtf/diy-your-own-dependency-injection-library</guid><category><![CDATA[dependency injection]]></category><category><![CDATA[Android]]></category><category><![CDATA[Java]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[dagger-hilt]]></category><category><![CDATA[dagger]]></category><category><![CDATA[Internals]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 18 Jan 2024 20:08:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705600821196/d9c26d7d-b262-4a69-8cec-480ad1cb5dc8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dependency Injection libraries are powerful tools, but they're often also intimidating &amp; confusing.</p>
<p>When that happens to me, I find that understanding how a tool works helps me get over the initial scare of the dark magic internals.</p>
<p>In this article, I'll walk you through how to implement your own dependency injection library. Starting with manual dependency injection, we'll progressively build a simplistic version of <a target="_blank" href="https://github.com/google/guice">Google Guice</a>, then <a target="_blank" href="https://github.com/square/dagger">Dagger 1</a> and eventually <a target="_blank" href="https://github.com/google/dagger">Dagger 2</a>.</p>
<p>By the end of this article, I'm hoping you'll have built up a good intuition for how all these libraries work under the hood. You'll be the life of the party when you casually drop with a straight face: <em>"oh yeah, a dependency injection library is mostly just a map of types to factories"</em>.</p>
<p>The code presented here is available at <a target="_blank" href="https://github.com/pyricau/diy">github.com/pyricau/diy</a>.</p>
<h2 id="heading-manual-dependency-injection">Manual Dependency Injection</h2>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Dependency_injection">Dependency Injection</a> is a pattern, so starting with no library is helpful. Manual dependency injection typically requires creating instances <strong>in the right order</strong>, in a <strong>dedicated configuration place</strong> in the code.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🙅</div>
<div data-node-type="callout-text">A common anti-pattern that is <strong>not</strong> manual dependency injection (looking at some of my iOS developer friends 😘) is having objects be in charge of creating their own collaborators and being passed in the dependencies of these collaborators. If adding a new dependency requires passing it through 10 classes, you're doing it wrong.</div>
</div>

<h3 id="heading-coffee-example">Coffee Example</h3>
<p>We want to create a <code>CoffeeMaker</code>, which needs a <code>CoffeeLogger</code>, a <code>Heater</code> and a <code>Pump</code> . For the <code>Heater</code> we'll use an <code>ElectricHeater</code> which also needs a <code>CoffeeLogger</code>, and for the <code>Pump</code> we'll use a <code>Thermosiphon</code> which needs a <code>CoffeeLogger</code> and a <code>Heater</code>.</p>
<p>Here's what that looks like in Kotlin:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CoffeeLogger</span></span>

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Heater</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ElectricHeater</span></span>(logger: CoffeeLogger) : Heater

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Pump</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Thermosiphon</span></span>(logger: CoffeeLogger, heater: Heater) : Pump

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CoffeeMaker</span></span>(logger: CoffeeLogger, heater: Heater, pump: Pump)
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🤯</div>
<div data-node-type="callout-text"><strong>Thermowhat?!</strong> This DI example <a target="_blank" href="https://github.com/square/dagger/blob/master/examples/simple/src/main/java/coffee/Thermosiphon.java">comes from Dagger 1</a>, and many folks found it to be a confusing example: they were learning DI, learning a new DI library, and the example didn't map to something they knew how to build in real life. I asked <a target="_blank" href="https://publicobject.com/">Jesse Wilson</a> why he chose that example, he said: <em>"I was reading about coffee machines and learned about how Mr Coffee doesn’t have a pump, just a heater"</em>. To make coffee, you need to pour water on ground beans. To move that water towards the beans, you can use a mechanical pump. But <a target="_blank" href="https://en.wikipedia.org/wiki/Mr._Coffee">Mr Coffee</a> uses a Thermosiphon instead (thermo = hot, siphon = tube), which is, <a target="_blank" href="https://en.wikipedia.org/wiki/Thermosiphon">according to Wikipedia</a>, <em>"a method of passive heat exchange, based on natural convection, which circulates a fluid without the necessity of a mechanical pump"</em>. Now you know how to make coffee!</div>
</div>

<p>We can represent the <code>CoffeeMaker</code> and its dependencies as a directed graph:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705686995499/df992516-f355-4348-911d-ece9a0bd8341.png" alt class="image--center mx-auto" /></p>
<p>A dependency graph is actually a <a target="_blank" href="https://en.wikipedia.org/wiki/Directed_acyclic_graph">Directed Acyclic Graph</a> aka DAG (hence the name Dagger!) as there cannot be cycles between dependencies.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">You can support dependency cycles by using lazy or setter injection, which breaks up the resolving of dependencies into several rounds. Each round then resolves a DAG of dependencies with no cycle.</div>
</div>

<p><code>CoffeeMaker</code> here is called an <em>Entry Point</em>, it's the thing we want to build and the root of our dependency graph.</p>
<p>Let's create a <code>CoffeeMaker</code> and brew!</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> logger = CoffeeLogger()
<span class="hljs-keyword">val</span> heater: Heater = ElectricHeater(logger)
<span class="hljs-keyword">val</span> pump: Pump = Thermosiphon(logger, heater)
<span class="hljs-keyword">val</span> coffeeMaker = CoffeeMaker(logger, heater, pump)

coffeeMaker.brew()
</code></pre>
<p>With manual Dependency Injection, creating dependencies in the right order quickly becomes a problem as the number of collaborators increases. To avoid these issues, we need a Dependency Injection library!</p>
<h2 id="heading-concepts">Concepts</h2>
<p>Let's first introduce a few API contracts that will be useful throughout this article.</p>
<h3 id="heading-objectgraph">ObjectGraph</h3>
<p>The <code>ObjectGraph</code> is our entry point into a DI library. It's also known as <code>Injector</code>, <code>Container</code>, or <code>Component</code>. It's what our application code uses to get started with doing things, and its main job is to provide instances of a requested type:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ObjectGraph</span> </span>{
  <span class="hljs-keyword">operator</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">get</span><span class="hljs-params">(requestedType: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;)</span></span>: T
}
</code></pre>
<p>The API is straightforward:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> coffeeMaker = objectGraph.<span class="hljs-keyword">get</span>(CoffeeMaker::<span class="hljs-keyword">class</span>.java)

<span class="hljs-comment">// Or using the get() operator overload:</span>
<span class="hljs-keyword">val</span> coffeeMaker = objectGraph[CoffeeMaker::<span class="hljs-keyword">class</span>.java]
</code></pre>
<p>We can write a reified extension function to leverage the power of the Kotlin compiler:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;<span class="hljs-keyword">reified</span> T&gt;</span> ObjectGraph.<span class="hljs-title">get</span><span class="hljs-params">()</span></span> = <span class="hljs-keyword">get</span>(T::<span class="hljs-keyword">class</span>.java)

<span class="hljs-comment">// Thank you Kotlin compiler!</span>
<span class="hljs-keyword">val</span> coffeeMaker = objectGraph.<span class="hljs-keyword">get</span>&lt;CoffeeMaker&gt;()
</code></pre>
<h3 id="heading-factory">Factory</h3>
<p>A <code>Factory</code> knows how to create instances of a particular type. It can leverage the <code>ObjectGraph</code> to retrieve the dependencies needed to create a collaborator.</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-keyword">interface</span> Factory<span class="hljs-type">&lt;T&gt;</span> {</span>
  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">get</span><span class="hljs-params">(objectGraph: <span class="hljs-type">ObjectGraph</span>)</span></span>: T
}
</code></pre>
<p>The <code>Factory</code> for <code>CoffeeMaker</code> could be implemented as:</p>
<pre><code class="lang-kotlin">  <span class="hljs-keyword">val</span> coffeeMakerFactory = Factory { objectGraph -&gt;
    CoffeeMaker(objectGraph.<span class="hljs-keyword">get</span>(), objectGraph.<span class="hljs-keyword">get</span>(), objectGraph.<span class="hljs-keyword">get</span>())
  }
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">We don't have to write <code>CoffeeMaker(logger, heater, pump)</code> here and can just repeatedly call the <code>reified</code> function <code>ObjectGraph.get()</code>, the Kotlin compiler will then pass in the right <code>Class</code> objects.</div>
</div>

<h3 id="heading-module">Module</h3>
<p>A <code>Module</code> knows how to create a factory for a specific type. <code>Module.get()</code> might return null if a given module doesn't know how to create a factory for that requested type.</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Module</span> </span>{
  <span class="hljs-keyword">operator</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">get</span><span class="hljs-params">(requestedType: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;)</span></span>: Factory&lt;T&gt;?
}
</code></pre>
<p>At this point, we start seeing how these concepts connect: when calling <code>ObjectGraph.get()</code> , the object graph will leverage its list of <code>Module</code> to find a suitable <code>Factory</code> for that type and then use the <code>Factory</code> to create the instance.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705560646489/6b167978-b233-4ba8-b3bc-f6cac3b0b292.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-factoryholdermodule">FactoryHolderModule</h2>
<p>Our initial <code>Module</code> implementation is <code>FactoryHolderModule</code>, it holds a map of types to their associated <code>Factory</code>. We call <code>FactoryHolderModule.install(type, factory)</code> to add new factory, and <code>FactoryHolderModule.get(type)</code> to retrieve it:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FactoryHolderModule</span> : <span class="hljs-type">Module {</span></span>
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> factories = mutableMapOf&lt;Class&lt;<span class="hljs-keyword">out</span> Any?&gt;, Factory&lt;<span class="hljs-keyword">out</span> Any?&gt;&gt;()

  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> &lt;T&gt; <span class="hljs-keyword">get</span>(requestedType: Class&lt;T&gt;): Factory&lt;T&gt;? =</span>
    factories[requestedType] <span class="hljs-keyword">as</span> Factory&lt;T&gt;?

  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">install</span><span class="hljs-params">(
    requestedType: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;,
    factory: <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">T</span>&gt;
  )</span></span> {
    factories[requestedType] = factory
  }
}
</code></pre>
<p>Here's how we would add the <code>CoffeeMaker</code> factory to a <code>FactoryHolderModule</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> module = FactoryHolderModule()
module.install(CoffeeMaker::<span class="hljs-keyword">class</span>.java) { objectGraph -&gt;
  CoffeeMaker(objectGraph.<span class="hljs-keyword">get</span>(), objectGraph.<span class="hljs-keyword">get</span>(), objectGraph.<span class="hljs-keyword">get</span>())
}
</code></pre>
<p>Let's make this API nicer! We don't like having to pass in <code>CoffeeMaker::class.java</code> . Also, repeating <code>objectGraph</code> is annoying, could we use a lambda with receiver instead?</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;<span class="hljs-keyword">reified</span> T&gt;</span> FactoryHolderModule.<span class="hljs-title">install</span><span class="hljs-params">(
  <span class="hljs-keyword">noinline</span> factory: <span class="hljs-type">ObjectGraph</span>.() -&gt; <span class="hljs-type">T</span>
)</span></span> = install(T::<span class="hljs-keyword">class</span>.java, factory)

<span class="hljs-comment">// Nicer!</span>
<span class="hljs-keyword">val</span> module = FactoryHolderModule()
module.install {
  CoffeeMaker(<span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>())
}
</code></pre>
<h2 id="heading-objectgraph-implementation"><code>ObjectGraph</code> implementation</h2>
<p>Our <code>ObjectGraph</code> takes in a list of <code>Module</code> that knows how to create factories. <code>ObjectGraph.get()</code> retrieves the factory from the modules and then calls <code>Factory.get(ObjectGraph)</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ObjectGraph</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> modules: List&lt;Module&gt;) {

  <span class="hljs-keyword">constructor</span>(<span class="hljs-keyword">vararg</span> modules: Module) : <span class="hljs-keyword">this</span>(modules.asList())

  <span class="hljs-keyword">operator</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">get</span><span class="hljs-params">(requestedType: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;)</span></span>: T {
    <span class="hljs-keyword">val</span> factory = modules
      .firstNotNullOf { module -&gt; module[requestedType] }
    <span class="hljs-keyword">return</span> factory.<span class="hljs-keyword">get</span>(<span class="hljs-keyword">this</span>)
  }
}
</code></pre>
<p>Delegating to the provided modules on every call to <code>ObjectGraph.get()</code> could be wasteful, so we can leverage <code>FactoryHolderModule</code> to add a caching layer in <code>ObjectGraph</code> for the factories:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ObjectGraph</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> modules: List&lt;Module&gt;) {

  <span class="hljs-keyword">constructor</span>(<span class="hljs-keyword">vararg</span> modules: Module) : <span class="hljs-keyword">this</span>(modules.asList())

  <span class="hljs-comment">// Cache of factories already retrieves from modules.</span>
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> factoryHolder = FactoryHolderModule()

  <span class="hljs-keyword">operator</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">get</span><span class="hljs-params">(requestedType: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;)</span></span>: T {
    <span class="hljs-keyword">val</span> knownFactoryOrNull = factoryHolder[requestedType]
    <span class="hljs-keyword">val</span> factory = knownFactoryOrNull ?: modules
      .firstNotNullOf { module -&gt; module[requestedType] }
      .also { factory -&gt;
        factoryHolder.install(requestedType, factory)
      }
    <span class="hljs-keyword">return</span> factory.<span class="hljs-keyword">get</span>(<span class="hljs-keyword">this</span>)
  }
}
</code></pre>
<h2 id="heading-putting-it-all-together">Putting it all together</h2>
<p>Let's create a <code>CoffeeMaker</code> and brew! We can install our factories on a <code>FactoryHolderModule</code> , then create an <code>ObjectGraph</code> with that module and ask it for a <code>CoffeeMaker</code> instance.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> module = FactoryHolderModule()
module.install {
  CoffeeLogger()
}
module.install {
  ElectricHeater(<span class="hljs-keyword">get</span>())
}
module.install {
  Thermosiphon(<span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>())
}
module.install {
  CoffeeMaker(<span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>())
}
<span class="hljs-keyword">val</span> objectGraph = ObjectGraph(module)
<span class="hljs-keyword">val</span> coffeeMaker = objectGraph.<span class="hljs-keyword">get</span>&lt;CoffeeMaker&gt;()

coffeeMaker.brew()
</code></pre>
<p>Unfortunately, this doesn't work! <code>CoffeeMaker</code> needs a <code>Heater</code> and a <code>Pump</code>. We've added a factory for <code>Thermosiphon</code> which is a <code>Pump</code> and <code>ElectricHeater</code> which is a <code>Heater</code>, but we didn't connect the interfaces with their implementations.</p>
<h3 id="heading-bind">Bind</h3>
<p>Let's introduce a <code>bind()</code> function that associates a requested type to a factory of a provided subtype:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;<span class="hljs-keyword">reified</span> REQUESTED, <span class="hljs-keyword">reified</span> PROVIDED : REQUESTED&gt;</span></span>
    FactoryHolderModule.bind() {

  install(REQUESTED::<span class="hljs-keyword">class</span>.java) { objectGraph -&gt;
    objectGraph[PROVIDED::<span class="hljs-keyword">class</span>.java]
  }
}

<span class="hljs-comment">// Nice!</span>
module.bind&lt;Heater, ElectricHeater&gt;()
module.bind&lt;Pump, Thermosiphon&gt;()
</code></pre>
<h3 id="heading-singletons">Singletons</h3>
<p><code>CoffeeMaker</code> and <code>Thermosiphon</code> both need a <code>Heater</code> . The <code>CoffeeMaker</code> turns the <code>Heater</code> on, and the <code>Thermosiphon</code> starts pumping if the <code>Heater</code> is hot. For things to work correctly, <code>CoffeeMaker</code> and <code>Thermosiphon</code> should use the same <code>Heater</code> instance. We need singleton support!</p>
<p>Let's create a function that transforms any <code>Factory</code> into a caching factory that will reuse the instance after the first call:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">singleton</span><span class="hljs-params">(factory: <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">T</span>&gt;)</span></span>: Factory&lt;T&gt; {
  <span class="hljs-keyword">var</span> instance: Any? = UNINITIALIZED
  <span class="hljs-keyword">return</span> Factory { linker -&gt;
    <span class="hljs-keyword">if</span> (instance === UNINITIALIZED) {
      instance = factory.<span class="hljs-keyword">get</span>(linker)
    }
    instance <span class="hljs-keyword">as</span> T
  }
}

<span class="hljs-keyword">val</span> UNINITIALIZED = Any()
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This code isn't thread-safe! For a thread-safe implementation, see Dagger's <a target="_blank" href="https://github.com/google/dagger/blob/69ac5d8ea7ed8e296f83c3eb399e84814403eca8/java/dagger/internal/DoubleCheck.java#L41-L56">DoubleCheck</a>.</div>
</div>

<p>We already have a nice <code>install</code> function that takes a lambda with receiver, let's create a variant for singletons:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;<span class="hljs-keyword">reified</span> T&gt;</span> FactoryHolderModule.<span class="hljs-title">installSingleton</span><span class="hljs-params">(
  <span class="hljs-keyword">noinline</span> factory: <span class="hljs-type">ObjectGraph</span>.() -&gt; <span class="hljs-type">T</span>
)</span></span> {
  install(T::<span class="hljs-keyword">class</span>.java, singleton(factory))
}
</code></pre>
<h3 id="heading-it-works">It works!</h3>
<p>We've connected interfaces to implementations and added singletons:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> module = FactoryHolderModule()
module.bind&lt;Heater, ElectricHeater&gt;()
module.bind&lt;Pump, Thermosiphon&gt;()
module.installSingleton {
  CoffeeLogger()
}
module.installSingleton {
  ElectricHeater(<span class="hljs-keyword">get</span>())
}
module.install {
  Thermosiphon(<span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>())
}
module.install {
  CoffeeMaker(<span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>(), <span class="hljs-keyword">get</span>())
}
<span class="hljs-keyword">val</span> objectGraph = ObjectGraph(module)
<span class="hljs-keyword">val</span> coffeeMaker = objectGraph.<span class="hljs-keyword">get</span>&lt;CoffeeMaker&gt;()

coffeeMaker.brew()
</code></pre>
<p>Ugh, that's a lot more boilerplate than our manual DI:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> logger = CoffeeLogger()
<span class="hljs-keyword">val</span> heater: Heater = ElectricHeater(logger)
<span class="hljs-keyword">val</span> pump: Pump = Thermosiphon(logger, heater)
<span class="hljs-keyword">val</span> coffeeMaker = CoffeeMaker(logger, heater, pump)

coffeeMaker.brew()
</code></pre>
<p>Can we get rid of the boilerplate?</p>
<h2 id="heading-reflectivemodule-guice-style"><code>ReflectiveModule</code> - Guice style</h2>
<p>What if we used reflection to figure out how to create object instances?</p>
<h3 id="heading-inject"><code>@Inject</code></h3>
<p>First, we need a way to indicate which constructor to call, and convey which instances should be singletons. We can leverage the <code>javax.inject</code> library, which provides the <code>@Inject</code> and <code>@Singleton</code> annotations:</p>
<pre><code class="lang-kotlin">dependencies {
    <span class="hljs-comment">// ...</span>
    api(<span class="hljs-string">"javax.inject:javax.inject:1"</span>)
}
</code></pre>
<p>Let's sprinkle our annotations:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">import</span> javax.inject.Inject
<span class="hljs-keyword">import</span> javax.inject.Singleton

<span class="hljs-meta">@Singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ElectricHeater</span> <span class="hljs-meta">@Inject</span> <span class="hljs-keyword">constructor</span></span>(
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> logger: CoffeeLogger
) : Heater {
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<h3 id="heading-injected-constructor">Injected constructor</h3>
<p>For a given class to inject, we use reflection to find the constructor annotated with <code>@Inject</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> requestedType: Class&lt;T&gt; = <span class="hljs-comment">//...</span>
<span class="hljs-keyword">val</span> injectConstructor = requestedType.constructors.single {
  it.isAnnotationPresent(Inject::<span class="hljs-keyword">class</span>.java)
}
</code></pre>
<p>We extract the types of the constructor parameters, ask the <code>ObjectGraph</code> for an instance of each parameter type then pass these parameters to the constructor:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> objectGraph: ObjectGraph = <span class="hljs-comment">// ...</span>
<span class="hljs-keyword">val</span> parameters = injectConstructor.parameterTypes.map { paramType -&gt;
  objectGraph[paramType]
}.toTypedArray()
<span class="hljs-keyword">val</span> instance = injectConstructor.newInstance(*parameters)
</code></pre>
<h3 id="heading-reflectivefactory">ReflectiveFactory</h3>
<p>All together, we get a <code>ReflectiveFactory</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReflectiveFactory</span>&lt;<span class="hljs-type">T</span>&gt;</span>(
  requestedType: Class&lt;T&gt;
) : Factory&lt;T&gt; {
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> injectConstructor = requestedType.constructors.single {
    it.isAnnotationPresent(Inject::<span class="hljs-keyword">class</span>.java)
  } <span class="hljs-keyword">as</span> Constructor&lt;T&gt;

  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">get</span><span class="hljs-params">(objectGraph: <span class="hljs-type">ObjectGraph</span>)</span></span>: T {
    <span class="hljs-keyword">val</span> parameters = injectConstructor.parameterTypes.map { paramType -&gt;
      objectGraph[paramType]
    }.toTypedArray()
    <span class="hljs-keyword">return</span> injectConstructor.newInstance(*parameters)
  }
}
</code></pre>
<p>Then we create a <code>ReflectiveModule</code> that creates the right <code>ReflectiveFactory</code> for each requested type. It also checks if the class is annotated with <code>@Singleton</code>, in which case it wraps the factory in a caching factory:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ReflectiveModule</span> : <span class="hljs-type">Module {</span></span>
  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">get</span><span class="hljs-params">(requestedType: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;)</span></span>: Factory&lt;T&gt; {
    <span class="hljs-keyword">val</span> reflectiveFactory = ReflectiveFactory(requestedType)
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> (requestedType.isAnnotationPresent(Singleton::<span class="hljs-keyword">class</span>.java)) {
      singleton(reflectiveFactory)
    } <span class="hljs-keyword">else</span> {
      reflectiveFactory
    }
  }
}
</code></pre>
<h3 id="heading-less-boilerplate">Less boilerplate!</h3>
<p>Our coffee example looks a lot nicer, it's similar to how <a target="_blank" href="https://github.com/google/guice/wiki/GettingStarted">Google Guice</a> works:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> bindingModule = FactoryHolderModule().apply {
  bind&lt;Heater, ElectricHeater&gt;()
  bind&lt;Pump, Thermosiphon&gt;()
}

<span class="hljs-keyword">val</span> objectGraph = ObjectGraph(
  bindingModule,
  ReflectiveModule()
)
<span class="hljs-keyword">val</span> coffeeMaker = objectGraph.<span class="hljs-keyword">get</span>&lt;CoffeeMaker&gt;()

coffeeMaker.brew()
</code></pre>
<p>This works well, but object creation is done through reflection which is slow. Could we generate code instead?</p>
<h2 id="heading-injectprocessor-dagger-1-style"><code>InjectProcessor</code> <strong>—</strong> Dagger-1 style</h2>
<h3 id="heading-generated-factories">Generated factories</h3>
<p>What if we generated the factory for each injected object, at compile time:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Thermosiphon_Factory</span> : <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">Thermosiphon</span>&gt; </span>{
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">get</span><span class="hljs-params">(objectGraph: <span class="hljs-type">ObjectGraph</span>)</span></span> = Thermosiphon(
      objectGraph.<span class="hljs-keyword">get</span>(),
      objectGraph.<span class="hljs-keyword">get</span>()
    )
}
</code></pre>
<p>We'd also need to implement singleton support in the generated factories, leveraging the <code>singleton</code> function we defined earlier that transforms any <code>Factory</code> into a caching factory:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ElectricHeater_Factory</span> : <span class="hljs-type">Factory</span>&lt;<span class="hljs-type">ElectricHeater</span>&gt; </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> singletonFactory = singleton { objectGraph -&gt;
        ElectricHeater(
          objectGraph.<span class="hljs-keyword">get</span>()
        )
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">get</span><span class="hljs-params">(objectGraph: <span class="hljs-type">ObjectGraph</span>)</span></span> = singletonFactory
      .<span class="hljs-keyword">get</span>(objectGraph)
}
</code></pre>
<h3 id="heading-injectprocessor"><code>InjectProcessor</code></h3>
<p>To generate the factory classes, we can use <a target="_blank" href="https://kotlinlang.org/docs/ksp-overview.html">KSP</a>. This article is already long so I won't bore you with all the details (<a target="_blank" href="https://github.com/pyricau/diy/blob/main/diy-processor/src/main/kotlin/InjectProcessor.kt">read the source</a>), here's how we generate the factory classes:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> className = <span class="hljs-string">"<span class="hljs-subst">${injectedClassSimpleName}</span>_Factory"</span>

ktFile.appendLine(<span class="hljs-string">"class <span class="hljs-variable">$className</span> : Factory&lt;<span class="hljs-variable">$injectedClassSimpleName</span>&gt; {"</span>)

<span class="hljs-keyword">val</span> constructorInvocation =
  <span class="hljs-string">"<span class="hljs-subst">${injectedClassSimpleName}</span>("</span> + function.parameters.joinToString(<span class="hljs-string">", "</span>) {
    <span class="hljs-string">"objectGraph.get()"</span>
  } + <span class="hljs-string">")"</span>

<span class="hljs-keyword">if</span> (injectedClass.isAnnotationPresent(Singleton::<span class="hljs-class"><span class="hljs-keyword">class</span>)) </span>{
  ktFile.appendLine(<span class="hljs-string">"    private val singletonFactory = singleton { objectGraph -&gt;"</span>)
  ktFile.appendLine(<span class="hljs-string">"        <span class="hljs-variable">$constructorInvocation</span>"</span>)
  ktFile.appendLine(<span class="hljs-string">"    }"</span>)
  ktFile.appendLine()
  ktFile.appendLine(
    <span class="hljs-string">"    override fun get(objectGraph: ObjectGraph) = singletonFactory.get(objectGraph)"</span>
  )
} <span class="hljs-keyword">else</span> {
  ktFile.appendLine(
    <span class="hljs-string">"    override fun get(objectGraph: ObjectGraph) = <span class="hljs-variable">$constructorInvocation</span>"</span>
  )
}
ktFile.appendLine(<span class="hljs-string">"}"</span>)
</code></pre>
<h3 id="heading-injectprocessormodule"><code>InjectProcessorModule</code></h3>
<p>We still need to use reflection to create an instance for each generated factory class:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InjectProcessorModule</span> : <span class="hljs-type">Module {</span></span>
  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;T&gt;</span> <span class="hljs-title">get</span><span class="hljs-params">(requestedType: <span class="hljs-type">Class</span>&lt;<span class="hljs-type">T</span>&gt;)</span></span> : Factory&lt;T&gt; {
    <span class="hljs-keyword">val</span> factoryClass = Class.forName(<span class="hljs-string">"<span class="hljs-subst">${requestedType.name}</span>_Factory"</span>)
    <span class="hljs-keyword">val</span> factoryConstructor = factoryClass.getDeclaredConstructor()
    <span class="hljs-keyword">return</span> factoryConstructor.newInstance() <span class="hljs-keyword">as</span> Factory&lt;T&gt;?
  }
}
</code></pre>
<p>The generated factory will create objects without any reflection involved.</p>
<h3 id="heading-less-reflection">Less reflection!</h3>
<p>Our coffee example runs faster, and the setup is almost identical, although we have to enable KSP:</p>
<pre><code class="lang-kotlin">plugins {
    id(<span class="hljs-string">"com.google.devtools.ksp"</span>)
    kotlin(<span class="hljs-string">"jvm"</span>)
}

dependencies {
  <span class="hljs-comment">// ...</span>
  ksp(project(<span class="hljs-string">":diy-processor"</span>))
}
</code></pre>
<p>The result is similar to how Dagger 1 works:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> objectGraph = ObjectGraph(
  FactoryHolderModule().apply {
    bind&lt;Heater, ElectricHeater&gt;()
    bind&lt;Pump, Thermosiphon&gt;()
  },
  InjectProcessorModule()
)
<span class="hljs-keyword">val</span> coffeeMaker = objectGraph.<span class="hljs-keyword">get</span>&lt;CoffeeMaker&gt;()

coffeeMaker.brew()
</code></pre>
<p>Could we remove the last remaining use of reflection, get rid of the map of factories, and just invoke the right generated code as needed?</p>
<h2 id="heading-componentprocessor-dagger-2-style"><code>ComponentProcessor</code> <strong>—</strong> Dagger-2 style</h2>
<p>We want to generate code that doesn't use reflection at all. To do this, we need a way to define what instance our object graph should be able to provide. We really only care about one thing: retrieving <code>CoffeeMaker</code> instances.</p>
<p>The dependency graph is a Directed Acyclic Graph, and <code>CoffeeMaker</code> is its root, which we call an entry point. We can resolve the entire dependency graph by looking at <code>CoffeeMaker</code> dependencies and then recursively looking at the dependencies of these dependencies. And we can do all that at compile time!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705687755566/f0ea174c-a65a-4c96-97b8-2017286adb52.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-component-interface"><code>@Component</code> interface</h3>
<p>Let's define an interface that provides <code>CoffeeMaker</code> instances:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Component</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CoffeeComponent</span> </span>{
  <span class="hljs-keyword">val</span> coffeeMaker: CoffeeMaker
}
</code></pre>
<h3 id="heading-ksp-componentprocessor">KSP <code>ComponentProcessor</code></h3>
<p>We then create a KSP <code>ComponentProcessor</code> to find this interface at compile time:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> symbols = resolver.getSymbolsWithAnnotation(Component::<span class="hljs-keyword">class</span>.java.name)
<span class="hljs-keyword">val</span> componentInterfaces = symbols
      .filterIsInstance&lt;KSClassDeclaration&gt;()
      .filter { it.validate() &amp;&amp; it.classKind == INTERFACE }
</code></pre>
<p>We can look for properties on that interface, which we'll call entry points:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">readEntryPoints</span><span class="hljs-params">(classDeclaration: <span class="hljs-type">KSClassDeclaration</span>)</span></span> =
   classDeclaration.getDeclaredProperties().map { property -&gt;
     <span class="hljs-keyword">val</span> resolvedPropertyType = property.type.resolve().declaration
     EntryPoint(property, resolvedPropertyType)
   }.toList()
</code></pre>
<p>For each of these entry points class, we can look for an <code>@Inject</code> constructor, list the constructor parameters, then look for <code>@Inject</code> constructors for these parameters as well, etc. Here's the code, it might seem like a lot of code but at the core it's a while loop that explores the dependency graph from the entry points:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">traverseDependencyGraph</span><span class="hljs-params">(factoryEntryPoints: <span class="hljs-type">List</span>&lt;<span class="hljs-type">KSDeclaration</span>&gt;)</span></span>:
  List&lt;ComponentFactory&gt; {
  <span class="hljs-keyword">val</span> typesToProcess = mutableListOf&lt;KSDeclaration&gt;()
  typesToProcess += factoryEntryPoints

  <span class="hljs-keyword">val</span> factories = mutableListOf&lt;ComponentFactory&gt;()
  <span class="hljs-keyword">val</span> typesVisited = mutableListOf&lt;KSDeclaration&gt;()
  <span class="hljs-keyword">while</span> (typesToProcess.isNotEmpty()) {
    <span class="hljs-keyword">val</span> visitedClassDeclaration = typesToProcess.removeFirst()
      <span class="hljs-keyword">as</span> KSClassDeclaration
    <span class="hljs-keyword">if</span> (visitedClassDeclaration !<span class="hljs-keyword">in</span> typesVisited) {
      typesVisited += visitedClassDeclaration
      <span class="hljs-keyword">val</span> injectConstructors = visitedClassDeclaration.getConstructors()
        .filter { it.isAnnotationPresent(Inject::<span class="hljs-class"><span class="hljs-keyword">class</span>) }</span>
        .toList()
      check(injectConstructors.size &lt; <span class="hljs-number">2</span>) {
        <span class="hljs-string">"There should be a most one @Inject constructor"</span>
      }
      <span class="hljs-keyword">if</span> (injectConstructors.isNotEmpty()) {
        <span class="hljs-keyword">val</span> injectConstructor = injectConstructors.first()
        <span class="hljs-keyword">val</span> constructorParams = injectConstructor.parameters.map {
          it.type.resolve().declaration
        }
        typesToProcess += constructorParams
        <span class="hljs-keyword">val</span> isSingleton = visitedClassDeclaration
          .isAnnotationPresent(Singleton::<span class="hljs-class"><span class="hljs-keyword">class</span>)</span>
        factories += ComponentFactory(
          visitedClassDeclaration,
          constructorParams,
          isSingleton
        )
      }
    }
  }
  <span class="hljs-keyword">return</span> factories
}
</code></pre>
<h3 id="heading-binds"><code>@Binds</code></h3>
<p>While the above code takes care of classes annotated with <code>@Inject</code> and <code>@Singleton</code>, remember that we also need a way to bind <code>Heater</code> to <code>ElectricHeater</code> and <code>Pump</code> to <code>Thermosiphon</code>. But we don't have a module to call methods on anymore, all we have is our component interface.</p>
<p>So we'll do the same weird trick that Dagger 2 did: define a new interface that will <strong>never be implemented</strong>, and only exists to hold <strong>methods that will never be invoked</strong>. These methods are our <strong>compile time API</strong> for defining an association between an interface and its implementation:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Component(modules = [CoffeeBindsModule::class])</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CoffeeComponent</span> </span>{
  <span class="hljs-keyword">val</span> coffeeMaker: CoffeeMaker
}

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CoffeeBindsModule</span> </span>{
  <span class="hljs-meta">@Binds</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">bindHeater</span><span class="hljs-params">(heater: <span class="hljs-type">ElectricHeater</span>)</span></span>: Heater
  <span class="hljs-meta">@Binds</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">bindPump</span><span class="hljs-params">(pump: <span class="hljs-type">Thermosiphon</span>)</span></span>: Pump
}
</code></pre>
<p>We can now read the binding modules at compile time in our <code>ComponentProcessor</code> and build a map of requested types to provided types:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">readBinds</span><span class="hljs-params">(componentAnnotation: <span class="hljs-type">KSAnnotation</span>)</span></span>:
  Map&lt;KSDeclaration, KSDeclaration&gt; {
  <span class="hljs-keyword">val</span> bindModules = componentAnnotation
    .getArgument(<span class="hljs-string">"modules"</span>)
    .value <span class="hljs-keyword">as</span> List&lt;KSType&gt;
  <span class="hljs-keyword">val</span> binds = bindModules
    .map { it.declaration <span class="hljs-keyword">as</span> KSClassDeclaration }
    .flatMap { it.getDeclaredFunctions() }
    .filter { it.isAnnotationPresent(Binds::<span class="hljs-class"><span class="hljs-keyword">class</span>) }</span>
    .associate { function -&gt;
      <span class="hljs-keyword">val</span> resolvedReturnType = function.returnType!!
        .resolve().declaration
      <span class="hljs-keyword">val</span> resolvedParamType = function.parameters
        .single().type.resolve().declaration
      resolvedReturnType to resolvedParamType
    }
  <span class="hljs-keyword">return</span> binds
}
</code></pre>
<h3 id="heading-generating-the-component-implementation">Generating the component implementation</h3>
<p>All that's left for us is to generate the component implementation:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">generateComponent</span><span class="hljs-params">(
  model: <span class="hljs-type">ComponentModel</span>,
  ktFile: <span class="hljs-type">OutputStream</span>
)</span></span> {
  with(model) {
    ktFile.appendLine(<span class="hljs-string">"package <span class="hljs-variable">$packageName</span>"</span>)
    ktFile.appendLine()

    imports.forEach { <span class="hljs-keyword">import</span> -&gt;
      ktFile.appendLine(<span class="hljs-string">"import <span class="hljs-variable">$import</span>"</span>)
    }

    ktFile.appendLine()
    ktFile.appendLine(<span class="hljs-string">"class <span class="hljs-variable">$className</span> : <span class="hljs-variable">$componentInterfaceName</span> {"</span>)

    factories.forEach { (classDeclaration, parameterDeclarations, isSingleton) -&gt;
      <span class="hljs-keyword">val</span> name = classDeclaration.simpleName.asString()
      <span class="hljs-keyword">val</span> parameters = parameterDeclarations.map { requestedType -&gt;
        <span class="hljs-keyword">val</span> providedType = binds[requestedType] ?: requestedType
        providedType.simpleName.asString()
      }
      <span class="hljs-keyword">val</span> singleton = <span class="hljs-keyword">if</span> (isSingleton) <span class="hljs-string">"componentSingleton "</span> <span class="hljs-keyword">else</span> <span class="hljs-string">""</span>

      ktFile.appendLine(<span class="hljs-string">"    private val provide<span class="hljs-variable">$name</span> = <span class="hljs-variable">$singleton</span>{"</span>)
      ktFile.appendLine(
        <span class="hljs-string">"        <span class="hljs-variable">$name</span>(<span class="hljs-subst">${parameters.joinToString(<span class="hljs-string">", "</span>) { <span class="hljs-string">"provide<span class="hljs-variable">$it</span>()"</span> }</span>})"</span>
      )
      ktFile.appendLine(<span class="hljs-string">"    }"</span>)
    }

    entryPoints.forEach { (propertyDeclaration, type) -&gt;
      <span class="hljs-keyword">val</span> name = propertyDeclaration.simpleName.asString()
      <span class="hljs-keyword">val</span> typeSimpleName = type.simpleName.asString()
      ktFile.appendLine(<span class="hljs-string">"    override val <span class="hljs-variable">$name</span>: <span class="hljs-variable">$typeSimpleName</span>"</span>)
      ktFile.appendLine(<span class="hljs-string">"      get() = provide<span class="hljs-variable">$typeSimpleName</span>()"</span>)
    }
    ktFile.appendLine(<span class="hljs-string">"}"</span>)
  }
}
</code></pre>
<p>This generates the following <code>CoffeeComponent</code> implementation:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">package</span> coffee

<span class="hljs-keyword">import</span> diy.componentSingleton

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GeneratedCoffeeComponent</span> : <span class="hljs-type">CoffeeComponent {</span></span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> provideCoffeeMaker = {
        CoffeeMaker(
          provideCoffeeLogger(),
          provideElectricHeater(),
          provideThermosiphon()
        )
    }
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> provideElectricHeater = componentSingleton {
        ElectricHeater(provideCoffeeLogger())
    }
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> provideThermosiphon = {
        Thermosiphon(provideCoffeeLogger(), provideElectricHeater())
    }
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> provideCoffeeLogger = componentSingleton {
        CoffeeLogger()
    }
    <span class="hljs-keyword">override</span> <span class="hljs-keyword">val</span> coffeeMaker: CoffeeMaker
      <span class="hljs-keyword">get</span>() = provideCoffeeMaker()
}
</code></pre>
<p>Notice how there's no mention of <code>Pump</code> or <code>Heater</code> in this code, instead the factories are directly retrieving the appropriate implementation.</p>
<h3 id="heading-no-more-reflection">No more reflection!</h3>
<p>Let's create a <code>CoffeeMaker</code> and brew!</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> component = GeneratedCoffeeComponent()
<span class="hljs-keyword">val</span> coffeeMaker = component.coffeeMaker

coffeeMaker.brew()
</code></pre>
<h2 id="heading-a-different-approach-to-binding-types">A different approach to binding types</h2>
<p>After I reading this article, <a target="_blank" href="https://discuss.systems/@manusridharan">Manu Sridharan</a> reached out with some feedback (thanks!) and an interesting question: <em>"Regarding the "weird trick" for bindings from Dagger 2, it might be interesting to suggest why they did it this way. My guess is because of limitations in what types of arguments you can pass into a Java annotation."</em></p>
<p>I'm not sure why the Dagger 2 team decided to use abstract methods to define bindings, but I thought it'd be an interesting experiment to try an alternative approach.</p>
<h3 id="heading-repeatable-annotation">Repeatable annotation</h3>
<p>First, I defined a new <code>@Bind</code> annotation (to differentiate from <code>@Binds</code>, no <code>s</code>) and decided each annotation would define a single binding from a requested type to a provided type. Since we'll need more than binding, I made it <a target="_blank" href="https://kotlinlang.org/docs/annotations.html#repeatable-annotations">repeatable</a>:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Repeatable</span>
<span class="hljs-meta">@Target(CLASS)</span>
<span class="hljs-meta">@Retention(SOURCE)</span>
<span class="hljs-keyword">annotation</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bind</span></span>(
  <span class="hljs-keyword">val</span> requested: KClass&lt;*&gt;,
  <span class="hljs-keyword">val</span> provided: KClass&lt;*&gt;
)
</code></pre>
<p>Our <code>CoffeeBindsModule</code> can now be updated:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Bind(
  requested = Heater::class,
  provided = ElectricHeater::class
)</span>
<span class="hljs-meta">@Bind(
  requested = Pump::class,
  provided = Thermosiphon::class
)</span>
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CoffeeBindsModule</span></span>
</code></pre>
<p>While the contract is a lot clearer, that feels more verbose than the previous approach:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CoffeeBindsModule</span> </span>{
  <span class="hljs-meta">@Binds</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">bindHeater</span><span class="hljs-params">(heater: <span class="hljs-type">ElectricHeater</span>)</span></span>: Heater
  <span class="hljs-meta">@Binds</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">bindPump</span><span class="hljs-params">(pump: <span class="hljs-type">Thermosiphon</span>)</span></span>: Pump
}
</code></pre>
<h3 id="heading-annotation-type-parameters">Annotation type parameters?</h3>
<p>Then I thought: it'd be nice if I could enforce that <code>provided</code> has to extend <code>requested</code>. I wonder if annotations can have type parameters?</p>
<p>Turns out, they can!</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Repeatable</span>
<span class="hljs-meta">@Target(CLASS)</span>
<span class="hljs-meta">@Retention(SOURCE)</span>
<span class="hljs-keyword">annotation</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bind</span>&lt;<span class="hljs-type">REQUESTED : Any, PROVIDED : REQUESTED</span>&gt;</span>(
  <span class="hljs-keyword">val</span> requested: KClass&lt;REQUESTED&gt;,
  <span class="hljs-keyword">val</span> provided: KClass&lt;PROVIDED&gt;
)
</code></pre>
<p>Here our updated module:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Bind</span>&lt;Heater, ElectricHeater&gt;(
  requested = Heater::<span class="hljs-class"><span class="hljs-keyword">class</span>,
  <span class="hljs-type">provided = ElectricHeater::class</span></span>
)
<span class="hljs-meta">@Bind</span>&lt;Pump, Thermosiphon&gt;(
  requested = Pump::<span class="hljs-class"><span class="hljs-keyword">class</span>,
  <span class="hljs-type">provided = Thermosiphon::class</span></span>
)
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CoffeeBindsModule</span></span>
</code></pre>
<h3 id="heading-only-type-parameters">Only type parameters</h3>
<p>But wait a minute: if I'm providing type arguments to the annotation, then I can read those types at compile time, and I don't need the annotation arguments!</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Repeatable</span>
<span class="hljs-meta">@Target(CLASS)</span>
<span class="hljs-meta">@Retention(SOURCE)</span>
<span class="hljs-keyword">annotation</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Bind</span>&lt;<span class="hljs-type">REQUESTED : Any, PROVIDED : REQUESTED</span>&gt;</span>
</code></pre>
<p>The result looks really nice:</p>
<pre><code class="lang-kotlin"><span class="hljs-meta">@Bind</span>&lt;Heater, ElectricHeater&gt;
<span class="hljs-meta">@Bind</span>&lt;Pump, Thermosiphon&gt;
<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CoffeeBindsModule</span></span>
</code></pre>
<p>One nice benefit is that the IDE can now surface binding type errors as we type:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705948103892/68240b64-075b-4a23-a4d1-1a4c9bd0888c.png" alt class="image--center mx-auto" /></p>
<p>This new <code>@Bind</code> annotation would be so much better than <code>@Binds</code> from Dagger 2:</p>
<ul>
<li><p>Less boilerplate</p>
</li>
<li><p>Easier to use: the annotation requires two parameters, with names that make it clear which is which.</p>
</li>
<li><p>Less prone to errors, you can't get the types wrong or pass in too many parameters.</p>
</li>
<li><p>No more weird abstract methods that are never called or implemented.</p>
</li>
</ul>
<p>Unfortunately, Dagger 2 is a java annotation processor and, unlike Kotlin, Java annotations cannot have type parameters:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705948411061/abf5e3a3-e947-4aa7-b99e-74245d33bec3.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-bind-implementation"><code>@Bind</code> Implementation</h3>
<p>The KSP implementation is straightforward, we update our annotation processor to read the annotation type arguments instead of the interface declared methods:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">readBinds</span><span class="hljs-params">(componentAnnotation: <span class="hljs-type">KSAnnotation</span>)</span></span>:
  Map&lt;KSDeclaration, KSDeclaration&gt; {
  <span class="hljs-keyword">val</span> bindModules = componentAnnotation
    .getArgument(<span class="hljs-string">"modules"</span>)
    .value <span class="hljs-keyword">as</span> List&lt;KSType&gt;
  <span class="hljs-keyword">val</span> binds = bindModules
    .map { it.declaration <span class="hljs-keyword">as</span> KSClassDeclaration }
    .flatMap { it.annotations }
    .filter { it isInstance Bind::<span class="hljs-class"><span class="hljs-keyword">class</span> }</span>
    .associate { <span class="hljs-keyword">annotation</span> -&gt;
      <span class="hljs-keyword">val</span> annotationArguments = <span class="hljs-keyword">annotation</span>
        .annotationType.resolve().arguments
      <span class="hljs-keyword">val</span> requested = annotationArguments.first()
        .type!!.resolve().declaration
      <span class="hljs-keyword">val</span> provided = annotationArguments.last()
        .type!!.resolve().declaration
      requested to provided
    }
  <span class="hljs-keyword">return</span> binds
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The code presented in this article is available at <a target="_blank" href="https://github.com/pyricau/diy">github.com/pyricau/diy</a>, feel free to experiment with it! Who knows, you might end up creating Dagger 3 (if you do, hit me up, I have feature requests 😉).</p>
]]></content:encoded></item><item><title><![CDATA[ANR internals: touch dispatching through the view hierarchy]]></title><description><![CDATA[I'm writing a blog series on ANR internals, where I'll use ANRs as an excuse to learn more about how various parts of Android work. This first article is focused on touch dispatching through the view hierarchy.

ANR triggers
How is an "Application No...]]></description><link>https://blog.p-y.wtf/anr-internals-touch-dispatching-through-the-view-hierarchy</link><guid isPermaLink="true">https://blog.p-y.wtf/anr-internals-touch-dispatching-through-the-view-hierarchy</guid><category><![CDATA[Android]]></category><category><![CDATA[anr]]></category><category><![CDATA[performance]]></category><category><![CDATA[Deep Dive]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 14 Sep 2023 16:32:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1694709086979/731e9974-ccad-4a68-9633-a45b7f02b75c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>I'm writing a blog series on ANR internals, where I'll use ANRs as an excuse to learn more about how various parts of Android work. This first article is focused on touch dispatching through the view hierarchy.</p>
</blockquote>
<h1 id="heading-anr-triggers">ANR triggers</h1>
<p>How is an "Application Not Responding" (ANR) error triggered?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1690313666949/5ab1e3a7-d9c2-4bb0-ad43-9abe1e50964a.png" alt class="image--center mx-auto" /></p>
<p>According to the <a target="_blank" href="https://developer.android.com/topic/performance/vitals/anr">Android documentation on ANRs</a>:</p>
<blockquote>
<p>When the UI thread of an Android app is blocked for too long, an "Application Not Responding" (ANR) error is triggered.</p>
</blockquote>
<p>While blocking the UI thread is the cause of most ANRs, the Android OS doesn't care what your app's main thread is doing. Instead, it has expectations for how long apps should take to handle a few specific events. ANR means the application is not responding <strong>to the system</strong> (rather than to the user):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694453270629/17bf7da3-62bf-40be-b1b8-dc21982a1eda.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-input-dispatching-timed-out"><strong>Input dispatching timed out</strong></h1>
<p><strong>Input dispatching timed-out</strong> is the ANR trigger that Android developers are most familiar with:</p>
<blockquote>
<p>An ANR is triggered for your app when your app has not responded to an input event (such as key press or screen touch) within 5 seconds.</p>
</blockquote>
<p>To understand how these ANRs get triggered, it's helpful to understand how input dispatching works. In this article, we'll start by looking at touch dispatching through the view hierarchy.</p>
<h1 id="heading-view-touch-event-dispatching">View touch event dispatching</h1>
<p>To start, let's add a breakpoint in a <code>View.OnClickListener</code> to see what happens when we tap a button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694461510330/610d0e30-dd65-414a-ba71-d44804e87358.png" alt class="image--center mx-auto" /></p>
<p><code>View.PerformClick</code> is a <code>Runnable</code> that invokes <code>View.OnClickListener#onClick</code> :</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">View</span> </span>{

  <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PerformClick</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Runnable</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{
      performClick();
    }
  }

  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">performClick</span><span class="hljs-params">()</span> </span>{
    ListenerInfo li = mListenerInfo;
    <span class="hljs-keyword">if</span> (li != <span class="hljs-keyword">null</span> &amp;&amp; li.mOnClickListener != <span class="hljs-keyword">null</span>) {
      li.mOnClickListener.onClick(<span class="hljs-keyword">this</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
    }
  }
}
</code></pre>
<p>(<a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/View.java;l=29486;drc=50c590e82c167a07f7fae84d81293fa52aad93af">source</a>)</p>
<p>The <code>View.PerformClick</code> runnable is <strong>posted</strong> to the main thread on <code>MotionEvent.ACTION_UP</code> in <code>View#onTouchEvent</code>, and runs later on as the main thread looper dequeues its messages.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">View</span> </span>{

  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onTouchEvent</span><span class="hljs-params">(MotionEvent event)</span> </span>{
    <span class="hljs-keyword">switch</span> (action) {
      <span class="hljs-keyword">case</span> MotionEvent.ACTION_UP:
        <span class="hljs-comment">// Use a Runnable and post this rather than calling</span>
        <span class="hljs-comment">// performClick directly. This lets other visual state</span>
        <span class="hljs-comment">// of the view update before click actions start.</span>
        <span class="hljs-keyword">if</span> (mPerformClick == <span class="hljs-keyword">null</span>) {
          mPerformClick = <span class="hljs-keyword">new</span> PerformClick();
        }
        mHandler.post(mPerformClick)
    }
  }
}
</code></pre>
<p>(<a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/View.java;l=16494;drc=50c590e82c167a07f7fae84d81293fa52aad93af">source</a>)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694554893786/d7745021-a717-4c1b-b777-fc1ae2a123cc.png" alt class="image--center mx-auto" /></p>
<p>Now let's add a breakpoint to <code>View#onTouchEvent</code> to understand where touch events come from:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694462134721/e9209802-4f99-4f2e-8a92-11d7c036de68.png" alt class="image--center mx-auto" /></p>
<p><code>MessageQueue#next</code> invokes <code>InputEventReceiver#dispatchInputEvent</code> from native code. The event goes through a chain of <code>ViewRootImpl.InputStage</code> delegates before getting dispatched through the view hierarchy via <code>ViewGroup#dispatchTouchEvent</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694555484529/588e05ef-c17f-4c6f-a0cc-a26b1dac816c.png" alt class="image--center mx-auto" /></p>
<p>We merge those two sequence diagrams:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694555961491/e8187a07-5131-4438-af6b-4d218a1ae1c2.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-compose-touch-event-dispatching">Compose touch event dispatching</h1>
<p>Let's add a breakpoint in a Compose click lambda to understand how Compose handles taps:</p>
<pre><code class="lang-kotlin">Button(
    onClick = { <span class="hljs-comment">/* breakpoint here */</span> },
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694462815084/420e6eaa-2c68-4a74-a281-b322a5748041.png" alt class="image--center mx-auto" /></p>
<p><code>MessageQueue#next</code> invokes <code>InputEventReceiver#dispatchInputEvent</code> from native code. The event goes through a chain of <code>ViewRootImpl.InputStage</code> delegates before getting dispatched to through the view hierarchy and then getting dispatched through Compose nodes which eventually invoke the click lambda.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694556965358/cdc7ed4f-ba82-4e79-954e-b67234ec69fb.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-aside-smoke-amp-mirrors">Aside: smoke &amp; mirrors</h2>
<p>Notice how the view framework posts the invocation of the view listener, whereas Compose invokes the lambda immediately on <code>MotionEvent.ACTION_UP</code>. This is presumably more efficient (no delay in handling of taps). However, if your tap handling happens to be slow and blocks the main thread for a bit (e.g. updating the entire UI in response to a tap), the view posting allows the render thread to start a ripple animation on the button on <code>MotionEvent.ACTION_UP</code> and the ripple will animate while the main thread is blocked. I noticed this when, after migrating a button from views to compose, the interaction <em>felt</em> worse and slower even though the performance was similarly bad.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Today we saw that the Android framework has native code that invokes <code>InputEventReceiver#dispatchInputEvent</code> which then dispatches touch events to the view hierarchy of the target window. With Compose, clicks listeners are invoked immediately on <code>MotionEvent.ACTION_UP</code> whereas with views click listeners are invoked from a main thread post enqueued on <code>MotionEvent.ACTION_UP</code>.</p>
<p>This article was a warm-up, next time we'll dig into something a little more interesting: Looper internals.</p>
]]></content:encoded></item><item><title><![CDATA[A script to compare two Macrobenchmarks runs]]></title><description><![CDATA[In Statistically Rigorous Android Macrobenchmarks, I laid out a methodology for rigorously comparing the outcome of two Jetpack Macrobenchmark runs. To summarize the article:

Remove sources of variations until the distribution fits a normal distribu...]]></description><link>https://blog.p-y.wtf/a-script-to-compare-two-macrobenchmarks-runs</link><guid isPermaLink="true">https://blog.p-y.wtf/a-script-to-compare-two-macrobenchmarks-runs</guid><category><![CDATA[Android]]></category><category><![CDATA[performance]]></category><category><![CDATA[Script]]></category><category><![CDATA[Benchmark]]></category><category><![CDATA[Kotlin]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 07 Sep 2023 16:36:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1694104519520/876fbcb1-5452-4b34-a991-0e69e3540281.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a target="_blank" href="https://blog.p-y.wtf/statistically-rigorous-android-macrobenchmarks">Statistically Rigorous Android Macrobenchmarks</a>, I laid out a methodology for rigorously comparing the outcome of two <a target="_blank" href="https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview">Jetpack Macrobenchmark</a> runs. To summarize the article:</p>
<ul>
<li>Remove sources of variations until the distribution fits a normal distribution with a stable standard deviation.</li>
<li>Then compute the confidence interval for a difference between two means.</li>
</ul>
<p>When I published the article, I also shared a <a target="_blank" href="https://docs.google.com/spreadsheets/d/1fqR99ROoSYtMrkoL5PWkUWDsW0E3JEXSTcXQqZJJCow/template/preview">Google Spreadsheet template</a> that did some of that work.</p>
<p>Later on, a colleague (thanks Aaron!) shared a <a target="_blank" href="https://github.com/kaushikgopal/kotlin-scripts">Github repo of kscripts</a> from Kaushik Gopal, and I realized I could easily turn my spreadsheet into a small Kotlin script to make it easier for other Square Android developers to play with comparing Macrobenchmark runs. So I did that.</p>
<p>Then I went on paternity leave and forgot about this, until recently when Saket Narayan reminded me that it could be worth sharing with the community.</p>
<p>Without further ado, <a target="_blank" href="https://gist.github.com/pyricau/07fd9598c5cdec0bc9f62505b6329df7">here's the script</a>.</p>
<p>You first need to install Kotlin, download the script and make it executable</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install kotlin</span>
brew install kotlin

<span class="hljs-comment"># Download the comparison script</span>
curl -O https://gist.githubusercontent.com/pyricau/07fd9598c5cdec0bc9f62505b6329df7/raw/977b2a84532758fd614f6cc44dab43a242922cdb/compare.benchmarks.main.kts
chmod u+x compare.benchmarks.main.kts
</code></pre>
<p>Then you can run the comparison script:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Compare the json output in run1 and run2 folders.</span>
compare.benchmarks.main.kts run1/com.example.macrobenchmark-benchmarkData.json run2/com.example.macrobenchmark-benchmarkData.json

<span class="hljs-comment">###########################################################################</span>
Results <span class="hljs-keyword">for</span> com.example.InteractionLatencyBenchmarks<span class="hljs-comment">#openHomeScreen</span>
<span class="hljs-comment">##################################################</span>
NavigationMs
<span class="hljs-comment">#########################</span>
DATA CHECKS
✓ All checks passed, the comparison conclusion is meaningful.

Data checks <span class="hljs-keyword">for</span> Benchmark 1
- ✓ At least 30 iterations (100)
- ✓ CV (5.26) &lt;= 6%
- ✓ Latencies pass normality <span class="hljs-built_in">test</span>

Data checks <span class="hljs-keyword">for</span> Benchmark 2
- ✓ At least 30 iterations (100)
- ✓ CV (4.32) &lt;= 6%
- ✓ Latencies pass normality <span class="hljs-built_in">test</span>

- ✓ Variance less than doubles (0.66)
<span class="hljs-comment">#########################</span>
RESULT
Mean difference confidence interval at 95% confidence level:
The change yielded no statistical significance (the mean difference confidence interval crosses 0): from -6 ms (-2.36%) to 1 ms (0.3%).
<span class="hljs-comment">#########################</span>
MEDIANS
The median went from 259 ms to 231 ms.
DO NOT REPORT THE DIFFERENCE IN MEDIANS.
This data helps contextualize results but is not statistically meaningful.
<span class="hljs-comment">#########################</span>
</code></pre>
<p>While I'd love to get feedback and ideas for improvements (<a target="_blank" href="https://androiddev.social/@py">hit me up!</a>), I'm providing this script as is, with no guarantees and no intention to maintain it. Do whatever you want with it!</p>
]]></content:encoded></item><item><title><![CDATA[Freezes & ANRs? Check memory leaks!]]></title><description><![CDATA[In this article, I show how Android memory leaks lead to jank, freezes and ANRs more often than they lead to OutOfMemoryError crashes.
Navigation Latency
At Square, we've been tracking a User-Centric performance metric: Interaction Latency. We track ...]]></description><link>https://blog.p-y.wtf/freezes-anrs-check-memory-leaks</link><guid isPermaLink="true">https://blog.p-y.wtf/freezes-anrs-check-memory-leaks</guid><category><![CDATA[Android]]></category><category><![CDATA[performance]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[outofmemory]]></category><category><![CDATA[jank]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 20 Jul 2023 22:09:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689890892599/a05210f8-204a-4d8a-9506-2ea251f2e163.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, I show how Android memory leaks lead to jank, freezes and ANRs more often than they lead to <code>OutOfMemoryError</code> crashes.</p>
<h1 id="heading-navigation-latency">Navigation Latency</h1>
<p>At Square, we've been tracking a <a target="_blank" href="https://blog.p-y.wtf/user-centric-mobile-performance">User-Centric</a> performance metric: <strong>Interaction Latency</strong>. We track this on every app navigation (example implementation: <a target="_blank" href="https://dev.to/pyricau/tap-response-time-jetpack-navigation-4738">Tap Response Time: Jetpack Navigation 🗺</a>).</p>
<p>In other words, for every navigation, we report a latency metric that measures the duration from when the tap was received to when the display was updated, i.e. how much latency users perceive.</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> durationMillis = frameCommitted - actionUpMotionEvent.eventTime
analytics.logNavigation(
  originScreen,
  destinationScreen,
  durationMillis
)
</code></pre>
<h1 id="heading-memory-usage-on-navigation">Memory usage on navigation</h1>
<p>Resource consumption metrics like memory usage are often reported as time series, which isn't useful when trying to correlate app usage with memory leaks.</p>
<p>In January 2023, Pavlo Stavytskyi published <a target="_blank" href="https://eng.lyft.com/detecting-android-memory-leaks-in-production-29e9c97e2ba1">Detecting Android memory leaks in production</a> on the Lyft Engineering blog.</p>
<p>One interesting idea in the article was to report memory usage metrics on every screen navigation instead of as a time series because memory leaks tend to accumulate with app usage.</p>
<p>Let's update our navigation analytics to add memory usage:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> runtime = Runtime.getRuntime()
<span class="hljs-keyword">val</span> javaHeapUsage = runtime.totalMemory() - runtime.freeMemory()

analytics.logNavigation(
  sourceScreen,
  destinationScreen,
  durationMillis,
  javaHeapUsage
)
</code></pre>
<h1 id="heading-memory-limit">Memory limit</h1>
<p>If Android devices had infinite memory, memory leaks wouldn't be an issue. Android devices have limited RAM, every app is allowed to use only a subset of the device RAM for its Java heap, and memory leaks become an issue when memory usage is close to the limit. That limit is configured per device and can be queried with <a target="_blank" href="https://developer.android.com/reference/java/lang/Runtime#maxMemory()">Runtime.maxMemory()</a>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> javaHeapLimit = Runtime.getRuntime().maxMemory()

analytics.logNavigation(
  sourceScreen,
  destinationScreen,
  durationMillis,
  javaHeapUsage,
  javaHeapLimit
)
</code></pre>
<h1 id="heading-example-leaky-session">Example leaky session</h1>
<p>We can now graph memory usage over time for a single session, where each data point in a single navigation. Here's a real example session with 1591 navigations where we see memory usage grow over time:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689871366438/f2a95720-a419-4b28-9137-abb5f0bbfe32.png" alt class="image--center mx-auto" /></p>
<p>Notice how Java heap usage is constantly jumping up &amp; down as the GC runs, but the trend is upward which indicates a memory leak. Applying a linear regression shows a slope of +146 KB per navigation.</p>
<h1 id="heading-memory-usage-andamp-navigation-latency">Memory usage &amp; Navigation Latency</h1>
<p>Let's add Navigation Latency to the graph:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689873166318/c6f09086-aa10-499e-846f-e3c8161935b7.png" alt class="image--center mx-auto" /></p>
<p>Notice how Navigation Latency is fairly flat throughout the session until memory usage gets close to the memory limit, at which point Navigation Latency shoots up. We can zoom in on the last 200 navigations:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689873571402/0a9c8149-3920-4946-b63f-5942cc715e75.png" alt class="image--center mx-auto" /></p>
<p>In this example session, the UI freezes up for seconds at a time while the GC is blocking the main thread to reclaim memory. This starts happening when memory gets within 18 MB of the limit.</p>
<h1 id="heading-the-progressive-impact-of-memory-leaks">The progressive impact of memory leaks</h1>
<p>As Java heap memory gets close to the app memory limit, the impact of memory leaks is more and more noticeable.</p>
<ol>
<li><p>First, small GC pauses cause animation jank.</p>
</li>
<li><p>Then longer GC pauses cause increasingly longer UI freezes, for seconds at a time.</p>
</li>
<li><p>If the main thread freezes for more than 5 seconds while touch events are pending dispatch, the app triggers an Application Not Responding (ANR) error.</p>
</li>
<li><p>Eventually, there's so little memory left that we can't allocate new objects and the app crashes with an <code>OutOfMemoryError</code> exception.</p>
</li>
</ol>
<h1 id="heading-missing-the-real-impact-of-memory-leaks">Missing the real impact of memory leaks</h1>
<p>If you have crash reporting in place and a process to fix top crashes, well done! Unfortunately, you can't just look at <code>OutOfMemoryError</code> crashes to decide when to look into fixing Java memory leaks, for two reasons:</p>
<ul>
<li><p>Crash reporting tools typically group crashes by identical stack traces and provide a count by crash group. When memory is low an <code>OutOfMemoryError</code> can be thrown from anywhere in the app code, which means that every <code>OutOfMemoryError</code> potentially has a different stack trace. Instead of one crash entry with 1000 crashes, <code>OutOfMemoryError</code> crashes get reported as 1000 distinct crashes and hide in the long tail of low-occurring crashes.</p>
</li>
<li><p>As the app slows down and freezes for several seconds, mobile users will either stop using it, or kill it and restart it. So the app might never crash with <code>OutOfMemoryError</code> even though the customer impact is real.</p>
</li>
</ul>
<h1 id="heading-takeaways">Takeaways</h1>
<ul>
<li><p>Android memory leaks progressively lead to jank, then freezes, then ANRs and eventually <code>OutOfMemoryError</code> crashes (if the user hasn't already left or killed the app).</p>
</li>
<li><p>When an ANR report shows a stacktrace that doesn't seem like it could actually cause an ANR, you should look at memory usage and memory limit. If memory is close to the limit, the ANR is probably happening because the GC is blocking the main thread.</p>
</li>
<li><p>To avoid these performance issues, you should systematically fix all memory leaks surfaced by <a target="_blank" href="https://square.github.io/leakcanary">LeakCanary</a>.</p>
</li>
<li><p>By combining memory usage &amp; memory limit data with performance data in production, you can surface the relationship between memory leaks &amp; performance.</p>
<ul>
<li>While I can't share the actual numbers, we saw a direct correlation between user activity, leak rate, and freeze / ANR rate.</li>
</ul>
</li>
<li><p>A linear regression of memory usage over navigations per session can show whether a session has a memory leak, and how bad the leak is.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Tracking Android App Launch in production]]></title><description><![CDATA[👋 In this article, I summarize my findings on Android app launch after a few years of deep dives, with the hope that these guidelines help implementations track app launches correctly.

User-Centric App Launch analytics
In User-Centric Mobile Perfor...]]></description><link>https://blog.p-y.wtf/tracking-android-app-launch-in-production</link><guid isPermaLink="true">https://blog.p-y.wtf/tracking-android-app-launch-in-production</guid><category><![CDATA[Android]]></category><category><![CDATA[analytics]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 13 Jul 2023 00:57:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689209807078/23e77547-6d1b-49c3-8c8b-bc743ec16cbb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>👋 In this article, I summarize my findings on Android app launch after a few years of deep dives, with the hope that these guidelines help implementations track app launches correctly.</p>
</blockquote>
<h1 id="heading-user-centric-app-launch-analytics">User-Centric App Launch analytics</h1>
<p>In <a target="_blank" href="https://blog.p-y.wtf/user-centric-mobile-performance">User-Centric Mobile Performance</a>, I argued that mobile app teams should primarily track <strong>user-centric performance metrics</strong> that represent the experience of using the app, that <strong>responsiveness thresholds should be context specific</strong> as user expectations vary based on what they are doing and what they're used to.</p>
<p>I used the example of app launches and a few days later <a target="_blank" href="https://hanson.wtf/2023/07/11/rethinking-the-app-startup-metric/">Hanson Ho published a blog post</a> arguing, amongst other things, that you should think about the percentage makeup of cold vs warm launches rather than just focusing on improving cold launches. Read it, it's a great perspective.</p>
<p>Hanson made a really good point: users don't really care about the specifics of whether an app is a cold or warm launch.</p>
<p>So what is it that users care about? As I highlighted above, user expectations vary based on what they are doing and what they're used to. We should put cold / hot / warm launch aside. Users can launch an app in different ways:</p>
<ul>
<li><p>Tapping on a <strong>launcher</strong> icon</p>
<ul>
<li>Users expect this to take a few seconds.</li>
</ul>
</li>
<li><p>Tapping on a <strong>notification</strong></p>
<ul>
<li>Recent notifications are expected to open fast, but it's fairly common for long-running notifications to take a while to open the app.</li>
</ul>
</li>
<li><p>Launch <strong>from another app</strong> (integration / chooser intent)</p>
<ul>
<li>This should be fairly fast, as users are in a flow trying to accomplish a task.</li>
</ul>
</li>
<li><p>Bringing it back from <strong>Recents</strong>.</p>
<ul>
<li>This is expected to be almost instant, as the OS gives the illusion of all these apps being available to go back to at any point in time.</li>
</ul>
</li>
</ul>
<p>The first 3 cases can be determined by looking at the intent of the launched activity, and the last case by looking at whether the activity is provided with a saved instance state.</p>
<p>While it's useful to track whether a launch was cold / warm / hot, your primary dashboards and metrics should instead focus on the different scenarios under which users are launching the app, and target different thresholds for each.</p>
<p>For example, if 80% of app launches are from <em>Recents</em> (i.e. users constantly swap in &amp; out of the app), then you should focus on optimizing that. And if 50% of the launches from <em>Recents</em> are <em>cold launches</em>, bringing that metric down, then you should look into why the app keeps getting killed (e.g. crash or high memory usage).</p>
<h1 id="heading-app-launch-app-start-or-app-startup">App launch, app start or app startup?</h1>
<p>These words tend to be used interchangeably. However, I prefer <em>Launch</em> because it reminds me of the app launcher. <em>Start</em> can be confusing because an app launch can involve a <em>process start</em> or not. Also, in a <em>Hot Launch</em> the activity isn't actually started as it goes from paused to resumed.</p>
<h1 id="heading-what-is-an-app-launch">What is an App Launch?</h1>
<p>An App Launch is when an app that <strong>had no visible activity</strong> now has visible activities.</p>
<p>In other words, an App Launch is when an app that had no activity in started or resumed state now has activities in started or resumed state.</p>
<ul>
<li><p>I focus on <strong>activities</strong> because Android places each process into an importance hierarchy where a process can have a foreground or visible importance yet have no created activity (see <a target="_blank" href="https://developer.android.com/guide/components/activities/process-lifecycle">Processes and app lifecycle</a>).</p>
</li>
<li><p>I focus on <strong>visible</strong> rather than <strong>foreground</strong> because I don't want to count as app launch the case where the app had an activity visible in the background of an activity from another app and then came to the foreground.</p>
</li>
</ul>
<h1 id="heading-app-launch-temperature">App Launch Temperature</h1>
<p>The <a target="_blank" href="https://developer.android.com/topic/performance/vitals/launch-time">App startup time</a> documentation does a great job of describing the difference between cold, warm, and hot launches.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689197067223/4ace5587-1944-4b04-a3f0-7047b23be968.png" alt="the various startup states" class="image--center mx-auto" /></p>
<p>However, there are different flavors of cold and warm launches, so it's worth digging into the details.</p>
<h2 id="heading-cold-launch">Cold Launch</h2>
<p>A Cold Launch is a launch where there was <strong>no existing app process</strong> and the system started the app process <strong>for the purpose of launching an activity</strong>. This is key: we need to check why the system decided to start the process. If the process was started for another reason and then sometimes during the init the system asked the app to launch an activity, then it's not a cold launch.</p>
<p>Some interesting attributes to track for cold launches:</p>
<ul>
<li>Is it the first launch after an install?<ul>
<li>First launch after install is critical for new users, yet it often triggers additional first time init work.</li>
</ul>
</li>
<li>Is it the first launch after an upgrade?<ul>
<li>Upgrades often trigger additional migration work on launch.</li>
</ul>
</li>
<li>Is it the first launch after clear data?<ul>
<li>This should be a similar launch to the first launch after install, but is interesting to track separately to detect when users clear data which likely indicates an issue with the app.</li>
</ul>
</li>
<li>Did the launched activity have a saved instance state (process recreation for when bringing the activity back from Recents).</li>
</ul>
<h2 id="heading-hot-launch">Hot Launch</h2>
<p>A Hot Launch is a launch where there was an existing app process as well as an activity in <em>Created</em> state (i.e. stopped) that was then started.</p>
<h2 id="heading-warm-launch">Warm Launch</h2>
<p>A Warm Launch is any launch that isn't a cold launch or a hot launch:</p>
<ul>
<li>A launch where there was an existing app process but no activity. The activity might be created with a saved state to restore (brought back from <em>Recents</em>) or with no saved state.</li>
<li>A launch where the process was started for a reason not related to the launch, wasn't done with startup yet (i.e. <code>Application.onCreate()</code> wasn't finished running) then sometimes during the init the system asked the app to launch an activity. While this might look like a cold launch, users will actually not experience the full cold launch duration.</li>
</ul>
<h2 id="heading-pre-launch-state">Pre Launch state</h2>
<p>The launch temperature is determined by the state of the app before the launch. In <a target="_blank" href="https://github.com/square/papa/blob/main/papa/src/main/java/papa/PreLaunchState.kt">square/papa</a>, I defined the following list of pre-launch states:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">enum</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PreLaunchState</span></span>(<span class="hljs-keyword">val</span> launchType: AppLaunchType) {
  <span class="hljs-comment">/**
   * This is typically referred to as a "cold start".
   * The process was started with a FOREGROUND importance and
   * the launched activity was created, started and resumed before our first post
   * ran.
   */</span>
  NO_PROCESS(COLD),

  <span class="hljs-comment">/**
   * Same as [NO_PROCESS] but this was the first launch ever,
   * which might trigger first launch additional work.
   */</span>
  NO_PROCESS_FIRST_LAUNCH_AFTER_INSTALL(COLD),

  <span class="hljs-comment">/**
   * Same as [NO_PROCESS] but this was the first launch after the app was upgraded, which might
   * trigger additional migration work. Note that if the upgrade if the first upgrade
   * that introduces this library, the value will be [NO_PROCESS_FIRST_LAUNCH_AFTER_CLEAR_DATA]
   * instead.
   */</span>
  NO_PROCESS_FIRST_LAUNCH_AFTER_UPGRADE(COLD),

  <span class="hljs-comment">/**
   * Same as [NO_PROCESS] but this was either the first launch after a clear data, or
   * this was the first launch after the upgrade that introduced this library.
   */</span>
  NO_PROCESS_FIRST_LAUNCH_AFTER_CLEAR_DATA(COLD),

  <span class="hljs-comment">/**
   * This is the coldest type of "warm start". The process was not started with
   * a FOREGROUND importance yet the launched activity was created, started and resumed
   * before our first post ran. This means that while the process while starting, the
   * system decided to launch the activity.
   */</span>
  PROCESS_WAS_LAUNCHING_IN_BACKGROUND(WARM),

  <span class="hljs-comment">/**
   * This is a "warm start" where the activity brought to the foreground had to be created,
   * started and resumed, and the task had no saved instance state bundle.
   */</span>
  NO_ACTIVITY_NO_SAVED_STATE(WARM),

  <span class="hljs-comment">/**
   * This is a "warm start" where the activity brought to the foreground had to be created,
   * started and resumed, and the task can benefit somewhat from the saved instance state bundle
   * passed into onCreate().
   */</span>
  NO_ACTIVITY_BUT_SAVED_STATE(WARM),

  <span class="hljs-comment">/**
   * This is a "hot start", the activity was already created and had been stopped when the app
   * went in background. Bringing it to the foreground means the activity was started and then
   * resumed. Note that there isn't a "ACTIVITY_WAS_PAUSED" entry here. We do not consider
   * going from PAUSE to RESUME to be a launch because the activity was still visible so there
   * is nothing to redraw on resume.
   */</span>
  ACTIVITY_WAS_STOPPED(HOT);
}
</code></pre>
<p>Here's the <a target="_blank" href="https://github.com/square/papa/blob/9005c4394de6802398d552063d42ed072f30e998/papa/src/main/java/papa/internal/Perfs.kt#L361C70-L361C70">code to compute the pre-launch state</a> which checks for cold starts by looking at <strong>process importance</strong> and whether the launched activity was created <strong>before the first post ran</strong> (as highlighted in <a target="_blank" href="https://dev.to/pyricau/android-vitals-why-did-my-process-start-4d0e">Why did my process start? 🌄</a>).</p>
<h1 id="heading-launch-start-timestamp">Launch start timestamp</h1>
<h2 id="heading-cold-launch-1">Cold Launch</h2>
<p>In <a target="_blank" href="https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4">When did my app start? ⏱</a> I concluded that you should use:</p>
<ul>
<li>Up to API 24: Use the class load time of a content provider which high priority.</li>
<li>API 24 - API 28: Use <a target="_blank" href="https://developer.android.com/reference/android/os/Process.html#getStartUptimeMillis()">Process.getStartUptimeMillis()</a>.</li>
<li>API 28 and beyond: Use <a target="_blank" href="https://developer.android.com/reference/android/os/Process.html#getStartUptimeMillis()">Process.getStartUptimeMillis()</a> but filter out weird values (e.g. more than 1 min to get to Application.onCreate()) and fallback to the time <code>ContentProvider.onCreate()</code>.</li>
</ul>
<p>Since then API 33 added <a target="_blank" href="https://developer.android.com/reference/android/os/Process#getStartRequestedUptimeMillis()">Process.getStartRequestedUptimeMillis()</a> which is super promising, though I haven't tried using yet.</p>
<ul>
<li><code>Process.getStartRequestedUptimeMillis()</code>: when the user started waiting for the app to launch</li>
<li><code>Process.getStartUptimeMillis()</code>: when the app started having an influence on launch time (when the APK starts loading)</li>
</ul>
<h2 id="heading-warm-andamp-hot-launch">Warm &amp;  Hot Launch</h2>
<p>As far as I know, there's no API that provides the launch intent timestamp. There should be! </p>
<p>So our best option here is to leverage ActivityLifecycleCallbacks to capture timestamps:</p>
<ul>
<li>Warm launch: <a target="_blank" href="https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks#onActivityPreCreated(android.app.Activity,%20android.os.Bundle)">onActivityPreCreated()</a> on API 29+ and [onActivityCreated()] (https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks#onActivityCreated(android.app.Activity,%20android.os.Bundle)) pre API 29.</li>
<li>Hot launch: <a target="_blank" href="https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks#onActivityPreStarted(android.app.Activity)">onActivityPreStarted()</a> on API 29+ and <a target="_blank" href="https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks#onActivityStarted(android.app.Activity)">onActivityStarted()</a> pre API 29.</li>
</ul>
<h1 id="heading-launch-end-timestamp">Launch end timestamp</h1>
<p>The end of a launch is when the window of the launched activity has been rendered into a frame and submitted to the swap chain.</p>
<p>In <a target="_blank" href="https://dev.to/pyricau/android-vitals-first-draw-time-m1d">First draw time 👩‍🎨</a> I showed how to access the decor view of the launched activity to add an <a target="_blank" href="https://developer.android.com/reference/android/view/ViewTreeObserver#addOnDrawListener(android.view.ViewTreeObserver.OnDrawListener)">OnDrawListener</a>.</p>
<p>From the <code>onDraw()</code> callback, we need to figure out when that frame traversal is submitted to the swap chain with:</p>
<ul>
<li><a target="_blank" href="https://developer.android.com/reference/android/view/ViewTreeObserver#registerFrameCommitCallback(java.lang.Runnable)">registerFrameCommitCallback()</a> on API 29+</li>
<li><a target="_blank" href="https://developer.android.com/reference/android/os/Handler#postAtFrontOfQueue(java.lang.Runnable)">Handler.postAtFrontOfQueue()</a> pre API 29.</li>
</ul>
<h2 id="heading-all-together-in-squarepapa">All together in <code>square/papa</code></h2>
<p>I'm hopeful that some day Google will release a new AndroidX library to track app launches (LaunchStats?). Until then, you can roll your own code following the guidelines I outlined, or leverage <a target="_blank" href="https://github.com/square/papa">square/papa</a>:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleApplication</span> : <span class="hljs-type">Application</span></span>() {
  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">super</span>.onCreate()

    PapaEventListener.install { event -&gt;
      <span class="hljs-keyword">when</span> (event) {
        <span class="hljs-keyword">is</span> AppLaunch -&gt; {
          Analytics.logAppLaunch(
            preLaunchState = event.preLaunchState
            durationUptimeMillis = event.durationUptimeMillis
          )
        }
      }
    }
  }
}
</code></pre>
<p>I just realized that the <code>AppLaunch</code> event does not provide the intent and saved state details which are necessary to track User-Centric App Launch analytics as highlighted at the start of this article, so I filed <a target="_blank" href="https://github.com/square/papa/issues/57">square/papa#57</a> to fix that.</p>
<blockquote>
<p>Header image generated by DALL-E.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[User-Centric Mobile Performance]]></title><description><![CDATA[👋 Hi, this is P-Y, over the last three years I've been steering Square's focus on mobile performance and building a framework for thinking about it and prioritizing work. In this article I share my approach, let me know what you think!

Useful metri...]]></description><link>https://blog.p-y.wtf/user-centric-mobile-performance</link><guid isPermaLink="true">https://blog.p-y.wtf/user-centric-mobile-performance</guid><category><![CDATA[Android]]></category><category><![CDATA[performance]]></category><category><![CDATA[user experience]]></category><category><![CDATA[metrics]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 06 Jul 2023 20:15:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688674392754/499a309c-b8d1-4e90-bb15-a39d0c80e54c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>👋 Hi, this is P-Y, over the last three years I've been steering Square's focus on mobile performance and building a framework for thinking about it and prioritizing work. In this article I share my approach, let me know what you think!</p>
</blockquote>
<h1 id="heading-useful-metrics">Useful metrics</h1>
<p>As an app codebase and user base grows, it becomes harder for product teams to evaluate the performance of an app just by playing with it.</p>
<blockquote>
<p>When talking about performance, it's important to be precise and to refer to performance in terms of objective criteria that can be quantitatively measured. These criteria are known as <em>metrics</em>.</p>
<p>But just because a metric is based on objective criteria and can be quantitatively measured, it doesn't necessarily mean those measurements are <strong>useful</strong>.</p>
<p>Source: <a target="_blank" href="https://web.dev/user-centric-performance-metrics/">web.dev</a></p>
</blockquote>
<p>Useful for what? Product teams deal with competing priorities, they need a signal to know when and where to prioritize performance work, i.e. they need to know when poor performance is impacting their customer's experience and ultimately driving business metrics down.</p>
<p>Mobile performance metrics often take inspiration from the backend world and measure resource usage (CPU usage, memory usage) and workload durations (how long it takes to run a piece of code).</p>
<p>While <strong>app performance directly impacts User Experience</strong>, metrics surfacing high CPU usage or slow database reads are not useful measurements to convey the actual user experience. They're not <strong>user-centric</strong>.</p>
<p>For example, when exporting an Insta360 video, as a customer I want the app to maximize GPU and CPUs usage so that the export goes faster.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688663129684/06226897-b1cf-4f64-91f3-10aad94b0703.png" alt class="image--center mx-auto" /></p>
<p><em>Insta360 threads (in purple) scheduled on all CPUs while exporting a video.</em></p>
<h1 id="heading-user-centric-performance-metrics">User-centric performance metrics</h1>
<p>Mobile app teams should <strong>primarily</strong> track <strong>user-centric performance metrics</strong> that represent the experience of using the app.</p>
<p>This helps product teams make <strong>better performance investments</strong>. Unlike technical metrics, we can't dance around user-centric performance metrics: while we could argue on the right amount of memory usage or the right duration of a database query, we can't argue against a metric that demonstrates a bad customer experience.</p>
<p>Of course, app teams should still track resource usage and technical workload durations as <strong>secondary</strong> metrics, to help understand the components influencing user-centric performance metrics.</p>
<p>There are two broad categories of user-centric performance metrics:</p>
<ul>
<li><p><strong>Smoothness</strong> metrics, which track whether <strong>motion</strong> on screen is perceived as janky.</p>
</li>
<li><p><strong>Responsiveness</strong> metrics, which track the delay between a user action and a visible response from the system.</p>
</li>
</ul>
<h1 id="heading-human-based-thresholds">Human-based thresholds</h1>
<p><a target="_blank" href="https://www.tactuallabs.com/papers/howMuchFasterIsFastEnoughCHI15.pdf">User experience research</a> suggests that humans do not perceive latency improvements beyond specific thresholds: <strong>11 ms</strong> for the latency of on-screen drag motion (<strong>smoothness</strong>) and <strong>69 ms</strong> for the latency of on-screen tap interactions (<strong>responsiveness</strong>).</p>
<p>Apple's <a target="_blank" href="https://developer.apple.com/documentation/xcode/improving-app-responsiveness">app responsiveness documentation</a> makes a similar distinction:</p>
<blockquote>
<p>An app that responds instantly to users’ interactions gives an impression of supporting their workflow. When the app responds to gestures and taps in real time, it creates an experience for users that they’re directly manipulating the objects on the screen. Apps with a noticeable delay in user interaction (a hang) or movement on screen that appears to jump (a hitch), shatter that illusion. This leaves the user wondering whether the app is working correctly. To avoid hangs and hitches, keep the following rough thresholds in mind as you develop and test your app.</p>
<ul>
<li><p>&lt; 100 ms: Synchronous main thread work in response to a <strong>discrete user interaction</strong>.</p>
</li>
<li><p>&lt; 1 display refresh interval (8 or 17ms): Main thread work and work to handle <strong>continuous</strong> user interaction.</p>
</li>
</ul>
</blockquote>
<h1 id="heading-smoothness">Smoothness</h1>
<p>Any sort of screen motion requires frame updates to be synced with the display refresh rate, otherwise humans notice jank.</p>
<p><strong>Screen motion can be interactive or non-interactive</strong>. Interactive is when a finger is touching the display and the UI is following it, i.e. a drag (slow scrolling a list, or drag &amp; drop). Non-interactive can be animations or a fling based scroll.</p>
<p><strong>Jank on interactive screen motion has a stronger negative impact</strong> on user experience than jank on non-interactive screen motion.</p>
<p>Smoothness is typically measured by reporting <strong>frame rate</strong> and <strong>missed frames</strong>. Keep in mind that smoothness only matters when there's <strong>actual motion on screen</strong>, and matters more when that motion is interactive.</p>
<p>For example, it's actually ok to take 50 ms to update the UI in response to a tap. A tap isn't a drag, there's no motion. However, if after 50 ms we start an animation to open the new UI, that animation should render every frame.</p>
<h1 id="heading-responsiveness">Responsiveness</h1>
<p>A responsiveness metric is any metric that tracks the delay between a user action and a visible response from the system, i.e. an <strong>interaction</strong>. Here are a few examples:</p>
<ul>
<li><p>Launching an app by tapping its icon in the launcher.</p>
</li>
<li><p>Bringing an app back to the foreground from Recents.</p>
</li>
<li><p>Tapping a <em>Like</em> button and seeing a counter increase.</p>
</li>
<li><p>Tapping a list item to open up its details in a new screen.</p>
</li>
<li><p>Typing with a connected hardware keyboard.</p>
</li>
<li><p>Using a connected watch to trigger taking a picture from a phone.</p>
</li>
</ul>
<h1 id="heading-responsiveness-thresholds">Responsiveness thresholds</h1>
<p>We have <strong>different expectations</strong> for how long these interactions should take: we expect an app launch to take significantly longer than seeing a counter increase after tapping a <em>Like</em> button.</p>
<p>These different expectations come from our ability to do pattern matching. Humans are really good at picking up a trend and detecting outliers. If most apps launch in 2 seconds, users immediately notice apps that launch in 500 ms or 5 seconds.</p>
<p>This means we can have 2 responsiveness thresholds per metric:</p>
<ul>
<li><p>a low threshold below which the app is <em>"significantly better than most apps"</em></p>
</li>
<li><p>a high threshold above which the app is <em>"significantly worst than most apps"</em></p>
</li>
</ul>
<p>These thresholds can't be universal, they're highly context-specific (low-end vs high-end devices, which other apps the users are exposed to, etc).</p>
<p>A similar approach can be found in the <a target="_blank" href="https://web.dev/inp/">Interaction to Next Paint (INP)</a> documentation, where two duration thresholds (200 ms &amp; 500 ms) define a score of GOOD / NEEDS IMPROVEMENT / POOR for each measured sample.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688703032860/bfa7857d-4d64-4fb4-b4e4-0952b2006904.png" alt class="image--center mx-auto" /></p>
<p>I have used similar thresholds at Square but wasn't sure how I could turn these into a single performance number, so I mostly focused on the P90 instead.</p>
<p>Ty Smith just <a target="_blank" href="https://www.threads.net/t/CuYZWYKLmWA/">pointed me</a> to the <a target="_blank" href="https://en.wikipedia.org/wiki/Apdex">Apdex</a> score which also defines two thresholds to splits samples in 3 buckets (Satisfied, Tolerated, Frustrated) then provides a score as a weighted average of counts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688703634699/4f2a13b6-4311-4d34-85a1-028be46ec620.png" alt="apdex process as described on apdex.org" class="image--center mx-auto" /></p>
<h1 id="heading-critical-metrics">Critical metrics</h1>
<p>App launch time is critical when customers use an app for short amounts of time. App launch time is less critical when customers intend to use an app continuously for a long period of time (e.g. a Lyft Driver app, a point-of-sale app, a Check-In app for visitors, etc).</p>
<p>After taking your order in a restaurant, a waiter needs to be able to input it in the point-of-sale app really quickly without thinking and relies on muscle memory. This requires a predictable UI and consistent tap latency. In that context, tap latency is critical whereas app launch time (which happens once a day) is not.</p>
<p>Similarly, scroll smoothness is probably more critical for a feed than for a list of settings</p>
<p>Product teams should identify which interactions are critical for their customers, then use responsiveness thresholds to prioritize performance work.</p>
<h1 id="heading-biais-in-aggregates">Biais in aggregates</h1>
<p>Production metrics yield large volumes of data, and product teams look at aggregate numbers. When applying uniform sampling or percentile-based aggregates, the resulting number will be biased toward the experience of high-activity users.</p>
<p>In a low-intent context where users don't need to stick around (e.g. Twitter), high performance typically correlates with high activity, so the metrics will not account for users that would have used the app if the performance was better. So a good performance number could hide really poor performance that led customers to not use the app. A possible solution to avoid this is to get a single number per device (or per user) and then aggregate that single number.</p>
<p>In a high-intent context where users absolutely need to use the app (e.g. to take a payment), aggregates are more likely to represent the full spectrum of users, and high activity often maps to customers that are more important to the business.</p>
<h1 id="heading-capturing-these-metrics-correctly-is-hard">Capturing these metrics correctly is hard</h1>
<p>Both Google &amp; Apple have failed to provide useful performance observability APIs that would allow app developers to easily track user-centric performance metrics.</p>
<p>Observability vendors do provide SDKs that track these for you, the problem is, they all do a terrible job of it. Seriously.</p>
<h1 id="heading-example-1-app-launch">Example 1: app launch</h1>
<p>The guidance and tooling for measuring app launch time on Android is lacking and inconsistent, let's dive into the details.</p>
<h2 id="heading-play-store-android-vitals">Play Store Android Vitals</h2>
<p>The Play Store Android Vitals provide <strong>sampled &amp; anonymous</strong> aggregate data on cold, warm &amp; hot launches.</p>
<ul>
<li><p>There's no ability to slice &amp; dice based on custom properties so it's really hard to use it to investigate or build a metric aligned with business needs.</p>
</li>
<li><p>There are no details on how the tracking is implemented. I had to look into the AOSP sources!</p>
</li>
</ul>
<p>The PlayStore reads data from an internal log tracker (not logcat), which the <code>system_server</code> process writes to. The reported launch time is the same value as what's logged to logcat by <code>ActivityTaskManager</code> on startup:</p>
<pre><code class="lang-kotlin">I/ActivityTaskManager: Displayed com.example.logstartup/.MainActivity: +<span class="hljs-number">1</span>s185ms
</code></pre>
<p>I investigated the logged values in <a target="_blank" href="https://dev.to/pyricau/android-vitals-how-adb-measures-app-startup-5n7">How adb measures App Startup 🔎</a> . That investigation surfaced that:</p>
<ul>
<li><p>The measured <strong>start</strong> time is when <code>system_server</code> receives the activity start intent, which can add a long time (in my debug test: ~300 ms / 30% of app startup) before the point where app developers have an influence (which is when the APK file starts loading).</p>
</li>
<li><p>The measured <strong>end</strong> time is the timestamp of when the window of the first resumed activity is done drawing.</p>
</li>
</ul>
<h2 id="heading-jetpack-macrobenchmark">Jetpack Macrobenchmark</h2>
<p><a target="_blank" href="https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview">Macrobenchmark</a> pulls its launch time data from <a target="_blank" href="https://perfetto.dev/">Perfetto</a>, which computes it based on atrace logs. For example the <code>ActivityMetricsLogger.LaunchingState</code> constructor <a target="_blank" href="https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.java;l=218;drc=853d0c6f52e3b815bd4e9061d7349d830a74359c">starts a trace</a>:</p>
<pre><code class="lang-java">        LaunchingState() {
            <span class="hljs-keyword">if</span> (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                <span class="hljs-keyword">return</span>;
            }
            <span class="hljs-comment">// Use an id because the launching app is not yet known before resolving intent.</span>
            sTraceSeqId++;
            mTraceName = <span class="hljs-string">"launchingActivity#"</span> + sTraceSeqId;
            Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName, <span class="hljs-number">0</span>);
        }
</code></pre>
<p>The trace is then <a target="_blank" href="https://cs.android.com/android/platform/superproject/+/master:external/perfetto/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql;l=24;drc=6e0c50cdf2f5f03271982e5b25689697edd7d0da">pulled by Perfetto</a>.</p>
<p>In summary, Jetpack Macrobenchmark, Perfetto, Play Store Android Vitals and logcat all report almost (Perfetto traces are started with a small offset) the same value. That's great, though it should be more systematically documented.</p>
<p>Unfortunately, measuring app launch time in-app to report production analytics is a different story.</p>
<h2 id="heading-production-analytics">Production analytics</h2>
<p>Most implementations capture cold app launch by measuring:</p>
<ul>
<li><p>The <strong>start</strong> as a timestamp when the first class loads or in an <code>onCreate()</code> callback.</p>
</li>
<li><p>The <strong>end</strong> when the first activity is resumed.</p>
</li>
</ul>
<p>Both the measured start and end are incorrect yet that's exactly what <a target="_blank" href="https://firebase.google.com/docs/perf-mon/app-start-foreground-background-traces?platform=android#app-start">Firebase Analytics is doing</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689010200176/5fe58872-1330-46dc-a22a-1cf8365a87cd.png" alt class="image--center mx-auto" /></p>
<p>This approach is <strong>incorrect</strong> for several reasons:</p>
<ul>
<li><p>This doesn't track <em>hot launch</em> &amp; <em>warm launch</em>, which the Play Store Android Vitals reports.</p>
</li>
<li><p>This also includes in the <em>cold launch</em> count cases where the process is first started in background for unrelated reasons and then an activity is a bit later launched (within e.g. a minute of process start). This will inflate the aggregate durations of cold start launch metrics and should not be accounted for. These fake cold starts should be ignored, by looking at process importance in <code>Application.onCreate()</code> and making sure that the first <code>Activity.onResume()</code> happens before a main thread post scheduled from <code>Application.onCreate()</code> (see <a target="_blank" href="https://dev.to/pyricau/android-vitals-why-did-my-process-start-4d0e">Android Vitals - Why did my process start? 🌄</a>).</p>
</li>
<li><p>The start time is entirely disconnected from what Play Store Android Vitals report: the loading of a random class or a <code>ContentProvider.onCreate()</code> call happens long after <code>system_server</code> receives the launch intent.</p>
</li>
<li><p><s>Unfortunately, there's no API that provides the launch intent timestamp. There should be!</s> Actually I just found out there's <a target="_blank" href="https://developer.android.com/reference/android/os/Process#getStartRequestedUptimeMillis()">Process.getStartRequestedUptimeMillis()</a> since API 33.</p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/reference/android/os/Process.html#getStartUptimeMillis()">Process.getStartUptimeMillis()</a> (API 28+) is captured right before APK loading and is a much better option for reporting launch start than <code>ContentProvider.onCreate()</code> or class load time. Unfortunately real production data surfaced that on API 28+ we sometimes get start times hours in the past (see <a target="_blank" href="https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4">Android Vitals - When did my app start? ⏱</a>).</p>
</li>
<li><p>The <strong>end time</strong> should be when the first frame traversal after first activity resume is committed (see <a target="_blank" href="https://developer.android.com/reference/android/view/ViewTreeObserver#registerFrameCommitCallback(java.lang.Runnable)">ViewTreeObserver.registerFrameCommitCallback</a>. The first traversal is likely to involve quite a bit of work, and capturing the end in <code>Activity.onResume()</code> ignores all that work.</p>
</li>
</ul>
<p>I ended up writing a longer article on how to do this right: <a target="_blank" href="https://blog.p-y.wtf/tracking-android-app-launch-in-production">Tracking Android App Launch in production</a>.</p>
<h1 id="heading-example-2-tap-interaction-latency">Example 2: tap interaction latency</h1>
<p>Observability vendors provide APIs to record spans. It's tempting to use spans to record the latency of an interaction, for example here how long it took to navigate to the About screen when tapping on the About button:</p>
<pre><code class="lang-kotlin">showAboutScreenButton.setOnClickListener {
  <span class="hljs-keyword">val</span> span = tracer.buildSpan(<span class="hljs-string">"showAboutScreen"</span>).start()

  findNavController().navigate(R.id.about_screen)

  span.finish()
}
</code></pre>
<p>Unfortunately, this doesn't account for the actual customer experience, as there's a lot of work that happens between when the finger leaves the screen and when the click listener is invoked, as well as between when the view hierarchy is updated and when the change is visible on display. Here's a Perfetto trace that demonstrates this common mistake:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688672245749/6dd74939-dc1d-401b-b706-6e6b921d28a7.png" alt class="image--center mx-auto" /></p>
<p>App developers often don't have the bandwidth to dive into this complexity and end up using incorrect but easier to implement measurements.</p>
<h1 id="heading-a-better-way">A better way?</h1>
<h2 id="heading-app-launch">App launch</h2>
<p>Here's what we need from Google:</p>
<ul>
<li><p>Documentation that covers how all tools across the ecosystem (Play Store Android Vitals, Logcat, Perfetto, Macrobenchmark) currently measure app launch times (for cold, hot and warm start).</p>
</li>
<li><p><s>New Android APIs to access the timestamp of when the launch activity intent was received by <code>system_server</code></s>. This <a target="_blank" href="https://developer.android.com/reference/android/os/Process#getStartRequestedUptimeMillis()">exists now</a>, very cool!</p>
</li>
<li><p>An Android SDK that implements the correct way to track app launch as highlighted in the section above. This should include cold start, warm start, hot start. I followed up and wrote an in-depth article on how one might go about that: <a target="_blank" href="https://blog.p-y.wtf/tracking-android-app-launch-in-production">Tracking Android App Launch in production</a></p>
</li>
<li><p>Firebase (and 3rd party vendors, OpenTelemetry, etc) could then use that SDK.</p>
</li>
</ul>
<h2 id="heading-going-forward">Going forward</h2>
<p>My long term goal with these articles and the <a target="_blank" href="https://github.com/square/papa">square/papa</a> library is that either Apple &amp; Google step up and provide strong guidance and more useful observability APIs (e.g. <a target="_blank" href="https://developer.android.com/topic/performance/jankstats">JankStats</a>), or we come together as a community and build solid foundational open source measurement SDKs.</p>
<p>Let's start a conversation! You can reach me on <a target="_blank" href="https://androiddev.social/@py">Mastodon</a>, <a target="_blank" href="https://bsky.app/profile/p-y.wtf">Bluesky</a>, <a target="_blank" href="https://www.threads.net/@py.ricau">Threads</a> or <a target="_blank" href="https://twitter.com/Piwai">Twitter</a> 🙃.</p>
<blockquote>
<p>Header image generated by DALL-E.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Avoid Java double brace initialization]]></title><description><![CDATA[TL;DR
Avoid doing this:
new HashMap<String, String>() {{
  put("key", value);
}};

Leak Trace
I was recently looking at the following leak trace from LeakCanary:
┬───
│ GC Root: Global variable in native code
│
├─ com.bugsnag.android.AnrPlugin instan...]]></description><link>https://blog.p-y.wtf/avoid-java-double-brace-initialization</link><guid isPermaLink="true">https://blog.p-y.wtf/avoid-java-double-brace-initialization</guid><category><![CDATA[Android]]></category><category><![CDATA[Java]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[patterns]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Tue, 27 Jun 2023 20:58:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1687899384200/0d27b831-a154-4a62-a3bb-aece042cdc82.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-tldr">TL;DR</h1>
<p>Avoid doing this:</p>
<pre><code class="lang-java"><span class="hljs-keyword">new</span> HashMap&lt;String, String&gt;() {{
  put(<span class="hljs-string">"key"</span>, value);
}};
</code></pre>
<h1 id="heading-leak-trace">Leak Trace</h1>
<p>I was recently looking at the following leak trace from <a target="_blank" href="https://square.github.io/leakcanary">LeakCanary</a>:</p>
<pre><code>┬───
│ GC Root: Global variable <span class="hljs-keyword">in</span> native code
│
├─ com.bugsnag.android.AnrPlugin instance
│    Leaking: UNKNOWN
│    ↓ AnrPlugin.client
│                ~~~~~~
├─ com.bugsnag.android.Client instance
│    Leaking: UNKNOWN
│    ↓ Client.breadcrumbState
│             ~~~~~~~~~~~~~~~
├─ com.bugsnag.android.BreadcrumbState instance
│    Leaking: UNKNOWN
│    ↓ BreadcrumbState.store
│                      ~~~~~
├─ com.bugsnag.android.Breadcrumb[] array
│    Leaking: UNKNOWN
│    ↓ Breadcrumb[<span class="hljs-number">494</span>]
│                ~~~~~
├─ com.bugsnag.android.Breadcrumb instance
│    Leaking: UNKNOWN
│    ↓ Breadcrumb.impl
│                 ~~~~
├─ com.bugsnag.android.BreadcrumbInternal instance
│    Leaking: UNKNOWN
│    ↓ BreadcrumbInternal.metadata
│                         ~~~~~~~~
├─ com.example.MainActivity$<span class="hljs-number">1</span> instance
│    Leaking: UNKNOWN
│    Anonymous subclass <span class="hljs-keyword">of</span> java.util.HashMap
│    ↓ MainActivity$<span class="hljs-number">1.</span>this$<span class="hljs-number">0</span>
│                     ~~~~~~
╰→ com.example.MainActivity instance
​     Leaking: YES (​Activity#mDestroyed is <span class="hljs-literal">true</span>)
</code></pre><p>When opening a leak trace, my first step is to look at the object at the bottom to understand what its lifecycle is, as that'll help me understand if other objects in the leak trace are expected to have the same lifecycle or not.</p>
<p>At the bottom, we see: </p>
<pre><code>╰→ com.example.MainActivity instance
​     Leaking: YES (​Activity#mDestroyed is <span class="hljs-literal">true</span>)
</code></pre><p>The activity is destroyed and should have been garbage collected, but it's held in memory.</p>
<p>So, at that point, I start looking for known types in the leak trace and try to figure out if they belong to the same destroyed scope (=&gt; they're leaking) or to a higher scope (=&gt; they're not leaking).</p>
<p>At the top, we see:</p>
<pre><code>├─ com.bugsnag.android.Client instance
│    Leaking: UNKNOWN
</code></pre><p>Our <a target="_blank" href="https://bugsnag.com/">BugSnag</a> client is a singleton that we use for crash reporting purposes, we create a single instance per app, so it's not leaking.</p>
<pre><code>├─ com.bugsnag.android.Client instance
│    Leaking: NO
</code></pre><p>So now we can update the leak trace to focus only on the section from the last <code>Leaking: NO</code> to the first <code>Leaking: YES</code>:</p>
<pre><code>…
├─ com.bugsnag.android.Client instance
│    Leaking: NO
│    ↓ Client.breadcrumbState
│             ~~~~~~~~~~~~~~~
├─ com.bugsnag.android.BreadcrumbState instance
│    Leaking: UNKNOWN
│    ↓ BreadcrumbState.store
│                      ~~~~~
├─ com.bugsnag.android.Breadcrumb[] array
│    Leaking: UNKNOWN
│    ↓ Breadcrumb[<span class="hljs-number">494</span>]
│                ~~~~~
├─ com.bugsnag.android.Breadcrumb instance
│    Leaking: UNKNOWN
│    ↓ Breadcrumb.impl
│                 ~~~~
├─ com.bugsnag.android.BreadcrumbInternal instance
│    Leaking: UNKNOWN
│    ↓ BreadcrumbInternal.metadata
│                         ~~~~~~~~
├─ com.example.MainActivity$<span class="hljs-number">1</span> instance
│    Leaking: UNKNOWN
│    Anonymous subclass <span class="hljs-keyword">of</span> java.util.HashMap
│    ↓ MainActivity$<span class="hljs-number">1.</span>this$<span class="hljs-number">0</span>
│                     ~~~~~~
╰→ com.example.MainActivity instance
​     Leaking: YES (​Activity#mDestroyed is <span class="hljs-literal">true</span>)
</code></pre><p>The BugSnag client keeps a ring buffer of <a target="_blank" href="https://docs.bugsnag.com/platforms/android/automatically-captured-data/#breadcrumbs">breadcrumbs</a>. Those are meant to stay in memory, they're not leaking. So let's update again:</p>
<pre><code>├─ com.bugsnag.android.BreadcrumbInternal instance
│    Leaking: NO
</code></pre><p>Once again, we update the leak trace to focus only on the section from the last <code>Leaking: NO</code> to the first <code>Leaking: YES</code>:</p>
<pre><code>…
├─ com.bugsnag.android.BreadcrumbInternal instance
│    Leaking: NO
│    ↓ BreadcrumbInternal.metadata
│                         ~~~~~~~~
├─ com.example.MainActivity$<span class="hljs-number">1</span> instance
│    Leaking: UNKNOWN
│    Anonymous subclass <span class="hljs-keyword">of</span> java.util.HashMap
│    ↓ MainActivity$<span class="hljs-number">1.</span>this$<span class="hljs-number">0</span>
│                     ~~~~~~
╰→ com.example.MainActivity instance
​     Leaking: YES (​Activity#mDestroyed is <span class="hljs-literal">true</span>)
</code></pre><ul>
<li><code>BreadcrumbInternal.metadata</code>: the leak trace goes through the metadata field of the breadcrumb implementation.</li>
<li><code>MainActivity$1 instance Anonymous subclass of java.util.HashMap</code>: <code>MainActivity$1</code> is an anonymous subclass of <code>HashMap</code>, defined in <code>MainActivity</code>. It's the first anonymous class defined from the top of <code>MainActivity.java</code> (because <code>$1</code>).</li>
<li><code>this$0</code>: every anonymous class has an implicit field reference to the outer class in which it is defined, and that field is always named <code>this$0</code>.</li>
</ul>
<p>Translated to English: one of the breadcrumbs logged to BugSnag has a metadata map that is an anonymous subclass of HashMap that holds a reference to an outer class, the destroyed activity.</p>
<p>Let's look at where we log breadcrumbs in MainActivity:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">logSavingTicket</span><span class="hljs-params">(String ticketId)</span> </span>{
  Map&lt;String, Object&gt; metadata = <span class="hljs-keyword">new</span> HashMap&lt;String, Object&gt;() {{
    put(<span class="hljs-string">"ticketId"</span>, ticketId);
  }};
  bugsnagClient.leaveBreadcrumb(<span class="hljs-string">"Saving Ticket"</span>, metadata, LOG);
}
</code></pre>
<p>This code is leveraging a fun Java pattern known as <strong>double brace initialization</strong>. It allows you to create a HashMap and initialize it at the same time by adding code to the constructor of an anonymous subclass of HashMap.</p>
<pre><code class="lang-java"><span class="hljs-keyword">new</span> HashMap&lt;String, Object&gt;() {{
  put(<span class="hljs-string">"ticketId"</span>, ticketId);
}};
</code></pre>
<p>Java anonymous classes always have <strong>implicit references to their outer class</strong>. So this code:</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">logSavingTicket</span><span class="hljs-params">(String ticketId)</span> </span>{
  Map&lt;String, Object&gt; metadata = <span class="hljs-keyword">new</span> HashMap&lt;String, Object&gt;() {{
    put(<span class="hljs-string">"ticketId"</span>, ticketId);
  }};
  bugsnagClient.leaveBreadcrumb(<span class="hljs-string">"Saving Ticket"</span>, metadata, LOG);
}
</code></pre>
<p>is actually compiled as:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span>$1 <span class="hljs-keyword">extends</span> <span class="hljs-title">HashMap</span>&lt;<span class="hljs-title">String</span>, <span class="hljs-title">Object</span>&gt; </span>{
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> MainActivity <span class="hljs-keyword">this</span>$<span class="hljs-number">1</span>;

  MainActivity$<span class="hljs-number">1</span>(MainActivity <span class="hljs-keyword">this</span>$<span class="hljs-number">1</span>, String ticketId) {
     <span class="hljs-keyword">this</span>.<span class="hljs-keyword">this</span>$<span class="hljs-number">1</span> = <span class="hljs-keyword">this</span>$<span class="hljs-number">1</span>;
     put(<span class="hljs-string">"ticketId"</span>, ticketId);
  }
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">logSavingTicket</span><span class="hljs-params">(String ticketId)</span> </span>{
  Map&lt;String, Object&gt; metadata = <span class="hljs-keyword">new</span> MainActivity$<span class="hljs-number">1</span>(<span class="hljs-keyword">this</span>, ticketId);
  bugsnagClient.leaveBreadcrumb(<span class="hljs-string">"Saving Ticket"</span>, metadata, LOG);
}
</code></pre>
<p>As a result, the breadcrumb is holding on to the destroyed activity instance.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Avoid using Java double brace initialization, it's cute but creates additional classes for no good reason and risks introducing leaks. Instead, you can do things the boring and safe way:</p>
<pre><code class="lang-java">Map&lt;String, Object&gt; metadata = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
metadata.put(<span class="hljs-string">"ticketId"</span>, ticketId);
bugsnagClient.leaveBreadcrumb(<span class="hljs-string">"Saving Ticket"</span>, metadata, LOG);
</code></pre>
<p>Or leverage <code>Collections.singletonMap()</code> which make this nicer:</p>
<pre><code><span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">Object</span>&gt; metadata = singletonMap(<span class="hljs-string">"ticketId"</span>, ticketId);
bugsnagClient.leaveBreadcrumb(<span class="hljs-string">"Saving Ticket"</span>, metadata, LOG);
</code></pre><p>Or just convert the file to Kotlin.</p>
<blockquote>
<p>Header image generated by DALL-E, prompt: "A coffee mug wearing orthodontic braces, digital art".</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Statistically Rigorous Android Macrobenchmarks]]></title><description><![CDATA[👋 Hi, this is P.Y., I work as an Android Engineer at Block. As we started evaluating the performance impact of software changes, I brushed up on my statistics classes, talked to industry peers, read several papers, wrote an internal doc to help us i...]]></description><link>https://blog.p-y.wtf/statistically-rigorous-android-macrobenchmarks</link><guid isPermaLink="true">https://blog.p-y.wtf/statistically-rigorous-android-macrobenchmarks</guid><category><![CDATA[Android]]></category><category><![CDATA[performance]]></category><category><![CDATA[Benchmark]]></category><category><![CDATA[statistics]]></category><category><![CDATA[Macrobenchmark]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Mon, 01 May 2023 20:57:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1682973661722/4b5537a0-24b9-46a9-8cb6-9159c5378155.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>👋 Hi, this is P.Y., I work as an Android Engineer at <a target="_blank" href="https://block.xyz/">Block</a>. As we started evaluating the performance impact of software changes, I brushed up on my statistics classes, talked to industry peers, read several papers, wrote an internal doc to help us interpret benchmark results correctly and turned it into this article.</p>
</blockquote>
<p>At Square, we leverage <a target="_blank" href="https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview">Jetpack Macrobenchmark</a> to benchmark the UI latency of critical user interactions. We trace user interactions with <a target="_blank" href="https://developer.android.com/reference/android/os/Trace#beginAsyncSection(java.lang.String,%20int)">Trace.beginAsyncSection</a> and capture their durations with <a target="_blank" href="https://developer.android.com/reference/kotlin/androidx/benchmark/macro/TraceSectionMetric">TraceSectionMetric</a>.</p>
<p>The official documentation shows how to <a target="_blank" href="https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview#benchmark-results">see</a> and <a target="_blank" href="https://developer.android.com/topic/performance/benchmarking/benchmarking-in-ci#collect-results">collect</a> the results, but <strong>does not provide any guidance on interpreting these results</strong> in the context of a software change. That's unfortunate: the primary goal of benchmarks is typically to compare two situations and draw conclusions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682693989441/381053c0-79be-481b-b052-715e39a28c9c.png" alt class="image--center mx-auto" /></p>
<p>Should I compare the min, the median, or the max? If I get a difference of 1 ms, is that an improvement? What if I run the same 2 benchmarks again and get different results? How do I know I can trust my benchmark results?</p>
<p>The <a target="_blank" href="https://dri.es/files/oopsla07-georges.pdf">Statistically rigorous java performance evaluation</a> paper demonstrates how <strong>comparing two aggregate values</strong> (e.g. min, max, mean, median) from a benchmark can <strong>be misleading and lead to incorrect conclusions</strong>.</p>
<p>This article leverages statistics fundamentals to suggest a scientifically sound approach to analyzing Jetpack Macrobenchmark results. If you're planning on setting up infrastructure to measure the impact of changes on performance, you should probably follow a similar approach. This is a lot of work, so you might be better off buying a solution from a vendor. I talked extensively with <a target="_blank" href="https://www.ryanjeffreybrooks.com">Ryan Brooks</a> from <a target="_blank" href="https://www.emergetools.com">Emerge Tools</a> and was pleased to learn that they follow a similar approach to what I'm describing in this article.</p>
<p>Towards the end, I'll also provide an interpretation of the statistics behind the <a target="_blank" href="https://medium.com/androiddevelopers/fighting-regressions-with-benchmarks-in-ci-6ea9a14b5c71">Fighting regressions with Benchmarks in CI</a> article that the Jetpack documentation recommends as a resource.</p>
<blockquote>
<p>I wasn't particularly strong at statistics in college. If you notice a mistake please let me know!</p>
</blockquote>
<h1 id="heading-deterministic-benchmarks">Deterministic benchmarks</h1>
<p>In an <strong>ideal world</strong>, running a benchmark in identical conditions (e.g. a specific version of our software on specific hardware with a specific account) would always result in the same performance and the same benchmark results. Changing some of these conditions, e.g. the hardware or software, would provide a different result.</p>
<p>For example, let's say we have an ideal work benchmark where we run 100 iterations, and for each iteration the measured duration is always 242 ms. So the mean is 242 ms. We optimize the code, run the 100 iterations again, and the measured duration is now 212 ms for each iteration. We can conclude that we improved performance by 30 ms or 12.4%. But if every iteration yields a slightly different result, we can't make that claim.</p>
<blockquote>
<p>💡 Deterministic measurements can be summarized with a mean or sum. Unfortunately, Jetpack Macrobenchmark runs are not deterministic. It's possible to design alternate benchmarking methods that don't rely on time but instead track deterministic measurements (e.g. number of CPU instructions, number of IO reads, number of allocations), which provide clear-cut results. However, when evaluating <strong>performance as perceived by humans, time is the only measurement that matters</strong>.</p>
</blockquote>
<h1 id="heading-fixed-environment">Fixed environment</h1>
<p>If we change more than one condition at a time in between two benchmark runs (e.g. both the software and hardware), we can't tell if a benchmark result change is caused by a software change or hardware change.</p>
<p>So, as a rule of thumb, we should <strong>only have one changing variable between two benchmark changes</strong>. Since we're interested in tracking the impact of code changes (i.e. the app software stack), we need everything else to be constant (hardware, OS version, account, etc). For example, we should not compare one benchmark on a phone with version 6 of the app with another one on a tablet with version 7 of the app.</p>
<p>It's still worth running a specific benchmark with several varying conditions (phone, tablet, types of users, etc), as long as we only do paired comparisons of the before &amp; after change.</p>
<h1 id="heading-nondeterminism-amp-probability-distribution">Nondeterminism &amp; probability distribution</h1>
<p>With Jetpack Macrobenchmark, even though we're in a fixed environment (identical device, OS version, etc), we're still benchmarking incredibly complex systems with many variables at play, such as dynamic CPU frequencies, RAM bandwidth, GPU frequency, OS processes running concurrently, etc. This introduces variation in the measured performance, so <strong>every iteration will give us a different result</strong>.</p>
<blockquote>
<p>💡 One core idea is that the performance our benchmark is trying to measure follows a <strong>probability distribution</strong>: a mathematical function that gives the probabilities of occurrence of different possible outcomes for an experiment (<a target="_blank" href="https://en.wikipedia.org/wiki/Probability_distribution">wiki</a>). We don't know the actual function for that probability distribution.</p>
</blockquote>
<p>By running benchmarks before &amp; after a change, we sample two probability distributions. We can draw <a target="_blank" href="https://en.wikipedia.org/wiki/Histogram">histograms</a> to get an approximate representation of the sample distributions. Let's look at an example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682966450818/28f5d697-dd5a-46c7-9a78-a568362e47f3.png" alt class="image--center mx-auto" /></p>
<p>The blue benchmark has a median of 295.8 ms and the red benchmark has a median of 292.9 ms. We could conclude that there's a 2.9 ms improvement, but the histograms show that these two sample distributions look very different. Is the red benchmark better than the blue one?</p>
<p>More importantly, if we run the before or the after benchmark several times, we will get a different median. Here, the difference in the medians is small enough that we might sometimes get better a median before the code change (blue), even though this time red was better.</p>
<p>We have to use <strong>statistical techniques</strong> to derive valid conclusions or surface inconclusive benchmarks.</p>
<h1 id="heading-identifying-sources-of-variation">Identifying sources of variation</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682966450818/28f5d697-dd5a-46c7-9a78-a568362e47f3.png" alt class="image--center mx-auto" /></p>
<p>In the previous chart, the first benchmark (blue) seems to have two modes (peaks). Instead of a histogram, we can graph the measured values in iteration order:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682966621059/dfe2eec2-38c1-41c6-9986-ed716eb37132.png" alt class="image--center mx-auto" /></p>
<p>This tells a much different story! After investigating, we discover that this benchmark triggered <a target="_blank" href="https://en.wikipedia.org/wiki/Dynamic_frequency_scaling">thermal throttling</a>: when a device temperature goes over a target, the CPU frequencies are scaled down until the temperature goes down. As a result, everything on the device runs slower.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682967585626/d8d0c5d5-6090-46a9-8088-7170a069fca2.jpeg" alt class="image--center mx-auto" /></p>
<p>We can work around this variance by avoiding thermal throttling: using a device with better thermal dissipation, running the benchmark in a <strong>colder room</strong>, or running the entire benchmark at a lower CPU frequency.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682966450818/28f5d697-dd5a-46c7-9a78-a568362e47f3.png" alt class="image--center mx-auto" /></p>
<p>Similarly, notice how the red histogram above has a long right tail with a few high measures. Graphing the benchmark in iteration order, we see the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682967700158/4c11c024-4a93-4e77-aa0c-93f9db092b18.png" alt class="image--center mx-auto" /></p>
<p>This looks like a warm-up effect, where the first iterations are slower, due to e.g. cold caches or JIT compilation. To work around this, we could install the app with full AOT compiling and perform warm-up iterations.</p>
<p>🤨 <strong>Should we leave these sources of variation in to keep the benchmarks realistic?</strong></p>
<p>No. We evaluate the impact of the change as precisely as we can by removing as many variables as we can. If we want to measure performance with &amp; without thermal throttling, or cold start performance, we can also test for those as separate benchmarks (e.g. keep the code identical and run the benchmark with and without thermal throttling to evaluate the impact that thermal throttling has on runtime performance).</p>
<h1 id="heading-normal-distribution">Normal distribution</h1>
<p>As we remove all identified sources of variation, the benchmark histograms start to look more and more like a bell curve, a graph depicting the <a target="_blank" href="https://en.wikipedia.org/wiki/Normal_distribution">normal distribution</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682967781674/dcba8494-659c-4e23-b69f-955c400f200a.png" alt class="image--center mx-auto" /></p>
<p>A normal distribution is a parametric distribution, i.e. a distribution that is based on a mathematical function whose shape and range are determined by distribution parameters. A normal distribution is symmetric and centered around its mean (which is therefore equal to its median), and the spread of its tails is defined by a second parameter, its standard deviation.</p>
<p>Comparing two normal distributions with a <strong>roughly equal standard deviation</strong> is easy: one distribution is simply shifted. Since they're symmetric around their mean we can just <strong>compute the difference between the two means</strong>.</p>
<p>However, we're not certain that the underlying distributions are normal, we can only estimate the <strong>likelihood that they're normal</strong> based on the sample data we got from the benchmark.</p>
<h1 id="heading-normality-fitness-test">Normality fitness test</h1>
<p>There are many popular normality tests to assess the probability that a set of sample measures follows a normal distribution. Unfortunately, the golden standard to this day seems to be the eyeball test, i.e. generate a histogram or a <a target="_blank" href="https://en.wikipedia.org/wiki/Q%E2%80%93Q_plot">Q-Q plot</a> and see if it looks normal. The eyeball test is hard to automate 😏.</p>
<p>The <a target="_blank" href="https://www.tandfonline.com/doi/pdf/10.1080/00949655.2010.520163">Comparisons of various types of normality tests</a> study concludes that selecting the best fitness tests depends on the distribution shape:</p>
<p><em>For symmetric short-tailed distributions, D’Agostino and Shapiro–Wilk tests have better power. For symmetric long-tailed distributions, the power of Jarque–Bera and D’Agostino tests is quite comparable with the Shapiro–Wilk test. As for asymmetric distributions, the Shapiro–Wilk test is the most powerful test followed by the Anderson–Darling test.</em></p>
<p>The <a target="_blank" href="https://github.com/datumbox/datumbox-framework">DatumBox Framework</a> provides a <code>ShapiroWilk.test()</code> function that we can use to assess if the measures are not normally distributed, in which case we shouldn't use the benchmark results as is and need to perform additional work to fix error sources.</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// alpha level (5%): probability of wrongly rejecting the hypothesis that the distribution is normal (null hypothesis).</span>
<span class="hljs-keyword">val</span> alphaLevel = <span class="hljs-number">0.05</span>
<span class="hljs-keyword">val</span> rejectNullHypothesis = ShapiroWilk.test(FlatDataCollection(distribution), alphaLevel)
<span class="hljs-keyword">if</span> (rejectNullHypothesis) {
  error(<span class="hljs-string">"Distribution failed normality test"</span>)
}
</code></pre>
<h1 id="heading-sample-mean-difference">Sample mean difference</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682967781674/dcba8494-659c-4e23-b69f-955c400f200a.png" alt class="image--center mx-auto" /></p>
<p>In the example above, the data for the first benchmark was generated by sampling two normal distributions, one with a mean of 242 ms and the other with a mean of 212 ms, so the <strong>real mean difference was 30 ms</strong>. However, the first sample distribution has a mean of 244 ms and the second has a mean of 209 ms, so the <strong>sample mean difference is 35 ms, 17% higher</strong>.</p>
<p>As you can see, even if the likelihood of normality is high enough, we don't know the actual real means of the underlying distributions, so we can't compute the exact difference between the two real means, and the difference between the two sample means can be significantly off. However, <strong>we can compute a likely interval for where the mean difference falls</strong>.</p>
<h1 id="heading-confidence-interval-for-a-difference-between-two-means">Confidence interval for a difference between two means</h1>
<p>It's formula time (<a target="_blank" href="https://sphweb.bumc.bu.edu/otlt/mph-modules/bs/bs704_confidence_intervals/bs704_confidence_intervals5.html">source</a>)! If the following is true:</p>
<ul>
<li><p>Both benchmarks yield sample data that follows a normal distribution.</p>
</li>
<li><p>Each benchmark has more than 30 iterations.</p>
</li>
<li><p>The variance of one benchmark is no more than double the variance of the other benchmark.</p>
</li>
</ul>
<p>Then the <strong>confidence interval for the difference of means</strong> between the two benchmarks can be computed as:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682969792691/59f16ec8-64ef-48c0-8a80-670ecf3926b1.gif" alt class="image--center mx-auto" /></p>
<ul>
<li><p>The confidence interval is the difference between the means, plus minus the margin of error.</p>
</li>
<li><p><code>z</code> is the <a target="_blank" href="https://en.wikipedia.org/wiki/Standard_score">Z score</a>.</p>
</li>
<li><p><code>Sp</code> is the pooled estimate of the common standard deviation, computed as:</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682969803854/db3a9aec-51e5-4f8a-a315-52bc5a61955e.gif" alt class="image--center mx-auto" /></p>
<p>Note: if the confidence interval crosses 0 (i.e. the interval goes from a positive value to a negative value) then the benchmarks do not demonstrate any impact for the change.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682967781674/dcba8494-659c-4e23-b69f-955c400f200a.png" alt class="image--center mx-auto" /></p>
<p>In this example above, the real mean difference from normal distributions used to generate sample data was <strong>30 ms</strong>. The sample mean difference was <strong>35 ms</strong>, but if we sample again we'll get another mean difference. The formula shared above allows us to say that the <strong>95% confidence interval</strong> of the mean difference is an improvement of somewhere between <strong>24.92 ms</strong> and <strong>45.10 ms</strong>. If we run the two benchmarks 100 times, there's a 95% probability that the sample mean difference will fall in that interval.</p>
<p>In other words, we're fairly confident that the real mean difference is somewhere between 24.92 ms and 45.10 ms. But we can't get any more precise than that unless we remove the sources of noise or increase the number of iterations by the square of the target increase. E.g. if we wanted to <strong>divide the confidence interval range by 3</strong>, we'd need to <strong>multiply the number of iterations by 9</strong>, from 100 to 900 iterations 🤯.</p>
<h1 id="heading-coefficient-of-variation-check"><strong>Coefficient of variation check</strong></h1>
<p>The confidence interval grows linearly with the standard deviation, so if we want our benchmarks to be conclusive we need to keep the standard deviation in check. One way to do this is to compute the <a target="_blank" href="https://en.wikipedia.org/wiki/Coefficient_of_variation">Coefficient of Variation</a> (standard deviation divided by mean) for each benchmark and ensure that it's below a threshold.</p>
<h1 id="heading-putting-it-all-together">Putting it all together</h1>
<p>I created a <a target="_blank" href="https://docs.google.com/spreadsheets/d/1fqR99ROoSYtMrkoL5PWkUWDsW0E3JEXSTcXQqZJJCow/template/preview">Google Spreadsheet template</a> that does most of the stats math for you (except the Shapiro-Wilk normality test) so that you can easily compute the impact of a change and the validity of your benchmarks. Click <strong>USE TEMPLATE</strong> and paste the iteration results of each benchmark runs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682971701809/e64e069d-e76d-4b3e-bf21-d09aa208192d.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-what-if-the-benchmarks-arent-normally-distributed"><strong>What if the benchmarks aren't normally distributed?</strong></h1>
<h2 id="heading-remove-sources-of-variation">Remove sources of variation</h2>
<p>You can fix a benchmark distribution that doesn't look normal by fixing sources of variation. The simplest way to do this is to open the perfetto trace for the slowest iteration and the faster interaction, figure out what's causing the difference and fix it.</p>
<h2 id="heading-remove-outliers">Remove outliers</h2>
<p>If we cannot isolate &amp; remove error sources, we can try to eliminate them by cleaning up our data set, in 2 ways:</p>
<ul>
<li><p>Implement a sliding window such as only the last N iterations of a run are considered. This helps with sources of variation like classloading or JIT compiling. This is effectively the same as performing a benchmark warm-up.</p>
</li>
<li><p>Eliminating outliers</p>
<ul>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/Outlier#Tukey's_fences">Tukey's fences</a>: removing values that are below Q1 - 1.5 <em>IQR or above Q3 + 1.5</em> IQR. Note: Q1 = p25, Q3 = p75, IQR = Q3 - Q1.</p>
</li>
<li><p>Another approach is to discard outliers that are more than ~2 standard deviations from the mean.</p>
</li>
<li><p>This can work if the distribution does otherwise have a normal shape.</p>
</li>
</ul>
</li>
</ul>
<p>Generally speaking, you're better off investigating and finding the root cause rather than blindly removing outliers.</p>
<h2 id="heading-try-a-log-normal-distribution-fit">Try a log-normal distribution fit</h2>
<p>From <a target="_blank" href="https://htor.inf.ethz.ch/publications/img/hoefler-scientific-benchmarking.pdf">Scientific Benchmarking of Parallel Computing Systems</a>:</p>
<p><em>Many nondeterministic measurements that are always positive are skewed to the right and have a long tail following a so-called log-normal distribution. Such measurements can be normalized by transforming each observation logarithmically. Such distributions are typically summarized with the log-average, which is identical to the geometric mean.</em></p>
<p>I haven't spent too much time looking into this as our distributions pass normality tests.</p>
<h2 id="heading-kolmogorovsmirnov-test">Kolmogorov–Smirnov test</h2>
<p>From <a target="_blank" href="https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test">Wikipedia</a>:</p>
<p><em>The Kolmogorov–Smirnov test is a nonparametric test of the equality of continuous, one-dimensional probability distributions that can be used to compare a sample with a reference probability distribution (one-sample K–S test), or to compare two samples (two-sample K–S test).</em></p>
<p>We can leverage this test to compute the probability that our two benchmarks are from the same probability distribution, to establish whether a change has a statistically significant impact on performance. However, this won't tell us anything about the size of that impact.</p>
<h2 id="heading-central-limit-theorem">Central Limit Theorem</h2>
<p>From <a target="_blank" href="https://en.wikipedia.org/wiki/Central_limit_theorem">Wikipedia</a>:</p>
<p><em>The</em> <strong><em>central limit theorem (CLT)</em></strong> <em>establishes that, in many situations,</em> <strong><em>for identically distributed independent samples*</em></strong>, the standardized sample mean tends towards the standard normal distribution even if the original variables themselves are not normally distributed.*</p>
<p>When we run a benchmark several times and keep the mean for each run, that mean will be normally distributed around the real mean of the underlying benchmark distribution. If we do this before and after the change, we now have 2 normal distributions of means.</p>
<p>We can leverage this to detect regressions by applying a <a target="_blank" href="https://en.wikipedia.org/wiki/Student%27s_t-test#Unpaired_and_paired_two-sample_t-tests">two-sample t-test</a> which will tell us the probability that the two normal distributions of means have the same mean. If they don't, then we can conclude that the underlying benchmark distributions have a different mean, i.e. that there is a change, but we can't conclude much in terms of the impact of the change.</p>
<p>This approach is what Google has been recommending in an article frequently referenced by Android developers: <a target="_blank" href="https://medium.com/androiddevelopers/fighting-regressions-with-benchmarks-in-ci-6ea9a14b5c71">Fighting regressions with Benchmarks in CI</a>. The article calls it "step-fitting of results" and doesn't mention the CLT or two-sample t-tests, probably because the technique was copied over from <a target="_blank" href="https://skia.org/docs/dev/testing/skiaperf/">Skia Perf</a>.</p>
<p>If you assume that sources of variation are a fact of life and that you won't be able to fix them and consistently get normal distributions with stable variance, then this approach is probably your best option.</p>
<p>Running N benchmarks before and after a change is a lot more expensive, so the suggested approach is to run a single benchmark per change, wait until N/2 changes have landed, then separate the N benchmarks around the change in a before and after group. Alternatively, you could leverage <a target="_blank" href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)">bootstrapping</a>.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<ul>
<li><p>Jetpack Macrobenchmark runs are non-deterministic. Don't look at aggregate values such as min, max, mean or median.</p>
</li>
<li><p>Check that the benchmark measures follow a normal distribution.</p>
<ul>
<li><p>If not, find the root cause. Common fixes:</p>
<ul>
<li><p>Killing / uninstalling unrelated apps.</p>
</li>
<li><p>Setting the device to Airplane Mode during the measured portion.</p>
</li>
<li><p>Full AOT compiling of the APK.</p>
</li>
<li><p>Locking CPU frequencies.</p>
</li>
<li><p>Stable room temperature.</p>
</li>
<li><p>Plugging in the device to power.</p>
</li>
<li><p>Killing Android Studio and anything that might run random ADB commands while profiling.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Compute the <strong>confidence interval for a difference between two means</strong> (see this <a target="_blank" href="https://docs.google.com/spreadsheets/d/1fqR99ROoSYtMrkoL5PWkUWDsW0E3JEXSTcXQqZJJCow/template/preview">Google Spreadsheet template</a>), that's the result you want to share. Remember to share just the interval and hide the specific sample mean difference from the 2 benchmarks you ran, as it has no meaning.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Callback leaks: cancel your Picasso requests!]]></title><description><![CDATA[👋 Hi, this is P.Y., I work as an Android Engineer at Block. Every month I organize an internal Ensemble Leak Hunting session where we look at the top leaks reported by LeakCanary to learn how to read]]></description><link>https://blog.p-y.wtf/callback-leaks-cancel-your-picasso-requests</link><guid isPermaLink="true">https://blog.p-y.wtf/callback-leaks-cancel-your-picasso-requests</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[performance]]></category><category><![CDATA[Picasso]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Tue, 17 Jan 2023 21:06:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673986764034/e75de70f-a135-4493-96f0-193c08daf904.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>👋 Hi, this is P.Y., I work as an Android Engineer at <a href="https://block.xyz/">Block</a>. Every month I organize an internal Ensemble Leak Hunting session where we look at the top leaks reported by LeakCanary to learn how to read and investigate leak traces. This is a write-up of our latest investigation which surfaced a common mistake when using <a href="https://github.com/square/picasso">Picasso</a>.</p>
</blockquote>
<p>Before we start, you might want to get a quick refresher on <a href="https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/">how to read leak traces</a>. In this article, we'll find the root cause of the leak, discuss how to fix it, and what could change in Picasso to help avoid this common mistake.</p>
<p>Here's the leak trace:</p>
<pre><code class="language-plaintext">┬───
│ GC Root: System class
│
├─ com.example.PicassoHolder class
│    Leaking: NO (a class is never leaking)
│    ↓ static PicassoHolder.singleton
├─ com.squareup.picasso3.Picasso instance
│    ↓ Picasso.targetToAction
│              ~~~~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│    ↓ WeakHashMap.table
│                  ~~~~~
├─ java.util.WeakHashMap$Entry[] array
│    ↓ WeakHashMap$Entry[0]
│                       ~~~
├─ java.util.WeakHashMap$Entry instance
│    ↓ WeakHashMap$Entry.value
│                        ~~~~~
├─ com.squareup.picasso3.ImageViewAction instance
│    ↓ ImageViewAction.callback
│                      ~~~~~~~~
├─ com.example.ProfileLayout\(loadImage\)1 instance
│    Anonymous class implementing com.squareup.picasso3.Callback
│    ↓ ProfileController\(loadImage\)1.this$0
│                                    ~~~~~~
╰→ com.example.ProfileLayout instance
​     Leaking: YES (ObjectWatcher was watching this because ProfileLayout received View#onDetachedFromWindow() callback)
</code></pre>
<p><a href="https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNpl0VFrwjAQAOC_EkIfHChzr30Q1CoVJsgm20PSh7O5tsE0J2k6Eet_X9aKbOwtd_ddOO6uPCeFPOaFoXNegfNsn0j7RuSFlDudQ9NQSkahe87YZMIabUuDnmwIZmwu7Vzc1U951nlwJfo9zXOvyXZsMfpEOKbQVFs4PUm76PsCOxjs2HIk-a96tLLeXUQmeZDLQUoupiHRsUT8k5m0yaC-wLThv7XY1FDih8bzMEAQ60HkYMwB8mPHUrFzVGiDr3Ch1keGQPVt0Uvg6X3ASjfRtGMb8VdngfAxr9HVoFVY3FVaxiT3FdYoeRyeCgtojZdc2lug0Hp6v9icx961OObtSYHHREPpoOZxAaZ5ZFdKe3KPJPbhdrhQf6jbN1XLlF8"><img src="https://mermaid.ink/img/pako:eNpl0VFrwjAQAOC_EkIfHChzr30Q1CoVJsgm20PSh7O5tsE0J2k6Eet_X9aKbOwtd_ddOO6uPCeFPOaFoXNegfNsn0j7RuSFlDudQ9NQSkahe87YZMIabUuDnmwIZmwu7Vzc1U951nlwJfo9zXOvyXZsMfpEOKbQVFs4PUm76PsCOxjs2HIk-a96tLLeXUQmeZDLQUoupiHRsUT8k5m0yaC-wLThv7XY1FDih8bzMEAQ60HkYMwB8mPHUrFzVGiDr3Ch1keGQPVt0Uvg6X3ASjfRtGMb8VdngfAxr9HVoFVY3FVaxiT3FdYoeRyeCgtojZdc2lug0Hp6v9icx961OObtSYHHREPpoOZxAaZ5ZFdKe3KPJPbhdrhQf6jbN1XLlF8?type=png" alt="" style="display:block;margin:0 auto" /></a></p>
<h1>Picasso singleton</h1>
<pre><code class="language-plaintext">┬───
│ GC Root: System class
│
├─ com.example.PicassoHolder class
│    Leaking: NO (a class is never leaking)
│    ↓ static PicassoHolder.singleton
├─ com.squareup.picasso3.Picasso instance
...
</code></pre>
<p>At the top of the leak trace, a Picasso instance. We know that Picasso instances are long-lived singletons, meant to stay around as UI comes and goes. So we know this is legit and we can move the investigation further down in the leak trace.</p>
<h1><code>Picasso.targetToAction</code></h1>
<pre><code class="language-plaintext">...
├─ com.squareup.picasso3.Picasso instance
│    ↓ Picasso.targetToAction
│              ~~~~~~~~~~~~~~
├─ java.util.WeakHashMap instance
│    ↓ WeakHashMap.table
│                  ~~~~~
├─ java.util.WeakHashMap$Entry[] array
│    ↓ WeakHashMap$Entry[0]
│                       ~~~
├─ java.util.WeakHashMap$Entry instance
│    ↓ WeakHashMap$Entry.value
│                        ~~~~~
├─ com.squareup.picasso3.ImageViewAction instance
...
</code></pre>
<p><code>Picasso.targetToAction</code> is a <code>WeakHashMap</code> of target keys (i.e. views) to action values (i.e. what to load on those views). <code>WeakHashMap</code> has weak keys and strong values, i.e. keys are held by weak references, and when a weak reference to a key clears, its entry is removed. So <code>Picasso.targetToAction</code> holds weak references to the target views &amp; strong references to the corresponding actions.</p>
<h1><code>ImageViewAction.callback</code></h1>
<pre><code class="language-plaintext">...
├─ com.squareup.picasso3.ImageViewAction instance
│    ↓ ImageViewAction.callback
│                      ~~~~~~~~
├─ com.example.ProfileLayout\(loadImage\)1 instance
│    Anonymous class implementing com.squareup.picasso3.Callback
│    ↓ ProfileController\(loadImage\)1.this$0
│                                    ~~~~~~
╰→ com.example.ProfileLayout instance
​     Leaking: YES (ObjectWatcher was watching this because ProfileLayout received View#onDetachedFromWindow() callback)
</code></pre>
<p><code>ImageViewAction.callback</code> is a Picasso action that holds a custom callback created in <code>ProfileLayout.loadImage()</code>. We can see that this custom Picasso callback is an anonymous class that has a reference to its outer class, <code>ProfileLayout</code>. We know <code>ProfileLayout</code> has been detached for at least 5 seconds as that's LeakCanary's minimum trigger duration. The <code>ProfileLayout.loadImage()</code> code shows that the callback sets a custom background color when the image fails to load:</p>
<pre><code class="language-kotlin">fun loadImage() {
  picasso.load(imageUri)
    .into(
      imageView,
      object : Callback {
        override fun onSuccess() = Unit
    
        override fun onError(t: Throwable) {
          // Set dark background if image fails to load
          imageView.setBackgroundColor(backgroundColor)
        }
      }
    )
}
</code></pre>
<p>Looking at the Picasso source code we can see that <code>Picasso.targetToAction</code> entries are removed when a request completes, so we can conclude that the image loading request was still in flight and that <code>ProfileLayout</code> was detached and prevented from being garbage collected by that request in flight.</p>
<h1>Fix: cancelation</h1>
<p>It's a common mistake when using Picasso: image-loading requests should be canceled if the UI goes away before the request completes, otherwise, they'll keep going and consume resources until success or failure, which could take a long time on a slow network.</p>
<p>We forgot to cancel the request! Let's fix that:</p>
<pre><code class="language-kotlin">override fun onDetachedFromWindow() {
  super.onDetachedFromWindow()
  picasso.cancelRequest(imageView)
}
</code></pre>
<p>This fixes the leak, yay! Alternatively, we could also <a href="https://square.github.io/picasso/2.x/picasso/com/squareup/picasso/RequestCreator.html#tag-java.lang.Object-">use a tag</a>.</p>
<h1>Leaks everywhere?</h1>
<p>Forgetting to cancel Picasso requests when the UI goes away is a fairly common mistake. How comes leaks don't show up more often?</p>
<p>First, Picasso automatically cancels any previous request when loading a new image on the same image view. This helps with adapter views: when a list item view gets recycled and bound to a new row, a new request is started for that view and the previous one is automatically canceled.</p>
<p>Second, even if we forget to cancel a Picasso request, that will only trigger a leak when using a custom callback with a strong reference to the detached <code>ImageView</code>. How comes?</p>
<h1><code>WeakHashMap</code></h1>
<p><code>Picasso.targetToAction</code> is a <code>WeakHashMap</code> that holds weak references to the target views &amp; strong references to the corresponding actions.</p>
<p><a href="https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNplkU9rwkAQxb_KMlSxoLbnCAU1_ulBKFbaQ8bDNJnE0M2ubCYVUb9716RNC9523vvt28fOCWKbMASQanuId-REbUI0a2slQnzJYypLu7Q6YfewVYOBKnOTaRZr_PCkxmjG0Q91tZ_OQi5j2dhxLLk1ZzXpvTN9LqncrWh_j2ZS3_PYh-azmvYQ_vl3MyPuGG0RPDltSITo0QtnFUY35BZNqAZD5Thlx0bU0N-YRc8FZfyW86HxrylfpCv_3vzPawp6otOZ_3a6Vl83WbGnF1HTrpW6WkZtQDeTEUIdsLgtAX0o2BWUJ_5zT2iUQpAdF4wQ-GPCKVVaENBcPEqV2NejiSEQV3Efqn1CwmFOmaMCgpR02aqzJBfrWpHrcdVssV7m5RtTqp47"><img src="https://mermaid.ink/img/pako:eNplkU9rwkAQxb_KMlSxoLbnCAU1_ulBKFbaQ8bDNJnE0M2ubCYVUb9716RNC9523vvt28fOCWKbMASQanuId-REbUI0a2slQnzJYypLu7Q6YfewVYOBKnOTaRZr_PCkxmjG0Q91tZ_OQi5j2dhxLLk1ZzXpvTN9LqncrWh_j2ZS3_PYh-azmvYQ_vl3MyPuGG0RPDltSITo0QtnFUY35BZNqAZD5Thlx0bU0N-YRc8FZfyW86HxrylfpCv_3vzPawp6otOZ_3a6Vl83WbGnF1HTrpW6WkZtQDeTEUIdsLgtAX0o2BWUJ_5zT2iUQpAdF4wQ-GPCKVVaENBcPEqV2NejiSEQV3Efqn1CwmFOmaMCgpR02aqzJBfrWpHrcdVssV7m5RtTqp47?type=png" alt="" style="display:block;margin:0 auto" /></a></p>
<p><code>ImageViewAction</code> itself (<a href="https://github.com/square/picasso/blob/master/picasso/src/main/java/com/squareup/picasso3/ImageViewAction.kt#L34">source</a>) holds a weak reference to its target <code>ImageView</code>, and a strong reference to its optional <code>Callback</code>. So when the custom callback isn't set, <code>ImageViewAction</code> doesn't hold any strong reference to the view:</p>
<pre><code class="language-kotlin">internal class ImageViewAction(
  picasso: Picasso,
  target: ImageView,
  data: Request,
  var callback: Callback?
) : Action(picasso, data) {
  private val targetReference = WeakReference(target)
</code></pre>
<p><a href="https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNplkU9rwkAQxb_KMhSxoLbnCIIa__QgFCvtIeNhmkxi6GZXNpOKGL97V1PTgred935v97FzgtgmDAGk2h7iHTlRmxDN2lqJEF_zmMrSLq1O2D1tVb-vytxkmsUaP4zUGM04-qUu9qgWchnLxo5jya2p1aT7wfS1pHK3ov0jmsk157FPzbWadhH--Q8zI-4YbRE8OW1IhOjZC7UKoztyiyZU_YFynLJjI2rgE7PopaCM33M-NP7llm_SlX9v_uc1BT0xvzW6FF83N8WeXURNt1bqaBm28U4mQwQfX9wXgB4U7ArKE_-xJzRKIciOC0YI_DHhlCotCGjOHqVK7NvRxBCIq7gH1T4h4TCnzFEBQUq6bNVZkot1rcjXcdVs8LrI8w9h2p2n"><img src="https://mermaid.ink/img/pako:eNplkU9rwkAQxb_KMhSxoLbnCIIa__QgFCvtIeNhmkxi6GZXNpOKGL97V1PTgred935v97FzgtgmDAGk2h7iHTlRmxDN2lqJEF_zmMrSLq1O2D1tVb-vytxkmsUaP4zUGM04-qUu9qgWchnLxo5jya2p1aT7wfS1pHK3ov0jmsk157FPzbWadhH--Q8zI-4YbRE8OW1IhOjZC7UKoztyiyZU_YFynLJjI2rgE7PopaCM33M-NP7llm_SlX9v_uc1BT0xvzW6FF83N8WeXURNt1bqaBm28U4mQwQfX9wXgB4U7ArKE_-xJzRKIciOC0YI_DHhlCotCGjOHqVK7NvRxBCIq7gH1T4h4TCnzFEBQUq6bNVZkot1rcjXcdVs8LrI8w9h2p2n?type=png" alt="" style="display:block;margin:0 auto" /></a></p>
<p>As you can see, so far there are no strong references to any view. This means that the default usage of Picasso will not introduce leaks, even if we forget to cancel requests when UI goes away.</p>
<p>Now let's add a custom callback with a strong reference to a view:</p>
<p><a href="https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNplkV9rwjAUxb9KCCIO1LlXhYH_K2wgTraHxIdre9sG00TS24lYv_tSq27Dt-Se3z3nwD3x0EbI-zzW9hCm4IitJ9KsrCUh5VKFkOc2sDpC97xhnQ7LlUk0kjX-88qG0gzFlark15LAJUhrOwxJWVOyUesLYRdAnr7D_kma0WXPY1uNJRu3JP-jN6aG3FFsJPfkuCYlFz0_KNlEPJAbaSas02UOY3RoiHX9xlQsMkjwU-Gh1iuXb9CFz5v9anVBT8xujariq9op9Oxc1N3uo6amwX29mdBAcr8-fyxw9wxB6y2Eu5IFYulsrDS-wdEW1NAWootX48V7BNcKqcobvZItxH9645FFjahbflnFTKuLZEu4RFf6grd5hi4DFfmbnqRhTHJKMUPJ-_4ZYQyFJsmlOXsUCrIfRxPyPrkC27zYR0A4UZA4yHg_Bp3fp9NIkXXX4fkHlALAJw"><img src="https://mermaid.ink/img/pako:eNplkV9rwjAUxb9KCCIO1LlXhYH_K2wgTraHxIdre9sG00TS24lYv_tSq27Dt-Se3z3nwD3x0EbI-zzW9hCm4IitJ9KsrCUh5VKFkOc2sDpC97xhnQ7LlUk0kjX-88qG0gzFlark15LAJUhrOwxJWVOyUesLYRdAnr7D_kma0WXPY1uNJRu3JP-jN6aG3FFsJPfkuCYlFz0_KNlEPJAbaSas02UOY3RoiHX9xlQsMkjwU-Gh1iuXb9CFz5v9anVBT8xujariq9op9Oxc1N3uo6amwX29mdBAcr8-fyxw9wxB6y2Eu5IFYulsrDS-wdEW1NAWootX48V7BNcKqcobvZItxH9645FFjahbflnFTKuLZEu4RFf6grd5hi4DFfmbnqRhTHJKMUPJ-_4ZYQyFJsmlOXsUCrIfRxPyPrkC27zYR0A4UZA4yHg_Bp3fp9NIkXXX4fkHlALAJw?type=png" alt="" style="display:block;margin:0 auto" /></a></p>
<p>Note that if we replaced the <code>callback</code> strong reference with a weak reference, there would be no strong reference holding the callback in memory and it would be garbage collected before the request completes, even when the view is still attached. That's not what we want. So we do need the <code>callback</code> strong reference, which unfortunately means we have a strong reference path to <code>ProfileLayout</code> and its associated view hierarchy, which causes a leak.</p>
<p>This is exactly what the <code>WeakHashMap</code> <a href="https://docs.oracle.com/javase/7/docs/api/java/util/WeakHashMap.html">documentation</a> warns us about:</p>
<blockquote>
<p>The value objects in a WeakHashMap are held by ordinary strong references. Thus care should be taken to ensure that value objects do not strongly refer to their own keys, either directly or indirectly, since that will prevent the keys from being discarded.</p>
</blockquote>
<h1><code>View.setTag(int, Object)</code></h1>
<p>Ideally Picasso would have a consistent memory behavior whether or not <code>callback</code> is set. One way to do that is to store the <code>ImageViewAction</code> directly in the <code>ImageView</code>, as a <a href="https://developer.android.com/reference/android/view/View#setTag(int,%20java.lang.Object)">view tag</a>. That way, <code>Picasso.targetToAction</code> would only hold a weak reference to the <code>ImageView</code> and as soon as the view is detached it becomes unreachable:</p>
<p><a href="https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNplkctOwzAQRX_FsrooEoWyzaJSS1P1AaKCChZ2FtNkkpo6dmU7oIr037Gd8hI7-87xnTvjD5rrAmlCS6nf8x0YRzZTrh61dozztcjBWj3XskBznZHBgFihKolOK38ZkTFXY3amQnnUOjAVuo0e505o1ZJJ_wVhPwe7u4fDBVeT-M5jW4ktue1z-qveS5UzR5Zx6snbjuSUDb3Qkin7R2ZcTcngihgs0aBy5Mq_SNmihgqfBb77ehpC1ys8YrGBykbPJXs6gLE4NgaCxzIwnL6BbNCG7pFaMU4ftq-YuyB5bNVhMU4kZj-dunG7PCF19GqJaqTkavY1c1hNS9JvJQcpt5DvWzJna6NLIfEOjrpxPamhiOa9G286PxvshO0NW7Jgf-nMI4sOEV-BYptu-DXE3YT6gl7SGk0NovB__sEV8QO5HdbIaeKPBZbQSMcpVyePQuP001HlNHGmwUvaHApwOBVQGahpUoK032paCKfNWTx9As1lx4c"><img src="https://mermaid.ink/img/pako:eNplkctOwzAQRX_FsrooEoWyzaJSS1P1AaKCChZ2FtNkkpo6dmU7oIr037Gd8hI7-87xnTvjD5rrAmlCS6nf8x0YRzZTrh61dozztcjBWj3XskBznZHBgFihKolOK38ZkTFXY3amQnnUOjAVuo0e505o1ZJJ_wVhPwe7u4fDBVeT-M5jW4ktue1z-qveS5UzR5Zx6snbjuSUDb3Qkin7R2ZcTcngihgs0aBy5Mq_SNmihgqfBb77ehpC1ys8YrGBykbPJXs6gLE4NgaCxzIwnL6BbNCG7pFaMU4ftq-YuyB5bNVhMU4kZj-dunG7PCF19GqJaqTkavY1c1hNS9JvJQcpt5DvWzJna6NLIfEOjrpxPamhiOa9G286PxvshO0NW7Jgf-nMI4sOEV-BYptu-DXE3YT6gl7SGk0NovB__sEV8QO5HdbIaeKPBZbQSMcpVyePQuP001HlNHGmwUvaHApwOBVQGahpUoK032paCKfNWTx9As1lx4c?type=png" alt="" style="display:block;margin:0 auto" /></a></p>
<h1>Auto cancel on detach</h1>
<p>Alternatively, Picasso could set a <a href="https://developer.android.com/reference/android/view/View#addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener)">detach listener</a> on the <code>ImageView</code> and auto cancel the requests on detach. This would be best for saving resources but might create surprises if e.g. the app is preloading an image into a detached view.</p>
<p>That's all for now, hope you enjoyed reading this!</p>
<blockquote>
<p>Header image generated by DALL-E, prompt: "image inspired by the style of Pablo Picasso, depicting a leaky faucet with bold, abstract shapes and bold colors".</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Let's investigate a Gradle IntelliJ memory leak!]]></title><description><![CDATA[👋 Hi, this is P.Y., I work as an Android Engineer at Block. This article shares a team investigation by Tony Robalik, Pablo Baxter, Roger Hu and myself into a recent Gradle / IntelliJ memory leak.

O]]></description><link>https://blog.p-y.wtf/gradle-intellij-memory-leak</link><guid isPermaLink="true">https://blog.p-y.wtf/gradle-intellij-memory-leak</guid><category><![CDATA[gradle]]></category><category><![CDATA[intellij]]></category><category><![CDATA[idea]]></category><category><![CDATA[Memory Leak]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Wed, 12 Oct 2022 17:26:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665572235433/JGXvjNeRQ.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>👋 Hi, this is P.Y., I work as an Android Engineer at <a href="https://block.xyz/">Block</a>. This article shares a team investigation by <a href="https://twitter.com/AutonomousApps">Tony Robalik</a>, <a href="https://github.com/pablobaxter">Pablo Baxter</a>, <a href="https://twitter.com/rogerjhu">Roger Hu</a> and <a href="https://twitter.com/Piwai">myself</a> into a recent <strong>Gradle / IntelliJ memory leak</strong>.</p>
</blockquote>
<p>On September 29th, <a href="https://twitter.com/AutonomousApps">Tony Robalik</a> reaches out to our friends at Gradle to report memory issues with the Gradle process when importing a project in IntelliJ IDEA. The heap size keeps climbing to new heights, reaching 60+ GB! Tony writes:</p>
<blockquote>
<p>Normally, after I start another build, the daemon gives up most of the memory it had used in the first build, i.e. it takes until that moment for the GC to run. In the past, I've been able to force the gc to run with <code>jcmd &lt;pid&gt; GC.run</code> and get my memory back or just run a simple build like <code>help</code>. However, right now, that's not happening.</p>
</blockquote>
<h1>Dominators</h1>
<p>The Java heap is an object graph. One useful tool we can leverage from graph theory is something called the <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">dominator</a> tree:</p>
<blockquote>
<p>A node <code>d</code> dominates a node <code>n</code> if every path in the object graph from GC roots to <code>n</code> must go through <code>d</code>.</p>
</blockquote>
<p>In practice, the dominator tree provides us with the list of biggest objects sorted by retained size. The retained size is the sum of the size of all the objects that would become unreachable if the dominator object was unreachable.</p>
<p>Tony takes a heap dump of the Gradle process and shares a screenshot from the <strong>Biggest Objects - Dominators</strong> tab in YourKit:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665566645848/9ZzZB-QqK.png" alt="Yourkit Biggest Objects - Dominators" />

<p>We immediately notice that 95% of the 44 GB heap is retained by <code>java.lang.ref.Finalizer</code>, which means, as YourKit gently points out, that the memory is retained by an object that is pending finalization.</p>
<h1>Pending Finalization</h1>
<p>Once an object is unreachable, it can be garbage collected and its memory reclaimed. If that objects implements the <code>finalize()</code> method, then that method must be called before garbage collection. Once objects with a <code>finalize()</code> are detected as unreachable, they're put in a finalizer queue and are in a "pending finalization" state until <code>finalize()</code> is called.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665566663404/IEaFt0zW6.png" alt="ProjectImportActionWithCustomSerializer dominator" />

<p>Here we can see that the lowest dominator that retains most of the memory is <code>ProjectImportActionWithCustomSerializer</code>. It is <strong>unreachable</strong> &amp; <strong>transitively pending finalization</strong>: even though it has no <code>finalize()</code> method, it is dominated by an object that is pending finalization, which means it is still indirectly reachable by that object which itself can still run code in its <code>finalize()</code> method. This means <code>ProjectImportActionWithCustomSerializer</code> cannot be garbage collected until its dominator is finalized.</p>
<h1>I Am GCroot 🌳</h1>
<p>To understand which references exactly are keeping <code>ProjectImportActionWithCustomSerializer</code> in memory, I ask Tony to compute the shortest paths from GC Roots in YourKit:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665567126397/RJ6VUX2WW.png" alt="shortest paths from GC Roots" />

<p>Here's how to read this trace:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568584070/v18zVeoNA.png" alt="1" />

<ul>
<li>At the top is <code>ProjectImportActionWithCustomSerializer</code>. We want to understand why it's retained in memory.</li>
</ul>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568608139/h-0UmNBWX.png" alt="2" />

<p>At the bottom is a GC root, here a <code>JNIGlobal</code> that keeps a reference to <code>CleanerImpl$PhantomCleanableRef</code>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568639643/PkZ_KeWIM.png" alt="3" />

<p>From the bottom to the top we see the chain of references that is retaining <code>ProjectImportActionWithCustomSerializer</code>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568681621/kFVDGTgTB.png" alt="4" />

<p>The bottom part of the trace is the finalizer queue. The finalizer queue is implemented as a <a href="https://en.wikipedia.org/wiki/Doubly_linked_list">doubly linked list</a>, where each <code>Finalizer</code> instance has a reference to the previous entry (<code>prev</code>) and next entry (<code>next</code>) in the finalizer queue, as well as a reference to the object that is pending finalization (<code>referent</code>).</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568710120/iuz32ggqK.png" alt="5" />

<p>As we move towards the top of the trace, we see that a <code>Finalizer</code> has a <code>referent</code> field referencing <code>Executors$FinalizableDelegatedExecutorService</code>. This is the object that implements <code>finalize()</code> and is pending finalization.</p>
<pre><code class="language-java">    private static class FinalizableDelegatedExecutorService
            extends DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor) {
            super(executor);
        }
        @SuppressWarnings("deprecation")
        protected void finalize() {
            super.shutdown();
        }
    }
</code></pre>
<p>As you can see, <code>FinalizableDelegatedExecutorService</code> is an <code>ExecutorService</code> that automatically shuts down the thread pool when it becomes unreachable. Developers are expected to shut down thread pools manually when they stop being in use, but sometimes mistakes happen and this is a safety net.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568907968/V3hcppQOn.png" alt="6" />

<p>The <code>Executors$FinalizableDelegatedExecutorService.e</code> field references a <code>ThreadPoolExecutor</code> instance.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568930629/SYEKVIsYq.png" alt="7" />

<p>The <code>ThreadPoolExecutor.threadFactory</code> field references a <code>ProjectImportAction\(1</code> instance. So we can assume <code>ProjectImportAction\)1</code> is an anonymous class (because its name is <code>$1</code>) that implements <code>ThreadFactory</code>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665568954635/sQCWkprCy.png" alt="8" />

<p>The <code>ProjectImportAction\(1.this\)0</code> field references the <code>ProjectImportActionWithCustomSerializer</code> instance. In Java, anonymous classes have a hidden reference to their outer class, compiled as a field name <code>this$0</code>.</p>
<h1>Reveal</h1>
<p>At this point we can conclude that <code>ProjectImportActionWithCustomSerializer</code> is a class that extends <code>ProjectImportAction</code>, and that <code>ProjectImportAction</code> defines an anonymous class that implements <code>ThreadFactory</code> which is then passed to a <code>ThreadPoolExecutor</code>.</p>
<p>Let's look at the <a href="https://github.com/JetBrains/intellij-community/blob/ff07590cb24b25c055ce00dcd5c6f0db109e2bfa/plugins/gradle/tooling-extension-api/src/org/jetbrains/plugins/gradle/model/ProjectImportAction.java#L114-L120">ProjectImportAction</a> sources:</p>
<pre><code class="language-java">  myConverterExecutor =  Executors.newSingleThreadExecutor(
    new ThreadFactory() {
      @Override
      public Thread newThread(@NotNull Runnable runnable) {
        return new Thread(runnable, "idea-tooling-model-converter");
      }
    }
  );
 }
</code></pre>
<p><code>ProjectImportAction</code> creates a single threaded executor, and passes in a <code>ThreadFactory</code> in order to set the thread name. That anonymous <code>ThreadFactory</code> doesn't actually use the hidden <code>this$0</code> reference to its <code>ProjectImportAction</code> outer class, unfortunately the Java compiler (unlike Kotlin) will still add that reference.</p>
<p>If we extract that anonymous class into a static class, this <code>this$0</code> reference will disappear and the <code>ProjectImportAction</code> implementation will not be retained while the thread pool executor is pending finalization.</p>
<pre><code class="language-java">private static final class SimpleThreadFactory implements ThreadFactory {
  @Override
  public Thread newThread(@NotNull Runnable runnable) {
    return new Thread(runnable, "idea-tooling-model-converter");
  }
}
</code></pre>
<p><a href="https://github.com/pablobaxter">Pablo Baxter</a> files a <a href="https://youtrack.jetbrains.com/issue/IDEA-303282/Memory-leak-in-ProjectImportAction">bug</a> and opens a <a href="https://github.com/JetBrains/intellij-community/pull/2186/files">pull request</a> which is swiftly merged into the IntelliJ master branch.</p>
<p><a href="https://twitter.com/rogerjhu">Roger Hu</a> &amp; <a href="https://twitter.com/AutonomousApps">Tony Robalik</a> apply this fix locally by patching the <code>gradle-tooling-extension-api.jar</code> jar with <a href="https://www.coley.software/Recaf/">Recaf</a> and confirm that the memory is now properly reclaimed 🎉 !</p>
<p>The git history shows that this bug was <a href="https://github.com/pablobaxter/intellij-community/commit/8daa06d04f6e9ae2a2d32f2f09ea30e97c05bd24">introduced</a> in IntelliJ IDEA <strong>2022.1</strong> 221.4165.146 (that version is the base for Android Studio Electric Eel Canary 5). Last week, folks from JetBrains said they would "apply the changes and include it in next EAP of <strong>2022.3</strong> and next bugfix release of <strong>2022.2</strong> branch" while folks from Google said "we will cherry pick in EE". I love this quick turnaround!</p>
<h1>Are we done though?</h1>
<p>Wait a minute, we fixed the leak, but why was the thread pool executor pending finalization for such a long time? Tony reproduces the bug a few more times and takes a peak at the finalization queue. It turns out there's a <code>ZipEntry</code> for a jar that is systematically hanging out near the head of the finalization queue. <code>ZipEntry</code> calls <code>close()</code> when finalized. We haven't quite figured out why <code>close()</code> takes so long, so we're leaving that as an exercise for you, dear reader 😘.</p>
<blockquote>
<p>Header image generated by DALL-E, prompt: "a photo of canary flying holding an elephant in the air".</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Using an Activity from a Hilt ViewModel]]></title><description><![CDATA[👋 Hi, this is P.Y., I work as an Android Engineer at Block. This blog shares a bit of hackery to be able to access an activity instance within a Hilt ViewModel. If you come up with other interesting ]]></description><link>https://blog.p-y.wtf/using-an-activity-from-a-hilt-viewmodel</link><guid isPermaLink="true">https://blog.p-y.wtf/using-an-activity-from-a-hilt-viewmodel</guid><category><![CDATA[Android]]></category><category><![CDATA[dagger-hilt]]></category><category><![CDATA[dependency injection]]></category><category><![CDATA[ViewModel]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Wed, 07 Sep 2022 21:59:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662587912484/iH6sIf7sT.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>👋 Hi, this is P.Y., I work as an Android Engineer at</em> <a href="https://block.xyz/"><em>Block</em></a><em>. This blog shares a bit of hackery to be able to access an activity instance within a Hilt ViewModel. If you come up with other interesting ways to do this, let me know</em> <a href="https://twitter.com/Piwai"><em>on Twitter</em></a><em>! If you're mad because you think I'm encouraging bad practices, try yoga.</em></p>
<h1>I need that god object</h1>
<p>I've been playing with Hilt's support for view models in a small app, and needed my view model to start a sharing activity:</p>
<pre><code class="language-kotlin">@HiltViewModel
class MyCuteLittleViewModel @Inject constructor(
) : ViewModel() {

  // ... some code that invokes share()

  private fun share(content: String) {
    val intent = Intent(Intent.ACTION_SEND).apply {
      type = "text/plain"
      putExtra(Intent.EXTRA_TEXT, content)
    }
    val chooserIntent = Intent.createChooser(intent, "Share with…")

    val activity = TODO("Need an activity here!")
    activity.startActivity(chooserIntent)
  }
}
</code></pre>
<p>View models are retained across activity config changes, so the activity isn't injectable, which makes total sense: injecting the activity in a view model would lead to leaks on config changes.</p>
<p>Unfortunately, the <code>Activity</code> class provides a lot of utility so it's fairly common to need access to it (see <a href="https://en.wikipedia.org/wiki/God_object">God object</a>).</p>
<p>Most online resources recommend moving the code to the activity or a collaborator that has access to it, have that listen to events that indicate the action to perform, then send the events from the ViewModel.</p>
<h1>You're not the boss of me.</h1>
<p>I don't care for these "best" practices. I want that code right there where it's used, and I don't want unnecessary decoupling (also I pinky swear I'll write unit tests tomorrow).</p>
<p>Anyway, here's a little bit of Hilt hackery to support this without changing any <code>Activity</code> code.</p>
<p>First, let's create a <code>CurrentActivityProvider</code> scoped to <code>@ActivityRetainedScoped</code>, which will be in charge of holding the current activity instance:</p>
<pre><code class="language-kotlin">@ActivityRetainedScoped
class CurrentActivityProvider @Inject constructor() {

  // TODO Set and clear currentActivity
  private var currentActivity: Activity? = null

  fun &lt;T&gt; withActivity(block: Activity.() -&gt; T) : T {
    checkMainThread()
    val activity = currentActivity
    check(activity != null) {
      "Don't call this after the activity is finished!"
    }
    return activity.block()
  }
}
</code></pre>
<p>Then we can use it as needed. Notice that <code>withActivity()</code> makes it slightly harder to store the activity instance in the wrong place accidentally:</p>
<pre><code class="language-kotlin">@HiltViewModel
class MyCuteLittleViewModel @Inject constructor(
  private val activityProvider: CurrentActivityProvider
) : ViewModel() {

  private fun share(content: String) {
    // ...
    activityProvider.withActivity {
      startActivity(chooserIntent)
    }
  }
}
</code></pre>
<p>Now we need to set up <code>CurrentActivityProvider.currentActivity</code> for each <code>ActivityRetainedComponent</code> scope. For that, we create an entry point scoped to the activity (<code>ActivityComponent</code>) which will provide access to the <code>CurrentActivityProvider</code> (which lives in a parent <code>ActivityRetainedComponent</code> scope). The entry point:</p>
<pre><code class="language-kotlin">@EntryPoint
@InstallIn(ActivityComponent::class)
interface ActivityProviderEntryPoint {
  val activityProvider: CurrentActivityProvider
}
</code></pre>
<p>Now we can retrieve the scoped activity provider from an activity instance with:</p>
<pre><code class="language-kotlin">val entryPoint: ActivityProviderEntryPoint =
  EntryPointAccessors.fromActivity(this)
val activityProvider = entryPoint.activityProvider
</code></pre>
<p>This only works if the activity is Hilt-aware, so let's check that it implements <code>GeneratedComponentManagerHolder</code> (🤫 it's in Hilt's internal package but it's also public so 🤷‍♂️) and let's make a small <code>Activity.withProvider()</code> utility for that:</p>
<pre><code class="language-kotlin">activity.withProvider { activityProvider -&gt;
  // TODO
}

    private fun Activity.withProvider(
      block: CurrentActivityProvider.() -&gt; Unit
    ) {
      if (this is GeneratedComponentManagerHolder) {
        val entryPoint: ActivityProviderEntryPoint =
          EntryPointAccessors.fromActivity(this)
        val provider = entryPoint.activityProvider
        provider.block()
      }
    }
</code></pre>
<blockquote>
<p>Note: Android apps can have multiple activities in created state at the same time. The code here supports that by relying on the <code>ActivityRetainedComponent</code> scope which will give us a new component for each activity in the stack, but still return the same logical component when an activity is recreated through a config change.</p>
</blockquote>
<p>Now let's add methods to update the activity reference on lifecycle changes:</p>
<pre><code class="language-kotlin">@ActivityRetainedScoped
class CurrentActivityProvider @Inject constructor() {

  private var currentActivity: Activity? = null

  fun &lt;T&gt; withActivity(block: Activity.() -&gt; T) : T { /* ... */  }

  companion object {
    private fun Activity.withProvider(
      block: CurrentActivityProvider.() -&gt; Unit
    ) { /* ... */ }

    fun onActivityCreated(activity: Activity) {
      activity.withProvider {
        currentActivity = activity
      }
    }

    fun onActivityDestroyed(activity: Activity) {
      activity.withProvider {
        if (currentActivity === activity) {
          currentActivity = null
        }
      }
    }
  }
}
</code></pre>
<p>And finally let's hook the lifecycle callbacks from my <code>Application</code> class:</p>
<pre><code class="language-kotlin">@HiltAndroidApp
class MyCuteLittleApp : Application() {

  override fun onCreate() {
    super.onCreate()
    
    registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        CurrentActivityProvider.onActivityCreated(activity)
      }

      override fun onActivityDestroyed(activity: Activity) {
        CurrentActivityProvider.onActivityDestroyed(activity)
      }
    })
  }
}
</code></pre>
<p>With this, we can now inject <code>CurrentActivityProvider</code> in any <code>ActivityRetainedComponent</code> scope (as well as lower scopes) and easily access the activity with <code>activityProvider.withActivity()</code>.</p>
<h1>Testing testing 1 2 3 🎤</h1>
<p>To make <code>MyCuteLittleViewModel</code> easier to test we can move the sharing responsibility to an injected collaborator, e.g. <code>Sharer</code>:</p>
<pre><code class="language-kotlin">interface Sharer {
  fun share(content: String)
}

class ActivitySharer @Inject constructor(
  private val activityProvider: CurrentActivityProvider
) : Sharer {
  override fun share(content: String) {
    // ...
    activityProvider.withActivity {
      startActivity(chooserIntent)
    }
  }
}

@Module
@InstallIn(ActivityRetainedComponent::class)
interface SharerModule {
  @Binds fun bindSharer(sharer: ActivitySharer): Sharer
}
</code></pre>
<blockquote>
<p>Header image generated by DALL-E, prompt: "sword held by an Android with glasses, 3d render"</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[WhileSubscribed(5000)]]></title><description><![CDATA[👋 Hi, this is P.Y., I work as an Android Engineer at Block. I know nothing about Compose so please let me know if I messed up on Twitter!
I've been hacking on a new LeakCanary standalone app to visua]]></description><link>https://blog.p-y.wtf/whilesubscribed5000</link><guid isPermaLink="true">https://blog.p-y.wtf/whilesubscribed5000</guid><category><![CDATA[Android]]></category><category><![CDATA[Jetpack Compose]]></category><category><![CDATA[coroutines]]></category><category><![CDATA[Kotlin]]></category><category><![CDATA[stream]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Tue, 30 Aug 2022 23:01:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661899406295/iqLtpgO3d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>👋 Hi, this is P.Y., I work as an Android Engineer at</em> <a href="https://block.xyz/"><em>Block</em></a><em>. I know nothing about Compose so please let me know if I messed up</em> <a href="https://twitter.com/Piwai"><em>on Twitter</em></a><em>!</em></p>
<p>I've been hacking on a new LeakCanary standalone app to visualize leaks, which is going to be 100% Compose. As I started following tutorials and looking at sample apps, I noticed a strange pattern in both <a href="https://github.com/android/nowinandroid/search?q=WhileSubscribed">Now In Android</a> and <a href="https://github.com/chrisbanes/tivi/search?q=WhileSubscribed">tivi</a>:</p>
<pre><code class="language-kotlin">class AuthorViewModel : ViewModel() {

    val authorUiState: StateFlow&lt;AuthorUiState&gt; = authorUiStateStream()
        .stateIn(
            scope = viewModelScope,
            // {-_-} 5000?!
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = AuthorUiState.Loading
        )
}

@Composable
fun AuthorScreen(authorUiState: AuthorUiState) {
  when (authorUiState) {
    AuthorUiState.Loading -&gt; {
      // ...
    }
  }
}
</code></pre>
<p>I wonder what this <code>WhileSubscribed(5_000)</code> is all about! Let's look at the <a href="https://cs.android.com/android/platform/superproject/+/master:external/kotlinx.coroutines/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt;l=105-110;drc=7a8f34fddad84b82e8ccc7b178a03b4a331a2c24">source</a>:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661895066796/hYxr4lZGh.png" alt="Screenshot of WhileSubscribed sources" />

<p>Using <code>WhileSubscribed()</code> without any timeout would make sense here, i.e. we want to keep the sharing coroutine running as long as there's a UI consuming it. When that UI goes away, why would we want to wait an additional 5 seconds before we stop sharing?</p>
<p>I find more details in a <a href="https://medium.com/androiddevelopers/things-to-know-about-flows-sharein-and-statein-operators-20e6ccb2bc74">post from the Android Developers blog</a>:</p>
<blockquote>
<p><strong>Tip for Android apps!</strong> You can use <code>WhileSubscribed(5000)</code> most of the time to keep the upstream flow active for 5 seconds more after the disappearance of the last collector. That avoids restarting the upstream flow in certain situations such as configuration changes. This tip is especially helpful when upstream flows are expensive to create and when these operators are used in ViewModels.</p>
</blockquote>
<p>Surprise Surprise, it's <strong>config changes</strong> once again, the bane of my Android career...</p>
<p>On Twitter <a href="https://twitter.com/Zhuinden/status/1564586130108026881">Gabor Varadi</a> pointed out that <a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt;l=36;bpv=0;bpt=0">CoroutineLiveData</a> has the same 5000 ms default timeout.</p>
<p>In my experience, introducing random delays does not properly solve whatever underlying issue I'm running into. I'm also not comfortable with the idea that every ViewModel exposing state as a flow should now have this weird 5000 <a href="https://en.wikipedia.org/wiki/Magic_number_(programming)">magic number</a> baked in.</p>
<p>What happens when we remove the delay?</p>
<pre><code class="language-kotlin">class AuthorViewModel : ViewModel() {

    val authorUiState: StateFlow&lt;AuthorUiState&gt; = authorUiStateStream()
        .stateIn(
            scope = viewModelScope,
            // Timeout is now 0!
            started = SharingStarted.WhileSubscribed(),
            initialValue = AuthorUiState.Loading
        )
}

@Composable
fun AuthorScreen(authorUiState: AuthorUiState)
// ...
</code></pre>
<p>When I rotate the screen, the sharing coroutine is stopped &amp; restarted, whereas previously it stayed in started state and didn't restart the upstream flow.</p>
<p>Here's why: as part of a configuration change, the activity is destroyed. Then the activity is recreated, and resumed. Somewhere during that recreation the scope that <code>AuthorScreen</code> used to collect <code>authorUiState</code> completes, which brings the subscription count to 0, and stops the state sharing. And then on the first frame post resume, sharing restarts, which re-triggers the <code>authorUiStateStream()</code> but also immediately reuses the latest cached value (<code>WhileSubscribed.replayExpiration</code> defaults to never).</p>
<p>My next idea was to use <code>SharingStarted.Lazily</code> instead, which starts sharing on subscribe and never stops:</p>
<pre><code class="language-kotlin">class AuthorViewModel : ViewModel() {

    val authorUiState: StateFlow&lt;AuthorUiState&gt; = authorUiStateStream()
        .stateIn(
            scope = viewModelScope,
            // Start on subscribe and never stop!
            started = SharingStarted.Lazily,
            initialValue = AuthorUiState.Loading
        )
}

@Composable
fun AuthorScreen(authorUiState: AuthorUiState)
// ...
</code></pre>
<p>The state is shared with the <code>viewModelScope</code> scope so the sharing will stop as soon as the view model is cleared.</p>
<p>This works, but there's one limitation: the upstream flow stays active for as long as the ViewModel is around, which is usually tied to an Activity or Fragment lifecycle. If a state flow is tied only to a small part of the UI that then goes away and unsubscribes, we'll be keeping that flow active for no good reason.</p>
<p>The same is true of <code>SharingStarted.WhileSubscribed(5_000)</code> of course. That <code>5_000</code> timeout is meant as "wait long enough to be sure that if we went through a config change we'd have time to resubscribe before we stop sharing". Unfortunately, this also means that whenever that state is unsubscribed we wait an additional 5 seconds before stopping the upstream flow.</p>
<p>Can we create a <code>SharingStarted</code> that behaves like <code>SharingStarted.WhileSubscribed</code> but will also wait for config changes to settle and for the UI to have a chance to resubscribe before stopping the upstream flow? Let's call it <code>WhileSubscribedOrRetained</code>:</p>
<pre><code class="language-kotlin">class AuthorViewModel : ViewModel() {

    val authorUiState: StateFlow&lt;AuthorUiState&gt; = authorUiStateStream()
        .stateIn(
            scope = viewModelScope,
            started = WhileSubscribedOrRetained,
            initialValue = AuthorUiState.Loading
        )
}

@Composable
fun AuthorScreen(authorUiState: AuthorUiState)
// ...
</code></pre>
<p>Yes we can! I was chatting with <a href="https://twitter.com/romainguy">Romain Guy</a> and he jokingly suggested:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661897804934/nbhrt1QDb.png" alt="Romain Guy suggesting post(post(…))" />

<p>Little did he know... that's exactly what I did, except with even more posting!</p>
<p>The implementation is inspired from <code>WhileSubscribed</code>. Thanks <a href="https://twitter.com/billjings">Bill Phillips</a> for suggesting <code>CompletableDeferred</code>:</p>
<pre><code class="language-kotlin">object WhileSubscribedOrRetained : SharingStarted {

  private val handler = Handler(Looper.getMainLooper())

  override fun command(subscriptionCount: StateFlow&lt;Int&gt;): Flow&lt;SharingCommand&gt; = subscriptionCount
  .transformLatest { count -&gt;
    if (count &gt; 0) {
      emit(SharingCommand.START)
    } else {
      val posted = CompletableDeferred&lt;Unit&gt;()
      // This code is perfect. Do not change a thing.
      Choreographer.getInstance().postFrameCallback {
        handler.postAtFrontOfQueue {
          handler.post {
            posted.complete(Unit)
          }
        }
      }
      posted.await()
      emit(SharingCommand.STOP)
    }
  }
  .dropWhile { it != SharingCommand.START }
  .distinctUntilChanged()

  override fun toString(): String = "WhileSubscribedOrRetained"
}
</code></pre>
<p>Wait, what?!</p>
<pre><code class="language-kotlin">Choreographer.getInstance().postFrameCallback {
  handler.postAtFrontOfQueue {
    handler.post {
      posted.complete(Unit)
    }
  }
}
</code></pre>
<p>Ok so this is the fun part: the <code>subscriptionCount</code> updates are dispatched async on the main thread. When a config change occurs, the activity is destroyed, recreated and resumed. As part of the teardown, the subscription count decrement event is posted and runs right after <code>Activity.onResume()</code> but also right before the first frame renders. So we can't stop the subscription right there, as the first composition (where we resubscribe) will happen as part of the first frame.</p>
<p>The resubscription happens during the first frame callback, and the subscription count increment is posted and runs after the first frame.</p>
<p>Last but not least, composition runs during the measure part of a traversal, whereas callbacks manually posted with <code>Choreographer.getInstance().postFrameCallback()</code> run before that, during the animation phase.</p>
<p>So, this is what we do:</p>
<ul>
<li><p>We receive the decrement to 0 during a post that runs in between <code>Activity.onResume()</code> and the first frame.</p>
</li>
<li><p>We schedule a frame callback with <code>Choreographer.getInstance().postFrameCallback {}</code> so that we can be called during the animation part of the first frame.</p>
</li>
<li><p>We know that a bit later during that frame callback the resubscription will happen and will trigger a post. We want to run code <em>after</em> that post runs.</p>
</li>
<li><p>So we enqueue a post at the front of the main thread queue with <code>handler.postAtFrontOfQueue {}</code>, which runs immediately after the frame callback.</p>
</li>
<li><p>And from that we enqueue a post at the back of the main thread queue with <code>handler.post{}</code>, which is guaranteed to run after the subscription count increase notification.</p>
</li>
<li><p>When the subscription count increase notification runs, <code>transformLatest</code> ensures that the work to stop (which was suspended with <code>posted.await()</code>) is canceled.</p>
</li>
<li><p>If the subscription count doesn't increase, we proceed with stopping the sharing.</p>
</li>
</ul>
<p>Phew, so much trampolining!</p>
<p>The result is nice though: the flow stays active during config changes, and stops immediately when the subscribed UI goes away.</p>
<h1>The root cause: state lifecycle</h1>
<p>We have state presented on an active screen. We want the lifecycle of that state to be tied to when that screen is visible, and survive config changes.</p>
<p>Our core issue is that we're shoving state in a ViewModel that has a longer lifecycle than what we want the state to have.</p>
<p>The fix is to use fine grained scopes that are tied to the app navigation state ("what am I currently doing?") which does not get torn down on config changes. If you use ViewModels from Jetpack navigation, you already get that, so you can replace <code>WhileSubscribed(5000)</code> with <code>Lazily</code>!</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661899497900/7UVYXm9IQ.png" alt="DALL·E 2022-08-30 15.41.41 - An Android jumping on a trampoline, vaporware, low angle.png" />

<blockquote>
<p>Generated by DALL-E, prompt: "An Android jumping on a trampoline, vaporware, low angle"</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Of sharks and heaps of sticky marshmallows]]></title><description><![CDATA[👋 Hi, this is P.Y., I work as an Android Engineer at Block, the rockey company formerly known as Square. I spend a lot of time focusing on performance and try to share my experience with deep-dive bl]]></description><link>https://blog.p-y.wtf/of-sharks-and-heaps-of-sticky-marshmallows</link><guid isPermaLink="true">https://blog.p-y.wtf/of-sharks-and-heaps-of-sticky-marshmallows</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 07 Apr 2022 19:57:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1649361266293/I6Ml0Nf6k.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>👋 Hi, this is P.Y., I work as an Android Engineer at</em> <a href="https://block.xyz/"><em>Block</em></a><em>, the rockey company formerly known as Square. I spend a lot of time focusing on performance and try to share my experience with deep-dive blog posts. I hope you like this one, let me know</em> <a href="https://twitter.com/Piwai"><em>on Twitter</em></a><em>!</em></p>
<h1>Introduction</h1>
<p>At Square, we run <a href="https://github.com/square/leakcanary/">LeakCanary</a> in CI after every UI test thanks to the <a href="https://square.github.io/leakcanary/changelog/#leak-detection-in-tests">DetectLeaksAfterTestSuccess</a> test rule:</p>
<pre><code class="language-kotlin">class CartTest {
  @get:Rule
  val rules = RuleChain.outerRule(DetectLeaksAfterTestSuccess())
    .around(ActivityScenarioRule(CartActivity::class.java))

  @Test
  fun addItemToCart() {
    // ...
  }
}
</code></pre>
<p>Last week a colleague noticed that our Android CI heap analysis occasionally took several minutes. This blog is a deep dive based on my notes from the investigation!</p>
<h1>GC Roots</h1>
<p>We realize this is happening only on API 23 emulators (API 23 is Android 6, aka Android Marshmallow), and we can reproduce the issue locally. I add <a href="https://py.hashnode.dev/tracing-main-thread-messages">trace sections</a> and hook up <a href="https://perfetto.dev/">Perfetto</a> while the test is running:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649309739460/gvuRUJKKA.jpeg" alt="perfetto_memory_usage.jpeg" style="display:block;margin:0 auto" />

<p>I immediately notice something weird: the heap size increases more than expected when LeakCanary starts its analysis. Could that be somehow related to the slow down?</p>
<p>I capture a heap dump while the heap analysis is running and look at instance counts sorted by total shallow size in <a href="https://www.yourkit.com/java/profiler/features/">YourKit Java Profiler</a>:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649310049801/nHQsuW7xz.png" alt="yourkit.png" style="display:block;margin:0 auto" />

<p>The 4th entry is surprising: 1.3M instances of <code>GcRoot.StickyClass</code>. This class is a part of <a href="https://square.github.io/leakcanary/shark/">Shark</a>, LeakCanary's heap dump parser. Here's how the YourKit doc describes GC roots:</p>
<blockquote>
<p>The so-called GC (Garbage Collector) roots are objects special for garbage collector. Garbage collector collects those objects that are not GC roots and are not accessible by references from GC roots.</p>
</blockquote>
<blockquote>
<p>There are several kinds of GC roots. One object can belong to more than one kind of root. The root kinds are:</p>
</blockquote>
<blockquote>
<ul>
<li><p>Class - class loaded by system class loader. Such classes can never be unloaded. They can hold objects via static fields.</p>
</li>
<li><p>...</p>
</li>
</ul>
</blockquote>
<p>Classes loaded by the system class loader are never garbage collected, so they stick around, and are therefore known as <strong>sticky classes</strong>. In a JVM, custom classes can be loaded and then unloaded, but on Android, they're never unloaded. So any loaded class stays in memory forever and acts as a GC root that holds static field references forever.</p>
<p>Shark parses a heap dump and keeps the list of all GC roots in memory, and that's usually a reasonably small list. 1.3M sticky class GC roots is not expected!</p>
<h1>Shark</h1>
<p>I decide to write ad hoc code with Shark to analyze an API 23 heap dump more systematically and compute aggregates. Let's start by printing the counts of GC Root by type:</p>
<pre><code class="language-kotlin">// openHeapGraph() parses the heap dump file content
heapDumpFile.openHeapGraph().use { graph -&gt;
  // Grab all GC roots
  val roots = graph.gcRoots
  // Create a map of GC root type =&gt; count of that type
  val counts = roots.countBy { it.javaClass }
  // Turn the map into a list of entries, sorted by the counts.
  val sortedCounts = counts.entries.sortedBy { -it.value }
  println(counts.joinToString("\n"))
}
</code></pre>
<pre><code class="language-text">class shark.GcRoot$StickyClass=1342062
class shark.GcRoot$JavaFrame=807
class shark.GcRoot$JniGlobal=402
class shark.GcRoot$ThreadObject=56
class shark.GcRoot$JniLocal=54
class shark.GcRoot$NativeStack=53
</code></pre>
<p>As expected, we see 1.3M StickyClass GC roots. Other types of GC roots have reasonable counts. Do we have 1.3M classes, though?</p>
<pre><code class="language-kotlin">heapDumpFile.openHeapGraph().use { graph -&gt;
  println("class count=${graph.classes.count()}")
}
</code></pre>
<pre><code class="language-text">class count=52940
</code></pre>
<p>Okay, how do we go from 53K classes to 1.3M sticky class GC roots?</p>
<pre><code class="language-kotlin">class StickyClass(override val id: Long) : GcRoot()
</code></pre>
<p>A sticky class GC root is solely defined by the id of the root object, so let's see if we have duplicate ids, and what objects these ids correspond to:</p>
<pre><code class="language-kotlin">heapDumpFile.openHeapGraph().use { graph -&gt;
  // Grab all GC roots
  val roots = graph.gcRoots
  // Keey only sticky class gc roots
  val stickyRoots = roots.filterIsInstance(StickyClass::class.java)
  // Create a map of id =&gt; count of that id in the stickyRoots list
  val stickyCounts = stickyRoots.countBy { it.id }
  // Turn the map into a list of entries, sorted by the counts.
  val sortedStickyCounts = stickyCounts.entries.sortedBy { it.value }

  // Map the id to the actual object it references join into a string
  val result = sortedStickyCounts.joinToString("\n") {
    "\({graph.findObjectById(it.key)}: \){it.value}"
  }
  println(result)
}
</code></pre>
<pre><code class="language-plaintext">...
object array @318259200 of java.lang.Class[]: 27182
primitive array @-1970475008 of int[]: 27235
object array @325763072 of java.lang.Class[]: 27235
primitive array @-1966432256 of int[]: 28152
object array @319406080 of java.lang.Class[]: 28152
primitive array @1879291968 of int[]: 28261
object array @1879261584 of java.lang.Class[]: 28261
primitive array @-1966821376 of int[]: 30331
object array @319721472 of java.lang.Class[]: 30331
</code></pre>
<p>What?! <code>primitive array</code>, <code>object array</code>.. these aren't classes! There are 53001 distinct object ids referenced by sticky class GC roots, out of which 52939 point to classes and 62 point to int and object arrays.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649358719293/KmEEgJXVK.png" alt="image.png" style="display:block;margin:0 auto" />

<p>Interestingly, these int arrays have a size very close to 65K. Notice the size of the top one: <em>65536</em>. You might have seen that number before... in the <a href="https://developer.android.com/studio/build/multidex">multidex documentation</a>:</p>
<blockquote>
<p>The Dalvik Executable specification limits the total number of methods that can be referenced within a single DEX file to 65,536.</p>
</blockquote>
<p>These non-class objects that sticky class GC roots are pointing to are objects referenced by <code>DexCache.resolvedMethods</code>, <code>DexCache.resolvedFields</code> and <code>DexCache.resolvedTypes</code>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649358904034/COxcpT9AL.png" alt="image.png" style="display:block;margin:0 auto" />

<p>That's a little weird but ok. We still don't know why we have all the duplicated GC roots. From what I gather, the class table was maintained by <a href="https://cs.android.com/android/platform/superproject/+/android-7.1.1_r60:art/runtime/class_linker.cc;drc=b47a1cc17f53951b900e56bb68c58c972517cb07">class_linker.cc</a> in Android M and apparently that changed in Android N, which seemingly fixed bugs related to visiting the same class roots over and over again.</p>
<h1>Bugfix</h1>
<p>I can quickly fix the increased memory usage in LeakCanary (<a href="https://github.com/square/leakcanary/pull/2324">PR</a>) by introducing a set to ignore repeated sticky class entries:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649359660528/iOZVUEOiv.png" alt="fix.png" style="display:block;margin:0 auto" />

<p>Let's not forget to add a unit test:</p>
<pre><code class="language-kotlin">@Test fun `duplicated StickyClass GC roots are deduplicated`() {
  val className = StringRecord(id, "com.example.VeryStickyClass")
  val loadClassRecord = LoadClassRecord(1, id, 1, className.id)
  val classDump = ClassDumpRecord(
    id = loadClassRecord.id,
    stackTraceSerialNumber = 1,
    superclassId = 0,
    classLoaderId = 0,
    signersId = 0,
    protectionDomainId = 0,
    instanceSize = 0,
    staticFields = emptyList(),
    fields = emptyList()
  )
  val stickyClassRecords = (1..5).map {
    GcRootRecord(StickyClass(loadClassRecord.id))
  }
  val hprofRecords = listOf(className, loadClassRecord, classDump) +
    stickyClassRecords
  val bytes = hprofRecords.asHprofBytes()

  val stickyClassRoots = bytes.openHeapGraph().use { graph -&gt;
    graph.gcRoots.filterIsInstance(StickyClass::class.java)
  }

  assertThat(stickyClassRoots).hasSize(1)
  val stickyClassRoot = stickyClassRoots.first()
  assertThat(stickyClassRoot.id).isEqualTo(loadClassRecord.id)
}
</code></pre>
<h1>Conclusion</h1>
<p>I capture a perfetto trace running the same analysis before and after the change:</p>
<p><strong>Before</strong></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649309739460/gvuRUJKKA.jpeg" alt="perfetto_memory_usage_before.jpeg" style="display:block;margin:0 auto" />

<p><strong>After</strong></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649340530181/t3NOu1lSh.jpeg" alt="perfetto_memory_usage_after.jpeg" style="display:block;margin:0 auto" />

<p>🎉 Heap consumption is halved from max 260Mb to max 140Mb!</p>
<p>You know what, though? Our heap analysis in CI is still super slow on API 23. Something else is going on! Wouldn't that be a great follow-up article?</p>
<blockquote>
<p>Cover image: <em>Jelly Fish On Blue</em> <a href="https://www.flickr.com/photos/romainguy/509046880/">by Romain Guy</a>.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Tracing main thread messages]]></title><description><![CDATA[👋 Hi, this is P.Y., I work as an Android Engineer at Block, the non-fungible company formerly known as Square. I spend a lot of time focusing on performance and try to share my experience with deep-d]]></description><link>https://blog.p-y.wtf/tracing-main-thread-messages</link><guid isPermaLink="true">https://blog.p-y.wtf/tracing-main-thread-messages</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Thu, 27 Jan 2022 19:53:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643312524384/F12Nq58L7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>👋 Hi, this is P.Y., I work as an Android Engineer at</em> <a href="https://block.xyz/"><em>Block</em></a><em>, the non-fungible company formerly known as Square. I spend a lot of time focusing on performance and try to share my experience with deep-dive blog posts. I hope you like this one, let me know</em> <a href="https://twitter.com/Piwai"><em>on Twitter</em></a><em>!</em></p>
<h1>Summary</h1>
<p>This blog shows how to see what the main thread is doing in Perfetto traces by leveraging a seldom-used <code>Looper</code> logger API.</p>
<h1>Profiling with Perfetto</h1>
<p>I use <a href="https://perfetto.dev/">Perfetto</a> tracing to get a good picture of the system-level &amp; high-level app behavior. The <a href="https://perfetto.dev/docs/quickstart/android-tracing">Android Quickstart</a> is a bit overwhelming, here's what I do:</p>
<pre><code class="language-bash"># Prepare the device
adb root
adb shell setprop persist.traced.enable 1

# Download the record_android_trace script
curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace
chmod u+x record_android_trace

# Capture a trace for com.example.myapp
./record_android_trace -o trace_file.perfetto-trace -b 500mb \
-a com.example.myapp sched freq idle am wm gfx view \
binder_driver hal dalvik camera input res

Trace started. Press CTRL+C to stop
</code></pre>
<p>I perform the interaction I'm profiling, then I press CTRL+C and the script automatically opens <a href="https://ui.perfetto.dev/">ui.perfetto.dev</a> with a new trace.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643260599127/D79TLCa82.png" alt="Perfetto screenshot" style="display:block;margin:0 auto" />

<p>Perfetto shows a lot of information! If you're lost, press <code>?</code> to bring up the navigation help, and learn that you can move around the timeline with the <a href="https://en.wikipedia.org/wiki/Arrow_keys#WASD_keys">WASD keys</a>. Then open up the row with your app's package name to see its list of threads.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643261254383/HD8RKY8eh.png" alt="Perfetto app process" style="display:block;margin:0 auto" />

<p>Here the main thread is shown on the first 2 rows (named after the app package name, <code>com.example.myapp</code>), and the render thread on the second 2 rows. For each thread, we see thread states (running, etc) in the first row, and then the system traces in the following row.</p>
<h1>BALEETED!</h1>
<p>Let's look at an example, a <a href="https://youtu.be/7rrZ-sA4FQc?t=94">delete</a> button that shows a confirm dialog.</p>
<pre><code class="language-kotlin">findViewById&lt;Button&gt;(R.id.delete).setOnClickListener {
  AlertDialog.Builder(this)
    .setTitle("Are you sure?")
    .setPositiveButton("Delete") { _, _ -&gt;
      delete()
      Toast.makeText(this@MainActivity, "BALEETED!", LENGTH_SHORT).show()
    }
    .show()
}
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643261908147/fVDlVHp4w.png" alt="confirm dialog" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643262034822/lGHbZhwFh.png" alt="baleeted!" style="display:block;margin:0 auto" />

<p>After recording a trace, this is what we see in Perfetto when the dialog is shown:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643261777707/NtkEoYKgk.png" alt="Clicking delete" style="display:block;margin:0 auto" />

<p>And then when the user confirms deletion, the dialog is dismissed and the toast is shown:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643261819000/-W91R5l9y.png" alt="showing toast" style="display:block;margin:0 auto" />

<p>We can tell that view inflation is happening, followed by a Choreographer frame. But it's hard to grasp a good understanding of the details, as well as what happens during the gaps in between. Is there any way we can add more details?</p>
<h1>Looper logging</h1>
<p>As I wrote in 2013 in <a href="https://developer.squareup.com/blog/a-journey-on-the-android-main-thread-psvm/">A journey on the Android Main Thread</a>, the main thread has a dedicated <code>Looper</code> which is in charge of looping, i.e. running main thread messages serially forever.</p>
<p><code>Looper</code> also has a seldom-used logging API: <a href="https://developer.android.com/reference/android/os/Looper#setMessageLogging(android.util.Printer)">Looper.setMessageLogging()</a>:</p>
<blockquote>
<p>Control logging of messages as they are processed by this Looper. If enabled, a log message will be sent to the logger at the beginning and end of each message dispatch, identifying the target Handler and message contents.</p>
</blockquote>
<p>We can use it to log the messages that run on the main thread:</p>
<pre><code class="language-kotlin">class ExampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()

    Looper.getMainLooper().setMessageLogging { log -&gt;
      Log.d("ExampleApplication", log)
    }
  }
}
</code></pre>
<p>For each message posted to the main thread, we get a log before the message runs and a log after:</p>
<pre><code class="language-plaintext">D ExampleApplication: &gt;&gt;&gt;&gt;&gt; Dispatching to Handler (android.view.View
  RootImpl\(ViewRootHandler) {c93a855} android.view.View\)PerformClick
  @a6c9a1a: 0

D ExampleApplication: &lt;&lt;&lt;&lt;&lt; Finished to Handler (android.view.ViewRoot
  Impl\(ViewRootHandler) {c93a855} android.view.View\)PerformClick
  @a6c9a1a
</code></pre>
<h1>Main thread message trace sections</h1>
<p>We can leverage the <code>Looper</code> logging APIs to add a trace section for each main thread message. First, we add the AndroidX tracing library:</p>
<pre><code class="language-groovy">dependencies {
  implementation "androidx.tracing:tracing-ktx:1.0.0"
}
</code></pre>
<p>Then we can use the <code>&gt;&gt;&gt;&gt;&gt;</code> / <code>&lt;&lt;&lt;&lt;&lt;</code> suffix in the message to determine if the log marks the start or the end of a main thread message, and delegate to <a href="https://developer.android.com/reference/androidx/tracing/Trace">androidx.tracing.Trace</a> accordingly:</p>
<pre><code class="language-kotlin">class ExampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    Looper.getMainLooper().setMessageLogging { log -&gt;
      if (log.startsWith('&gt;')) {
        Trace.beginSection(log)
      } else if (log.startsWith('&lt;')) {
        Trace.endSection()
      }
    }
  }
}
</code></pre>
<p>Unfortunately, this eventually crashes:</p>
<pre><code class="language-plaintext">Process: com.example.myapp, PID: 15870
java.lang.IllegalArgumentException: sectionName is too long
	at android.os.Trace.beginSection(Trace.java:333)
	at androidx.tracing.TraceApi18Impl.beginSection(TraceApi18Impl.java:49)
	at androidx.tracing.Trace.beginSection(Trace.java:81)
	at com.example.myapp.ExampleApplication\(onCreate\)1.println(ExampleApplication.kt:29)
	at android.os.Looper.loop(Looper.java:183)
	at android.app.ActivityThread.main(ActivityThread.java:7356)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
</code></pre>
<p>The <a href="https://developer.android.com/reference/androidx/tracing/Trace#beginSection(java.lang.String)">AndroidX Trace.beginSection() javadoc</a> is leaving out a crucial detail from the <a href="https://developer.android.com/reference/android/os/Trace#beginSection(java.lang.String)">AOSP Trace.beginSection() javadoc</a>:</p>
<blockquote>
<p>sectionName may be at most 127 Unicode code units long.</p>
</blockquote>
<p>Let's fix the crash:</p>
<pre><code class="language-kotlin">class ExampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    Looper.getMainLooper().setMessageLogging { log -&gt;
      if (log.startsWith('&gt;')) {
        // Would be nice if AndroidX automatically truncated to 127
        Trace.beginSection(log.take(127))
      } else if (log.startsWith('&lt;')) {
        Trace.endSection()
      }
    }
  }
}
</code></pre>
<p>Let's look at how the trace changes:</p>
<h2>Before</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643261819000/-W91R5l9y.png" alt="showing toast before" style="display:block;margin:0 auto" />

<h2>After</h2>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643303526056/8bLgxZIxm.png" alt="image.png after" style="display:block;margin:0 auto" />

<p>The result is useful, we have a lot less empty space and we know what work belongs to the same main thread message. However, every single section starts with <code>&gt;&gt;&gt;&gt;&gt; Dispatching to</code> . The most useful information is the callback class name, but it's appending at the end of the string so the 127 character limit means the callback class name is often truncated.</p>
<h1>Better section names</h1>
<p>Let's look at how <a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Looper.java;l=159-172;drc=2456c281f3c8b2c105860473c2bbbaac91f6ca2f">Looper assembles the log</a>:</p>
<pre><code class="language-java">    private boolean loopOnce() {
        Message msg = mQueue.next();

        Printer logging = mLogging;
        if (logging != null) {
            logging.println("&gt;&gt;&gt;&gt;&gt; Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what);
        }
</code></pre>
<p>The format for that string <a href="https://cs.android.com/android/_/android/platform/frameworks/base/+/7f9f99ea11051614a7727dfb9f9578b518e76e3c:core/java/android/os/Looper.java;l=119-122;drc=54b6cfa9a9e5b861a9930af873580d6dc20f773c">hasn't changed since the AOSP import commit in 2008</a>. Let's parse the log string back into its components:</p>
<pre><code class="language-kotllin">val logNoPrefix = log.removePrefix("&gt;&gt;&gt;&gt;&gt; Dispatching to ")
val indexOfWhat = logNoPrefix.lastIndexOf(": ")
val indexOfCallback = logNoPrefix.indexOf("} ")

val targetHandler = logNoPrefix.substring(0, indexOfCallback + 1)
val callback = logNoPrefix.substring(indexOfCallback + 2, indexOfWhat)
val what = logNoPrefix.substring(indexOfWhat + 2)
</code></pre>
<p><code>callback</code> is the result of calling <code>callback.toString()</code> on the <code>Runnable</code> passed to <code>Handler.post()</code>, which most of the time returns the default <code>Object.toString()</code> implementations. Coroutine continuations override <code>toString()</code> to provide useful information about the callsite, but add a lengthy prefix so let's get rid of that:</p>
<pre><code class="language-kotlin">val callback = logNoPrefix.substring(indexOfCallback + 2, indexOfWhat)
  .removePrefix("DispatchedContinuation[Dispatchers.Main, Continuation at ")
  .removePrefix("DispatchedContinuation[Dispatchers.Main.immediate, Continuation at ")
</code></pre>
<p>We can then reassemble the values into a better section label that puts the callback first:</p>
<pre><code class="language-kotlin">private fun buildSectionLabel(log: String): String {
  val logNoPrefix = log.removePrefix("&gt;&gt;&gt;&gt;&gt; Dispatching to ")
  val indexOfWhat = logNoPrefix.lastIndexOf(": ")
  val indexOfCallback = logNoPrefix.indexOf("} ")

  val targetHandler = logNoPrefix.substring(0, indexOfCallback + 1)
  val callback = logNoPrefix.substring(indexOfCallback + 2, indexOfWhat)
    .removePrefix("DispatchedContinuation[Dispatchers.Main, Continuation at ")
    .removePrefix("DispatchedContinuation[Dispatchers.Main.immediate, Continuation at ")
  val what = logNoPrefix.substring(indexOfWhat + 2)

  return if (callback != "null") {
    "\(callback \)targetHandler $what"
  } else {
    "\(targetHandler \)what"
  }
}
</code></pre>
<p>Now we clearly see the class name of callbacks posted to the main thread, e.g. <a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;l=28667;drc=629810015f7c2b5b2f9b882dc3a5ccb721cc88d7">View$PerformClick</a>:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643310491103/oWrILJnAe.png" alt="View.PerformClick" style="display:block;margin:0 auto" />

<h1>Cost of logging</h1>
<p>This hack is really cool, but it also leads to the app doing extra string concatenation for every main thread message. We should only do this when we want to profile the app, e.g. by adding a runtime check.</p>
<p><strong>Edit:</strong> as <strong>Chris Craik</strong> and <strong>John Reck</strong> reminded me (thanks!), we should also skip calling <code>buildSectionLabel()</code> when not actively tracing by checking <a href="https://developer.android.com/reference/android/os/Trace#isEnabled()">Trace.isEnabled()</a>:</p>
<pre><code class="language-plaintext">val debuggable = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
val profileable = Build.VERSION.SDK_INT &gt;= 29 &amp;&amp; applicationInfo.isProfileableByShell

if (debuggable || profileable) {
  Looper.getMainLooper().setMessageLogging { log -&gt;
    if (!Trace.isEnabled()) {
      return@setMessageLogging
    }
    if (log.startsWith('&gt;')) {
      val label = buildSectionLabel(log)
      Trace.beginSection(label.take(127))
    } else if (log.startsWith('&lt;')) {
      Trace.endSection()
    }
  }
}
</code></pre>
<h1>Pooled lambda crash</h1>
<p>If you run this code on Android 9, you will eventually run into a crash. The AOSP codebase supports lambdas and method references but uses recyclable anonymous functions via <a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/com/android/internal/util/function/pooled/PooledLambda.java;l=57-99;drc=fd49debdc094e3153cfbc2b397e6936dd10b5d5c">PooledLambda</a> to avoid creating an extra instance for every lambda (<a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/UiAutomation.java;l=1557-1559;drc=master">example</a>). It's a neat optimization!</p>
<p>When <code>Looper</code> concatenates the log string, it calls <code>callback.toString()</code>, and that callback might be a <code>PooledLambda</code>. Unfortunately, on Android 9 calling <code>toString()</code> on a lambda with 0 argument would throw an exception, which was <a href="https://cs.android.com/android/_/android/platform/frameworks/base/+/75632d616dbf14b6c71ea0e3a8a55c6fc963ba10">fixed in Android 10</a>.</p>
<p>There's no work around, we can't even catch the exception, the only fix is to disable our message tracing for Android 9 / API 28.</p>
<h1>All together</h1>
<p>This is the final code with everything put together. If you improve upon it, feel free to let me know <a href="https://twitter.com/Piwai">on Twitter</a>. Happy profiling!</p>
<pre><code class="language-kotlin">package com.example.myapp

import android.app.Application
import android.content.pm.ApplicationInfo
import android.os.Build
import android.os.Looper
import androidx.tracing.Trace

class ExampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()

    val debuggable = (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
    val profileable = Build.VERSION.SDK_INT &gt;= 29 &amp;&amp; applicationInfo.isProfileableByShell

    if (Build.VERSION.SDK_INT != 28 &amp;&amp; (debuggable || profileable)) {
      Looper.getMainLooper().setMessageLogging { log -&gt;
        if (!Trace.isEnabled()) {
          return@setMessageLogging
        }
        if (log.startsWith('&gt;')) {
          val label = buildSectionLabel(log)
          Trace.beginSection(label.take(127))
        } else if (log.startsWith('&lt;')) {
          Trace.endSection()
        }
      }
    }
  }

  private fun buildSectionLabel(log: String): String {
    val logNoPrefix = log.removePrefix("&gt;&gt;&gt;&gt;&gt; Dispatching to ")
    val indexOfWhat = logNoPrefix.lastIndexOf(": ")
    val indexOfCallback = logNoPrefix.indexOf("} ")

    val targetHandler = logNoPrefix.substring(0, indexOfCallback + 1)
    val callback = logNoPrefix.substring(indexOfCallback + 2, indexOfWhat)
      .removePrefix("DispatchedContinuation[Dispatchers.Main, Continuation at ")
      .removePrefix("DispatchedContinuation[Dispatchers.Main.immediate, Continuation at ")
    val what = logNoPrefix.substring(indexOfWhat + 2)

    return if (callback != "null") {
      "\(callback \)targetHandler $what"
    } else {
      "\(targetHandler \)what"
    }
  }
}
</code></pre>
<blockquote>
<p>Cover image: <em>Lost at Sea</em> <a href="https://www.flickr.com/photos/romainguy/44309679170/">by Romain Guy</a>.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Fixing simpleperf broken records]]></title><description><![CDATA[Summary
This blog shows how to fix simpleperf traces which are otherwise unusable because they include samples with truncated callchain roots. Read on to learn more about what these crazy words mean!
Profiling Android apps
As an Android developer, I ...]]></description><link>https://blog.p-y.wtf/fixing-simpleperf-broken-records</link><guid isPermaLink="true">https://blog.p-y.wtf/fixing-simpleperf-broken-records</guid><category><![CDATA[Android]]></category><category><![CDATA[android app development]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Fri, 21 Jan 2022 18:13:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642661018430/PXlwW6YwO.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-summary">Summary</h1>
<p>This blog shows how to fix simpleperf traces which are otherwise unusable because they include samples with truncated callchain roots. Read on to learn more about what these crazy words mean!</p>
<h1 id="heading-profiling-android-apps">Profiling Android apps</h1>
<p>As an Android developer, I have many tools available to profile your Android apps. I typically use:</p>
<ul>
<li><strong>System-level span based</strong> tools (<a target="_blank" href="https://developer.android.com/topic/performance/tracing/command-line">systrace</a>, <a target="_blank" href="https://perfetto.dev/">Perfetto</a>) to get a good picture of the system-level app behavior. I usually start there, to answer questions like <em>"Are the app threads fully utilizing the CPUs or waiting for IO or IPC (aka binder calls)?"</em> or <em>"Are other apps running in parallel, interacting with the app or starving CPUs?"</em>.</li>
<li><strong>App-level stack sampling</strong> tools (<a target="_blank" href="https://developer.android.com/studio/profile/record-traces#configurations">sample Java Methods</a>, <a target="_blank" href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md">simpleperf</a>) to get a better understanding of what's going on inside the app, see what code is executing and how long each method call is taking.</li>
</ul>
<h1 id="heading-simpleperf">simpleperf</h1>
<p>According to the <a target="_blank" href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md">Readme</a>:</p>
<blockquote>
<p>Simpleperf is a native CPU profiling tool for Android. It can be used to profile both Android applications and native processes running on Android. It can profile both Java and C++ code on Android.</p>
</blockquote>
<p>The general idea is that it runs with less overhead than the good old <a target="_blank" href="https://developer.android.com/studio/profile/record-traces#configurations">sample Java Methods</a>, so the results are closer to reality.</p>
<p>In the past, I tried to follow the command line instructions to <a target="_blank" href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md">profile an Android application with simpleperf</a>, but I never fully understood how to use it.</p>
<p>I only recently realized that simpleperf has been integrated into Android Studio for a while, under the option  <em>"C / C++ trace recording"</em>. In Android Studio Bumblebee, the option was renamed to <em>"Callstack sample recording"</em> and <em>"sample Java Methods"</em> became <em>"legacy"</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642657870864/nvfciJvtl.jpeg" alt="Bumblebee.jpeg" /></p>
<blockquote>
<p>Note: <a href="https://developer.android.com/reference/android/os/Debug#startMethodTracingSampling(java.lang.String,%20int,%20int)">Debug.startMethodTracingSampling()</a> is still the only available API for instrumentation despite being the exact same thing as the now legacy <em>"sample Java Methods"</em>, although apparently starting with API 29 we can now <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:benchmark/benchmark-common/src/main/java/androidx/benchmark/Profiler.kt;l=200;drc=edaec69025bf0500388102f87b95f097e96be695">invoke simpleperf from code</a>.</p>
</blockquote>
<h1 id="heading-unusable-traces">Unusable traces</h1>
<p>When I record a simpleperf trace from a complex app, here's the result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642658378386/BWZNQzpSL.png" alt="broken simpleperf" /></p>
<p>Notice the many thin grey vertical lines that break up the main thread call tree, all the way from the top. That's weird!</p>
<p>If you zoom in, you can see that the left and right spans around these vertical lines are identical stacks:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642658557516/m8_x4ZRnG.png" alt="broken simpleperf zoom" /></p>
<p>These should be just one giant call stack, not two stacks separated by a weird tiny stack. What's going on?</p>
<p>If you run into this issue, you can work around it by selecting a time-based span manually for the analysis. It works but it's not great.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642658716860/zPX9G6sX9.png" alt="workaround" /></p>
<h1 id="heading-dwarf">DWARF</h1>
<p>Simpleperf generates <a target="_blank" href="https://en.wikipedia.org/wiki/DWARF">DWARF</a>-based call graphs. I have no idea what that means, but the simpleperf FAQ <a target="_blank" href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc#why-we-can_t-always-get-complete-dwarf_based-call-graphs">mentions it</a>:</p>
<blockquote>
<p><strong>Why can't we always get complete DWARF-based call graphs?</strong></p>
<p>DWARF-based call graphs are generated by unwinding thread stacks. When a sample is generated, up to 64KB of stack data is dumped by the kernel. By unwinding the stack based on dwarf information, we get a callchain. But the thread stack can be much longer than 64KB. In that case, we can't unwind to the thread start point.</p>
<p>To alleviate the problem, simpleperf joins callchains after recording them. If two callchains of a thread have an entry containing the same IP and SP address, then simpleperf <strong>tries</strong> to join them to make the callchains longer. </p>
</blockquote>
<p>In other words: for each thread stack sample, simpleperf can only capture the first 64KB at the top of the stack, and stitches it all back as a full callchain by finding the rest of it in other samples that share some common callchain entry. That's very cool!</p>
<p>Unfortunately, if the stack changes significantly in between consecutive samples, then simpleperf cannot find any common callchain entry, so it just keeps those truncated callchains in. Which explains why our call tree was broken up by weird super-thin vertical bars!</p>
<h1 id="heading-stitching-it-back">Stitching it back</h1>
<p>I tweeted about this bug <a target="_blank" href="https://twitter.com/Piwai/status/1450150976220909568">in October 2021</a> and then moved on with my life. But recently I've been using simpleperf again and I decided to see if I could fix the trace files.</p>
<p>I realized that those bad stack samples should be easy to spot, as they don't have the same root frames as every other sample (e.g. <code>__libc_init</code> followed by <code>main</code>). Once spotted, I can fix the bad stack samples by prepending a fake callchain based on the good samples that surround the bad sample.</p>
<p>Cool, let's write a trace parser! Fortunately, I found the Android Studio implementation: <a target="_blank" href="https://cs.android.com/android-studio/platform/tools/adt/idea/+/mirror-goog-studio-main:profilers/src/com/android/tools/profilers/cpu/simpleperf/SimpleperfTraceParser.java;l=54;drc=e79366d3c93f6715f53150de5b9cdce43c3e8ba5">SimpleperfTraceParser.java</a>.</p>
<p>I spent a few hours (mostly fighting gradle and protos) <a target="_blank" href="https://github.com/pyricau/simpleperf-cleanup/blob/main/app/src/main/kotlin/simpleperf/cleanup/SimpleperfTracerParser.kt#L51">adapting it</a> to do what I wanted:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">if</span> (sample.callchainList.last() == callStackRoot) {
  <span class="hljs-keyword">if</span> (brokenRecords.isNotEmpty()) {
    <span class="hljs-comment">// Reversed so that root is at index 0</span>
    <span class="hljs-keyword">val</span> lastCallchain = lastValidSample.callchainList.reversed()
    <span class="hljs-keyword">val</span> nextCallchain = sample.callchainList.reversed()

    <span class="hljs-keyword">var</span> divergenceIndex = <span class="hljs-number">0</span>
    <span class="hljs-keyword">while</span> (divergenceIndex &lt; nextCallchain.size
      &amp;&amp; divergenceIndex &lt; lastCallchain.size
      &amp;&amp; lastCallchain[divergenceIndex] == nextCallchain[divergenceIndex]
    ) {
      divergenceIndex++
    }
    <span class="hljs-keyword">val</span> sharedCallChain = nextCallchain.subList(<span class="hljs-number">0</span>, divergenceIndex)
      .reversed()

    <span class="hljs-keyword">for</span> (brokenRecord <span class="hljs-keyword">in</span> brokenRecords) {
      output.writeFixedRecord(brokenRecord, sharedCallChain)
    }
    brokenRecords.clear()
  }
  lastValidSample = sample
  output.writeInt(recordSize)
  output.write(recordBytes)
} <span class="hljs-keyword">else</span> {
  brokenRecords += record
  brokenMainThreadSampleCount++
}
</code></pre>
<p>Once a trace is fixed, I can import it in the Android Studio profiler:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642660545877/B7M2hEJGR.png" alt="fixed" /></p>
<p>Much better!</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>The code is available at <a target="_blank" href="https://github.com/pyricau/simpleperf-cleanup">github.com/pyricau/simpleperf-cleanup</a>.</p>
<p>I considered releasing it as a library or a CLI tool, but I figured, for now, anyone can use it reasonably easily:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> git@github.com:pyricau/simpleperf-cleanup.git
<span class="hljs-built_in">cd</span> simpleperf-cleanup

./gradlew app:run --args=<span class="hljs-string">"PATH/TO/TRACE.trace"</span>
</code></pre>
<p>Hopefully, this will eventually be fixed in simpleperf or AndroidStudio and we won't need this hack (the Android Studio team is aware).</p>
<p>This hack also made me realize it wouldn't be too hard to build additional tooling on top of simpleperf traces, e.g. to support SQL queries or code-based investigations, or new types of graphs. Stay tuned!</p>
<blockquote>
<p>Cover image: <em>Dead Tired</em> <a target="_blank" href="https://www.flickr.com/photos/romainguy/37702231391/">by Romain Guy</a>.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[WorkManager multi-process for libraries]]></title><description><![CDATA[Cover image: Beacons by Romain Guy.

Summary
This blog shows how LeakCanary builds on top of WorkManager to run work in a separate process, while also not messing with the configuration of the hosting app. WorkManager is an amazing library, but using...]]></description><link>https://blog.p-y.wtf/workmanager-multi-process-for-libraries</link><guid isPermaLink="true">https://blog.p-y.wtf/workmanager-multi-process-for-libraries</guid><category><![CDATA[Android]]></category><category><![CDATA[android development]]></category><category><![CDATA[android apps]]></category><category><![CDATA[process]]></category><category><![CDATA[android app development]]></category><dc:creator><![CDATA[Pierre-Yves Ricau]]></dc:creator><pubDate>Tue, 28 Dec 2021 07:15:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640673076312/UTCWqSOaM.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Cover image: <em>Beacons</em> <a target="_blank" href="https://www.flickr.com/photos/romainguy/31390069837/">by Romain Guy</a>.</p>
</blockquote>
<h1 id="heading-summary">Summary</h1>
<p>This blog shows how LeakCanary builds on top of WorkManager to run work in a separate process, while also not messing with the configuration of the hosting app. WorkManager is an amazing library, but using it to schedule remote process work as a library has several gotchas.</p>
<h1 id="heading-intro">Intro</h1>
<p>For LeakCanary 2.8 I'm <a target="_blank" href="https://github.com/square/leakcanary/pull/2237/">rewriting how the heap analysis is performed</a> to stop relying on a foreground service and use WorkManager instead, because <a target="_blank" href="https://developer.android.com/guide/components/foreground-services#background-start-restrictions">Android 12 happened</a>. To limit memory pressure on the hosting app, LeakCanary supports running the analysis in a separate process. Let's see how this all fits together!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640672867201/mFMvSMqm6.png" alt="Android_12_Developer_Preview_logo.svg.png" /></p>
<p>I need to support the following behavior:</p>
<ul>
<li>If the app using my library depends on WorkManager and WorkManager multi process, schedule the work to run in a separate process.</li>
<li>If the app using my library depends on just WorkManager, schedule the work with WorkManager.</li>
<li>Otherwise use a simple background thread. This isn't ideal but I'm ok with losing the work on process death.</li>
</ul>
<h1 id="heading-optional-workmanager">Optional WorkManager</h1>
<p>Libraries should <a target="_blank" href="https://twitter.com/Piwai/status/1474410599945805832">avoid forcing dependencies</a> down on their consumers when possible. Let's look at how we can do that for WorkManager.</p>
<p>First, I add the WorkManager dependencies to our compile classpath as <code>compileOnly</code> so that I can write code against the WorkManager APIs, without having those dependencies appear in the published <code>pom.xml</code> on Maven Central</p>
<pre><code class="lang-groovy">dependencies {
  // Optional dependencies
  // Note: using the Java artifact because the Kotlin one bundles coroutines.
  compileOnly 'androidx.work:work-runtime:2.7.0'
  compileOnly 'androidx.work:work-multiprocess:2.7.0'
}
</code></pre>
<p>Then I just need a runtime check for the <code>WorkManager</code> class:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">val</span> workManagerInClasspath <span class="hljs-keyword">by</span> lazy {
    <span class="hljs-keyword">try</span> {
      Class.forName(<span class="hljs-string">"androidx.work.WorkManager"</span>)
      <span class="hljs-literal">true</span>
    } <span class="hljs-keyword">catch</span> (ignored: Throwable) {
      <span class="hljs-literal">false</span>
    }
  }
</code></pre>
<h1 id="heading-thread-vs-workmanager">Thread vs WorkManager</h1>
<p>Here's a simple implementation that falls back on a background thread if WorkManager isn't available:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWorkScheduler</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> application: Application) {
  <span class="hljs-keyword">val</span> workManagerInClasspath = <span class="hljs-comment">// ...</span>

  <span class="hljs-keyword">val</span> backgroundHandler <span class="hljs-keyword">by</span> lazy {
    <span class="hljs-keyword">val</span> handlerThread = HandlerThread(<span class="hljs-string">"Background worker thread"</span>)
    handlerThread.start()
    Handler(handlerThread.looper)
  }

  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">enqueueWork</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> (workManagerInClasspath) {
      enqueueOnWorkManager()
    } <span class="hljs-keyword">else</span> {
      enqueueOnBackgroundThread()
    }
  }

  <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">enqueueOnWorkManager</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> request = OneTimeWorkRequest.Builder(MyWorker::<span class="hljs-keyword">class</span>.java)
      .build()
    WorkManager.getInstance(application).enqueue(request)
  }

  <span class="hljs-keyword">private</span> <span class="hljs-keyword">fun</span> enqueueOnBackgroundThread() {
    backgroundHandler.post {
      TODO(<span class="hljs-string">"perform the work"</span>)
    }
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWorker</span></span>(
  appContext: Context,
  workerParams: WorkerParameters
) : Worker(appContext, workerParams) {
  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">doWork</span><span class="hljs-params">()</span></span>: Result {
    TODO(<span class="hljs-string">"perform the work"</span>)
    <span class="hljs-keyword">return</span> Result.success()
  }
}
</code></pre>
<p>So far we have fairly standard WorkManager code. I'm intentionally staying away from setting up a WorkManager <a target="_blank" href="https://developer.android.com/reference/androidx/work/Configuration">Configuration</a> as that can only be set once, and I don't want to step on the toes of the developers using WorkManager for their own purposes.</p>
<p>WorkManager 2.7.0 introduces the concept of <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/workmanager/how-to/define-work#expedited">expedited work</a>, introduced for Android 12 as an alternative to the (now crashing) foreground services. Ideally I want to leverage that new API... but I don't want to force dependency upgrades, so let's add another runtime check:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWorkScheduler</span> </span>{

  <span class="hljs-comment">// ...</span>

  <span class="hljs-comment">// setExpedited() requires WorkManager 2.7.0+</span>
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> workManagerSupportsExpeditedRequests <span class="hljs-keyword">by</span> lazy {
    <span class="hljs-keyword">try</span> {
      Class.forName(<span class="hljs-string">"androidx.work.OutOfQuotaPolicy"</span>)
      <span class="hljs-literal">true</span>
    } <span class="hljs-keyword">catch</span> (ignored: Throwable) {
      <span class="hljs-literal">false</span>
    }
  }

  <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">enqueueOnWorkManager</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> request = OneTimeWorkRequest.Builder(MyWorker::<span class="hljs-keyword">class</span>.java).apply {
      <span class="hljs-keyword">if</span> (workManagerSupportsExpeditedRequests) {
        setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
      }
    }.build()
    WorkManager.getInstance(application).enqueue(request)
  }
}
</code></pre>
<blockquote>
<p>Note: the <code>OneTimeWorkRequest.Builder</code> API is unusual: there's no symmetry, i.e. you can't undo state changes (unset expedited, remove a tag...)</p>
</blockquote>
<h1 id="heading-multi-process">Multi process</h1>
<p>I want to schedule work from the main app process (e.g. <code>com.example</code>), and that work should execute in a separate process (e.g. <code>com.example:mywork</code>).</p>
<h2 id="heading-remoteworkerservice">RemoteWorkerService</h2>
<p>First, I register a <a target="_blank" href="https://developer.android.com/reference/androidx/work/multiprocess/RemoteWorkerService">RemoteWorkerService</a> that will run in the <code>:mywork</code> process:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyRemoteWorkerService</span> : <span class="hljs-type">RemoteWorkerService</span></span>()
</code></pre>
<blockquote>
<p>Note: I'm registering a subclass of <code>RemoteWorkerService</code> because component names are unique per app, so this avoids conflicts if the consuming app already registered a <code>RemoteWorkerService</code> in its manifest. The <code>RemoteWorkerService</code> class should probably have been abstract.</p>
</blockquote>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">manifest</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
    <span class="hljs-attr">package</span>=<span class="hljs-string">"com.example"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">application</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">service</span>
      <span class="hljs-attr">android:name</span>=<span class="hljs-string">".MyRemoteWorkerService"</span>
      <span class="hljs-attr">android:exported</span>=<span class="hljs-string">"false"</span>
      <span class="hljs-attr">android:process</span>=<span class="hljs-string">":mywork"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">manifest</span>&gt;</span>
</code></pre>
<h2 id="heading-remoteworker">RemoteWorker</h2>
<p>Since my remote process uses the same APK, my remote worker should ideally be fairly identical to my previous non remote worker, for example:</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyRemoteWorker</span></span>(
  appContext: Context,
  workerParams: WorkerParameters
) : RemoteWorker(appContext, workerParams) {
  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">doWork</span><span class="hljs-params">()</span></span>: Result {
    TODO(<span class="hljs-string">"perform the work"</span>)
    <span class="hljs-keyword">return</span> Result.success()
  }
}
</code></pre><p>Unfortunately, the <code>RemoteWorker</code> class doesn't exist, we only have <a target="_blank" href="https://developer.android.com/reference/androidx/work/multiprocess/RemoteListenableWorker">RemoteListenableWorker</a>. That's ok though, same as how <code>Worker</code> extends <code>ListenableWorker</code>, I can create a <code>RemoteWorker</code> that extends <code>RemoteListenableWorker</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RemoteWorker</span></span>(
  context: Context,
  workerParams: WorkerParameters
) : RemoteListenableWorker(context, workerParams) {

  <span class="hljs-keyword">abstract</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">doWork</span><span class="hljs-params">()</span></span>: Result

  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">startRemoteWork</span><span class="hljs-params">()</span></span>: ListenableFuture&lt;Result&gt; {
    <span class="hljs-keyword">val</span> future = SettableFuture.create&lt;Result&gt;()
    backgroundExecutor.execute {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">val</span> result = doWork()
        future.<span class="hljs-keyword">set</span>(result)
      } <span class="hljs-keyword">catch</span> (throwable: Throwable) {
        future.setException(throwable)
      }
    }
    <span class="hljs-keyword">return</span> future
  }
}
</code></pre>
<blockquote>
<p>Note: I'm not sure why <code>Worker</code> is provided but <code>RemoteWorker</code> isn't. That might be because blocking APIs tend to lead to implementations that don't support cancellation (as is the case here). The other thing is, there's so little difference between the remote and non remote implementations, I wish I could define a single worker class and decide where to run it when I schedule the work.</p>
</blockquote>
<h2 id="heading-scheduling-the-work">Scheduling the work</h2>
<p>Scheduling remote work is almost identical, except that we need to provide the component name for the remote service as part of the work request (these arguments are parsed by <code>RemoteListenableWorker</code>):</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWorkScheduler</span> </span>{
  <span class="hljs-comment">// ...</span>

  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> remoteWorkerServiceInClasspath <span class="hljs-keyword">by</span> lazy {
    <span class="hljs-keyword">try</span> {
      Class.forName(<span class="hljs-string">"androidx.work.multiprocess.RemoteWorkerService"</span>)
      <span class="hljs-literal">true</span>
    } <span class="hljs-keyword">catch</span> (ignored: Throwable) {
      <span class="hljs-literal">false</span>
    }
  }

  <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">enqueueWork</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> (remoteWorkerServiceInClasspath) {
      enqueueOnWorkManagerRemote()
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (workManagerInClasspath) {
      enqueueOnWorkManager()
    } <span class="hljs-keyword">else</span> {
      enqueueOnBackgroundThread()
    }
  }

  <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">enqueueOnWorkManagerRemote</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">val</span> request = OneTimeWorkRequest.Builder(MyRemoteWorker::<span class="hljs-keyword">class</span>.java).apply {
      putString(ARGUMENT_PACKAGE_NAME, application.packageName)
      putString(ARGUMENT_CLASS_NAME, <span class="hljs-string">"com.example.MyRemoteWorkerService"</span>)
      <span class="hljs-keyword">if</span> (workManagerSupportsExpeditedRequests) {
        setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
      }
    }.build()
    WorkManager.getInstance(application).enqueue(request)
  }
}
</code></pre>
<h1 id="heading-crash">Crash</h1>
<p>At this point, I feel pretty good about the whole thing. This should work! Let's run the code:</p>
<pre><code>java.lang.IllegalStateException: WorkManager <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> initialized properly.
You have explicitly disabled WorkManagerInitializer <span class="hljs-keyword">in</span> your manifest,
have <span class="hljs-keyword">not</span> manually <span class="hljs-keyword">called</span> WorkManager#initialize at this <span class="hljs-type">point</span>, <span class="hljs-keyword">and</span> your
Application does <span class="hljs-keyword">not</span> implement <span class="hljs-keyword">Configuration</span>.Provider.
    at androidx.<span class="hljs-keyword">work</span>.impl.WorkManagerImpl.getInstance(WorkManagerImpl.java:<span class="hljs-number">158</span>)
    at androidx.<span class="hljs-keyword">work</span>.multiprocess.ListenableWorkerImpl.&lt;init&gt;(ListenableWorkerImpl.java:<span class="hljs-number">72</span>)
    at androidx.<span class="hljs-keyword">work</span>.multiprocess.RemoteWorkerService.onCreate(RemoteWorkerService.java:<span class="hljs-number">37</span>)
    at android.app.ActivityThread.handleCreateService(ActivityThread.java:<span class="hljs-number">4487</span>)
    at android.app.ActivityThread.<span class="hljs-keyword">access</span><span class="hljs-meta">$1700</span>(ActivityThread.java:<span class="hljs-number">247</span>)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:<span class="hljs-number">2072</span>)
    at android.os.<span class="hljs-keyword">Handler</span>.dispatchMessage(<span class="hljs-keyword">Handler</span>.java:<span class="hljs-number">106</span>)
    at android.os.Looper.loopOnce(Looper.java:<span class="hljs-number">201</span>)
    at android.os.Looper.<span class="hljs-keyword">loop</span>(Looper.java:<span class="hljs-number">288</span>)
    at android.app.ActivityThread.main(ActivityThread.java:<span class="hljs-number">7839</span>)
    at java.lang.reflect.<span class="hljs-keyword">Method</span>.invoke(Native <span class="hljs-keyword">Method</span>)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:<span class="hljs-number">548</span>)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:<span class="hljs-number">1003</span>)
</code></pre><p>The exception message is confusing at first. I didn't do anything to WorkManager's initialization, why is it complaining?</p>
<p>See, that's the thing: when <code>RemoteWorkerService.onCreate()</code> runs in the <code>com.example:mywork</code> process, WorkManager needs to be already initialized. In the main process, that's automatically done by the Androidx startup library. However, the startup initializers don't run in other processes!</p>
<p>As a library developer, I don't have access to the <code>Application</code> class, so I can't make it implement <a target="_blank" href="https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration">Configuration.Provider</a> or have it call <code>WorkManager.initialize()</code>.</p>
<p>I can find another way to init WorkManager, however WorkManager initialization can only happen once, so if the developer set up custom WorkManager initialization then my init will conflict with their init. Unfortunately, there are no <code>WorkManager.isInitialized()</code> or  <code>WorkManager.getInstanceAndInitIfNotDoneYet()</code> APIs.</p>
<p>Sometimes you just have to follow the <a target="_blank" href="https://en.wikipedia.org/wiki/Desire_path">desire path</a>...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640674291423/0gvWS7BZi.png" alt="desire-path-usability.png" /></p>
<p>Let's create the API we need:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyRemoteWorkerService</span> : <span class="hljs-type">RemoteWorkerService</span></span>() {
  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> (!isWorkManagerInitialized()) {
      WorkManager.initialize(
        applicationContext,
        Configuration.Builder().build()
      )
    }
    <span class="hljs-keyword">super</span>.onCreate()
  }

  <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">isWorkManagerInitialized</span><span class="hljs-params">()</span></span> = <span class="hljs-keyword">try</span> {
    WorkManager.getInstance(applicationContext)
    <span class="hljs-literal">true</span>
  } <span class="hljs-keyword">catch</span> (ignored: Throwable) {
    <span class="hljs-literal">false</span>
  }
}
</code></pre>
<h1 id="heading-rescheduling">Rescheduling</h1>
<p>At this point, it works! However, I quickly notice that when I schedule remote work, the <code>:mywork</code> process starts, the work starts, then the work is immediately canceled, then it's rescheduled and eventually runs fine. That's weird.</p>
<p>After debugging the WorkManager library code in 2 parallel processes (😰) I eventually figure out that when WorkManager is initialized, it runs a <a target="_blank" href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:work/work-runtime/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java">ForceStopRunnable</a> which cancels and then reschedules all the work on init.</p>
<p>One way to prevent ForceStopRunnable from running is to set <a href="https://developer.android.com/reference/androidx/work/Configuration.Builder#setDefaultProcessName(java.lang.String)">Configuration.Builder.setDefaultProcessName</a> to the main app process name. Unfortunately that's not ideal: if the developer did set the work manager configuration in their application class and didn't set <code>setDefaultProcessName</code>, then <code>ForceStopRunnable</code> will run and I can't do anything to change that.</p>
<p>So we can only provide a fix for the non initialized case:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyRemoteWorkerService</span> : <span class="hljs-type">RemoteWorkerService</span></span>() {
  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onCreate</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">if</span> (!isWorkManagerInitialized()) {
      WorkManager.initialize(
        applicationContext,
        Configuration.Builder()
          .setDefaultProcessName(applicationContext.packageName)
          .build()
      )
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">// If the developer didn't set setDefaultProcessName in the</span>
      <span class="hljs-comment">// Configuration.Builder then the work will be rescheduled once </span>
      <span class="hljs-comment">// when :mywork starts and there's nothing we can do about it.</span>
    }
    <span class="hljs-keyword">super</span>.onCreate()
  }
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<h1 id="heading-an-ever-cooler-hack">An ever cooler hack</h1>
<p><strong>Edit: after thinking through this once more, I realized there was another way I could get this working, and it's probably a better hack. Here goes.</strong></p>
<p>WorkManager will automatically initialize itself on first use if the application <code>Context</code> implements <code>Configuration.Provider</code>. But there's nothing about this contract that says the <code>Application</code> class has to be the application <code>Context</code>. That's one of the <a target="_blank" href="https://stackoverflow.com/a/6760019/703646">most annoying parts</a> of the <code>Context</code> API, but for once, we can benefit from it!</p>
<p>The idea is to have <code>RemoteWorkerService.getApplicationContext()</code> return a fake app context that implements <code>Configuration.Provider</code> and provides the configuration we want:</p>
<pre><code class="lang-kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyRemoteWorkerService</span> : <span class="hljs-type">RemoteWorkerService</span></span>() {

  <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FakeAppContextConfigurationProvider</span></span>(base: Context)
    : ContextWrapper(base), Configuration.Provider {

    <span class="hljs-comment">// service.applicationContext.applicationContext still returns this</span>
    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getApplicationContext</span><span class="hljs-params">()</span></span> = <span class="hljs-keyword">this</span>

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getWorkManagerConfiguration</span><span class="hljs-params">()</span></span> = Configuration.Builder()
      .setDefaultProcessName(packageName)
      .build()
  }

  <span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> fakeAppContext <span class="hljs-keyword">by</span> lazy {
    FakeAppContextConfigurationProvider(<span class="hljs-keyword">super</span>.getApplicationContext())
  }

  <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">getApplicationContext</span><span class="hljs-params">()</span></span>: Context {
    <span class="hljs-keyword">return</span> fakeAppContext
  }
}
</code></pre>
<p>This is even cooler than the previous hack because, whether or not the developer implemented <code>Configuration.Builder</code> in their application class, we get to decide what configuration we want for our own process. This only way around it is if they called <code>WorkManager.initialize</code> directly.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Getting WorkManager multi-process to work well in a library isn't straightforward and currently requires a few hacks, but that's not surprising: Android has historically been fairly bad at building APIs with libraries in mind, and having a single Application class has always been a source of bugs for multi process apps. The AndroidX WorkManager and Startup libraries are going in the right direction!</p>
<blockquote>
<p>Huge thanks to <a target="_blank" href="https://twitter.com/tikurahul">Rahul Ravikumar</a> for his help with figuring out WorkManager multi-process. He's already working on <a target="_blank" href="https://android-review.googlesource.com/c/platform/frameworks/support/+/1934672">addressing some of these issues</a>!</p>
</blockquote>
]]></content:encoded></item></channel></rss>