<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Bin&apos;s Blog</title>
    <description>Bin&apos;s Blog
</description>
    <link>https://jubincn.github.io/index.html</link>
    <atom:link href="https://jubincn.github.io/feed-en.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 25 Apr 2026 10:29:14 +0000</pubDate>
    <lastBuildDate>Sat, 25 Apr 2026 10:29:14 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
    
      <item>
        <title>Suspend Under The Hood</title>
        <description>
</description>
        <pubDate>Mon, 13 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2023/11/13/suspend-under-the-hood.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2023/11/13/suspend-under-the-hood.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Kotlin Coroutine Async Launch</title>
        <description>&lt;h2 id=&quot;launch&quot;&gt;launch&lt;/h2&gt;
&lt;p&gt;Starts a new coroutine and doesn’t return the result to the caller. &lt;/p&gt;

&lt;h2 id=&quot;asyncawait&quot;&gt;async&amp;amp;await&lt;/h2&gt;
&lt;p&gt;Starts a new coroutine and allows you to return a result with a suspend function called await.&lt;/p&gt;

&lt;h2 id=&quot;return-type&quot;&gt;Return Type&lt;/h2&gt;
&lt;p&gt;Launch return a Job, while async return a Deffered implementation.&lt;/p&gt;

&lt;h2 id=&quot;error-handling&quot;&gt;Error Handling&lt;/h2&gt;
&lt;p&gt;try catch will work for both. For unhandled exceptions, launch will throw it directly while async will store the exception in deffered object and throw it when await() is called.&lt;/p&gt;
</description>
        <pubDate>Fri, 10 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2023/11/10/kotlin-coroutine-async-launch.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2023/11/10/kotlin-coroutine-async-launch.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Testing Coroutine</title>
        <description>&lt;h2 id=&quot;testing-coroutines-in-android&quot;&gt;Testing Coroutines in Android&lt;/h2&gt;
&lt;h3 id=&quot;invoking-suspending-functions-in-tests&quot;&gt;Invoking suspending functions in tests&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runTest{}&lt;/code&gt; is a coroutine builder designed for testing. &lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;suspend fun fetchData(): String {
    delay(1000L)
    return &quot;Hello world&quot;
}

