<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Py ⚔</title>
    <description>The latest articles on DEV Community by Py ⚔ (@pyricau).</description>
    <link>https://dev.to/pyricau</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F419769%2F4f2878c5-7014-4dc5-8ef0-4edd8a863cac.jpeg</url>
      <title>DEV Community: Py ⚔</title>
      <link>https://dev.to/pyricau</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pyricau"/>
    <language>en</language>
    <item>
      <title>Leak investigation: Rx disposal race in SQLDelight</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Mon, 17 May 2021 22:10:41 +0000</pubDate>
      <link>https://dev.to/pyricau/leak-investigation-rx-disposal-race-in-sqldelight-3n06</link>
      <guid>https://dev.to/pyricau/leak-investigation-rx-disposal-race-in-sqldelight-3n06</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;The In-Between&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/26364010648/"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this blog we'll look into how an easy mistake when using &lt;code&gt;Observable.create()&lt;/code&gt; can lead to subtle leaks.&lt;/p&gt;

&lt;p&gt;I recently investigated the following leak, which I couldn't reproduce systematically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┬───
...
├─ com.example.hockey.PlayerQueries$selectAllQuery instance
│    ↓ Query.listeners
│            ~~~~~~~~~
├─ java.util.concurrent.CopyOnWriteArrayList instance
│    ↓ CopyOnWriteArrayList.array
│                           ~~~~~
├─ java.lang.Object[] array
│    ↓ Object[].[0]
│               ~~~
├─ sqldelight.runtime.rx.QueryListenerAndDisposable instance
│    Retaining 4.3 kB in 56 objects
│    ↓ QueryListenerAndDisposable.emitter
│                                 ~~~~~~~
... RxJava observer chain
├─ com.example.hockey.PlayersView$onAttachedToWindow$1 instance
│    Anonymous class implementing io.reactivex.functions.Function
│    ↓ PlayersView$onAttachedToWindow$1.this$0
│                                       ~~~~~~
╰→ com.example.hockey.view.PlayersView instance
     Leaking: YES (View.mContext references a destroyed activity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above leaktrace, &lt;code&gt;PlayerQueries$selectAllQuery&lt;/code&gt; is a generated &lt;a href="https://cashapp.github.io/sqldelight/1.x/runtime/com.squareup.sqldelight/-query/"&gt;SQLDelight query&lt;/a&gt;. Our &lt;code&gt;PlayersView&lt;/code&gt; is listening for updates to that query while the view is attached by leveraging &lt;a href="https://cashapp.github.io/sqldelight/android_sqlite/rxjava/"&gt;Query.asObservable()&lt;/a&gt;. Once the view is detached, the observable chain is disposed and the query is expected to let go of the corresponding listener.&lt;/p&gt;

&lt;p&gt;I inspected the heap dump and found that the view was indeed detached, the observable chain was correctly disposed, and yet the &lt;code&gt;QueryListenerAndDisposable&lt;/code&gt; listener had not been removed from the query. Let's look at the &lt;code&gt;Query.asObservable()&lt;/code&gt; implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;asObservable&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;QueryOnSubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueryOnSubscribe&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObservableOnSubscribe&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObservableEmitter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QueryListenerAndDisposable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDisposable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueryListenerAndDisposable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObservableEmitter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;,&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AtomicBoolean&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Disposable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;queryResultsChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isDisposed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compareAndSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's now look at the implementation example from &lt;a href="http://reactivex.io/RxJava/javadoc/io/reactivex/Observable.html#create-io.reactivex.ObservableOnSubscribe-"&gt;Observable.create()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;observable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;emitter&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;closeable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;someMethod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCancellable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closeable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can rewrite the above example code to use a listener instead of a closeable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;observable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;emitter&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Create a listener&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Set the listener&lt;/span&gt;
  &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Remove the listener on dispose&lt;/span&gt;
  &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCancellable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above implementation is fairly close to the SQLDelight implementation, with one major difference: SQLDelight sets the disposable &lt;strong&gt;before&lt;/strong&gt; adding the listener to the query, i.e. something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;observable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;emitter&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Create a listener&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Remove the listener on dispose&lt;/span&gt;
  &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCancellable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Set the listener&lt;/span&gt;
  &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out that's a mistake! If &lt;code&gt;emitter&lt;/code&gt; is already disposed when the subscription runs, then we'll add the listener but never remove it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;observable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;emitter&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Create a listener&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. if emitter is currently already disposed,&lt;/span&gt;
  &lt;span class="c1"&gt;// the cancellable callback fires immediately and&lt;/span&gt;
  &lt;span class="c1"&gt;// there's no listener to remove yet.&lt;/span&gt;
  &lt;span class="n"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCancellable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// 3. Set the listener, which will never be removed. Leak!&lt;/span&gt;
  &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can &lt;code&gt;emitter&lt;/code&gt; be already disposed when the subscription runs? Yes! This can happen if the subscription runs on a separate thread from the thread that called &lt;code&gt;subscribe()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;subscription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Schedulers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// `dispose()` might execute at the time as the subscription&lt;/span&gt;
&lt;span class="c1"&gt;// callback.&lt;/span&gt;
&lt;span class="n"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Take aways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;When using &lt;code&gt;Observable.create()&lt;/code&gt;, check the order in which you set a listener vs set the disposable.&lt;/li&gt;
&lt;li&gt;I opened a &lt;a href="https://github.com/cashapp/sqldelight/pull/2408"&gt;PR to fix SQLDelight&lt;/a&gt;. In the meantime, remove &lt;code&gt;subscribeOn&lt;/code&gt; calls for observables that originate from &lt;code&gt;Query.asObservable()&lt;/code&gt;. As you saw from the code above, all &lt;code&gt;Query.asObservable()&lt;/code&gt; is doing is setting a listener so there's no need to be on any special scheduler here.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>leak</category>
      <category>rxjava</category>
      <category>sqldelight</category>
    </item>
    <item>
      <title>Tap Response Time: Jetpack Navigation 🗺</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Sat, 24 Apr 2021 19:27:34 +0000</pubDate>
      <link>https://dev.to/pyricau/tap-response-time-jetpack-navigation-4738</link>
      <guid>https://dev.to/pyricau/tap-response-time-jetpack-navigation-4738</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Surf&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/14889089000/" rel="noopener noreferrer"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-tap-response-time-19mj"&gt;Android Vitals - Tap Response Time 👉&lt;/a&gt; we established that the naive approach to measuring &lt;em&gt;Tap Response Time&lt;/em&gt; isn't accurate and doesn't scale. Today we'll build a better implementation step by step, on top Jetpack Navigation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  🗺 Navigation library
&lt;/h3&gt;

&lt;p&gt;We'll focus on &lt;a href="https://developer.android.com/guide/navigation" rel="noopener noreferrer"&gt;Jetpack Navigation&lt;/a&gt; here, however most of the content applies for any navigation library or tap action. In fact, I first implemented this at Square on top of &lt;a href="https://github.com/square/flow" rel="noopener noreferrer"&gt;Flow&lt;/a&gt; and &lt;a href="https://github.com/square/workflow-kotlin/" rel="noopener noreferrer"&gt;Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Advanced Navigation Sample
&lt;/h1&gt;

&lt;p&gt;We'll implement the &lt;em&gt;Tap Response Time&lt;/em&gt; measurement inside the &lt;a href="https://github.com/android/architecture-components-samples/tree/main/NavigationAdvancedSample" rel="noopener noreferrer"&gt;Advanced Navigation Sample&lt;/a&gt; and focus on the navigation from the &lt;a href="https://github.com/android/architecture-components-samples/blob/main/NavigationAdvancedSample/app/src/main/java/com/example/android/navigationadvancedsample/homescreen/Title.kt#L37-L39" rel="noopener noreferrer"&gt;Title screen to the About screen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazwon04dn3kyjg9iyht4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazwon04dn3kyjg9iyht4.png" alt="Navigation sample"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;aboutButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;findNavController&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action_title_to_about&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  From Tap to Render
&lt;/h1&gt;

&lt;p&gt;What happens exactly when we click on the about button?&lt;/p&gt;

&lt;h2&gt;
  
  
  Main thread tracing
&lt;/h2&gt;

&lt;p&gt;To figure that out, we enable Java method tracing while clicking on the button :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F200yjd1b1qhagbp0f3pu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F200yjd1b1qhagbp0f3pu.png" alt="Java Method Sampling"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;MotionEvent.ACTION_UP&lt;/code&gt; event is dispatched and a click is posted to the main thread.&lt;/li&gt;
&lt;li&gt;The posted click runs, the click listener calls &lt;code&gt;NavController.navigate()&lt;/code&gt; and a fragment transaction is posted to the main thread.&lt;/li&gt;
&lt;li&gt;The fragment transaction runs, the view hierarchy is updated, and a view traversal is scheduled for the next frame on the main thread.&lt;/li&gt;
&lt;li&gt;The view traversal runs, the view hierarchy is measured, laid out and drawn.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What happens after step 4?&lt;/p&gt;

&lt;h2&gt;
  
  
  Systrace
&lt;/h2&gt;

&lt;p&gt;We get a better high level view with systrace:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2jtyv9n0dqfults0eec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2jtyv9n0dqfults0eec.png" alt="systrace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In step 4, the view traversal &lt;strong&gt;draw pass&lt;/strong&gt; generates a list of drawing commands (known as &lt;strong&gt;display lists&lt;/strong&gt;) and sends that list of drawing commands to the &lt;em&gt;render thread&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Step 5: the &lt;strong&gt;render thread&lt;/strong&gt; optimizes the &lt;em&gt;display lists&lt;/em&gt;, adds effects such as ripples, then leverages the GPU to run the drawing commands and draw into a buffer (an OpenGL surface). Once done, the render thread tells the &lt;em&gt;surface flinger&lt;/em&gt; (which lives in a separate process) to &lt;strong&gt;swap the buffer&lt;/strong&gt; and put it on the display.&lt;/li&gt;
&lt;li&gt;Step 6 (not visible in the systrace screenshot): the surfaces for &lt;strong&gt;all visible windows are composited&lt;/strong&gt; by the surface flinger and hardware composer, and the result is &lt;strong&gt;sent to the display&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Tap Response Time
&lt;/h1&gt;

&lt;p&gt;We previously defined the &lt;em&gt;Tap Response Time&lt;/em&gt; as the time from when the user is &lt;strong&gt;done pressing&lt;/strong&gt; a button to when the app has &lt;strong&gt;visibly reacted&lt;/strong&gt; to the tap. In other words, we need to measure the &lt;strong&gt;total duration&lt;/strong&gt; of going through &lt;strong&gt;steps 1 to 6&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the next sections I'll explain how we can detect each step. If you'd rather immediately go to the final implementation, here's the &lt;a href="https://github.com/pyricau/architecture-components-samples/pull/1/files" rel="noopener noreferrer"&gt;PR against NavigationAdvancedSample&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Up dispatch
&lt;/h2&gt;

&lt;p&gt;We leverage &lt;a href="https://github.com/square/curtains" rel="noopener noreferrer"&gt;square/curtains&lt;/a&gt; to intercept touch events. We define &lt;code&gt;TapTracker&lt;/code&gt;, a touch event interceptor. &lt;code&gt;TapTracker&lt;/code&gt; stores the time of the last &lt;code&gt;MotionEvent.ACTION_UP&lt;/code&gt; touch event. When the posted click listener triggers, we retrieve the time of the up event that triggered it by calling &lt;code&gt;TapTracker.currentTap&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;TapTracker&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TouchEventInterceptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;currentTap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TapResponseTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Looper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMainLooper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;motionEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MotionEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MotionEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;DispatchState&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;DispatchState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isActionUp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;motionEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;MotionEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_UP&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isActionUp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tapUptimeMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;motionEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventTime&lt;/span&gt;
      &lt;span class="c1"&gt;// Set currentTap right before the click listener fires&lt;/span&gt;
      &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;TapTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentTap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TapResponseTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;tapUptimeMillis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tapUptimeMillis&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Dispatching posts the click listener.&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dispatchState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;motionEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isActionUp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Clear currentTap right after the click listener fires&lt;/span&gt;
      &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;currentTap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dispatchState&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then add the &lt;code&gt;TapTracker&lt;/code&gt; interceptor to each new window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleApplication&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;Curtains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onRootViewsChangedListeners&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt;
      &lt;span class="nc"&gt;OnRootViewAddedListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phoneWindow&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;windowAttachCount&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;touchEventInterceptors&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="nc"&gt;TapTracker&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Click listener &amp;amp; navigation
&lt;/h2&gt;

&lt;p&gt;Let's define an &lt;code&gt;ActionTracker&lt;/code&gt; that will be called when when the posted click listener triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ActionTracker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;reportTapAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentTap&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TapTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentTap&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentTap&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// to be continued...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how we could leverage it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;aboutButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;findNavController&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action_title_to_about&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nc"&gt;ActionTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reportTapAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"About"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, we don't want to add that code to every click listener. Instead, we can add a &lt;a href="https://developer.android.com/reference/androidx/navigation/NavController#addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener)" rel="noopener noreferrer"&gt;destination listener&lt;/a&gt; to the &lt;code&gt;NavController&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOnDestinationChangedListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;ActionTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reportTapAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;Advanced Navigation Sample&lt;/em&gt; has 3 tabs, and each tab contains its own &lt;code&gt;NavHostFragment&lt;/code&gt; and &lt;code&gt;NavController&lt;/code&gt;. We could add a destination listener for each tab. Or we can leverage lifecycle callbacks to add a destination listener to every new &lt;code&gt;NavHostFragment&lt;/code&gt; instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalNavHostDestinationChangedListener&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;FragmentActivity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;registerFragmentCreation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;registerFragmentCreation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FragmentActivity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supportFragmentManager&lt;/span&gt;
    &lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerFragmentLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;FragmentLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onFragmentCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FragmentManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;NavHostFragment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;registerDestinationChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;registerDestinationChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NavHostFragment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;navController&lt;/span&gt;
    &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOnDestinationChangedListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;actionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nc"&gt;ActionTracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reportTapAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actionName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Fragment transaction
&lt;/h2&gt;

&lt;p&gt;Calling &lt;code&gt;NavController.navigate()&lt;/code&gt; does not immediately update the view hierarchy. Instead, a fragment transaction is posted to the main thread. The view for the destination fragment will be created and attached when the fragment transaction executes. Since all pending fragment transactions are &lt;a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java;l=1545-1546;drc=31b10c21385fd9ba6350d9eed69847194a97a24d" rel="noopener noreferrer"&gt;executed at once&lt;/a&gt;, we add our own custom transaction to leverage the &lt;code&gt;runOnCommit()&lt;/code&gt; callback. Let's first build a utility, &lt;code&gt;OnTxCommitFragmentViewUpdateRunner.runOnViewsUpdated()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OnTxCommitFragmentViewUpdateRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;runOnViewsUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parentFragmentManager&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runOnCommit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then pass an instance to &lt;code&gt;ActionTracker.reportTapAction()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;class GlobalNavHostDestinationChangedListener
&lt;/span&gt; ...
     val navController = fragment.navController
     navController.addOnDestinationChangedListener { _, dest, _ -&amp;gt;
       val actionName = dest.label.toString()
&lt;span class="gd"&gt;-      ActionTracker.reportTapAction(actionName)
&lt;/span&gt;&lt;span class="gi"&gt;+      ActionTracker.reportTapAction(
+        actionName,
+        OnTxCommitFragmentViewUpdateRunner(fragment)
+      )
&lt;/span&gt;     }
   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; object ActionTracker {
&lt;span class="gd"&gt;-  fun reportTapAction(actionName: String) {
&lt;/span&gt;&lt;span class="gi"&gt;+  fun reportTapAction(
+      actionName: String,
+      viewUpdateRunner: OnTxCommitFragmentViewUpdateRunner
+  ) {
&lt;/span&gt;     val currentTap = TapTracker.currentTap
     if (currentTap != null) {
&lt;span class="gd"&gt;-      // to be continued...
&lt;/span&gt;&lt;span class="gi"&gt;+      viewUpdateRunner.runOnViewsUpdated { view -&amp;gt;
+        // to be continued...
+      }
&lt;/span&gt;     }
   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Frame &amp;amp; view hierarchy traversal
&lt;/h2&gt;

&lt;p&gt;When the fragment transaction executes, a view traversal is scheduled for the next frame, which we hook into with &lt;a href="https://developer.android.com/reference/android/view/Choreographer#postFrameCallback(android.view.Choreographer.FrameCallback)" rel="noopener noreferrer"&gt;Choreographer.postFrameCallback()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; object ActionTracker {
&lt;span class="gi"&gt;+
+  // Debounce multiple calls until the next frame
+  private var actionInFlight: Boolean = false
+
&lt;/span&gt;   fun reportTapAction(
       actionName: String,
       viewUpdateRunner: OnTxCommitFragmentViewUpdateRunner
   ) {
     val currentTap = TapTracker.currentTap
&lt;span class="gd"&gt;-    if (currentTap != null) {
&lt;/span&gt;&lt;span class="gi"&gt;+    if (!actionInFlight &amp;amp; currentTap != null) {
+      actionInFlight = true
&lt;/span&gt;       viewUpdateRunner.runOnViewsUpdated { view -&amp;gt;
&lt;span class="gd"&gt;-        // to be continued...
&lt;/span&gt;&lt;span class="gi"&gt;+        val choreographer = Choreographer.getInstance()
+        choreographer.postFrameCallback { frameTimeNanos -&amp;gt;
+          actionInFlight = false
+          // to be continued...
+        }
&lt;/span&gt;       }
     }
   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: RenderThread
&lt;/h2&gt;

&lt;p&gt;Once the view traversal is done, the main thread sends the &lt;strong&gt;display lists&lt;/strong&gt; to the &lt;strong&gt;render thread&lt;/strong&gt;. The render thread does additional work and then tells the &lt;strong&gt;surface flinger&lt;/strong&gt; to &lt;strong&gt;swap the buffer&lt;/strong&gt; and put it on the display. We register a &lt;a href="https://developer.android.com/reference/android/view/Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener,%20android.os.Handler)" rel="noopener noreferrer"&gt;OnFrameMetricsAvailableListener&lt;/a&gt; to get the total frame duration (including time spent on the render thread):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; object ActionTracker {
 ...
         val choreographer = Choreographer.getInstance()
         choreographer.postFrameCallback { frameTimeNanos -&amp;gt;
           actionInFlight = false
&lt;span class="gd"&gt;-          // to be continued...
&lt;/span&gt;&lt;span class="gi"&gt;+          val callback: (FrameMetrics) -&amp;gt; Unit = { frameMetrics -&amp;gt;
+            logTapResponseTime(currentTap, frameMetrics)
+          }
+          view.phoneWindow!!.addOnFrameMetricsAvailableListener(
+            CurrentFrameMetricsListener(frameTimeNanos, callback),
+            frameMetricsHandler
+          )
&lt;/span&gt;         }
       }
     }
   }
&lt;span class="gi"&gt;+
+  private fun logTapResponseTime(
+    currentTap: TapResponseTime.Builder,
+    fM: FrameMetrics
+  ) {
+    // to be continued...
+  }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have the frame metrics we can determine when the frame buffer was swapped, and therefore the &lt;strong&gt;Tap Response Time&lt;/strong&gt;, i.e. the time from &lt;code&gt;MotionEvent.ACTION_UP&lt;/code&gt; to buffer swap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; object ActionTracker {
 ...
     currentTap: TapResponseTime.Builder,
     fM: FrameMetrics
   ) {
&lt;span class="gd"&gt;-    // to be continued...
&lt;/span&gt;&lt;span class="gi"&gt;+    val tap = currentTap.tapUptimeMillis
+    val intendedVsync = fM.getMetric(INTENDED_VSYNC_TIMESTAMP)
+    // TOTAL_DURATION is the duration from the intended vsync
+    // time, not the actual vsync time.
+    val frameDuration = fM.getMetric(TOTAL_DURATION)
+    val bufferSwap = (intendedVsync + frameDuration) / 1_000_000
+    Log.d("TapResponseTime", "${bufferSwap-tap} ms")
&lt;/span&gt;   }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: SurfaceFlinger
&lt;/h2&gt;

&lt;p&gt;There's no Java API to determine when the composited frames end up being sent to the display by SurfaceFlinger, so I didn't include that part. Romain Guy mentioned this can be done with a native call to &lt;a href="https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_frame_timestamps.txt" rel="noopener noreferrer"&gt;EGL_ANDROID_get_frame_timestamps&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;When we click on the About button, we now see a nice log in Logcat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;D/TapResponseTime: 105 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran systrace at the same time. As you can see in the screenshot, the time from tap to buffer swap is also 105ms:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wci86y6v9ds514pf7fh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wci86y6v9ds514pf7fh.png" alt="systrace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;You can see the final result in &lt;a href="https://github.com/pyricau/architecture-components-samples/pull/1/files" rel="noopener noreferrer"&gt;this PR&lt;/a&gt;. Feel free to leave comments on the PR! I added a few more things that I didn't cover in this blog, such as back key support and logging tab navigation.&lt;/p&gt;

&lt;p&gt;I intend to eventually create a Square Open Source library for this. Until then, you have everything you need to get started.&lt;/p&gt;

&lt;p&gt;My hope is that you can leverage this code to start measuring tap response time in your production apps, which will help you improve the experience of your customers 👏.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚖️ This work is licensed under a &lt;a href="https://creativecommons.org/licenses/by/4.0" rel="noopener noreferrer"&gt;Creative Commons Attribution 4.0 International License&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>android</category>
      <category>performance</category>
      <category>jetpack</category>
      <category>navigation</category>
    </item>
    <item>
      <title>Android Vitals - Tap Response Time 👉</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Thu, 15 Apr 2021 14:10:20 +0000</pubDate>
      <link>https://dev.to/pyricau/android-vitals-tap-response-time-19mj</link>
      <guid>https://dev.to/pyricau/android-vitals-tap-response-time-19mj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Alone Together&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/10982291363/"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Android users expect apps to respond to their actions within a &lt;strong&gt;short time window&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  💡 Did you know?
&lt;/h3&gt;

&lt;p&gt;UX research teaches us that a response time shorter than &lt;strong&gt;100ms&lt;/strong&gt; feels &lt;strong&gt;immediate&lt;/strong&gt;, and a response time beyond &lt;strong&gt;1s&lt;/strong&gt; makes users &lt;strong&gt;lose focus&lt;/strong&gt;. When the response time gets closer to 10 seconds, users simply abandon their task (&lt;a href="https://web.dev/rail/#focus-on-the-user"&gt;source&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  👉📱
&lt;/h1&gt;

&lt;p&gt;Measuring &lt;strong&gt;user action response times&lt;/strong&gt; is critical to ensure a &lt;strong&gt;good user experience&lt;/strong&gt;. &lt;strong&gt;Taps&lt;/strong&gt; are the most common action apps must respond to. Can we measure &lt;em&gt;Tap Response Time&lt;/em&gt;?&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  🎓 Tap Response Time
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;Tap Response Time&lt;/em&gt; is the time from when the user is &lt;strong&gt;done pressing&lt;/strong&gt; a button to when the app has &lt;strong&gt;visibly reacted&lt;/strong&gt; to the tap.&lt;br&gt;
More precisely, it's the time from when the finger leaves the touch screen to when the display has rendered a frame with a visible reaction to that tap (e.g. the start of a navigation animation). The &lt;em&gt;Tap Response Time&lt;/em&gt; does not include any animation time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Naive Tap Response Time
&lt;/h1&gt;

&lt;p&gt;I opened the &lt;a href="https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample"&gt;Navigation Advanced Sample&lt;/a&gt; project and added a call to &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.system/measure-time-millis.html"&gt;measureTimeMillis()&lt;/a&gt; to measure the &lt;em&gt;Tap Response Time&lt;/em&gt; when tapping on the &lt;a href="https://github.com/android/architecture-components-samples/blob/main/NavigationAdvancedSample/app/src/main/java/com/example/android/navigationadvancedsample/homescreen/Title.kt#L37-L39"&gt;about button&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;aboutButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tapResponseTimeMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;measureTimeMillis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;findNavController&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action_title_to_about&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;PerfAnalytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logTapResponseTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tapResponseTimeMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple enough! However this approach presents several drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⌛️ It can return a &lt;strong&gt;negative time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;📈 It &lt;strong&gt;doesn't scale&lt;/strong&gt; with the codebase size.&lt;/li&gt;
&lt;li&gt;👉 It doesn't account for the time from when the &lt;strong&gt;finger leaves the touch screen&lt;/strong&gt; to when the click listener is called.&lt;/li&gt;
&lt;li&gt;📱 It doesn't account for the time from when we're done calling &lt;a href="https://developer.android.com/reference/androidx/navigation/NavController#navigate(int)"&gt;NavController.navigate()&lt;/a&gt; to when the &lt;strong&gt;display has rendered a frame&lt;/strong&gt; with the new screen visible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⌛️ Negative time
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.system/measure-time-millis.html"&gt;measureTimeMillis()&lt;/a&gt; calls &lt;code&gt;System.currentTimeMillis()&lt;/code&gt; whihch can be set by the user or the phone network, so the time may jump backwards or forwards unpredictably. Elapsed time measurements should not use &lt;code&gt;System.currentTimeMillis()&lt;/code&gt; (learn more: &lt;a href="https://dev.to/pyricau/android-vitals-what-time-is-it-2oih"&gt;Android Vitals - What time is it?&lt;br&gt;
&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  📈 Large codebases
&lt;/h2&gt;

&lt;p&gt;Adding measuring code to every single meaningful click listener is a daunting task. We need a solution that scales with the codebase size, which means we need &lt;strong&gt;central hooks&lt;/strong&gt; to detect when meaningful actions are triggered by taps.&lt;/p&gt;
&lt;h2&gt;
  
  
  👉 Touch pipeline
&lt;/h2&gt;

&lt;p&gt;When a finger leaves the touch screen, the following happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;system_server&lt;/code&gt; process receives the information from the touch screen and determines which window should receive a &lt;code&gt;MotionEvent.UP&lt;/code&gt; touch event.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  💡 Did you know?
&lt;/h3&gt;

&lt;p&gt;Every window is associated with an input event &lt;a href="https://www.gnu.org/software/libc/manual/html_node/Socket-Pairs.html"&gt;socket pair&lt;/a&gt;: the first socket is owned by &lt;code&gt;system_server&lt;/code&gt; to send input events. That first socket is paired with a second socket owned by the app that created the window, to receive input events.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;system_server&lt;/code&gt; process sends the touch event to the input event socket for the targeted window.&lt;/li&gt;
&lt;li&gt;The app receives the touch event on its listening socket, stores it in a queue (&lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewRootImpl.java;l=7866-7878;drc=344f4d71075b98ee15696ef81484f03bab1848cb"&gt;ViewRootImpl.QueuedInputEvent&lt;/a&gt;) and schedules a Choreographer frame to consume input events.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  💡 Did you know?
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;system_server&lt;/code&gt; process detects when an input event stays in the queue for more than 5 seconds, and that's when it knows it should show an Application Not Responding (ANR) dialog.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;When the Choreographer frame triggers, the touch event is dispatched to the root view of a window, which then dispatches it through its view hierarchy.&lt;/li&gt;
&lt;li&gt;The tapped view receives the &lt;code&gt;MotionEvent.UP&lt;/code&gt; touch event and &lt;strong&gt;posts a click listener callback&lt;/strong&gt; (&lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;l=15724?q=performClickInternal"&gt;source&lt;/a&gt;). &lt;em&gt;This lets other visual state of the view update before click actions start.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Then finally when the main thread runs that posted callback the view click listener is called.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, quite a lot happens from when the finger leaves the touch screen to when the click listener is called. Every motion event includes the time at which the event occurred (&lt;a href="https://developer.android.com/reference/android/view/MotionEvent#getEventTime()"&gt;MotionEvent.getEventTime()&lt;/a&gt;). If we could get access to that &lt;code&gt;MotionEvent.UP&lt;/code&gt; event leading to a click we could measure the true start of the Tap Response Time.&lt;/p&gt;
&lt;h2&gt;
  
  
  📱 Traversal and rendering
&lt;/h2&gt;

&lt;p&gt;What does this do?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;findNavController&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action_title_to_about&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In most apps, the above code starts a fragment transaction. That transaction may be immediate (&lt;code&gt;commitNow()&lt;/code&gt;) or posted (&lt;code&gt;commit()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;When the transaction execute, the view hierarchy is updated and a layout traversal is scheduled.&lt;/li&gt;
&lt;li&gt;When the layout traversal executes, a new frame is drawn into a surface.&lt;/li&gt;
&lt;li&gt;It is then composited with frames from other windows and sent to the display. To learn more, watch this talk from Romain Guy and Chet Haase: &lt;a href="https://youtu.be/zdQRIYOST64"&gt;Drawn out: How Android renders&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ideally we'd like to know exactly when a change to the view hierarchy becomes actually visible on the display. Unfortunately, as far as I know there's no Java API for that, so we'll have to get creative.&lt;/p&gt;

&lt;h1&gt;
  
  
  🤔 What now?
&lt;/h1&gt;

&lt;p&gt;We've seen that the naive approach to measuring &lt;em&gt;Tap Response Time&lt;/em&gt; isn't accurate and doesn't scale. We need to build something better!&lt;/p&gt;

&lt;p&gt;The good news is, I've implemented exactly that, on top of Jetpack Navigation. &lt;del&gt;The bad news is, you'll have to wait for me to publish Part 2!&lt;/del&gt;. The even better news is, you can read about it in Part 2: &lt;a href="https://dev.to/pyricau/tap-response-time-jetpack-navigation-4738"&gt;Tap Response Time: Jetpack Navigation 🗺&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚖️ This work is licensed under a &lt;a href="https://creativecommons.org/licenses/by/4.0/"&gt;Creative Commons Attribution 4.0 International License&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>android</category>
      <category>vitals</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Waldo, where's my UI?</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Wed, 27 Jan 2021 19:41:44 +0000</pubDate>
      <link>https://dev.to/pyricau/waldo-where-s-my-ui-4p53</link>
      <guid>https://dev.to/pyricau/waldo-where-s-my-ui-4p53</guid>
      <description>&lt;p&gt;Today, &lt;a href="https://waldo.io" rel="noopener noreferrer"&gt;Waldo.io&lt;/a&gt;, a &lt;strong&gt;no-code&lt;/strong&gt; platform for &lt;strong&gt;automated mobile tests&lt;/strong&gt;, announced general availability of their Android support.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1354481799611736075-839" src="https://platform.twitter.com/embed/Tweet.html?id=1354481799611736075"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1354481799611736075-839');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1354481799611736075&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I was recently talking with the CEO of Waldo, Amine Bellakrid. I told him that the llama logo was super cute and the UI gorgeous... but I had doubts a no-code platform was a good fit for automated mobile tests.&lt;/p&gt;

&lt;h1&gt;
  
  
  The problem with no-code tests
&lt;/h1&gt;

&lt;p&gt;No-code platforms typically provide a recorder tool that lets you navigate through the app, recording everywhere you tapped. Then later on you can replay the test automatically, and get notified when it fails.&lt;/p&gt;

&lt;p&gt;Automated tests are expected to fail when a change breaks the correct behavior of the app. Unfortunately, no-code platforms tend to generate &lt;strong&gt;brittle&lt;/strong&gt; tests that also fail on small changes that didn't introduce incorrect behavior.&lt;/p&gt;

&lt;p&gt;This creates a lot of noise, so teams tend to stop updating and running these tests and they just rot in a corner.&lt;/p&gt;

&lt;p&gt;Note: similar problems can happen with code based tests, but we've introduced patterns to work around them (e.g. &lt;a href="https://academy.realm.io/posts/kau-jake-wharton-testing-robots/" rel="noopener noreferrer"&gt;Testing Robots&lt;/a&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  Apparently Waldo is different
&lt;/h1&gt;

&lt;p&gt;When I shared my doubts, Amine smiled and said I should try Waldo. So I created a new example project with 3 tabs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fndu2yh1e955107fx9r5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fndu2yh1e955107fx9r5t.png" alt="Screen Shot 2020-11-20 at 10.50.09 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recorded a simple test on Waldo.io, tapping on the second tab (Dashboard) and then the 3rd tab (Notifications).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzz82o2tpr56n0n7nyd8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzz82o2tpr56n0n7nyd8u.png" alt="Screen Shot 2020-11-20 at 10.51.38 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Messing with Waldo
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Tab swap
&lt;/h2&gt;

&lt;p&gt;I started by swapping out the second and the third navigation tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fudq71arftkpznm1pet60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fudq71arftkpznm1pet60.png" alt="Screen Shot 2020-11-20 at 10.56.58 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result is interesting: Waldo clicked on the correct tab (Dashboard) even though it changed place, and the test passed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvl4lh9nr1pjbkxdq5pgu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvl4lh9nr1pjbkxdq5pgu.png" alt="Screen Shot 2020-11-20 at 10.59.09 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing strings
&lt;/h2&gt;

&lt;p&gt;Next, I renamed the &lt;em&gt;Dashboard&lt;/em&gt; tab to &lt;em&gt;Summary&lt;/em&gt; . Still ✅ .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg3tqqdaphhtqp1fxf77h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg3tqqdaphhtqp1fxf77h.png" alt="Screen Shot 2020-11-20 at 11.09.07 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing everything else
&lt;/h2&gt;

&lt;p&gt;Ok, time to make this really hard. I also changed the tab menu id and its icon. No way this can still work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frml4w21877f94iv4b21k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frml4w21877f94iv4b21k.png" alt="Screen Shot 2020-11-20 at 11.15.03 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;... the test still passes and finds the right tabs to tap on, in the right order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqq32v40o1pii2l4g185x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqq32v40o1pii2l4g185x.png" alt="Screen Shot 2020-11-20 at 11.17.21 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing that's really cool in the screenshot above:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Screen similarity should be at least 70%.&lt;br&gt;
Current similarity: 93%&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't know how the similarity is calculated, but it seems more advanced than a simple matching on view ids, bitmap content or string content. That explains why Waldo is totally fine with me changing the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok but I really want to break it
&lt;/h2&gt;

&lt;p&gt;I decided to just be mean and remove the tab. Take that Waldo!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9lu7bu6hxrkexz6x7p8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9lu7bu6hxrkexz6x7p8f.png" alt="Screen Shot 2020-11-20 at 11.23.07 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time the test is failing 😏.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftkbd4mbx19ip95x5j0ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftkbd4mbx19ip95x5j0ih.png" alt="Screen Shot 2020-11-20 at 11.25.28 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's see what happens when I click &lt;em&gt;Update test&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F78slpq3dwpbv3p7j677l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F78slpq3dwpbv3p7j677l.png" alt="Screen Shot 2020-11-20 at 11.29.57 AM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The recorder is on the right, the new scenario at the top left and the old scenario at the bottom left. Now that I have only two tabs, I click on the Notifications tab and the scenario is immediately updated. Nice!&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I changed my mind: no-code test platforms don't have to be brittle!&lt;/p&gt;

&lt;p&gt;I don't know all the details of how Waldo does it, so I hope they can share more about it some day and bring the community forward. In the meantime, I would definitely give &lt;a href="https://www.waldo.io/" rel="noopener noreferrer"&gt;Waldo&lt;/a&gt; a shot.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full disclosure: Amine is my friend and I've been advising the Waldo team every now and then on some cool Android tech problems. I wrote this blog on my own though.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>android</category>
      <category>automated</category>
      <category>testing</category>
      <category>waldo</category>
    </item>
    <item>
      <title>Android Vitals - How adb measures App Startup 🔎</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Tue, 01 Dec 2020 17:05:55 +0000</pubDate>
      <link>https://dev.to/pyricau/android-vitals-how-adb-measures-app-startup-5n7</link>
      <guid>https://dev.to/pyricau/android-vitals-how-adb-measures-app-startup-5n7</guid>
      <description>&lt;p&gt;Last week, Chet Haase published a great blog post: &lt;a href="https://medium.com/androiddevelopers/testing-app-startup-performance-36169c27ee55" rel="noopener noreferrer"&gt;Testing App Startup Performance&lt;/a&gt;. It leverages the output of &lt;code&gt;ActivityTaskManager&lt;/code&gt; to obtain the app startup duration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whenever an activity starts, you’ll see something like this in the logcat output:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ActivityTaskManager: Displayed
com.android.samples.mytest/.MainActivity: +1s380ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This duration (1,380ms in this example) represents the time that it took from launching the app to the time when the system consider it “launched,” which includes drawing the first frame (hence “Displayed”).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article is a deep dive to explore the question:&lt;/p&gt;

&lt;h3&gt;
  
  
  What does &lt;code&gt;ActivityTaskManager&lt;/code&gt; measure exactly?
&lt;/h3&gt;

&lt;p&gt;I know you're impatient, let's jump to the conclusion: &lt;code&gt;ActivityTaskManager&lt;/code&gt; measures the time (&lt;a href="https://dev.to/pyricau/android-vitals-what-time-is-it-2oih"&gt;uptime&lt;/a&gt; on API &amp;lt; 30, realtime on API 30+) from when &lt;code&gt;system_process&lt;/code&gt; receives an Intent to start an activity to when the window of that activity is done drawing.&lt;/p&gt;

&lt;p&gt;Key takeways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This measure includes a &lt;strong&gt;few hundred milliseconds prior to app code and resources loading&lt;/strong&gt;, i.e. time that an app developer cannot affect.&lt;/li&gt;
&lt;li&gt;You can measure this without the extra time from within the app, I'll share how at the end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now, let's dive into AOSP code!&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;code&gt;ActivityTaskManager&lt;/code&gt; log
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ActivityTaskManager: Displayed
com.android.samples.mytest/.MainActivity: +1s380ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We know what the log looks like so we can search for it on &lt;a href="https://cs.android.com/" rel="noopener noreferrer"&gt;cs.android.com&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8vcew9k8n0x0r3tg3arb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F8vcew9k8n0x0r3tg3arb.png" alt="search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This leads us to &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.java;l=883-899;drc=35a009097530cce79b87cab469af8e62aba18e43" rel="noopener noreferrer"&gt;ActivityTaskManager.logAppDisplayed()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;logAppDisplayed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TransitionInfoSnapshot&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mStringBuilder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLength&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Displayed "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;launchedActivityShortComponentName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;": "&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="nc"&gt;TimeUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;formatDuration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;windowsDrawnDelayMs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;i&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;TAG&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The startup duration is &lt;code&gt;TransitionInfoSnapshot.windowsDrawnDelayMs&lt;/code&gt;. It's calculated in &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.java;l=590-606;drc=35a009097530cce79b87cab469af8e62aba18e43" rel="noopener noreferrer"&gt;TransitionInfoSnapshot.notifyWindowsDrawn()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;TransitionInfoSnapshot&lt;/span&gt; &lt;span class="nf"&gt;notifyWindowsDrawn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;ActivityRecord&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;timestampNs&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  
  &lt;span class="nc"&gt;TransitionInfo&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getActiveTransitionInfo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mWindowsDrawnDelayMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;calculateDelay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestampNs&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TransitionInfoSnapshot&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransitionInfo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;calculateDelay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;timestampNs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;delayNanos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestampNs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mTransitionStartTimeNs&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NANOSECONDS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMillis&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delayNanos&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's find where &lt;code&gt;timestampNs&lt;/code&gt; and &lt;code&gt;mTransitionStartTimeNs&lt;/code&gt; are captured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.java;l=476;drc=35a009097530cce79b87cab469af8e62aba18e43" rel="noopener noreferrer"&gt;ActivityMetricsLogger.notifyActivityLaunching()&lt;/a&gt; captures the start of the activity transition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LaunchingState&lt;/span&gt; &lt;span class="nf"&gt;notifyActivityLaunching&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;Intent&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;ActivityRecord&lt;/span&gt; &lt;span class="n"&gt;caller&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;callingUid&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;transitionStartNs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elapsedRealtimeNanos&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="nc"&gt;LaunchingState&lt;/span&gt; &lt;span class="n"&gt;launchingState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LaunchingState&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;launchingState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mCurrentTransitionStartTimeNs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transitionStartNs&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;launchingState&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.java;l=590-606;drc=35a009097530cce79b87cab469af8e62aba18e43" rel="noopener noreferrer"&gt;TransitionInfoSnapshot.notifyWindowsDrawn()&lt;/a&gt; is called by &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java;l=5302;drc=35a009097530cce79b87cab469af8e62aba18e43" rel="noopener noreferrer"&gt;ActivityRecord.onWindowsDrawn()&lt;/a&gt;  which is called by &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java;l=5415;drc=35a009097530cce79b87cab469af8e62aba18e43" rel="noopener noreferrer"&gt;ActivityRecord.updateReportedVisibilityLocked()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateReportedVisibilityLocked&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nowDrawn&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;reportedDrawn&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;onWindowsDrawn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nowDrawn&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elapsedRealtimeNanos&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;reportedDrawn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowDrawn&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now know where the start and end timestamps are captured, but unfortunately &lt;code&gt;ActivityMetricsLogger.notifyActivityLaunching()&lt;/code&gt; and &lt;code&gt;ActivityRecord.updateReportedVisibilityLocked()&lt;/code&gt; have many call sites, so it's hard to dig further within the AOSP source.&lt;/p&gt;

&lt;h1&gt;
  
  
  Debugging &lt;code&gt;system_process&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;I was telling a friend I hit a dead end looking at the Android sources, and he asked me: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why don't you put a breakpoint?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Duh. I have never tried to debug &lt;code&gt;system_process&lt;/code&gt;, but there's no reason we can't. Thanks &lt;a href="https://twitter.com/vdurmont" rel="noopener noreferrer"&gt;Vincent&lt;/a&gt; for the idea! Lucky for us, Android Studio is set up to find sources for the Android version the app is compiled against:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhwa3p4yzwgk4piolr0y6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhwa3p4yzwgk4piolr0y6.png" alt="ActivityMetricsLogger sources"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can put a breakpoint like I would in my app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frcyt2889tvejy9d8g6lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frcyt2889tvejy9d8g6lj.png" alt="breakpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Side note: this screenshot shows &lt;code&gt;SystemClock.uptimeMillis()&lt;/code&gt; while the code I shared aboved showed &lt;code&gt;SystemClock.elapsedRealtimeNanos()&lt;/code&gt;. I'm using an API 29 device, and it turns out that API 29 measured startup time with &lt;code&gt;SystemClock.uptimeMillis()&lt;/code&gt;, and API 30 measures it with &lt;code&gt;SystemClock.elapsedRealtimeNanos()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using a rooted device&lt;/strong&gt;, I can connect a debugger to &lt;code&gt;system_process&lt;/code&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgbe9khu0seyxc6ksvhql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgbe9khu0seyxc6ksvhql.png" alt="debug system_process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I start my app, I hit a breakpoint for &lt;code&gt;ActivityMetricsLogger.notifyActivityLaunching()&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1y0qst5bo7azbynb3bzu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F1y0qst5bo7azbynb3bzu.png" alt="ActivityMetricsLogger.notifyActivityLaunching()"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and another breakpoint for &lt;code&gt;TransitionInfoSnapshot.notifyWindowsDrawn()&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv96yl1dnme1uljemt5t0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv96yl1dnme1uljemt5t0.png" alt="TransitionInfoSnapshot.notifyWindowsDrawn()"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Reading the stacktraces
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;The first stacktrace shows that the start timestamp is captured when &lt;code&gt;system_process&lt;/code&gt; receives an Intent to start an activity.&lt;/li&gt;
&lt;li&gt;The second stacktrace shows that the end timestamp is captured when the window of that activity is done drawing. The corresponding frame should be visible on the display within 16 ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  App startup time
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-diving-into-cold-start-waters-5hi6"&gt;Android Vitals - Diving into cold start waters 🥶&lt;/a&gt;, I explored what happens on app startup and concluded:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The user experience of launching an activity starts when the user touches the screen, however app developers have little influence on the time spent before  &lt;code&gt;ActivityThread.handleBindApplication()&lt;/code&gt;, so that's where app cold start monitoring should start.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpuh3wi86r25lhktnnyxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpuh3wi86r25lhktnnyxv.png" alt="Cold start sequence diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ActivityThread.handleBindApplication()&lt;/code&gt; loads the APK and the app components (&lt;code&gt;AppComponentFactory&lt;/code&gt;, &lt;code&gt;ContentProvider&lt;/code&gt;, &lt;code&gt;Application&lt;/code&gt;). Unfortunately, &lt;code&gt;ActivityTaskManager&lt;/code&gt; uses &lt;code&gt;ActivityTaskManagerService.startActivity()&lt;/code&gt; as start time, which happens some time before &lt;code&gt;ActivityThread.handleBindApplication()&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  How much time is &lt;code&gt;ActivityTaskManager&lt;/code&gt; adding?
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4"&gt;Android Vitals - When did my app start? ⏱&lt;/a&gt;, I showed that we can use &lt;a href="https://developer.android.com/reference/android/os/Process.html#getStartUptimeMillis()" rel="noopener noreferrer"&gt;Process.getStartUptimeMillis()&lt;/a&gt; to get the timestamp at which &lt;code&gt;ActivityThread.handleBindApplication()&lt;/code&gt; was  called. I also shared a code snippet to read the process fork time (see &lt;a href="https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4#process-fork-time"&gt;Processes.readProcessForkRealtimeMillis()&lt;/a&gt;). We can logs these 2 values to logcat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;forkRealtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Processes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readProcessForkRealtimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nowRealtimeMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elapsedRealtime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nowUptimeMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;elapsedRealtimeMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowRealtimeMs&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;forkRealtime&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;forkUptimeMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowUptimeMs&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;elapsedRealtimeMs&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AppStart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$forkUptimeMs fork timestamp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;processStart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStartUptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AppStart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"$processStart bindApplication() timestamp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to log &lt;code&gt;ActivityMetricsLogger.mCurrentTransitionStartTime&lt;/code&gt;. We can make our previous &lt;code&gt;system_process&lt;/code&gt; breakpoint &lt;strong&gt;non-suspending&lt;/strong&gt; and have it log the value. The output of &lt;em&gt;Evaluate and log&lt;/em&gt; goes to the debugger console. We want all logs in logcat so we're invoking &lt;code&gt;Log.d()&lt;/code&gt; from there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2oa1c078fzdhtnnp6ogz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2oa1c078fzdhtnnp6ogz.png" alt="log for mCurrentTransitionStartTime"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D/AppStart: 27464211 Intent received
D/AppStart: 27464340 fork timestamp
D/AppStart: 27464533 bindApplication() timestamp
...
I/ActivityTaskManager: Displayed
com.example.logstartup/.MainActivity: +1s185ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's 129 ms from receiving the intent to forking the zygote process, and 193 ms from forking to &lt;code&gt;ActivityThread.handleBindApplication()&lt;/code&gt;, i.e. 322 ms before the app starts loading its code and resources. In this example, that's ~30% of the app startup time reported by &lt;code&gt;ActivityTaskManager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The real number is lower than that, since &lt;code&gt;system_process&lt;/code&gt; was running with a debugger connected. Still, it's significant enough that you should have this extra penalty in mind when measuring app startup with &lt;code&gt;ActivityTaskManager&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Measuring app startup time from within the app
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-first-draw-time-m1d"&gt;Android Vitals - First draw time 👩‍🎨&lt;/a&gt; I showed how to determine the first draw time. I compared that timestamp with the timestamp passed to &lt;code&gt;TransitionInfoSnapshot.notifyWindowsDrawn()&lt;/code&gt;, and the two values are only a few milliseconds apart.&lt;/p&gt;

&lt;p&gt;We can put together what we've learnt to measure the app startup duration from within the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;simpleName&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;
        &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDecorViewReady&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decorView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@onNextDraw&lt;/span&gt;
            &lt;span class="n"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postAtFrontOfQueue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;start&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStartUptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;now&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;startDurationMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
              &lt;span class="nc"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"AppStart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="s"&gt;"Displayed $name in $startDurationMs ms"&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;D/AppStart: Displayed MainActivity in 863 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;The output from &lt;code&gt;ActivityTaskManager&lt;/code&gt; is convenient, and totally worth using if you're trying to compare startup time for different versions of your app. Just be aware that there's a non-trivial portion of that time that app developers cannot affect.&lt;/li&gt;
&lt;li&gt;You can measure app startup time from within the app. I intend to eventually publish that code as an open source library, in the meantime the details are in &lt;a href="https://dev.to/pyricau/android-vitals-first-draw-time-m1d"&gt;Android Vitals - First draw time 👩‍🎨&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Deep Dive&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/12599069954" rel="noopener noreferrer"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>android</category>
      <category>performance</category>
      <category>startup</category>
      <category>adb</category>
    </item>
    <item>
      <title>Android Vitals - Profiling App Startup 🔬</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Mon, 23 Nov 2020 23:03:19 +0000</pubDate>
      <link>https://dev.to/pyricau/android-vitals-profiling-app-startup-32ek</link>
      <guid>https://dev.to/pyricau/android-vitals-profiling-app-startup-32ek</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;The In-Between&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/26364010648/" rel="noopener noreferrer"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My previous articles focused on measuring Android app start in production. Once we've established a metric and scenarios that trigger a slow app start, the next step is to improve performance.&lt;/p&gt;

&lt;p&gt;To understand what makes the app start slow, we need to profile it. Android Studio provides several types of &lt;a href="https://developer.android.com/games/optimize/cpu-profiler?hl=en#configurations" rel="noopener noreferrer"&gt;profiler recording configurations&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trace System Calls&lt;/strong&gt; (aka systrace, perfetto): Low impact on runtime, great for understanding how the app interacts with the system and CPUs, but not the Java method calls that happen inside the app VM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample C/C++ Functions&lt;/strong&gt; (aka Simpleperf): Not interesting to me, the apps I deal with run much more bytecode than native code. On Q+ this is supposed to now also sample Java stacks in a low overhead way, but I haven't managed to get that working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace Java Methods&lt;/strong&gt;: This captures all VM method calls which introduces so much overhead that the results don't mean much.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample Java Methods&lt;/strong&gt;: Less overhead than tracing but shows the Java method calls that happen inside the VM. &lt;strong&gt;This is my preferred option when profiling app startup.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flz5k7pg38ysn3zcpe0ij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flz5k7pg38ysn3zcpe0ij.png" alt="trace from sampled java methods"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Start recording on app startup
&lt;/h1&gt;

&lt;p&gt;The Android Studio profiler has UI to start a trace by connecting to an already running process, but no obvious way to start recording on app startup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzp1x44mern9im1zicag9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzp1x44mern9im1zicag9.png" alt="recording a trace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The option exist but is hidden away in the &lt;strong&gt;run configuration&lt;/strong&gt; for your app: check &lt;em&gt;Start this recording on startup&lt;/em&gt; in the &lt;em&gt;profiling&lt;/em&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F56trfz9ru6x902mfxs4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F56trfz9ru6x902mfxs4n.png" alt="run configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then deploy the app via &lt;em&gt;Run&lt;/em&gt; &amp;gt; &lt;em&gt;Profile app&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk7jg9l5di7zpnr3l30zg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk7jg9l5di7zpnr3l30zg.png" alt="profile app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Profiling release builds
&lt;/h1&gt;

&lt;p&gt;Android developers typically use a &lt;strong&gt;debug&lt;/strong&gt; build type for their everyday work, and debug builds often include a debug drawer, extra libraries such as LeakCanary, etc. &lt;strong&gt;Developers should profile release builds rather than debug builds&lt;/strong&gt; to make sure they're fixing the actual issues that their customers are facing.&lt;/p&gt;

&lt;p&gt;Unfortunately, release builds are non debuggable so the Android profiler can't record traces on release builds.&lt;/p&gt;

&lt;p&gt;Here are a few options to work around that issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a debuggable release build
&lt;/h2&gt;

&lt;p&gt;We could temporarily make our release build debuggable, or create a new release build type just for profiling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;buildTypes&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;release&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;debuggable&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Libraries and Android Framework code often have a different behavior if the APK is debuggable. ART disables a lot of optimizations to enable connecting a debugger, which affects performance significantly and unpredictably (see &lt;a href="https://youtu.be/ZffMCJdA5Qc?t=631" rel="noopener noreferrer"&gt;this talk&lt;/a&gt;. So this solution is not ideal.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Profile on a rooted device
&lt;/h2&gt;

&lt;p&gt;Rooted devices allow the Android Studio profiler to record traces on non debuggable builds.&lt;/p&gt;

&lt;p&gt;Profiling on an emulator is generally not recommended - the performance of every system component will be different (cpu speed, cache sizes, disk perf), so an 'optimization' can actually make things slower by shifting the work to something that's slower on a phone. If you don't have a rooted physical device available, you can create an emulator &lt;strong&gt;without Play Services&lt;/strong&gt; and then run &lt;code&gt;adb root&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use simpleperf on Android Q
&lt;/h2&gt;

&lt;p&gt;There's a tool called &lt;a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md#prepare-an-android-application" rel="noopener noreferrer"&gt;simpleperf&lt;/a&gt; which supposedly enables profiling release builds on non rooted Q+ devices, if they have a special manifest flag. The doc calls it &lt;code&gt;profileableFromShell&lt;/code&gt;, the XML example has a &lt;code&gt;profileable&lt;/code&gt; tag with an &lt;code&gt;android:shell&lt;/code&gt; attribute, and the official manifest documentation shows nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;profileable&lt;/span&gt; &lt;span class="na"&gt;android:shell=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I looked at the manifest parsing code on &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/pm/PackageParser.java;l=3878-3885;drc=master" rel="noopener noreferrer"&gt;cs.android.com&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tagName&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"profileable"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;sa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;obtainAttributes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;R&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AndroidManifestProfileable&lt;/span&gt;
  &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBoolean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;R&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;styleable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AndroidManifestProfileable_shell&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;privateFlags&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="nc"&gt;ApplicationInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PRIVATE_FLAG_PROFILEABLE_BY_SHELL&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like you can trigger profiling from command line if the manifest has &lt;code&gt;&amp;lt;profileable android:shell="true" /&amp;gt;&lt;/code&gt; (I haven't tried). As far as I understand the Android Studio team is still working on integrating with this new capability.&lt;/p&gt;

&lt;h1&gt;
  
  
  Profiling a downloaded APK
&lt;/h1&gt;

&lt;p&gt;At Square our releases are built in CI. As we saw earlier, profiling app startup from Android Studio requires checking an option in a run configuration. How can we do this with a downloaded APK?&lt;/p&gt;

&lt;p&gt;Turns out, it's possible but hidden under &lt;em&gt;File&lt;/em&gt; &amp;gt; &lt;em&gt;Profile or Debug APK&lt;/em&gt;. This opens up a new window with the unzipped APK, and from that you can set up the run configuration and start profiling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F42zjjsi7t4sszdpwyqfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F42zjjsi7t4sszdpwyqfj.png" alt="profile APK"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Android Studio profiler slows everything down
&lt;/h1&gt;

&lt;p&gt;Unfortunately, when I tested these methods on a production app, profiling from Android Studio slowed down app startup a lot (~10x slower), even on recent Android versions. I'm not sure why, maybe it's the "advanced profiling", which doesn't seem like it can be disabled. We need to find another way!&lt;/p&gt;

&lt;h1&gt;
  
  
  Profiling from code
&lt;/h1&gt;

&lt;p&gt;Instead of profiling from Android Studio, we can start the trace directly from code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tracesDirPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path for trace directory"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fileNameFormat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleDateFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"yyyy-MM-dd_HH-mm-ss_SSS'.trace'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;US&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fileNameFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;traceFilePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracesDirPath&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fileName&lt;/span&gt;
&lt;span class="c1"&gt;// Save up to 50Mb data.&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;maxBufferSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="c1"&gt;// Sample every 1000 microsecond (1ms)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;samplingIntervalUs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="nc"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startMethodTracingSampling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;traceFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;maxBufferSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;samplingIntervalUs&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nc"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopMethodTracing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then pull the trace file from the device and load it in Android Studio.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to start recording
&lt;/h2&gt;

&lt;p&gt;We should start recording the trace as early as possible in the app lifecycle. As I explained in &lt;a href="https://dev.to/pyricau/android-vitals-diving-into-cold-start-waters-5hi6#early-initialization"&gt;Android Vitals - Diving into cold start waters 🥶&lt;/a&gt;, the earliest code that can run on app startup before Android P is a &lt;code&gt;ContentProvider&lt;/code&gt; and on Android P+ it's the &lt;code&gt;AppComponentFactory&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before Android P / API &amp;lt; 28
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppStartListener&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ContentProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startMethodTracingSampling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:tools=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/tools"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;application&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;provider&lt;/span&gt;
        &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;".AppStartListener"&lt;/span&gt;
        &lt;span class="na"&gt;android:authorities=&lt;/span&gt;&lt;span class="s"&gt;"com.example.appstartlistener"&lt;/span&gt;
        &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;del&gt;Unfortunately we cannot control the order in which &lt;code&gt;ContentProvider&lt;/code&gt; instances are created, so we may be missing some of the early startup code.&lt;/del&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Edit:&lt;/em&gt; &lt;a class="mentioned-user" href="https://dev.to/anjalsaneen"&gt;@anjalsaneen&lt;/a&gt; pointed out &lt;a href="https://dev.to/anjalsaneen/comment/1ccod"&gt;in the comments&lt;/a&gt; that when defining a provider we can set a &lt;a href="https://developer.android.com/guide/topics/manifest/provider-element#init" rel="noopener noreferrer"&gt;initOrder&lt;/a&gt; tag, and the highest number gets initialized first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Android P+ / API 28+
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RequiresApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAppComponentFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;androidx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppComponentFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nd"&gt;@RequiresApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;instantiateClassLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ApplicationInfo&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ClassLoader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SDK_INT&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startMethodTracingSampling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiateClassLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;instantiateApplicationCompat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SDK_INT&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startMethodTracingSampling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiateApplicationCompat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:tools=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/tools"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt;
    &lt;span class="na"&gt;android:appComponentFactory=&lt;/span&gt;&lt;span class="s"&gt;".MyAppComponentFactory"&lt;/span&gt;
    &lt;span class="na"&gt;tools:replace=&lt;/span&gt;&lt;span class="s"&gt;"android:appComponentFactory"&lt;/span&gt;
    &lt;span class="na"&gt;tools:targetApi=&lt;/span&gt;&lt;span class="s"&gt;"p"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Where to store traces
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tracesDirPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"path for trace directory"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API &amp;lt; 28&lt;/strong&gt;: The broadcast receiver has access to a context on which we can call &lt;a href="https://developer.android.com/reference/android/content/Context#getDataDir()" rel="noopener noreferrer"&gt;Context.getDataDir()&lt;/a&gt; to store the trace in the app directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API 28&lt;/strong&gt;: &lt;a href="https://developer.android.com/reference/android/app/AppComponentFactory#instantiateApplication(java.lang.ClassLoader,%20java.lang.String)" rel="noopener noreferrer"&gt;AppComponentFactory.instantiateApplication()&lt;/a&gt; is in charge of creating a new application instance so there's no context available yet. We can hard code the path to &lt;code&gt;/sdcard/&lt;/code&gt; directly, though that requires the &lt;code&gt;WRITE_EXTERNAL_STORAGE&lt;/code&gt; permission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API 29+&lt;/strong&gt;: When targeting API 29, hardcoding &lt;code&gt;/sdcard/&lt;/code&gt; stops working. We can add the &lt;a href="https://developer.android.com/guide/topics/manifest/application-element#requestLegacyExternalStorage" rel="noopener noreferrer"&gt;requestLegacyExternalStorage&lt;/a&gt; flag but it's not supported on API 30 anyway. &lt;strong&gt;Edit:&lt;/strong&gt; &lt;a href="https://twitter.com/yrezgui/status/1330960375865544706" rel="noopener noreferrer"&gt;Yacine Rezgui suggested trying out MANAGE_EXTERNAL_STORAGE&lt;/a&gt; on API 30+. Either way, &lt;a href="https://developer.android.com/reference/android/app/AppComponentFactory#instantiateClassLoader(java.lang.ClassLoader,%20android.content.pm.ApplicationInfo)" rel="noopener noreferrer"&gt;AppComponentFactory.instantiateClassLoader()&lt;/a&gt; passes an &lt;code&gt;ApplicationInfo&lt;/code&gt; so we can use &lt;a href="https://developer.android.com/reference/android/content/pm/ApplicationInfo#dataDir" rel="noopener noreferrer"&gt;ApplicationInfo.dataDir&lt;/a&gt; to store the trace in the app directory. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to stop recording
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-first-draw-time-m1d"&gt;Android Vitals - First draw time 👩‍🎨&lt;/a&gt;, we learnt that cold start ends when the app's first frame completely loads. We can leverage the code from that article to know when to stop method tracing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;
        &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDecorViewReady&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decorView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="n"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postAtFrontOfQueue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nc"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopMethodTracing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could also record for a fixed amount of time greater than the app start, e.g. 5 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Looper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMainLooper&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;postDelayed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nc"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopMethodTracing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Profiling with Nanoscope
&lt;/h1&gt;

&lt;p&gt;Another option for profiling app startup is &lt;a href="https://github.com/uber/nanoscope" rel="noopener noreferrer"&gt;uber/nanoscope&lt;/a&gt;. It's an Android image with built-in low overhead tracing. It's great but has a few limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only traces the main thread.&lt;/li&gt;
&lt;li&gt;A large app will overfill the in-memory trace buffer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1330929229341442048-553" src="https://platform.twitter.com/embed/Tweet.html?id=1330929229341442048"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1330929229341442048-553');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1330929229341442048&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h1&gt;
  
  
  App Startup steps
&lt;/h1&gt;

&lt;p&gt;Once we have a startup trace, we can start investigating what's taking time. You should expect 3 main sections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ActivityThread.handlingBindApplication()&lt;/code&gt; contains the startup work before activity creation. If that's slow then we probably need to optimize &lt;code&gt;Application.onCreate()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TransactionExecutor.execute()&lt;/code&gt; is in charge of creating and resuming the activity, which includes inflating the view hierarchy.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ViewRootImpl.performTraversals()&lt;/code&gt; is where the framework performs the first measure, layout and draw. If this is slow then it could be the view hierarchy being too complex, or views with custom drawing that need to be optimized.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fq92b4ejsjf9t295t5twr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fq92b4ejsjf9t295t5twr.png" alt="startup steps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you notice that a service is being started before the first view traversal, it might be worth delaying the start of that service so that it happens after the view traversal.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;A few take aways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profile &lt;strong&gt;release builds&lt;/strong&gt; to focus on the actual issues.&lt;/li&gt;
&lt;li&gt;The state of profiling app start on Android is far from ideal. There's basically no good out of the box solution, but the Jetpack Benchmark team &lt;a href="https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/ProcessSpeedProfileValidation.kt;l=38;drc=ea99c3ac7be5b54a9493ba9fe00edee9e91baf6e" rel="noopener noreferrer"&gt;is working on this&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Start the recording from code to prevent Android Studio from slowing everything down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many thanks to the many folks who helped me out on Slack and &lt;a href="https://twitter.com/Piwai/status/1330914849019203584" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;: Kurt Nelson, Justin Wong, Leland Takamine, Yacine Rezgui, Raditya Gumay, Chris Craik, Mike Nakhimovich, Artem Chubaryan, Rahul Ravikumar, Yacine Rezgui, Eugen Pechanec, Louis CAD, Max Kovalev.&lt;/p&gt;

</description>
      <category>android</category>
      <category>performance</category>
      <category>profiling</category>
      <category>trace</category>
    </item>
    <item>
      <title>Leak detection: Android Studio vs LeakCanary ⚔️</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Fri, 30 Oct 2020 00:13:40 +0000</pubDate>
      <link>https://dev.to/pyricau/leak-detection-android-studio-vs-leakcanary-35j5</link>
      <guid>https://dev.to/pyricau/leak-detection-android-studio-vs-leakcanary-35j5</guid>
      <description>&lt;p&gt;I recently came across this comment in a post:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The thing really annoying about LeakCanary or Android Studio, most of the time leaks identified by LeakCanary do not appear in Profiler/Memory/memory leaks, I wonder if LeakCanary is showing false positives or Android Studio is missing positives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a good question, let's dig into code and figure this out!&lt;/p&gt;

&lt;h1&gt;
  
  
  False positive leaks in Android Studio
&lt;/h1&gt;

&lt;p&gt;Before answering the question, we need to talk about where the idea of false positive leaks comes from: Android Studio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2575vfd4s4wmmuq8xjxb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2575vfd4s4wmmuq8xjxb.png" alt="Android Studio warning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That warning was &lt;a href="https://cs.android.com/android/_/android/platform/tools/adt/idea/+/8307b226f765610925de9d870ab83e7457692a94:profilers/src/com/android/tools/profilers/memory/adapters/instancefilters/ActivityFragmentLeakInstanceFilter.java;dlc=16fd19249904435661d88ebe005508e7dcc68814" rel="noopener noreferrer"&gt;originally a longer description&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Activity and Fragment instances that might be causing memory leaks. For Activities, these are instances that have been destroyed but are still being referenced. For Fragments, these are instances that do not have a valid FragmentManager but are still being referenced. Note, these instance might include Fragments that were created but are not yet being utilized.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/studio/profile/memory-profiler#profiler-memory-leak-detection" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; provides more insights on false positive leaks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In certain situations, such as the following, the filter might yield false positives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;Fragment&lt;/code&gt; is created but has not yet been used.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Fragment&lt;/code&gt; is being cached but not as part of a FragmentTransaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The phrasing is vague but it looks like false positive leaks only applies to Fragments.&lt;/p&gt;

&lt;h1&gt;
  
  
  Android Studio leak filtering
&lt;/h1&gt;

&lt;p&gt;Android Studio dumps and analyzes the heap when you press the Dump Heap icon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv7fec52uyj9rytx3lplc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv7fec52uyj9rytx3lplc.png" alt="heap dump button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leaking instances are displayed by enabling the "Activity/Fragment Leaks" filter, which updates the bottom panel to only show leaking instances. The filtering is performed by &lt;a href="https://cs.android.com/android/platform/superproject/+/studio-master-dev:tools/adt/idea/profilers/src/com/android/tools/profilers/memory/adapters/instancefilters/ActivityFragmentLeakInstanceFilter.kt;l=65-97;drc=455bc8a78399ed66ab9767104ec19903087890a2" rel="noopener noreferrer"&gt;ActivityFragmentLeakInstanceFilter&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;FRAGFMENT_MANAGER_FIELD_NAME&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mFragmentManager"&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * A Fragment instance is determined to be potentially leaked if
 * its mFragmentManager field is null. This indicates that the
 * instance is in its initial state. Note that this can mean that
 * the instance has been destroyed, or just starting to be
 * initialized but before being attached to an activity. The
 * latter gives us false positives, but it should not uncommon
 * as long as users don't create fragments way ahead of the time
 * of adding them to a FragmentManager.
 */&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isPotentialFragmentLeak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;InstanceObject&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;isValidDepthWithAnyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;FRAGFMENT_MANAGER_FIELD_NAME&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Check if the instance has a valid depth and any field
 * satisfying predicates on its name and value
 */&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isValidDepthWithAnyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;InstanceObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;onName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;onVal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;depth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MAX_VALUE&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
         &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="nf"&gt;onName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;onVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(yes, Fragfment manager)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, a fragment is considered leaking if its &lt;em&gt;&lt;code&gt;mFragmentManager&lt;/code&gt; field is null&lt;/em&gt; and the fragment is &lt;em&gt;reachable via strong references&lt;/em&gt; (that's what &lt;em&gt;valid depth&lt;/em&gt; means in the above code). If you create a fragment instance but don't add it, it would be reported as a leak, hence the warning about false positive leaks.&lt;/p&gt;

&lt;h1&gt;
  
  
  How LeakCanary finds fragment leaks
&lt;/h1&gt;

&lt;p&gt;Unlike Android Studio, LeakCanary does not look at the &lt;code&gt;mFragmentManager&lt;/code&gt; field for all fragment instances in memory. LeakCanary hooks into the Android lifecycle to automatically detect when fragments are destroyed and should be garbage collected. These destroyed objects are passed to an &lt;code&gt;ObjectWatcher&lt;/code&gt;, which holds &lt;a href="https://en.wikipedia.org/wiki/Weak_reference" rel="noopener noreferrer"&gt;weak references&lt;/a&gt; to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;FragmentActivity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;supportFragmentManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;supportFragmentManager&lt;/span&gt;
  &lt;span class="n"&gt;supportFragmentManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerFragmentLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;FragmentManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FragmentLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onFragmentDestroyed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FragmentManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;objectWatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"Received Fragment#onDestroy() callback"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="k"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the weak reference held by &lt;code&gt;ObjectWatcher&lt;/code&gt; isn't cleared after &lt;strong&gt;waiting 5 seconds&lt;/strong&gt; and running garbage collection, the watched object is considered &lt;strong&gt;retained&lt;/strong&gt;, and potentially leaking. LeakCanary dumps the Java heap into a &lt;code&gt;.hprof&lt;/code&gt; file (a &lt;strong&gt;heap dump&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F19gg3vfbj66ettt5h3h3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F19gg3vfbj66ettt5h3h3.png" alt="dumping heap"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then LeakCanary parses the &lt;code&gt;.hprof&lt;/code&gt; file using &lt;a href="https://square.github.io/leakcanary/shark/" rel="noopener noreferrer"&gt;Shark&lt;/a&gt; and locates the retained objects in that heap dump.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftfg343lk5iqa8om8bd2l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftfg343lk5iqa8om8bd2l.png" alt="Finding retained objects"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is where LeakCanary differs from Android Studio: it looks for the custom weak references that it created (&lt;a href="https://github.com/square/leakcanary/blob/main/leakcanary-object-watcher/src/main/java/leakcanary/KeyedWeakReference.kt#L30" rel="noopener noreferrer"&gt;KeyedWeakReference&lt;/a&gt;) and checks their &lt;code&gt;referent&lt;/code&gt; field in &lt;a href="https://github.com/square/leakcanary/blob/main/shark/src/main/java/shark/KeyedWeakReferenceFinder.kt" rel="noopener noreferrer"&gt;KeyedWeakReferenceFinder&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Finds all objects tracked by a KeyedWeakReference, i.e. al
 * objects that were passed to ObjectWatcher.watch().
 */&lt;/span&gt;
&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;KeyedWeakReferenceFinder&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LeakingObjectFinder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;findLeakingObjectIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HeapGraph&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findClassByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"leakcanary.KeyedWeakReference"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instances&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;weakRef&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;weakRef&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"java.lang.ref.Reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"referent"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asObjectId&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;objectId&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;objectId&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="nc"&gt;ValueHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NULL_REFERENCE&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toSet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means LeakCanary &lt;strong&gt;only surfaces fragments that are actually leaking&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why LeakCanary reports more leaks
&lt;/h1&gt;

&lt;p&gt;Now that we've established that LeakCanary does not have the same false positive leaks as Android Studio, let's go back to the main observation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;most of the time leaks identified by LeakCanary do not appear in Profiler/Memory/memory leaks&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The explanation is in the &lt;a href="https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#1-detecting-retained-objects" rel="noopener noreferrer"&gt;LeakCanary documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;LeakCanary automatically detects leaks for the following objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;destroyed &lt;code&gt;Activity&lt;/code&gt; instances&lt;/li&gt;
&lt;li&gt;destroyed &lt;code&gt;Fragment&lt;/code&gt; instances&lt;/li&gt;
&lt;li&gt;destroyed fragment &lt;code&gt;View&lt;/code&gt; instances&lt;/li&gt;
&lt;li&gt;cleared &lt;code&gt;ViewModel&lt;/code&gt; instances&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Android Studio does not detect leaks for destroyed fragment &lt;code&gt;View&lt;/code&gt; instances or cleared &lt;code&gt;ViewModel&lt;/code&gt; instances. The former is actually a &lt;a href="https://square.github.io/leakcanary/fundamentals/#common-causes-for-memory-leaks" rel="noopener noreferrer"&gt;common cause of leak&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Adding a &lt;code&gt;Fragment&lt;/code&gt; instance to the backstack without clearing that Fragment’s view fields in &lt;code&gt;Fragment.onDestroyView()&lt;/code&gt; (more details in &lt;a href="https://stackoverflow.com/a/59504797/703646" rel="noopener noreferrer"&gt;this StackOverflow answer&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;If LeakCanary reports a leak trace, then &lt;strong&gt;there is definitely a leak&lt;/strong&gt;. LeakCanary is always right! Unfortunately, we can't blame false positive leaks on the little yellow bird as an excuse for not fixing leaks.&lt;/p&gt;

</description>
      <category>android</category>
      <category>leak</category>
      <category>leakcanary</category>
      <category>memory</category>
    </item>
    <item>
      <title>The real size of Android objects 📏</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Tue, 22 Sep 2020 22:31:08 +0000</pubDate>
      <link>https://dev.to/pyricau/the-real-size-of-android-objects-1i2e</link>
      <guid>https://dev.to/pyricau/the-real-size-of-android-objects-1i2e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Deep Dive&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/12599069954/" rel="noopener noreferrer"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm currently reimplementing how LeakCanary &lt;a href="https://github.com/square/leakcanary/pull/1938" rel="noopener noreferrer"&gt;computes the retained heap size of objects&lt;/a&gt;. As a quick reminder:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shallow heap size of an object&lt;/strong&gt;: The object size in the memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retained heap size of an object&lt;/strong&gt;: The shallow size of that object plus the shallow size of all the objects that are transitively held in memory by only that object. In other words, it's the amount of memory that will be freed when that object is garbage collected.&lt;/p&gt;

&lt;h1&gt;
  
  
  One cannot trust a shallow size
&lt;/h1&gt;

&lt;p&gt;As part of that work, I compared the shallow size of objects as reported in LeakCanary versus other heap dump tools such as YourKit, Eclipse Memory Analyzer Tool (MAT) and Android Studio Memory Analyzer. That's when I realized something was wrong: &lt;strong&gt;every tool provides a different answer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I asked Jesse Wilson about it and he pointed me to this article by Aleksey Shipilёv: &lt;a href="https://shipilev.net/blog/2014/heapdump-is-a-lie/" rel="noopener noreferrer"&gt;What Heap Dumps Are Lying To You About&lt;/a&gt;. Some take aways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every Java VM lays out its memory in a slightly different way and performs various optimizations, such as changing field order, aligning bits, etc.&lt;/li&gt;
&lt;li&gt;The heap dump format (.hprof) is a &lt;a href="http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html#mozTocId848088" rel="noopener noreferrer"&gt;standard&lt;/a&gt;. A class dump record contains the list of fields and their types as well as the &lt;strong&gt;instance size of the class&lt;/strong&gt;. Aleksey Shipilёv asked about having the instance size be the actual size of an instance in memory but &lt;a href="http://mail.openjdk.java.net/pipermail/serviceability-dev/2012-December/007852.html" rel="noopener noreferrer"&gt;the answer was nope&lt;/a&gt;: the sizes in the HPROF dump are VM and padding independent, to avoid breaking expectations from consuming tools.&lt;/li&gt;
&lt;li&gt;There's a tool called &lt;a href="http://openjdk.java.net/projects/code-tools/jol/" rel="noopener noreferrer"&gt;JOL&lt;/a&gt; that instruments the JVM runtime to report the actual size of an object. Aleksey used that to compare the size reported in hprof based tools with the actual size and found that they were all wrong in a different way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fij2t7zgtiy8cckghwlmu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fij2t7zgtiy8cckghwlmu.png" alt="Exploring Java's Hidden Costs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://academy.realm.io/posts/360andev-jake-wharton-java-hidden-costs-android/#collections-3321" rel="noopener noreferrer"&gt;Exploring Java's Hidden Costs&lt;/a&gt;, Jake Wharton showed how to use JOL. Unfortunately, JOL only runs on JVMs, and not the Dalvik or ART runtimes. To quote Jake:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this case, because the classes are exactly the same and the JVM 64 bit, and Android's now 64 bit, the number should be translatable. If that bothers you, treat them as an approximation and allow for some 20% variance. It's certainly a lot easier than figuring out how to get the object sizes on Android itself. Which is not impossible, it's just a lot easier this way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Damn it, in LeakCanary all we have is the heap dump. Sounds like we need to go the not easy way!&lt;/p&gt;

&lt;p&gt;I asked Romain Guy about this and he suggested looking at &lt;a href="https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#whatIs" rel="noopener noreferrer"&gt;JVM TI&lt;/a&gt;, an agent interface that is implemented on Android 8+ as &lt;a href="https://source.android.com/devices/tech/dalvik/art-ti" rel="noopener noreferrer"&gt;ART TI&lt;/a&gt;. JVM TI exposes a &lt;a href="https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html#GetObjectSize" rel="noopener noreferrer"&gt;GetObjectSize&lt;/a&gt; API.&lt;/p&gt;

&lt;h1&gt;
  
  
  Read the Source, Luke
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuobbygilx84kqm8s2cwn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuobbygilx84kqm8s2cwn.jpg" alt="Read the Source, Luke"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Learn to Read the Source, Luke&lt;/em&gt; - &lt;a href="https://blog.codinghorror.com/learn-to-read-the-source-luke/" rel="noopener noreferrer"&gt;Coding Horror&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Android is Open Source, so we can always find answers to our questions... as long as we know where to search!&lt;/p&gt;

&lt;h2&gt;
  
  
  JVM TI
&lt;/h2&gt;

&lt;p&gt;Here's the implementation of &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/openjdkjvmti/ti_object.cc;l=43-58;drc=6e8977690147b75096c0a993efb53ae329e3fd2c" rel="noopener noreferrer"&gt;JVM TI GetObjectSize()&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;

&lt;span class="n"&gt;jvmtiError&lt;/span&gt; &lt;span class="n"&gt;ObjectUtil&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;GetObjectSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;ATTRIBUTE_UNUSED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="n"&gt;jobject&lt;/span&gt; &lt;span class="n"&gt;jobject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="n"&gt;jlong&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;size_ptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;art&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ObjPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;art&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mirror&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
      &lt;span class="n"&gt;soa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;art&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mirror&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;size_ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NONE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The interesting code lives in &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/mirror/object-inl.h;l=348-371;drc=master" rel="noopener noreferrer"&gt;Object::SizeOf()&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;

&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VerifyObjectFlags&lt;/span&gt; &lt;span class="n"&gt;kVFlags&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="n"&gt;VerifyObjectFlags&lt;/span&gt; &lt;span class="n"&gt;kNewFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RemoveThisFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kVFlags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsArrayInstance&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kVFlags&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AsArray&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsClass&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AsClass&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsString&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AsString&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="n"&gt;SizeOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GetClass&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kWithoutReadBarrier&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="n"&gt;GetObjectSize&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kNewFlags&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Instance size
&lt;/h2&gt;

&lt;p&gt;Let's focus on instance size, which is the last &lt;code&gt;else&lt;/code&gt; in that conditional. If the object is an instance, then its size is retrieved from &lt;code&gt;GetClass()-&amp;gt;GetObjectSize()&lt;/code&gt; which returns the value from &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/mirror/class.h;l=1524-1527;drc=master" rel="noopener noreferrer"&gt;object_size_ in class.h&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// Total object size; used when allocating storage on gc heap.&lt;/span&gt;
&lt;span class="c1"&gt;// (For interfaces and abstract classes this will be zero.)&lt;/span&gt;
&lt;span class="c1"&gt;// See also class_size_.&lt;/span&gt;
&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;object_size_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Instance allocation
&lt;/h2&gt;

&lt;p&gt;Let's double check that this is the actual size of instances in memory by looking at usages. We find that &lt;code&gt;object_size_&lt;/code&gt; is used by &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/mirror/class-alloc-inl.h;l=50;drc=330d7ae3c860ee34a52b391dc8b6f22beea93f11" rel="noopener noreferrer"&gt;Class::Alloc() in class-alloc-inl.h&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;

&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;kIsInstrumented&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AddFinalizer&lt;/span&gt; &lt;span class="n"&gt;kAddFinalizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;kCheckAddFinalizer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;ObjPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AllocatorType&lt;/span&gt; &lt;span class="n"&gt;allocator_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;gc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Heap&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetHeap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;AllocObjectWithAllocator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kIsInstrumented&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;object_size_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allocator_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;VoidFunctor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So the actual memory allocated for an object is indeed defined by &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/mirror/class.h;l=1524-1527;drc=master" rel="noopener noreferrer"&gt;object_size_ in class.h&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the memory allocated by   &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/gc/heap-inl.h;l=45;drc=330d7ae3c860ee34a52b391dc8b6f22beea93f11" rel="noopener noreferrer"&gt;Heap::AllocObjectWithAllocator() in heap-inl.h&lt;/a&gt; might be rounded up to a multiple of 8 when using a Thread-local bump allocator (&lt;a href="https://source.android.com/devices/tech/dalvik/gc-debug#using_the_tlab_allocator_option" rel="noopener noreferrer"&gt;TLAB&lt;/a&gt;, see &lt;a href="https://youtu.be/ziSffggNkz4?t=1474" rel="noopener noreferrer"&gt;Trash Talk&lt;/a&gt; by Chet Haase and Romain Guy). However the &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/gc/heap.cc;l=2168-2173;drc=330d7ae3c860ee34a52b391dc8b6f22beea93f11" rel="noopener noreferrer"&gt;default CMS GC&lt;/a&gt; does not use that allocator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Class linking
&lt;/h2&gt;

&lt;p&gt;Looked at more usages for &lt;code&gt;object_size_&lt;/code&gt; we find that it's set by &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/class_linker.cc;l=8480-8622;drc=master" rel="noopener noreferrer"&gt;ClassLinker::LinkFields() in class_linker.cc&lt;/a&gt; when linking classes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;

&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ClassLinker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LinkFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;mirror&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;is_static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;class_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;MemberOffset&lt;/span&gt; &lt;span class="n"&gt;field_offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;ObjPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;mirror&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;super_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetSuperClass&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;super_class&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;field_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MemberOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;super_class&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetObjectSize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ... code that increases field_offset as fields are added&lt;/span&gt;

  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field_offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uint32Value&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;SetObjectSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Back to heap dumps
&lt;/h1&gt;

&lt;p&gt;Now that we know how to get the actual size of instances, let's compare that with the instance size reported in Android heap dumps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuobbygilx84kqm8s2cwn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuobbygilx84kqm8s2cwn.jpg" alt="Read the Source, Luke"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we trigger a heap dump via &lt;a href="https://developer.android.com/reference/android/os/Debug#dumpHprofData(java.lang.String)" rel="noopener noreferrer"&gt;Debug.dumpHprofData()&lt;/a&gt;, the VM calls &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/hprof/hprof.cc;l=1601-1612;drc=330d7ae3c860ee34a52b391dc8b6f22beea93f11" rel="noopener noreferrer"&gt;DumpHeap() in hprof.cc&lt;/a&gt;. Let's look at &lt;code&gt;Hprof::DumpHeapClass()&lt;/code&gt;, more specifically the part where the &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/hprof/hprof.cc;l=1261-1274;drc=330d7ae3c860ee34a52b391dc8b6f22beea93f11" rel="noopener noreferrer"&gt;instance size of a class is added&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// Instance size.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;IsClassClass&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// As mentioned above, we will emit instance fields as&lt;/span&gt;
  &lt;span class="c1"&gt;// synthetic static fields. So the base object is "empty."&lt;/span&gt;
  &lt;span class="n"&gt;__&lt;/span&gt; &lt;span class="n"&gt;AddU4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;IsStringClass&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Strings are variable length with character data at the end &lt;/span&gt;
  &lt;span class="c1"&gt;// like arrays. This outputs the size of an empty string.&lt;/span&gt;
  &lt;span class="n"&gt;__&lt;/span&gt; &lt;span class="n"&gt;AddU4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mirror&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;IsArrayClass&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;IsPrimitive&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;__&lt;/span&gt; &lt;span class="n"&gt;AddU4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;__&lt;/span&gt; &lt;span class="n"&gt;AddU4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetObjectSize&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;  &lt;span class="c1"&gt;// instance size&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The last &lt;code&gt;else&lt;/code&gt; in that conditional is the instance size for most instances, and once again points to &lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/mirror/class.h;l=1524-1527;drc=master" rel="noopener noreferrer"&gt;object_size_ in class.h&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, unlike Open JDK heap dumps, &lt;strong&gt;Android heap dumps contain the actual size of instances in memory&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring heap dump records
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://academy.realm.io/posts/360andev-jake-wharton-java-hidden-costs-android/#collections-3321" rel="noopener noreferrer"&gt;Exploring Java's Hidden Costs&lt;/a&gt;, Jake showed the output from JOL for &lt;code&gt;android.util.SparseArray&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

android.util.SparseArray object internals:
SIZE     TYPE DESCRIPTION 
4        (object header)
4        (object header)
4        (object header)
4        int SparseArray.mSize
1        boolean SparseArray.mGarbage
3        (alignment/padding gap)
4        int[] SparseArray.mKeys
4        Object[] SparseArray.mValues
4        (loss due to the next object alignment)

Instance size: 32 bytes


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's use the LeakCanary heap dump parser (&lt;a href="https://square.github.io/leakcanary/shark/" rel="noopener noreferrer"&gt;Shark&lt;/a&gt;) to see what Android heap dumps report:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hprofFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"heap_dump_android_o.hprof"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classpathFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sparseArraySize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hprofFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openHeapGraph&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findClassByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"android.util.SparseArray"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instanceByteSize&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Instance size: $sparseArraySize bytes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Instance size: 21 bytes


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Nice, that's way less than the 32 bytes reported by JOL!&lt;/p&gt;

&lt;p&gt;Let's look at the details of the reported fields:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hprofFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openHeapGraph&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findClassByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"android.util.SparseArray"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classHierarchy&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readRecord&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fieldSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;REFERENCE_HPROF_TYPE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifierByteSize&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;byteSizeByHprofType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;typeName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;REFERENCE_HPROF_TYPE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="s"&gt;"REF"&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
              &lt;span class="n"&gt;primitiveTypeByHprofType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
          &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;className&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
          &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fieldName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clazz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instanceFieldName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="s"&gt;"$fieldSize $typeName $className#$fieldName"&lt;/span&gt;
        &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;asSequence&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;joinToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nc"&gt;BOOLEAN&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SparseArray&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;mGarbage&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SparseArray&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;mKeys&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SparseArray&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;mSize&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SparseArray&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;mValues&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;shadow&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;_klass_&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;shadow&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;_monitor_&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So every SparseArray instance has a shallow size of 21 bytes, which includes 8 bytes from the Object class and 13 bytes for its own fields... and 0 bytes wasted!&lt;/p&gt;

&lt;h2&gt;
  
  
  Gaps and alignment
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/class_linker.cc;l=8480-8622;drc=master" rel="noopener noreferrer"&gt;ClassLinker::LinkFields() in class_linker.cc&lt;/a&gt; determines the position of every field in memory, with the following rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first N bytes are used to store the field values of the parent class, based on the parent &lt;code&gt;Class::GetObjectSize()&lt;/code&gt;. N could be anything, even an odd number. If the parent class has gaps (unused bytes), these won't be touched.&lt;/li&gt;
&lt;li&gt;Then fields are inserted, aligned on their size: longs are 8 byte aligned, ints are 4 byte aligned, etc.&lt;/li&gt;
&lt;li&gt;The insertion order is references first, then primitive fields with largest field first. E.g. &lt;code&gt;reference&lt;/code&gt; then &lt;code&gt;long&lt;/code&gt; then &lt;code&gt;int&lt;/code&gt; then &lt;code&gt;char&lt;/code&gt; then &lt;code&gt;boolean&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Because fields must be aligned on their own size, there may be gaps. Here's an example on a 32 bit ART VM:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myChar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="sc"&gt;'a'&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myBool1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myBool2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ref1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ref2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myLong&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt;     &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;shadow&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;_klass_&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;INT&lt;/span&gt;     &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;shadow&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;_monitor_&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nc"&gt;CHAR&lt;/span&gt;    &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myChar&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nc"&gt;BOOLEAN&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myBool1&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nc"&gt;BOOLEAN&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myBool2&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nc"&gt;GAP&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;alignment&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt;     &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ref1&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt;     &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ref2&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;GAP&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;alignment&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;long&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="nc"&gt;LONG&lt;/span&gt;    &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myLong&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here &lt;code&gt;com.example.Child&lt;/code&gt; is 32 bytes which includes 5 bytes wasted for field alignment.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a field can fit in an existing gap, that field gets moved forward:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myChar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myBool1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myBool2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ref1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ref2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myLong&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;
  &lt;span class="c1"&gt;// Added myInt and myBool3&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myInt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myBool3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt;     &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;shadow&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;_klass_&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;INT&lt;/span&gt;     &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;shadow&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;_monitor_&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="nc"&gt;CHAR&lt;/span&gt;    &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myChar&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nc"&gt;BOOLEAN&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myBool1&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nc"&gt;BOOLEAN&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myBool2&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;still&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nc"&gt;BOOLEAN&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;myBool3&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt;     &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ref1&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;REF&lt;/span&gt;     &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;ref2&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="nc"&gt;INT&lt;/span&gt;     &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;myInt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="nc"&gt;LONG&lt;/span&gt;    &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;myLong&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this example we added an int and a boolean to &lt;code&gt;Child&lt;/code&gt; and the instance size hasn't changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Unlike Open JDK heap dumps, Android heap dumps contain the actual size of instances in memory.&lt;/li&gt;
&lt;li&gt;We only looked into instance size, on the latest Art implementation. It'd be interesting to do a similar investigation for class size and array size, and also see if the same results can be observed on Dalvik.&lt;/li&gt;
&lt;li&gt;Reading the Android sources is fun! Even when, like me, you have no idea how C++ works. Comments, symbol names and git history usually provide enough details to figure it out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Many thanks to Romain Guy, Jesse Wilson and Artem Chubaryan who sent me so many pointers and entertained my stupid ideas.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>leakcanary</category>
      <category>performance</category>
      <category>memory</category>
    </item>
    <item>
      <title>Android Vitals - First draw time 👩‍🎨</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Sat, 29 Aug 2020 11:51:28 +0000</pubDate>
      <link>https://dev.to/pyricau/android-vitals-first-draw-time-m1d</link>
      <guid>https://dev.to/pyricau/android-vitals-first-draw-time-m1d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Light Field&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/12143262833/" rel="noopener noreferrer"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blog series is focused on stability and performance monitoring of Android apps in production. Last week, I wrote about &lt;a href="https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4"&gt;how to best determine the app start time&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, we focus on determining the &lt;strong&gt;time at which cold start ends&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://support.google.com/googleplay/android-developer/answer/7385505" rel="noopener noreferrer"&gt;Play Console documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Startup times are tracked when the app's &lt;strong&gt;first frame completely loads&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We learn a bit more from the &lt;a href="https://developer.android.com/topic/performance/vitals/launch-time#cold" rel="noopener noreferrer"&gt;App startup cold time documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once the app process has &lt;strong&gt;completed the first draw&lt;/strong&gt;, the system process swaps out the currently displayed background window, replacing it with the main activity. At this point, the user can start using the app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-rising-to-the-first-drawn-surface-1j9e"&gt;Android Vitals - Rising to the first drawn surface 🤽‍♂️&lt;/a&gt;, we learnt that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=4269;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;ActivityThread.handleResumeActivity()&lt;/a&gt; schedules the first frame.&lt;/li&gt;
&lt;li&gt;On the first frame &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/Choreographer.java;l=662;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;Choreographer.doFrame()&lt;/a&gt; calls &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewRootImpl.java;l=1736;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;ViewRootImpl.doTraversal()&lt;/a&gt; which performs a measure pass, a layout pass, and finally the &lt;strong&gt;first draw pass&lt;/strong&gt; on the view hierarchy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2ken9max4z9flkwlmdmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2ken9max4z9flkwlmdmf.png" alt="First draw"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  First frame
&lt;/h1&gt;

&lt;p&gt;Since API level 16, Android provides a simple API to schedule a callback when the next frame happens: &lt;a href="https://developer.android.com/reference/android/view/Choreographer#postFrameCallback(android.view.Choreographer.FrameCallback)" rel="noopener noreferrer"&gt;Choreographer.postFrameCallback()&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstFrameDoneMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;Choreographer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;postFrameCallback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;firstFrameDoneMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, calling &lt;code&gt;Choreographer.postFrameCallback()&lt;/code&gt; has the side effect of scheduling a frame that runs &lt;strong&gt;before the first traversal is scheduled&lt;/strong&gt;. So the time reported here is before the time of the frame that runs the first draw. I was able to reproduce this on API 25 but also noticed it doesn't happen in API 30, so this bug was probably fixed.&lt;/p&gt;

&lt;h1&gt;
  
  
  First draw
&lt;/h1&gt;

&lt;h2&gt;
  
  
  ViewTreeObserver
&lt;/h2&gt;

&lt;p&gt;On Android, each view hierarchy has a &lt;a href="https://developer.android.com/reference/android/view/View#getViewTreeObserver()" rel="noopener noreferrer"&gt;ViewTreeObserver&lt;/a&gt; which can hold callbacks for global events such as layout or draw.&lt;/p&gt;

&lt;h3&gt;
  
  
  ViewTreeObserver.addOnDrawListener()
&lt;/h3&gt;

&lt;p&gt;We can call &lt;a href="https://developer.android.com/reference/android/view/ViewTreeObserver#addOnDrawListener(android.view.ViewTreeObserver.OnDrawListener)" rel="noopener noreferrer"&gt;ViewTreeObserver.addOnDrawListener()&lt;/a&gt; to register a draw listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOnDrawListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="c1"&gt;// report first draw&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ViewTreeObserver.removeOnDrawListener()
&lt;/h3&gt;

&lt;p&gt;We only care about the first draw, so we need to remove the &lt;a href="https://developer.android.com/reference/android/view/ViewTreeObserver.OnDrawListener" rel="noopener noreferrer"&gt;OnDrawListener&lt;/a&gt; as soon as we've received a callback. Unfortunately, &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewTreeObserver.java;l=739-754;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;ViewTreeObserver.removeOnDrawListener()&lt;/a&gt; cannot be called from the &lt;code&gt;onDraw()&lt;/code&gt; callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViewTreeObserver&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;removeOnDrawListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OnDrawListener&lt;/span&gt; &lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;checkIsAlive&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mInDispatchOnDraw&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="s"&gt;"Cannot call removeOnDrawListener inside of onDraw"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;mOnDrawListeners&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we have to do the removal in a post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NextDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OnDrawListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Looper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMainLooper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;invoked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onDraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;invoked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="nf"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isAlive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeOnDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOnDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;NextDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the nice extension function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="c1"&gt;// report first draw&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  FloatingTreeObserver
&lt;/h3&gt;

&lt;p&gt;If we call &lt;a href="https://developer.android.com/reference/android/view/View#getViewTreeObserver()" rel="noopener noreferrer"&gt;View.getViewTreeObserver()&lt;/a&gt; before the view hierarchy is attached, there is no real ViewTreeObserver already available so the view will create &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;l=24281-24288;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;a fake one&lt;/a&gt; to store the callbacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ViewTreeObserver&lt;/span&gt; &lt;span class="nf"&gt;getViewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mAttachInfo&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mAttachInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mTreeObserver&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mFloatingTreeObserver&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;mFloatingTreeObserver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ViewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mFloatingTreeObserver&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then when the view is attached the callbacks are &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/View.java;l=20093-20096;drc=69e700c7922c34583bddbab4b7ce78adadb90730?q=View.java&amp;amp;ss=android%2Fplatform%2Fsuperproject" rel="noopener noreferrer"&gt;merged back into the real ViewTreeObserver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's nice, except there was a bug &lt;a href="https://android.googlesource.com/platform/frameworks/base/+/9f8ec54244a5e0343b9748db3329733f259604f3" rel="noopener noreferrer"&gt;fixed in API 26&lt;/a&gt;: the draw listeners were not merged back into the real view tree observer.&lt;/p&gt;

&lt;p&gt;We work around that by waiting until the view is attached before registering our draw listeners:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NextDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OnDrawListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Looper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMainLooper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;invoked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onDraw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;invoked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="nf"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isAlive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeOnDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isAlive&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;isAttachedToWindow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;addNextDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Wait until attached&lt;/span&gt;
        &lt;span class="nf"&gt;addOnAttachStateChangeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;OnAttachStateChangeListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onViewAttachedToWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;addNextDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;onDrawCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;removeOnAttachStateChangeListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onViewDetachedFromWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNextDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;viewTreeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOnDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;NextDrawListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  DecorView
&lt;/h2&gt;

&lt;p&gt;Now that we have a nice utility to listen to the next draw, we can use it when an activity is created. Note that the first created activity may not draw: it's fairly common for apps to have a &lt;strong&gt;trampoline activity&lt;/strong&gt; as launcher activity which immediately starts another activity and finishes itself. We register our draw listener on the activity window &lt;a href="https://developer.android.com/reference/android/view/Window#getDecorView()" rel="noopener noreferrer"&gt;DecorView&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decorView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
          &lt;span class="n"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
          &lt;span class="c1"&gt;// report first draw&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Locking window characteristics
&lt;/h3&gt;

&lt;p&gt;According to the documentation for &lt;a href="https://developer.android.com/reference/android/view/Window#getDecorView()" rel="noopener noreferrer"&gt;Window.getDecorView()&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that calling this function for the first time "locks in" various window characteristics as described in setContentView().&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, we're calling &lt;code&gt;Window.getDecorView()&lt;/code&gt; from &lt;a href="https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks?hl=en#onActivityCreated(android.app.Activity,%20android.os.Bundle)" rel="noopener noreferrer"&gt;ActivityLifecycleCallbacks.onActivityCreated()&lt;/a&gt; which is &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/Activity.java;l=1479;drc=1c7cc22adf0d1e5a405f1ad56e2ae73912c2fee3" rel="noopener noreferrer"&gt;called by Activity.onCreate()&lt;/a&gt;. In a typical activity, &lt;code&gt;setContentView()&lt;/code&gt; is called &lt;strong&gt;after&lt;/strong&gt; &lt;code&gt;super.onCreate()&lt;/code&gt; so we're calling &lt;code&gt;Window.getDecorView()&lt;/code&gt; before &lt;code&gt;setContentView()&lt;/code&gt; is called, which has unexpected side effects.&lt;/p&gt;

&lt;p&gt;We need to wait for &lt;code&gt;setContentView()&lt;/code&gt; to be called before we retrieve the decor view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Window.Callback.onContentChanged()
&lt;/h3&gt;

&lt;p&gt;We can use &lt;a href="https://developer.android.com/reference/android/view/Window#peekDecorView()" rel="noopener noreferrer"&gt;Window.peekDecorView()&lt;/a&gt; to determine if we already have a decor view. If not, we can register a callback on our window, which provides the hook we need, &lt;a href="https://developer.android.com/reference/android/view/Window.Callback#onContentChanged()" rel="noopener noreferrer"&gt;Window.Callback.onContentChanged()&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This hook is called whenever the content view of the screen changes (due to a call to Window#setContentView() or Window#addContentView()).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, a window can only have one callback, and the activity already &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/Activity.java;l=7736;drc=1c7cc22adf0d1e5a405f1ad56e2ae73912c2fee3" rel="noopener noreferrer"&gt;sets itself&lt;/a&gt; as the window callback. So we'll need to replace that callback and delegate to it.&lt;/p&gt;

&lt;p&gt;Here's a utility class which does that and adds a &lt;code&gt;Window.onDecorViewReady()&lt;/code&gt; extension function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WindowDelegateCallback&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Callback&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Callback&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;delegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;onContentChangedCallbacks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onContentChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;onContentChangedCallbacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAll&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onContentChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDecorViewReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;peekDecorView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;onContentChanged&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@onContentChanged&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onContentChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;callback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onContentChangedCallbacks&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrapCallback&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;WindowDelegateCallback&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentCallback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentCallback&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;WindowDelegateCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;currentCallback&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;newCallback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WindowDelegateCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newCallback&lt;/span&gt;
        &lt;span class="n"&gt;newCallback&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Leveraging &lt;code&gt;Window.onDecorViewReady()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Let's put it all together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;
        &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDecorViewReady&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decorView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="n"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="c1"&gt;// report first draw&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Not quite there yet
&lt;/h2&gt;

&lt;p&gt;Let's look at the &lt;a href="https://developer.android.com/reference/android/view/ViewTreeObserver.OnDrawListener#onDraw()" rel="noopener noreferrer"&gt;OnDrawListener.onDraw()&lt;/a&gt; documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Callback method to be invoked when the view tree is about to be drawn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Drawing can still take a while. We want to know when the drawing is done, not when it starts. Unfortunately, there is no &lt;code&gt;ViewTreeObserver.OnPostDrawListener&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-rising-to-the-first-drawn-surface-1j9e"&gt;Android Vitals - Rising to the first drawn surface 🤽‍♂️&lt;/a&gt;, we learnt that the first frame and traversal all happen in just one &lt;code&gt;MSG_DO_FRAME&lt;/code&gt; message. If we could determine when that message ends, we would know when we're done drawing.&lt;/p&gt;

&lt;h1&gt;
  
  
  Handler.postAtFrontOfQueue()
&lt;/h1&gt;

&lt;p&gt;Instead of determining when the &lt;code&gt;MSG_DO_FRAME&lt;/code&gt; message ends, we can detect when the next message starts by posting to the front of the message queue with &lt;a href="https://developer.android.com/reference/android/os/Handler#postAtFrontOfQueue(java.lang.Runnable)" rel="noopener noreferrer"&gt;Handler.postAtFrontOfQueue()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstDrawMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;window&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;
        &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDecorViewReady&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decorView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onNextDraw&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="n"&gt;firstDraw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postAtFrontOfQueue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;firstDrawMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; I measured the time difference between the first &lt;code&gt;onNextDraw()&lt;/code&gt; and the following &lt;code&gt;postAtFrontOfQueue()&lt;/code&gt; in production on a large number of devices, here are the results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10th percentile: 25ms&lt;/li&gt;
&lt;li&gt;25th percentile: 37ms&lt;/li&gt;
&lt;li&gt;50th percentile: 61ms&lt;/li&gt;
&lt;li&gt;75th percentile: 109ms&lt;/li&gt;
&lt;li&gt;90th percentile: 194ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That interval is significant enough to not be left out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We now have everything we need to monitor cold start times in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-why-did-my-process-start-4d0e"&gt;Why did my process start? 🌄&lt;/a&gt; we learnt how to detect a cold start.&lt;/li&gt;
&lt;li&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4"&gt;When did my app start? ⏱&lt;/a&gt; we learnt how to determine the app start time.&lt;/li&gt;
&lt;li&gt;In this blog we learnt how to determine the first draw time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you enjoyed these deep dives, stay tuned for more!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1291126907426099200-671" src="https://platform.twitter.com/embed/Tweet.html?id=1291126907426099200"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1291126907426099200-671');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1291126907426099200&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>android</category>
      <category>vitals</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Android Vitals - When did my app start? ⏱</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Fri, 21 Aug 2020 21:01:05 +0000</pubDate>
      <link>https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4</link>
      <guid>https://dev.to/pyricau/android-vitals-when-did-my-app-start-24p4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Ressence Type 5 Tilt&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/48887849497/in/photostream/"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blog series is focused on stability and performance monitoring of Android apps in production. Last week, I wrote about using process importance to &lt;a href="https://dev.to/pyricau/android-vitals-why-did-my-process-start-4d0e"&gt;determine why an app was started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To track cold start time, we need to know when the app started. There are many ways to do that and this blog evaluates different approaches.&lt;/p&gt;

&lt;p&gt;As a reminder, I already established in &lt;a href="https://dev.to/pyricau/android-vitals-what-time-is-it-2oih"&gt;Android Vitals - What time is it?&lt;/a&gt; that I would use &lt;code&gt;SystemClock.uptimeMillis()&lt;/code&gt; to measure time intervals.&lt;/p&gt;

&lt;h1&gt;
  
  
  Application.onCreate()
&lt;/h1&gt;

&lt;p&gt;The simplest approach is to capture the time at which &lt;a href="https://developer.android.com/reference/android/app/Application#onCreate()"&gt;Application.onCreate()&lt;/a&gt; is called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;applicationOnCreateMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;applicationOnCreateMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  ContentProvider.onCreate()
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://firebase.googleblog.com/2016/12/how-does-firebase-initialize-on-android.html"&gt;How does Firebase initialize on Android?&lt;/a&gt; we learn that a safe early initialization hook for library developers is &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/ContentProvider.java;l=1048;drc=69e700c7922c34583bddbab4b7ce78adadb90730"&gt;ContentProvider.onCreate()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StartTimeProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ContentProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;providerOnCreateMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;providerOnCreateMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/ContentProvider.java;l=1048;drc=69e700c7922c34583bddbab4b7ce78adadb90730"&gt;ContentProvider.onCreate()&lt;/a&gt; also works for app developers and it's called earlier in the app lifecycle than &lt;a href="https://developer.android.com/reference/android/app/Application#onCreate()"&gt;Application.onCreate()&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Class load time
&lt;/h1&gt;

&lt;p&gt;Before any class can be used, it has to be loaded. We can rely on static initializers to store the time at which specific classes are loaded.&lt;/p&gt;

&lt;p&gt;We could track the time at which the Application class is loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;applicationClassLoadMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;a href="https://dev.to/pyricau/android-vitals-diving-into-cold-start-waters-5hi6"&gt;Android Vitals - Diving into cold start waters 🥶&lt;/a&gt;, we learnt that on Android P+ the first class loaded is the &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/AppComponentFactory.java;drc=69e700c7922c34583bddbab4b7ce78adadb90730;bpv=1;bpt=1;l=35"&gt;AppComponentFactory&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RequiresApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION_CODES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;P&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StartTimeFactory&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;androidx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppComponentFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;factoryClassLoadMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;manifest&lt;/span&gt; 
    &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:tools=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/tools"&lt;/span&gt;
    &lt;span class="na"&gt;package=&lt;/span&gt;&lt;span class="s"&gt;"com.example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Replace AndroidX appComponentFactory. --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt;
      &lt;span class="na"&gt;android:appComponentFactory=&lt;/span&gt;&lt;span class="s"&gt;"com.example.StartTimeFactory"&lt;/span&gt;
      &lt;span class="na"&gt;tools:replace=&lt;/span&gt;&lt;span class="s"&gt;"android:appComponentFactory"&lt;/span&gt;
      &lt;span class="na"&gt;tools:targetApi=&lt;/span&gt;&lt;span class="s"&gt;"p"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To track start time before Android P, Library developers can rely on the class load time of providers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StartTimeProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ContentProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;providerClassLoadMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The class loading order will usually be &lt;code&gt;AppComponentFactory&lt;/code&gt; &amp;gt; &lt;code&gt;Application&lt;/code&gt; &amp;gt; &lt;code&gt;ContentProvider&lt;/code&gt;. That may change if your &lt;code&gt;AppComponentFactory&lt;/code&gt; loads other classes on class load.&lt;/p&gt;

&lt;h1&gt;
  
  
  Process fork time
&lt;/h1&gt;

&lt;p&gt;We know that every app process starts by being &lt;a href="https://dev.to/pyricau/android-vitals-diving-into-cold-start-waters-5hi6"&gt;forked from Zygote&lt;/a&gt;. On Linux &amp;amp; Android, there's a file called &lt;code&gt;/proc/[pid]/stat&lt;/code&gt; that is readable and contains stats for each process, including the process start time.&lt;/p&gt;

&lt;p&gt;Let's check out &lt;code&gt;man proc&lt;/code&gt;, under the &lt;code&gt;/proc/[pid]/stat&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(2) comm  %s
         The filename of the executable, in parentheses.
         This is visible whether or not the executable is
         swapped out.

...

(22) starttime  %llu
    The time the process started after system boot.  In
    kernels before Linux 2.6, this value was expressed
    in jiffies.  Since Linux 2.6, the value is expressed
    in clock ticks (divide by sysconf(_SC_CLK_TCK)).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/proc/[pid]/stat&lt;/code&gt; is a file with one line of text, where each stat is separated by a space. However, the second entry is the filename of the executable, which may contain spaces, so we'll have to jump past it by looking for the first &lt;code&gt;)&lt;/code&gt; character. Once we've done that, we can split the remaining string by spaces and pick the 20th entry at index 19.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Processes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;readProcessForkRealtimeMillis&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myPid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;myPid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ticksAtProcessStart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readProcessStartTicks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myPid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Min API 21, use reflection before API 21.&lt;/span&gt;
    &lt;span class="c1"&gt;// See https://stackoverflow.com/a/42195623/703646&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;ticksPerSecond&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sysconf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OsConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_SC_CLK_TCK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ticksAtProcessStart&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;ticksPerSecond&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Benchmarked (with Jetpack Benchmark) on Pixel 3 running &lt;/span&gt;
  &lt;span class="c1"&gt;// Android 10. Median time: 0.13ms&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;readProcessStartTicks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/proc/$pid/stat"&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;stat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BufferedReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substringAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;") "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us the realtime for when the process was forked. We can convert that to uptime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;forkRealtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Processes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readProcessForkRealtimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nowRealtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elapsedRealtime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nowUptime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;elapsedRealtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowRealtime&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;forkRealtime&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;forkUptimeMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nowUptime&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;elapsedRealtimeMs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us the &lt;strong&gt;real&lt;/strong&gt; process start time. However, in &lt;a href="https://dev.to/pyricau/android-vitals-diving-into-cold-start-waters-5hi6"&gt;Android Vitals - Diving into cold start waters 🥶&lt;/a&gt;, we concluded that app cold start monitoring should start when &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=6162;drc=69e700c7922c34583bddbab4b7ce78adadb90730"&gt;ActivityThread.handleBindApplication()&lt;/a&gt; is called, because app developers have little influence on the time spent before that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre forking
&lt;/h2&gt;

&lt;p&gt;There's another downside to relying on fork time: the &lt;strong&gt;process can be forked long before the application starts specializing&lt;/strong&gt; in &lt;code&gt;ActivityThread.handleBindApplication()&lt;/code&gt;. I measured the time from fork to &lt;code&gt;Application.onCreate()&lt;/code&gt; in a production app: while the median time was &lt;strong&gt;350ms&lt;/strong&gt;, the &lt;strong&gt;max was 4 days&lt;/strong&gt;, and the interval was &lt;strong&gt;greater than 1 minute for 0.5% of app starts&lt;/strong&gt;. This might be due to some systems keeping a pool of pre forked zygotes to accelerate app start.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bind application time
&lt;/h1&gt;

&lt;p&gt;One of the first things &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=6162;drc=69e700c7922c34583bddbab4b7ce78adadb90730"&gt;ActivityThread.handleBindApplication()&lt;/a&gt; does is call &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Process.java;l=644;drc=69e700c7922c34583bddbab4b7ce78adadb90730"&gt;Process.setStartTimes()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActivityThread&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;handleBindApplication&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AppBindData&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Note when this process has started.&lt;/span&gt;
    &lt;span class="nc"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStartTimes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elapsedRealtime&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; 
        &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uptimeMillis&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The corresponding timestamp is available via &lt;a href="https://developer.android.com/reference/android/os/Process.html#getStartUptimeMillis()"&gt;Process.getStartUptimeMillis()&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Return the SystemClock#uptimeMillis() at which this process was started.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds great, right? Well, it was great, until API 28. I measured the time from bind application to &lt;code&gt;Application.onCreate()&lt;/code&gt; in a production app. While the median time was 250ms, on &lt;strong&gt;API 28+&lt;/strong&gt; the &lt;strong&gt;max was 14 hours&lt;/strong&gt;, and the interval was &lt;strong&gt;greater than 1 minute for 0.05% of app starts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I also found similar issues with the time from class loading of &lt;code&gt;AppComponentFactory&lt;/code&gt; to &lt;code&gt;Application.onCreate()&lt;/code&gt;: greater than 1 minute for 0.1% of app start on &lt;strong&gt;API 28+&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This can't be due to the device sleeping, since we only measure time intervals using &lt;code&gt;SystemClock.uptimeMillis()&lt;/code&gt;. I haven't been able to figure out exactly what's going on here, it looks like sometimes bind application starts then halts mid way and the actual app start is much later. &lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Here's how we can most accurately measure the app start time when monitoring cold start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up to API 24: Use the class load time of a content provider.&lt;/li&gt;
&lt;li&gt;API 24 - API 28: Use &lt;a href="https://developer.android.com/reference/android/os/Process.html#getStartUptimeMillis()"&gt;Process.getStartUptimeMillis()&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;API 28 and beyond: Use &lt;a href="https://developer.android.com/reference/android/os/Process.html#getStartUptimeMillis()"&gt;Process.getStartUptimeMillis()&lt;/a&gt; but filter out weird values (e.g. more than 1 min to get to &lt;code&gt;Application.onCreate()&lt;/code&gt;) and fallback to the time &lt;code&gt;ContentProvider.onCreate()&lt;/code&gt; is called.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>vitals</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Android Vitals - Why did my process start? 🌄</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Sat, 15 Aug 2020 14:06:06 +0000</pubDate>
      <link>https://dev.to/pyricau/android-vitals-why-did-my-process-start-4d0e</link>
      <guid>https://dev.to/pyricau/android-vitals-why-did-my-process-start-4d0e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Windmill Sunrise&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/3961050471"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blog series is focused on stability and performance monitoring of Android apps in production. Last week, I wrote about how to &lt;a href="https://dev.to/pyricau/android-vitals-is-this-a-cold-start-3m44"&gt;determine if an app start is a cold start&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If we post a message and no activity was created when that message runs, then we know this isn't a cold start, even if an activity is eventually launched 20 seconds later.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;:
        &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstActivityCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstActivityCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO Report cold start&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this approach, we must wait for an activity to be launched or that message to run before we know if an app start is a cold start. Sometimes it would be useful to know that from within &lt;code&gt;Application.onCreate()&lt;/code&gt;. For example, we might want to preload resources asynchronously to optimize cold start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isColdStart&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;preloadDataForUiAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Process importance
&lt;/h1&gt;

&lt;p&gt;While there is no Android API to know why a process was started, there is one to know why a process is still running: &lt;a href="https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#importance"&gt;RunningAppProcessInfo.importance&lt;/a&gt;, which we can read from &lt;a href="https://developer.android.com/reference/android/app/ActivityManager#getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo)"&gt;ActivityManager.getMyMemoryState()&lt;/a&gt;. According to the &lt;a href="https://developer.android.com/guide/components/activities/process-lifecycle"&gt;Processes and Application Lifecycle documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To determine which processes should be killed when low on memory, Android places each process into an "importance hierarchy" based on the components running in them and the state of those components. [...] When deciding how to classify a process, the system will base its decision on the most important level found among all the components currently active in the process. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Right when the process starts, we could check its importance.  If the importance is &lt;a href="https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND"&gt;IMPORTANCE_FOREGROUND&lt;/a&gt;, it must be a cold start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isForegroundProcess&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;preloadDataForUiAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isForegroundProcess&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;processInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunningAppProcessInfo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;ActivityManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMyMemoryState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;processInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;importance&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;IMPORTANCE_FOREGROUND&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Confirming with data
&lt;/h1&gt;

&lt;p&gt;I implemented cold start detection with both approaches in a sample app and got identical results either way. I then added the detection code to a production app with enough installs to provide meaningful results. Here are the (anonymized) results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VoHlOdaA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w05pxgsdit8pmcywycbl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VoHlOdaA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w05pxgsdit8pmcywycbl.png" alt="importance at startup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This pie chart includes only app startups where an &lt;strong&gt;activity was created before the first post&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;97.4%&lt;/strong&gt; had an importance of &lt;strong&gt;100&lt;/strong&gt;, i.e. &lt;a href="https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND"&gt;IMPORTANCE_FOREGROUND&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2.4%&lt;/strong&gt; had an importance of &lt;strong&gt;400&lt;/strong&gt;, i.e. &lt;a href="https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_CACHED"&gt;IMPORTANCE_CACHED&lt;/a&gt; (previously named &lt;a href="https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_BACKGROUND"&gt;IMPORTANCE_BACKGROUND&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0.2%&lt;/strong&gt; map to other importances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at that data from another angle: given a specific importance, how often was an activity created before the first post?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vV_LsBy8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/prhr5t415hox62977ei2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vV_LsBy8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/prhr5t415hox62977ei2.png" alt="importance at startup 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When startup importance is 100 there's always an activity created before first post. And when an activity is created before first post, 97.4% of the time the importance is 100.&lt;/li&gt;
&lt;li&gt;When startup importance is 400 there's &lt;strong&gt;almost never&lt;/strong&gt; an activity created before first post. Almost never is not never, there are still enough cases that when an activity is created before first post, 2.4% of the time the importance is 400.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most likely explanation for the 2.4% with importance 400 is that those were not cold starts, however the system received a query to start an activity almost immediately after the process started, right when running &lt;code&gt;Application.onCreate()&lt;/code&gt; but before we had a chance to add our first post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; I logged the importance after first post and compared that to the importance on app start. My data showed that 74% of app starts where an activity was created before first post and the start process importance was not 100 had  that process importance changed to 100 after first post. This seem to confirm  the theory that the system decided to start the activity after the app had already began to start.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;We can combine our findings from the previous post to determine cold start accurately. A cold start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has a process importance of &lt;a href="https://developer.android.com/reference/android/app/ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND"&gt;IMPORTANCE_FOREGROUND&lt;/a&gt; on app startup.&lt;/li&gt;
&lt;li&gt;Has the first activity created before the first post is dequeued.&lt;/li&gt;
&lt;li&gt;Has the first activity created with a null bundle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the updated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isForegroundProcess&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstPostEnqueued&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
      &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;firstPostEnqueued&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;:
          &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;unregisterActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstPostEnqueued&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// TODO Report cold start&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isForegroundProcess&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;processInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunningAppProcessInfo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nc"&gt;ActivityManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMyMemoryState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;processInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;importance&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;IMPORTANCE_FOREGROUND&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope you're enjoying these investigations!&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--HVdrgqWO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/448720603072049152/JUT0Ef_S_normal.jpeg" alt="Py ⚔ profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Py ⚔
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @piwai
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Android Vitals, a thread!&lt;br&gt;&lt;br&gt;I'm writing a blog series focused on stability and performance monitoring of Android apps in production.&lt;br&gt;&lt;br&gt;I'll update this thread with links to new articles as I publish them.&lt;br&gt;&lt;br&gt;Check in every week for new content!&lt;br&gt;&lt;br&gt;🧵⬇️⬇️⬇️⬇️⬇️⬇️🧵
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      21:40 PM - 05 Aug 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1291126907426099200" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1291126907426099200" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      0
      &lt;a href="https://twitter.com/intent/like?tweet_id=1291126907426099200" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      2
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


</description>
      <category>android</category>
      <category>vitals</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Android Vitals - Is this a cold start? 🦋</title>
      <dc:creator>Py ⚔</dc:creator>
      <pubDate>Wed, 05 Aug 2020 22:00:07 +0000</pubDate>
      <link>https://dev.to/pyricau/android-vitals-is-this-a-cold-start-3m44</link>
      <guid>https://dev.to/pyricau/android-vitals-is-this-a-cold-start-3m44</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Header image: &lt;em&gt;Follow the Light&lt;/em&gt; &lt;a href="https://www.flickr.com/photos/romainguy/46217553745/" rel="noopener noreferrer"&gt;by Romain Guy&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blog series is focused on stability and performance monitoring of Android apps in production. In the last 2 posts, I wrote about what happens from when &lt;a href="https://dev.to/pyricau/android-vitals-diving-into-cold-start-waters-5hi6"&gt;the user taps a launcher icon&lt;/a&gt; to when the &lt;a href="https://dev.to/pyricau/android-vitals-rising-to-the-first-drawn-surface-1j9e"&gt;first activity is drawn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;cold start&lt;/strong&gt; is an &lt;strong&gt;activity launch&lt;/strong&gt; where the app process &lt;strong&gt;starts from scratch&lt;/strong&gt; in response to an intent to start an activity. According to the &lt;a href="https://developer.android.com/topic/performance/vitals/launch-time#cold" rel="noopener noreferrer"&gt;App startup time documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This type of start presents the &lt;strong&gt;greatest challenge&lt;/strong&gt; in terms of minimizing startup time, because the system and app have more work to do than in the other launch states.&lt;br&gt;
We recommend that you always optimize based on an assumption of a cold start. Doing so can improve the performance of warm and hot starts, as well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To &lt;strong&gt;optimize&lt;/strong&gt; cold start, we need to &lt;strong&gt;measure&lt;/strong&gt; it, which means we need to &lt;strong&gt;monitor&lt;/strong&gt; cold start times in production.&lt;/p&gt;

&lt;p&gt;Unfortunately, there is no &lt;code&gt;Activity.isThisAColdStart()&lt;/code&gt; API on Android. This is by design: the &lt;a href="https://developer.android.com/guide/components/activities/activity-lifecycle" rel="noopener noreferrer"&gt;Activity lifecycle APIs&lt;/a&gt; indicate when to save and restore state and abstract away the death and rebirth of processes. The engineers who designed the Android APIs didn't want us to write overly complex code with special cases for all the various ways an activity can be started. So there's no API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F48xeo1kaz207ib35lwhd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F48xeo1kaz207ib35lwhd.jpg" alt="meme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How are we supposed to monitor cold start if we can't tell a cold start from any other process start?
&lt;/h3&gt;

&lt;p&gt;This post leverages what we learnt from our previous deep dives on cold start to start building out our own version of the missing &lt;code&gt;Activity.isThisAColdStart()&lt;/code&gt; API. &lt;/p&gt;

&lt;h1&gt;
  
  
  Traditional approach
&lt;/h1&gt;

&lt;p&gt;Most apps and libraries report a cold start if the first activity was created within a minute of the app start. It looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;appCreateMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;:
        &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstActivityCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;activityCreateMs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SystemClock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uptimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activityCreateMs&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;appCreateMs&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// TODO Report cold start&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, this approach also includes cases where the app process was started to respond to a broadcast receiver, a content provider query or to start a service, and then an activity was launched sometimes later within the first minute. We should exclude these cases from our cold start monitoring to avoid skewing our results.&lt;/p&gt;

&lt;h1&gt;
  
  
  Leveraging our research
&lt;/h1&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/pyricau/android-vitals-rising-to-the-first-drawn-surface-1j9e"&gt;previous blog post&lt;/a&gt;, we learnt that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the app process starts, it calls &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7350;drc=d9b11b058c6a50fa25b75d6534a2deaf0e62d4b3" rel="noopener noreferrer"&gt;ActivityThread.main()&lt;/a&gt; which makes  a &lt;strong&gt;blocking IPC call&lt;/strong&gt; to &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java;l=5213;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;ActivityManagerService.attachApplication()&lt;/a&gt; in the &lt;code&gt;system_server&lt;/code&gt; process.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;system_server&lt;/code&gt; process makes an IPC call to &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=1005;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;ActivityThread.bindApplication()&lt;/a&gt; in the app process which enqueues a &lt;code&gt;BIND_APPLICATION&lt;/code&gt; message to the main thread message queue.&lt;/li&gt;
&lt;li&gt;Then, for each activity that needs to launch, the &lt;code&gt;system_server&lt;/code&gt; process makes an IPC call to &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=1679;drc=69e700c7922c34583bddbab4b7ce78adadb90730;bpv=1;bpt=1" rel="noopener noreferrer"&gt;ActivityThread.scheduleTransaction()&lt;/a&gt; in the app process which enqueues an &lt;code&gt;EXECUTE_TRANSACTION&lt;/code&gt; message to the main thread message queue.&lt;/li&gt;
&lt;li&gt;When the IPC call to &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java;l=5213;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;ActivityManagerService.attachApplication()&lt;/a&gt; is done, &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7350;drc=d9b11b058c6a50fa25b75d6534a2deaf0e62d4b3" rel="noopener noreferrer"&gt;ActivityThread.main()&lt;/a&gt; then calls &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Looper.java;l=153;drc=bc3d8b9071d4f0b2903d6836770d974e70366290" rel="noopener noreferrer"&gt;Looper.loop()&lt;/a&gt; which loops forever, processing messages posted to its &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/MessageQueue.java;l=41;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;MessageQueue&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The first message processed is &lt;code&gt;BIND_APPLICATION&lt;/code&gt;, which calls &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=6162;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;ActivityThread.handleBindApplication()&lt;/a&gt; which &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=6500;drc=d9b11b058c6a50fa25b75d6534a2deaf0e62d4b3" rel="noopener noreferrer"&gt;calls Application.onCreate()&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The next message processed is &lt;code&gt;EXECUTE_TRANSACTION&lt;/code&gt; which calls &lt;a href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java;l=69;drc=69e700c7922c34583bddbab4b7ce78adadb90730" rel="noopener noreferrer"&gt;TransactionExecutor.execute()&lt;/a&gt; which launches the activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbyzacpqn1gz9h3fo6612.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbyzacpqn1gz9h3fo6612.png" alt="Main thread start - continued"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that in &lt;code&gt;Application.onCreate()&lt;/code&gt;, the main thread message queue already has the &lt;code&gt;EXECUTE_TRANSACTION&lt;/code&gt; message enqueued. If we post a new message from &lt;code&gt;Application.onCreate()&lt;/code&gt;, it will execute after &lt;code&gt;EXECUTE_TRANSACTION&lt;/code&gt; and therefore after the activity has been created. If we post a message and no activity was created when it executes, then we know this isn't a cold start, even if an activity is eventually launched 20 seconds later.&lt;/p&gt;

&lt;p&gt;Here's how we can detect a cold start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;:
        &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstActivityCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstActivityCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO Report cold start&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Lukewarm start
&lt;/h1&gt;

&lt;p&gt;According to the &lt;a href="https://developer.android.com/topic/performance/vitals/launch-time#warm" rel="noopener noreferrer"&gt;App startup time documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are many potential states that could be considered warm starts. For instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user backs out of your app, but then re-launches it. The process may have continued to run, but the app must recreate the activity from scratch via a call to onCreate().&lt;/li&gt;
&lt;li&gt;The system evicts your app from memory, and then the user re-launches it. The process and the activity need to be restarted, but the task can benefit somewhat from the saved instance state bundle passed into onCreate().&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;So if the activity is created with a saved instance state bundle, then that shouldn't be considered a cold start. However, since the process needs to be restarted, there's still a lot more work to do than just creating an activity. Let's call this a &lt;strong&gt;lukewarm start&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We can update our code to take this into account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;hasSavedState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

    &lt;span class="nf"&gt;registerActivityLifecycleCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;:
        &lt;/span&gt;&lt;span class="nc"&gt;ActivityLifecycleCallbacks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onActivityCreated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstActivityCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;firstActivityCreated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;hasSavedState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstActivityCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hasSavedState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// TODO Report lukewarm start&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// TODO Report cold start&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The Android framework team doesn't want us to think too hard about process start and activity launch, yet the Android app performance team insists that we optimize cold start. Challenge accepted! We're starting to shape our own version of the missing &lt;code&gt;Activity.isThisAColdStart()&lt;/code&gt; API, but we're far from done. Stay tuned for more!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1291126907426099200-759" src="https://platform.twitter.com/embed/Tweet.html?id=1291126907426099200"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1291126907426099200-759');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1291126907426099200&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>android</category>
      <category>vitals</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