@Test
fun dataShouldBeHelloWorld() = runTest {
    val data = fetchData()    
    assertEquals(&quot;Hello world&quot;, data)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, there are &lt;strong&gt;additional considerations&lt;/strong&gt; to make, depending on what happens in your code under test:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;When your code creates new coroutines other than the top-level test coroutine that runTest creates, you will need to control how those new coroutines are scheduled by choosing the appropriate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestDispatcher&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If your code moves the coroutine execution to other dispatchers (for example, by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withContext&lt;/code&gt;), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runTest&lt;/code&gt; will still generally work, but delays will no longer be skipped, and tests will be less predictable as code runs on multiple threads. For these reasons, in tests you should &lt;strong&gt;&lt;em&gt;inject test dispatchers&lt;/em&gt;&lt;/strong&gt; to replace real dispatchers.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;testdispatchers&quot;&gt;TestDispatchers&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;TestDispatchers&lt;/strong&gt; shalll be used if new coroutines are created during the test to make the execution of the new coroutines predictable.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runTest&lt;/code&gt; runs the test coroutine in a &lt;em&gt;TestScope&lt;/em&gt;, using a &lt;em&gt;TestDispatcher&lt;/em&gt;. TestDispatchers use a &lt;em&gt;TestCoroutineScheduler&lt;/em&gt; to control virtual time and schedule new coroutines in a test. All TestDispatchers in a test must use the same scheduler instance.&lt;/p&gt;

&lt;h4 id=&quot;standardtestdispatcher&quot;&gt;StandardTestDispatcher&lt;/h4&gt;
&lt;p&gt;When you start new coroutines on a StandardTestDispatcher, they are queued up on the underlying scheduler, to be run whenever the test thread is free to use. &lt;/p&gt;

&lt;h4 id=&quot;unconfinedtestdispatcher&quot;&gt;UnconfinedTestDispatcher&lt;/h4&gt;
&lt;p&gt;When new coroutines are started on an UnconfinedTestDispatcher, they are started eagerly on the current thread.&lt;/p&gt;

&lt;h3 id=&quot;injecting-testdispatchers&quot;&gt;Injecting TestDispatchers&lt;/h3&gt;
&lt;p&gt;Code under test might use dispatchers to switch threads (using withContext) or to start new coroutines. When code is executed on multiple threads in parallel, tests can become flaky. &lt;/p&gt;

&lt;p&gt;In tests, replace real dispatchers with instances of TestDispatchers to ensure that all code runs on the single test thread.&lt;/p&gt;

&lt;h3 id=&quot;setting-the-main-dispatcher&quot;&gt;Setting the Main dispatcher&lt;/h3&gt;
&lt;p&gt;Local unit tests run on JVM instead of real android device, if your code under test references the main thread, it’ll throw an exception during unit tests.&lt;/p&gt;

&lt;p&gt;To replace the Main dispatcher with a TestDispatcher in all cases, use the Dispatchers.setMain and Dispatchers.resetMain functions.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
class HomeViewModelTest { 
    @Test
    fun settingMainDispatcher() = runTest {
        val testDispatcher = UnconfinedTestDispatcher(testScheduler)      
        Dispatchers.setMain(testDispatcher)        
        
        try {           
            val viewModel = HomeViewModel()            
            viewModel.loadMessage() // Uses testDispatcher, runs its coroutine eagerly            
            assertEquals(&quot;Greetings!&quot;, viewModel.message.value)        
        } finally {
            Dispatchers.resetMain()        
       }   
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 08 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2023/11/08/testing-coroutine.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2023/11/08/testing-coroutine.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Coroutine Android Best Practices</title>
        <description>&lt;h2 id=&quot;best-practices-for-coroutines-in-android&quot;&gt;Best practices for coroutines in Android&lt;/h2&gt;

&lt;h4 id=&quot;inject-dispatchers&quot;&gt;Inject Dispatchers&lt;/h4&gt;
&lt;p&gt;Easier to test.&lt;/p&gt;

&lt;h4 id=&quot;suspend-functions-should-be-safe-to-call-from-the-main-thread&quot;&gt;Suspend functions should be safe to call from the main thread&lt;/h4&gt;
&lt;p&gt;Using withContext and specify dispatchers in coroutine.&lt;/p&gt;

&lt;h4 id=&quot;the-viewmodel-should-create-coroutines&quot;&gt;The ViewModel should create coroutines&lt;/h4&gt;
&lt;p&gt;Hide suspend functions and flows from UI Layer.&lt;/p&gt;

&lt;h4 id=&quot;the-data-and-business-layer-should-expose-suspend-functions-and-flows&quot;&gt;The data and business layer should expose suspend functions and Flows&lt;/h4&gt;

&lt;h4 id=&quot;make-your-coroutine-cancellable&quot;&gt;Make your coroutine cancellable&lt;/h4&gt;
&lt;p&gt;Cancellation in coroutines is cooperative, which means that when a coroutine’s Job is cancelled, the coroutine isn’t cancelled until it suspends or checks for cancellation.&lt;/p&gt;

&lt;p&gt;Check your coroutine is active using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ensureActive&lt;/code&gt; . If your coroutine is inactive, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CancellationException&lt;/code&gt;  will be thrown.&lt;/p&gt;
</description>
        <pubDate>Wed, 08 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2023/11/08/coroutine-android-best-practices.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2023/11/08/coroutine-android-best-practices.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Coroutine Intro</title>
        <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;h3 id=&quot;feature&quot;&gt;Feature&lt;/h3&gt;
&lt;h4 id=&quot;lightweight&quot;&gt;Lightweight&lt;/h4&gt;
&lt;h5 id=&quot;suspension&quot;&gt;Suspension&lt;/h5&gt;
&lt;p&gt;Multi coroutinues can be run on one thread due to support for suspension, which doesn’t block the thread where coroutine is running.&lt;/p&gt;

&lt;h4 id=&quot;fewer-memory-leaks&quot;&gt;Fewer Memory Leaks&lt;/h4&gt;
&lt;h5 id=&quot;structured-concurrency&quot;&gt;Structured Concurrency&lt;/h5&gt;
&lt;p&gt;New coroutines can only be launched in a specific CoroutineScope which delimits the lifetime of the coroutine. &lt;/p&gt;

&lt;h4 id=&quot;built-in-cancellation-support&quot;&gt;Built-in cancellation support&lt;/h4&gt;
&lt;p&gt;Cancellation is propagated automatically through the running coroutine hierarchy.&lt;/p&gt;

&lt;h4 id=&quot;jetpack-integration&quot;&gt;Jetpack integration&lt;/h4&gt;

&lt;h2 id=&quot;advanced-coroutine-concepts&quot;&gt;Advanced Coroutine Concepts&lt;/h2&gt;
&lt;h3 id=&quot;suspend---resume-stack-frame&quot;&gt;Suspend - Resume (Stack Frame)&lt;/h3&gt;
&lt;p&gt;Kotlin uses a stack frame to manage which function is running along with any local variables. When suspending a coroutine, the current stack frame is copied and saved for later. When resuming, the stack frame is copied back from where it was saved, and the function starts running again. &lt;/p&gt;

&lt;h3 id=&quot;dispatchers&quot;&gt;Dispatchers&lt;/h3&gt;
&lt;h4 id=&quot;assign-dispatchers&quot;&gt;Assign Dispatchers&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;Dispatchers.Main&lt;/li&gt;
  &lt;li&gt;Dispatchers.IO&lt;/li&gt;
  &lt;li&gt;Dispatchers.Default&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;withContext(Dispatchers.IO)&lt;/p&gt;

&lt;h4 id=&quot;withcontext-performance&quot;&gt;withContext() Performance&lt;/h4&gt;
&lt;p&gt;withContext() does not add extra overhead compared to an equivalent callback-based implementation. Furthermore, it’s possible to optimize withContext() calls beyond an equivalent callback-based implementation in some situations. &lt;/p&gt;

&lt;h3 id=&quot;start-a-coroutine&quot;&gt;Start a Coroutine&lt;/h3&gt;
&lt;h4 id=&quot;async&quot;&gt;async&lt;/h4&gt;
&lt;ol&gt;
  &lt;li&gt;Starts a new coroutine and allows you to return a result with a suspend function called await&lt;/li&gt;
  &lt;li&gt;Exception: Async expects an eventual call to await, it holds exceptions and rethrows them as part of the await call. The exception will not show in crash metrics.
    &lt;h4 id=&quot;launch&quot;&gt;launch&lt;/h4&gt;
  &lt;/li&gt;
  &lt;li&gt;Starts a new coroutine and doesn’t return the result to the caller. &lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;parallel-decomposition&quot;&gt;Parallel Decomposition&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;All coroutines that are started inside a suspend function must be stopped when that function returns&lt;/strong&gt;, so you likely need to guarantee that those coroutines finish before returning.&lt;/p&gt;

&lt;p&gt;With structured concurrency in Kotlin, you can** define a coroutineScope** that starts one or more coroutines.&lt;/p&gt;

&lt;p&gt;Then, using &lt;strong&gt;await() (for a single coroutine)&lt;/strong&gt; or &lt;strong&gt;awaitAll() (for multiple coroutines)&lt;/strong&gt;, you can guarantee that these coroutines finish before returning from the function.&lt;/p&gt;
&lt;h5 id=&quot;summary&quot;&gt;Summary&lt;/h5&gt;
&lt;p&gt;Build a coroutineScope with async and await them to finish&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;suspend fun fetchTwoDocs() = coroutineScope {
    val deferredOne = async { fetchDoc(1) }        
    val deferredTwo = async { fetchDoc(2) }       
    deferredOne.await()        
    deferredTwo.await()    
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;coroutines-concepts&quot;&gt;Coroutines concepts&lt;/h3&gt;
&lt;h4 id=&quot;coroutinescope&quot;&gt;CoroutineScope&lt;/h4&gt;
&lt;p&gt;A CoroutineScope keeps track of any coroutine it creates using launch or async. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope.cancel()&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class ExampleClass { 
    ...
    fun exampleMethod() {
        // Handle to the coroutine, you can control its lifecycle
        val job = scope.launch { 
            // New coroutine       
        }
        if (...) {
            // Cancel the coroutine started above, this doesn&apos;t affect the scope           
            // this coroutine was launched in            
            job.cancel()        
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;coroutinecontext&quot;&gt;CoroutineContext&lt;/h4&gt;
&lt;p&gt;A &lt;strong&gt;CoroutineContext&lt;/strong&gt; defines the behavior of a coroutine using the following set of elements:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Job: Controls the lifecycle of the coroutine.&lt;/li&gt;
  &lt;li&gt;CoroutineDispatcher: Dispatches work to the appropriate thread.&lt;/li&gt;
  &lt;li&gt;CoroutineName: The name of the coroutine, useful for debugging.&lt;/li&gt;
  &lt;li&gt;CoroutineExceptionHandler: Handles uncaught exceptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;parent-coroutinecontext-explained&quot;&gt;Parent CoroutineContext explained&lt;/h5&gt;
&lt;p&gt;The resulting parent CoroutineContext of a coroutine can be different from the CoroutineContext of the parent since it’s calculated based on this formula:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Parent context = Defaults + inherited CoroutineContext + arguments&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Some elements have default values: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dispatchers.Default&lt;/code&gt; is the default of CoroutineDispatcher and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;“coroutine”&lt;/code&gt; the default of CoroutineName.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;inherited CoroutineContext&lt;/strong&gt; is the CoroutineContext of the CoroutineScope or coroutine that created it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Arguments&lt;/strong&gt; passed in the coroutine builder will take precedence over those elements in the inherited context.
&lt;img src=&quot;/../assets/pic/20231107_parent_context.png&quot; alt=&quot;Parent CoroutineContext&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;new-coroutinecontext&quot;&gt;New CoroutineContext&lt;/h5&gt;
&lt;blockquote&gt;
  &lt;p&gt;New coroutine context = parent CoroutineContext + Job()&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;val job = scope.launch(Dispatchers.IO) {// new coroutine}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/../assets/pic/20231107_new_coroutine_context.png&quot; alt=&quot;New CoroutineContext&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;job&quot;&gt;Job&lt;/h5&gt;
&lt;p&gt;A job is a handle to coroutine. If a job is not passed into CoroutineContext, Job() will be used and it will cancel all coroutines in the scope for uncaught exceptions.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;val job = scope.launch {
    // New coroutine        
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h6 id=&quot;job-lifecycle&quot;&gt;Job Lifecycle&lt;/h6&gt;
&lt;p&gt;A Job can go through a set of states: New, Active, Completing, Completed, Cancelling and Cancelled. 
&lt;img src=&quot;/../assets/pic/20231107_job_lifecycle.png&quot; alt=&quot;Job Lifecycle&quot; /&gt;&lt;/p&gt;

&lt;h5 id=&quot;coroutinedispatcher&quot;&gt;CoroutineDispatcher&lt;/h5&gt;
&lt;p&gt;Kotlin coroutines use dispatchers to determine which threads are used for coroutine execution. Coroutines can suspend themselves, and the dispatcher is responsible for resuming them.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;withContext&lt;/code&gt; is usually called to switch dispatcher.&lt;/p&gt;

&lt;h6 id=&quot;customize-dispatcher&quot;&gt;Customize Dispatcher&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;Private thread pools can be created with newSingleThreadContext and newFixedThreadPoolContext.&lt;/li&gt;
  &lt;li&gt;An arbitrary java.util.concurrent.Executor can be converted to a dispatcher with the asCoroutineDispatcher extension function.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;coroutinename&quot;&gt;CoroutineName&lt;/h5&gt;
&lt;p&gt;The name of the coroutine, useful for debugging.&lt;/p&gt;

&lt;h5 id=&quot;coroutineexceptionhandler&quot;&gt;CoroutineExceptionHandler&lt;/h5&gt;
&lt;p&gt;An optional element in the coroutine context to handle uncaught exceptions.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Normally, uncaught exceptions can only result from root coroutines as all children coroutines delegate  handling of their exceptions to their parent.&lt;/li&gt;
  &lt;li&gt;Coroutines running with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SupervisorJob&lt;/code&gt; do not propagate exceptions to their parent and treated like root coroutines.&lt;/li&gt;
  &lt;li&gt;A coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object, so it cannot result in uncaught exceptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;uncaught-exceptions-with-no-handler&quot;&gt;Uncaught exceptions with no handler&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;If exception is CancellationException, it is ignored, as these exceptions are used to cancel coroutines.&lt;/li&gt;
  &lt;li&gt;Otherwise, if there is a Job in the context, then Job.cancel is invoked.&lt;/li&gt;
  &lt;li&gt;Otherwise, as a last resort, the exception is processed in a platform-specific manner:
    &lt;ul&gt;
      &lt;li&gt;On JVM, all instances of CoroutineExceptionHandler found via ServiceLoader, as well as the current thread’s Thread.uncaughtExceptionHandler, are invoked.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/androiddevelopers/coroutines-first-things-first-e6187bf3bb21&quot;&gt;Coroutines: first things first&lt;/a&gt;
&lt;a href=&quot;https://developer.android.com/kotlin/coroutines&quot;&gt;Developer Android Kotlin Coroutine&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 07 Nov 2023 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2023/11/07/coroutine-intro.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2023/11/07/coroutine-intro.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Apksigner Unsupportedclassversionerror</title>
        <description>&lt;h1 id=&quot;error-description&quot;&gt;Error description&lt;/h1&gt;
&lt;p&gt;Error: A JNI error has occurred, please check your installation and try again
Exception in thread “main” java.lang.UnsupportedClassVersionError: com/android/apksigner/ApkSignerTool has been compiled by a more recent version of the Java Runtime (class file version 53.0), this version of the Java Runtime only recognizes class file versions up to 52.0
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)&lt;/p&gt;

&lt;h1 id=&quot;solution&quot;&gt;Solution&lt;/h1&gt;
&lt;p&gt;Remove Android SDK Build-tools v30.0.1 in SDK manager, use 29.x.x version instead. For me, Build-Tools v29.0.2 works.&lt;/p&gt;
</description>
        <pubDate>Mon, 17 Aug 2020 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2020/08/17/apksigner-UnsupportedClassVersionError.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2020/08/17/apksigner-UnsupportedClassVersionError.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Adnroid Wifi Adb</title>
        <description>&lt;h1 id=&quot;enable-wifi-adb&quot;&gt;Enable Wifi ADB&lt;/h1&gt;
&lt;ol&gt;
  &lt;li&gt;Connect device to computer with cable.&lt;/li&gt;
  &lt;li&gt;Ensure your device and computer are in save network, like connect to same wifi.&lt;/li&gt;
  &lt;li&gt;Find out your device ip address, such as 172.10.10.5&lt;/li&gt;
  &lt;li&gt;In terminal, command: adb tcpip 5555&lt;/li&gt;
  &lt;li&gt;In terminal, command: adb connect ip:5555, such as 172.10.10.5: 5555&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Mon, 17 Aug 2020 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2020/08/17/adnroid-wifi-adb.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2020/08/17/adnroid-wifi-adb.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Kotlin Vocabulary Readnote Suspend</title>
        <description>
&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/androiddevelopers/the-suspend-modifier-under-the-hood-b7ce46af624f&quot;&gt;The suspend modifier — Under the hood&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Wed, 08 Apr 2020 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2020/04/08/kotlin-vocabulary-readnote-suspend.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2020/04/08/kotlin-vocabulary-readnote-suspend.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Kotlin Vocabulary Readnote Inline Function</title>
        <description>&lt;h2 id=&quot;when-to-use-inline-function&quot;&gt;When to use inline function&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;For function with higher order function (lambda), make the function inline will save some memory by avoiding Function object creation.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If the reference for higher order function is used, inline can’t be applied to it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Inline a function will grow the size of app, and then better do it when the function is simple and contains only few lines of codes.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;noinline to annotate which higher order function should not converted to inline&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=wAQCs8-a6mg&quot;&gt;Inline functions - Kotlin Vocabulary&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 07 Apr 2020 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2020/04/07/kotlin-vocabulary-readnote-inline-function.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2020/04/07/kotlin-vocabulary-readnote-inline-function.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
      <item>
        <title>Kotlin Vocabulary Readnote D8 R8 Enum</title>
        <description>&lt;h2 id=&quot;how-java-enum-works-in-switch-statement&quot;&gt;How Java Enum works in switch statement&lt;/h2&gt;
&lt;h3 id=&quot;look-into-bytecode&quot;&gt;Look Into Bytecode&lt;/h3&gt;
&lt;p&gt;I write a demo code which will be compiled to bytecode later, which will show how switch with Enum works internally.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;public class EnumDemo {

    enum BlendMode {FADE, ADD, MIX}

    public static void main(String[] args) {
        testSwitch(BlendMode.ADD);
    }

    private static void testSwitch(BlendMode mode) {
        switch (mode) {
            case FADE:
                doSomething(&quot;fade&quot;);
                break;
            case ADD:
                doSomething(&quot;add&quot;);
                break;
            case MIX:
                doSomething(&quot;mix&quot;);
                break;
        }
    }

    private static void doSomething(String s) {

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The switch case part of bytecode:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;public class tech/jubin/demo/EnumDemo {
  ... truncated ...

  // access flags 0xA
  private static testSwitch(Ltech/jubin/demo/EnumDemo$BlendMode;)V
   L0
    LINENUMBER 12 L0
    GETSTATIC tech/jubin/demo/EnumDemo$1.$SwitchMap$tech$jubin$demo$EnumDemo$BlendMode : [I
    ALOAD 0
    INVOKEVIRTUAL tech/jubin/demo/EnumDemo$BlendMode.ordinal ()I
    IALOAD
    TABLESWITCH
      1: L1
      2: L2
      3: L3
      default: L4

      ... truncated ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the bytecode we can find that for switch statement, a mapping is created first and switch will use enum’s ordinal for TABLESWITCH.&lt;/p&gt;

&lt;h3 id=&quot;why-the-mapping-is-necessary&quot;&gt;Why The Mapping is necessary&lt;/h3&gt;
&lt;p&gt;Unlike Android application, Java application might not compiled together. In this case, if the orinal of enum from a library is changed, the application which depend on the library might break. To avoid this, javac will add a mapping to the place switch enum is used.&lt;/p&gt;

&lt;h2 id=&quot;how-kotlin-enum-works-in-when-statement&quot;&gt;How Kotlin Enum works in when statement&lt;/h2&gt;
&lt;p&gt;Kotlin Enum class will be translated to Java Enum and it also add a mapping to where the when statement with enum is called.&lt;/p&gt;

&lt;h3 id=&quot;when-statement-decompiled-to-java-code&quot;&gt;when statement decompiled to Java code&lt;/h3&gt;
&lt;p&gt;These code snippet is from blog &lt;a href=&quot;https://medium.com/androiddevelopers/when-using-enums-and-r8-3f8f314c0a13&quot;&gt;When using enums and R8…&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;public final class BlendingKt$WhenMappings {
    public static final int[] $EnumSwitchMapping$0 =
            new int[BlendMode.values().length];

    static {
        $EnumSwitchMapping$0[BlendMode.OPAQUE.ordinal()] = 1;
        $EnumSwitchMapping$0[BlendMode.TRANSPARENT.ordinal()] = 2;
        $EnumSwitchMapping$0[BlendMode.FADE.ordinal()] = 3;
        $EnumSwitchMapping$0[BlendMode.ADD.ordinal()] = 4;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;r8-optimization&quot;&gt;R8 Optimization&lt;/h2&gt;
&lt;p&gt;Android application is different from general Java application in that all codes are compiled together, thus the ordinal can be fixed during compilation.&lt;/p&gt;

&lt;p&gt;Proguard is a separate program which will run after dex, and as a result, it has no access to compile time resources and can’t do the optimisation.&lt;/p&gt;

&lt;p&gt;R8 is run after d8 and can be seen as a part of d8 compilation. Thus it has access to all compile time resources. As a result, the mappings are not necessary anymore and can be removed with R8, saving us runtime memory and speed up our application.&lt;/p&gt;

&lt;p&gt;R8 optimized code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;public static void blend(@NotNull BlendMode b) {
    switch (b.ordinal()) {
        case 0: {
            src();
            break;
        }
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=lTo03M2HzFY&amp;amp;list=PLWz5rJ2EKKc_T0fSZc9obnmnWcjvmJdw_&amp;amp;index=3&quot;&gt;D8, R8 and enums - Kotlin Vocabulary&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/androiddevelopers/when-using-enums-and-r8-3f8f314c0a13&quot;&gt;When using enums and R8…&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jakewharton.com/r8-optimization-enum-switch-maps/&quot;&gt;R8 Optimization: Enum Switch Maps&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Tue, 07 Apr 2020 00:00:00 +0000</pubDate>
        <link>https://jubincn.github.io/articles/2020/04/07/kotlin-vocabulary-readnote-d8-r8-enum.html</link>
        <guid isPermaLink="true">https://jubincn.github.io/articles/2020/04/07/kotlin-vocabulary-readnote-d8-r8-enum.html</guid>
        
        
        <category>articles</category>
        
      </item>
    
  </channel>
</rss>
