R8/ProGuard 徹底比較

2793f1fe246c9299c2416062d22bfb99?s=47 Sato Shun
February 07, 2019

R8/ProGuard 徹底比較

DroidKaigi2019:

R8はJavaコードを最適化されたdexコードに変換するためのシュリンカーです。Proguardを置き換える目的で作成されました。R8ではコンパイルタイムの軽減、dexコードのさらなる最適化を目指しています。
dexコードのさらなる最適化とは具体的にどのようなものでしょうか?

本セッションでは、R8でどのような最適化が行われているかをバイトコードレベルから説明します。また、Kotlinに関する最適化など、R8の特徴について説明し、Proguardと比べどこが進化したのかを紹介します。

具体的に以下のことを学ぶことが出来ます。

- R8の内部実装
- R8とProguardの違い
- R8ではどのような最適化が行われているか?

2793f1fe246c9299c2416062d22bfb99?s=128

Sato Shun

February 07, 2019
Tweet

Transcript

  1. R8/ProGuard పఈൺֱ ࠤ౻ ൏ / Sato Shun

  2. ΰʔϧ • R8ͷجຊߏ੒ɺ໨తΛ஌Δ • ؆୯ͳDalvikόΠτίʔυΛαϙʔτ͋ΓͰಡΊΔΑ͏ʹͳΔ • R8Ͱ৽͘͠௥Ճ͞ΕͨઃఆɺKotlinͷ࠷దԽΛ஌Δ • (͕࣌ؒ͋Ε͹࣮ફฤ΋…)

  3. R8ͷجຊߏ੒

  4. R8ɾProGuardͷ΍͍ͬͯΔ͜ͱ • όΠτίʔυ࠷దԽ / optimize • ෆཁͳίʔυͷ࡟আ / shrink •

    ೉ಡԽ / obfuscate
  5. ௨ৗͷϏϧυ Dalvik όΠτίʔυ (.dex) javac
 kotlinc D8 Java όΠτίʔυ (.class)

    ιʔε ίʔυ (.java/.kt)
  6. ProGuardΛ࢖ͬͨϏϧυ Optimized Dalvik όΠτίʔυ (.dex) javac
 kotlinc D8 Java όΠτίʔυ

    (.class) ιʔε ίʔυ (.java/.kt) Optimized Java όΠτίʔυ (.class) ProGuard
  7. R8Λ࢖ͬͨϏϧυ Optimized Dalvik όΠτίʔυ (.dex) javac
 kotlinc R8 Java όΠτίʔυ

    (.class) ιʔε ίʔυ (.java/.kt)
  8. R8ͷجຊߏ੒ • R8͸dexԽͱ࠷దԽ͢Δػೳ͕౷߹ͨ͠ • εςοϓ਺͕ݮΔ͜ͱͰɺI/Oͷίετ͕Լ͕ΔͳͲɺϏϧυ࣌ؒͷ୹ॖ ʹͭͳ͕Δ • R8ͱD8͸ಉ͡ϦϙδτϦͰ։ൃ͞Ε͓ͯΓɺଟ͘ͷίʔυΛ ڞ༗͍ͯ͠Δ

  9. ͪͳΈʹ

  10. JackΛ࢖ͬͨϏϧυ Optimized Dalvik όΠτίʔυ (.dex) Jack/Jill ιʔε ίʔυ (.java/.kt)

  11. Jack্͕ख͍͔͘ͳ͔ͬͨཧ༝ • JavaͷΤίγεςϜʹ৐Δ͜ͱ͕ग़དྷͳ͔ͬͨ • ͍͔ͭ͘ͷϥΠϒϥϦ͕ಈ͔ͳ͍ͳͲ • R8Ͱ͸JavaͷΤίγεςϜʹߟྀͨ͠ߏ੒ʹͳ͍ͬͯΔʂ

  12. R8ͱProGuardઃఆͷޓ׵ੑ

  13. R8ͱProGuardઃఆͷޓ׵ੑ • R8ͱProGuard͸ޓ׵ੑ͕͋Δ • ࠓ࢖͍ͬͯΔProGuardͷઃఆϑΝΠϧΛͦͷ··࢖͑Δ • ͨͩ͠ɺ͍͔ͭ͘ͷઃఆ͕ແޮʹͳ͍ͬͯΔ • ·ͨɺ͍͔ͭ͘ͷઃఆ͕௥Ճ͞Ε͍ͯΔ

  14. R8Ͱແޮͳओͳઃఆ • addconfigurationdebuggingɺdumpͳͲͷσόοάʹศརͳΦ ϓγϣϯ͕࢖͑ͳ͍

  15. addconfigurationdebugging? • Runtime࣌ʹΫϥογϡͨ͠Βɺ଍Γ͍ͯͳ͍ProGuardͷઃఆ ΛఏҊͯ͘͠ΕΔ

  16. addconfigurationdebuggingͷྫ • GsonͰઆ໌͢Δ • Gson͸ҎԼͷProGuardઃఆ͕ඞཁ • -keep class * implements

    com.google.gson.JsonDeserializer • ͜ͷઃఆ͕ͳ͍৔߹ʹͲ͏ͳΔ͔?
  17. E Process: com.github.satoshun.example, PID: 11495 E java.lang.RuntimeException: Unable to start

    activity ComponentInfo{com.github.satoshun.example/com.github.satoshun.example.MainActivity}: java.lang.Il legalArgumentException: Invalid attempt to bind an instance of com.github.satoshun.example.TestDeserializer as a @JsonAdapter for com.github.satoshun. example.Hoge. @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory, JsonSerializer or JsonDeserializer. E at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913) E at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
  18. E Process: com.github.satoshun.example, PID: 11495 E java.lang.RuntimeException: Unable to start

    activity ComponentInfo{com.github.satoshun.example/com.github.satoshun.example.MainActivity}: java.lang.Il legalArgumentException: Invalid attempt to bind an instance of com.github.satoshun.example.TestDeserializer as a @JsonAdapter for com.github.satoshun. example.Hoge. @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory, JsonSerializer or JsonDeserializer. E at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913) E at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) -addconfigurationdebuggingΛ ઃఆʹ௥Ճ
  19. ProGuard W The class 'com.google.gson.internal.ConstructorConstructor' is calling Class.getDeclaredConstructor W on

    class 'com.github.satoshun.example.TestDeserializer' to retrieve W the constructor with signature (), but the latter could not be found. W It may have been obfuscated or shrunk. W You should consider preserving the constructor, with a setting like: W -keepclassmembers class com.github.satoshun.example.TestDeserializer { W public <init>(); W }
  20. ProGuard W The class 'com.google.gson.internal.ConstructorConstructor' is calling Class.getDeclaredConstructor W on

    class 'com.github.satoshun.example.TestDeserializer' to retrieve W the constructor with signature (), but the latter could not be found. W It may have been obfuscated or shrunk. W You should consider preserving the constructor, with a setting like: W -keepclassmembers class com.github.satoshun.example.TestDeserializer { W public <init>(); W }
  21. R8Ͱݱঢ়ແޮͳओͳઃఆ • ։ൃऀ͕ҙࣝ͢Δແޮͳઃఆ͸σόοάपΓ͘Β͍ͩͱࢥ͏ • جຊతʹR8/ProGuard͸ޓ׵ੑ͕͋ΔͷͰɺ໰୊͕͓͖ͨΒɺ ProGuardΛONʹͯ͠ɺσόοάઃఆΛ௥Ճͯ͠ௐࠪ͢Δͷ͸ ͋Γ

  22. R8Ͱ௥Ճ͞Εͨઃఆ • identifiernamestring • checkdiscard • assumevalues

  23. identifiernamestring? • Ϋϥεɺϝϯόʔ໊͕೉ಡԽͰมߋ͞Εͨͱ͖ʹɺจࣈྻ΋௥ ैͯ͠มߋͯ͘͠ΕΔ • ϦϑϨΫγϣϯͳͲͰ༗ޮ

  24. checkdiscard • ର৅ͷΫϥεɺϝϯόʔ͕ ফ͍͑ͯΔ͔Λ֬ೝ͢Δ • ΠϯϥΠϯԽͰ͋ͬͨΓɺ unusedͰফ͞Ε͍ͯΕ͹͓̺ fun noInline(i: Int)

    { … } —- -checkdiscard class ** { *** noInline(***); } —- Item void CheckDiscardKt.noInline(int) was not discarded. Error: Discard checks failed.
  25. assumevalues • ஋ͷऔΓ͏ΔൣғΛࢦఆ͢Δ͜ͱ͕ग़དྷΔ • Ϣʔεέʔε: android.os.Build.VERSION.SDK_INT • ͜ͷ஋͸minSdkʹΑͬͯൣғ஋͕ܾ·ΔͷͰɺassumevaluesઃఆͰ࠷ద Խ͢Δ͜ͱ͕Ͱ͖Δ

  26. assumevaluesͷྫ ͜ͷͱ͖ɺminSdk͕16ͩ ͱɺelse۟ʹདྷΔ͜ͱ͸ͳ ͍ if (Build.VERSION.SDK_INT >= 16) { println("true")

    } else { println("false") }
  27. assumevaluesͷྫ if (Build.VERSION.SDK_INT >= 16) { println("true") } else {

    println("false") } -assumevalues class android.os.Build$VERSION { int SDK_INT return 16..2147483647; }
  28. assumevaluesͷྫ if (Build.VERSION.SDK_INT >= 16) { println("true") } else {

    println("false") } -assumevalues class android.os.Build$VERSION { int SDK_INT return 16..2147483647; } if (true) { println("true") } else { println("false") }
  29. assumevaluesͷྫ if (Build.VERSION.SDK_INT >= 16) { println("true") } else {

    println("false") } -assumevalues class android.os.Build$VERSION { int SDK_INT return 16..2147483647; } if (true) { println("true") } else { println("false") }
  30. assumevaluesͷྫ if (Build.VERSION.SDK_INT >= 16) { println("true") } else {

    println("false") } -assumevalues class android.os.Build$VERSION { int SDK_INT return 16..2147483647; } println("true") CompatܥͷϥΠϒϥϦͰ͸ԼҐޓ׵ʹ഑ྀ͍ͯ͠Δ ίʔυ͕ଟ͍ͨΊɺ͔ͳΓͷίʔυ࡟ݮ͕ظ଴Ͱ͖ Δɻ ·ͨɺAGP 3.4.0-beta02͔Β͸্هͷઃఆ͕σ ϑΥϧτͰೖΔΑ͏ʹͳͬͨʂʂ
  31. DalvikόΠτίʔυΛಡΉ

  32. DalvikόΠτίʔυ? • https://source.android.com/devices/tech/dalvik/dalvik- bytecode • Dalvik/ART VM্Ͱ࣮ߦ͢Δ͜ͱ͕ग़དྷΔػցޠ • D8/R8ίϚϯυͰม׵͞ΕΔ

  33. DalvikόΠτίʔυΛಡΉ • R8/ProGuardʹ͓͍ͯɺಛʹॏཁͳϙΠϯτ͸ • ΫϥεɺϝιουɺϑΟʔϧυͷએݴ • ϝιουͷத਎ • ͷͨΊɺ͜ͷ2ͭΛத৺ʹઆ໌͠·͢

  34. public class HelloWorld { private int a = 111; public

    void main() { System.out.println("Hello World!!" + a); } }
  35. $ javac *.java

  36. $ javac *.java $ ls HelloWorld.class …

  37. $ javac *.java $ ls HelloWorld.class … $ java -jar

    $R8_HOME/build/libs/d8.jar \ --release \ *.class $ ls classes.dex …
  38. $ javac *.java $ ls HelloWorld.class … $ java -jar

    $R8_HOME/build/libs/d8.jar \ --release \ *.class $ ls classes.dex … $ $ANDROID_HOME/build-tools/28.0.3/dexdump -d classes.dex Opened 'classes.dex', DEX version '035' Class #0 - Class descriptor : ‘LHelloWorld;' …
  39. Class #0 - Class descriptor : 'LHelloWorld;' Access flags :

    0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LHelloWorld;) name : 'a' type : 'I' access : 0x0002 (PRIVATE) …
  40. Class #0 - Class descriptor : 'LHelloWorld;' Access flags :

    0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LHelloWorld;) name : 'a' type : 'I' access : 0x0002 (PRIVATE) class HelloWorld
  41. Class #0 - Class descriptor : 'LHelloWorld;' Access flags :

    0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LHelloWorld;) name : 'a' type : 'I' access : 0x0002 (PRIVATE) public class HelloWorld
  42. Class #0 - Class descriptor : 'LHelloWorld;' Access flags :

    0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LHelloWorld;) name : 'a' type : 'I' access : 0x0002 (PRIVATE) public class HelloWorld
  43. Class #0 - Class descriptor : 'LHelloWorld;' Access flags :

    0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LHelloWorld;) name : 'a' type : 'I' access : 0x0002 (PRIVATE) public class HelloWorld
  44. Class #0 - Class descriptor : 'LHelloWorld;' Access flags :

    0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LHelloWorld;) name : 'a' type : 'I' access : 0x0002 (PRIVATE) public class HelloWorld
  45. Class #0 - Class descriptor : 'LHelloWorld;' Access flags :

    0x0001 (PUBLIC) Superclass : 'Ljava/lang/Object;' Interfaces - Static fields - Instance fields - #0 : (in LHelloWorld;) name : 'a' type : 'I' access : 0x0002 (PRIVATE) public class HelloWorld { private int a; }
  46. Direct methods - #0 : (in LHelloWorld;) name : '<init>'

    type : '()V' access : 0x10001 (PUBLIC CONSTRUCTOR) … [000194] HelloWorld.<init>:()V 0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0003 0003: const/16 v0, #int 111 // #6f 0005: iput v0, v1, LHelloWorld;.a:I // field@0000 0007: return-void …
  47. Direct methods - #0 : (in LHelloWorld;) name : '<init>'

    type : '()V' access : 0x10001 (PUBLIC CONSTRUCTOR) … [000194] HelloWorld.<init>:()V 0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0003 0003: const/16 v0, #int 111 // #6f 0005: iput v0, v1, LHelloWorld;.a:I // field@0000 0007: return-void … public class HelloWorld { private int a; public HelloWorld() { } }
  48. … [000194] HelloWorld.<init>:()V 0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0003 0003:

    const/16 v0, #int 111 // #6f 0005: iput v0, v1, LHelloWorld;.a:I // field@0000 0007: return-void …
  49. … [000194] HelloWorld.<init>:()V 0000: invoke-direct {v1}, Ljava/lang/ Object;.<init>:()V // method@0003

    0003: const/16 v0, #int 111 // #6f 0005: iput v0, v1, LHelloWorld;.a:I // field@0000 0007: return-void … public class HelloWorld { private int a; public HelloWorld() { super(); } }
  50. … [000194] HelloWorld.<init>:()V 0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0003 0003:

    const/16 v0, #int 111 // #6f 0005: iput v0, v1, LHelloWorld;.a:I // field@0000 0007: return-void … public class HelloWorld { private int a; public HelloWorld() { super(); int v0 = 111; } }
  51. … [000194] HelloWorld.<init>:()V 0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0003 0003:

    const/16 v0, #int 111 // #6f 0005: iput v0, v1, LHelloWorld;.a:I // field@0000 0007: return-void … public class HelloWorld { private int a; public HelloWorld() { super(); (int v0 = 111;) this.a = v0; } }
  52. … [000194] HelloWorld.<init>:()V 0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0003 0003:

    const/16 v0, #int 111 // #6f 0005: iput v0, v1, LHelloWorld;.a:I // field@0000 0007: return-void … public class HelloWorld { private int a; public HelloWorld() { super(); (int v0 = 111;) this.a = v0; } }
  53. Virtual methods - #0 : (in LHelloWorld;) name : 'main'

    type : '()V' access : 0x0001 (PUBLIC) … [0001b4] HelloWorld.main:()V 0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 0002: new-instance v1, Ljava/lang/StringBuilder; // type@0005 0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0004 0007: const-string v2, "Hello World!!" // string@0001 0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void …
  54. Virtual methods - #0 : (in LHelloWorld;) name : 'main'

    type : '()V' access : 0x0001 (PUBLIC) … [0001b4] HelloWorld.main:()V 0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 0002: new-instance v1, Ljava/lang/StringBuilder; // type@0005 0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0004 0007: const-string v2, "Hello World!!" // string@0001 0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { } }
  55. [0001b4] HelloWorld.main:()V 0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 0002: new-instance

    v1, Ljava/lang/StringBuilder; // type@0005 0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0004 0007: const-string v2, "Hello World!!" // string@0001 0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void …
  56. [0001b4] HelloWorld.main:()V 0000: sget-object v0, Ljava/lang/System;.out:Ljava/ io/PrintStream; // field@0001 0002:

    new-instance v1, Ljava/lang/StringBuilder; // type@0005 0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0004 0007: const-string v2, "Hello World!!" // string@0001 0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { java.io.PrintStream v0 = System.out; } }
  57. [0001b4] HelloWorld.main:()V 0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 0002: new-instance

    v1, Ljava/lang/StringBuilder; // type@0005 0004: invoke-direct {v1}, Ljava/lang/ StringBuilder;.<init>:()V // method@0004 0007: const-string v2, "Hello World!!" // string@0001 0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { java.io.PrintStream v0 = System.out; java.lang.StringBuilder v1 = new StringBuilder(); } }
  58. [0001b4] HelloWorld.main:()V 0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 0002: new-instance

    v1, Ljava/lang/StringBuilder; // type@0005 0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0004 0007: const-string v2, "Hello World!!" // string@0001 0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { java.io.PrintStream v0 = System.out; java.lang.StringBuilder v1 = new StringBuilder(); String v2 = "Hello World!!”; } }
  59. … 0009: invoke-virtual {v1, v2}, Ljava/lang/ StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006

    000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { java.io.PrintStream v0 = System.out; java.lang.StringBuilder v1 = new StringBuilder(); String v2 = "Hello World!!”; v1.append(v2); } }
  60. … lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I //

    field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { java.io.PrintStream v0 = System.out; java.lang.StringBuilder v1 = new StringBuilder(); String v2 = "Hello World!!”; v1.append(v2); int v2 = this.a; } }
  61. … 000e: invoke-virtual {v1, v2}, Ljava/lang/ StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005

    0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { java.io.PrintStream v0 = System.out; java.lang.StringBuilder v1 = new StringBuilder(); String v2 = "Hello World!!”; v1.append(v2); int v2 = this.a; v1.append(v2); }
  62. … 0011: invoke-virtual {v1}, Ljava/lang/ StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object

    v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { … v1.append(v2); v1.toString(); } }
  63. [0001b4] HelloWorld.main:()V … 0014: move-result-object v1 0015: invoke-virtual {v0, v1},

    Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { … v1.append(v2); v1.toString(); } }
  64. [0001b4] HelloWorld.main:()V … 0015: invoke-virtual {v0, v1}, Ljava/io/ PrintStream;.println:(Ljava/lang/String;)V //

    method@0002 0018: return-void … public class HelloWorld { private int a; … public void main() { … v1.append(v2); v0.println(v1.toString()); } }
  65. [0001b4] HelloWorld.main:()V 0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001 0002: new-instance

    v1, Ljava/lang/StringBuilder; // type@0005 0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // method@0004 0007: const-string v2, "Hello World!!" // string@0001 0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/ lang/StringBuilder; // method@0006 000c: iget v2, v3, LHelloWorld;.a:I // field@0000 000e: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/ StringBuilder; // method@0005 0011: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // method@0007 0014: move-result-object v1 0015: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0002 0018: return-void …
  66. public class HelloWorld { private int a; public HelloWorld() {

    super(); (int v0 = 111;) this.a = v0; } public void main() { java.io.PrintStream v0 = System.out; java.lang.StringBuilder v1 = new StringBuilder(); String v2 = "Hello World!!”; v1.append(v2); int v2 = this.a; v1.append(v2); v0.println(v1.toString()); } }
  67. DalvikόΠτίʔυ·ͱΊ • dexdumpɺD8Λ࢖͍DalvikόΠτίʔυʢdumpͨ͠ίʔυʣ ΛಡΜͩ • ϝιουͷத਎ɺએݴΛಡΉ͚ͩͳΒɺDalvikόΠτίʔυͦ Μͳʹ೉͘͠ͳ͍Α!!

  68. R8ͷKotlin࠷దԽ

  69. Kotlin Companion Object

  70. Kotlin Companion Object? • JavaͰ͍͏ͱ͜Ζͷstaticϝιου͕දݱͰ͖Δ • ΫϥεܧঝɺΠϯλʔϑΣʔε࣮૷͕Ͱ͖Δ JavaΑΓ΋ػೳ͕૿͕͑ͨɺ ΠϯελϯεΛ಺෦తʹੜ੒͢ΔͷͰίετ͕͔͔Δ

  71. class CompanionTest { companion object { fun show(i: Int) {

    println("started show method") println("processing show method action $i") println("finished show method") } } }
  72. public final class CompanionTest { public static final Companion Companion

    = new Companion(); public static final class Companion { private Companion() { } public /* synthetic */ Companion(DefaultConstructorMarker defaultConstructorMarker) { this(); } public final void show(int i) { System.out.println("started show method"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("processing show method action "); stringBuilder.append(i); System.out.println(stringBuilder.toString()); System.out.println("finished show method"); } } }
  73. public final class CompanionTest { public static final Companion Companion

    = new Companion(); public static final class Companion { private Companion() { } public /* synthetic */ Companion(DefaultConstructorMarker defaultConstructorMarker) { this(); } public final void show(int i) { System.out.println("started show method"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("processing show method action "); stringBuilder.append(i); System.out.println(stringBuilder.toString()); System.out.println("finished show method"); } } } CompanionΠϯελϯε͕ੜ੒͞ΕΔ & NestedΫϥε͕ఆٛ͞ΕΔ
  74. public final class CompanionTest { public static final Companion Companion

    = new Companion(); public static final class Companion { private Companion() { } public /* synthetic */ Companion(DefaultConstructorMarker defaultConstructorMarker) { this(); } public final void show(int i) { System.out.println("started show method"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("processing show method action "); stringBuilder.append(i); System.out.println(stringBuilder.toString()); System.out.println("finished show method"); } } } CompanionΠϯελϯε͕ੜ੒͞ΕΔ & NestedΫϥε͕ఆٛ͞ΕΔ ͜ΕΛR8ͰίϯύΠϧ͢Δ
  75. public abstract class CompanionTest { public static final void show(int

    i) { System.out.println("started show method"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("processing show method action "); stringBuilder.append(i); System.out.println(stringBuilder.toString()); System.out.println("finished show method"); } }
  76. public abstract class CompanionTest { public static final void show(int

    i) { System.out.println("started show method"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("processing show method action "); stringBuilder.append(i); System.out.println(stringBuilder.toString()); System.out.println("finished show method"); } } Πϯελϯεੜ੒͕ແ͘ͳͬͨ & NestedΫϥε͕ແ͘ͳͬͨ
  77. public abstract class CompanionTest { public static final void show(int

    i) { System.out.println("started show method"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("processing show method action "); stringBuilder.append(i); System.out.println(stringBuilder.toString()); System.out.println("finished show method"); } } R8Ͱ͸staticϝιουʹม׵͞ΕΔͨΊɺ ΠϯελϯεΛੜ੒͠ͳͯ͘΋ྑ͘ͳ͍ͬͯΔ
  78. public final class CompanionTest { public static final Companion Companion

    = new Companion(); public static final class Companion { private Companion() { } public /* synthetic */ Companion(byte b) { this(); } public static void show(int i) { System.out.println("started show method"); System.out.println("processing show method action ".concat(String.valueOf(i))); System.out.println("finished show method"); } } }
  79. public final class CompanionTest { public static final Companion Companion

    = new Companion(); public static final class Companion { private Companion() { } public /* synthetic */ Companion(byte b) { this(); } public static void show(int i) { System.out.println("started show method"); System.out.println("processing show method action ".concat(String.valueOf(i))); System.out.println("finished show method"); } } }
  80. public final class CompanionTest { public static final Companion Companion

    = new Companion(); public static final class Companion { private Companion() { } public /* synthetic */ Companion(byte b) { this(); } public static void show(int i) { System.out.println("started show method"); System.out.println("processing show method action ".concat(String.valueOf(i))); System.out.println("finished show method"); } } } ProGuradͰ͸CompanionΠϯελϯε͕ফ͑ͳ͍ & NestedΫϥε΋ఆٛ͞Εͨ··
  81. Companion Object·ͱΊ • KotlinͰstaticϝιουΛදݱ͢ΔͷʹCompanion ObjectΛ࢖ ͏ͱɺJavaΑΓ΋ύϑΥʔϚϯε໘ͰෆརʹͳΔ • R8͸ͦͷ఺Λߟྀ͠ɺ࠷దԽΛߦ͏ • Javaͷstaticϝιουͱಉ͡࢖͍ํΛ͍ͯ͠Δͱ͖ͷΈ༗ޮͳ

    ࠷దԽ • ΠϯλʔϑΣʔεͷ࣮૷Λ͍ͯ͠Δ৔߹ʹ͸CompanionΠϯελϯε͸ফ ͑ͳ͍
  82. Kotlin lambda࠷దԽ

  83. Kotlin lambda? • KotlinͰ͸ϥϜμ͕ࣜ࢖͑Δ • ͔ؔ͠͠਺ݺͼग़͠ɺΠϯελϯεੜ੒͸ίετֻ͕͔Δ

  84. fun main() { lambdaTest { println("Kotlin lambda1") } lambdaTest {

    println("Kotlin lambda2") } lambdaTest { println("Kotlin lambda3") } } private fun lambdaTest(body: () -> Unit) { println("before lambdaTest") body() println("after lambdaTest") }
  85. sget-object v0, LLambdaTestKt$main$1;.INSTANCE:LLambdaTestKt$main$1; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, LLambdaTestKt$main$2;.INSTANCE:LLambdaTestKt$main$2;

    invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, LLambdaTestKt$main$3;.INSTANCE:LLambdaTestKt$main$3; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V
  86. sget-object v0, LLambdaTestKt$main$1;.INSTANCE:LLambdaTestKt$main$1; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, LLambdaTestKt$main$2;.INSTANCE:LLambdaTestKt$main$2;

    invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, LLambdaTestKt$main$3;.INSTANCE:LLambdaTestKt$main$3; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V
  87. sget-object v0, LLambdaTestKt$main$1;.INSTANCE:LLambdaTestKt$main$1; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, LLambdaTestKt$main$2;.INSTANCE:LLambdaTestKt$main$2;

    invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, LLambdaTestKt$main$3;.INSTANCE:LLambdaTestKt$main$3; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V 3ͭͷΫϥε͕ੜ੒͞Εͨ - ϥϜμຖʹΫϥε͕ੜ੒͞ΕΔ
  88. sget-object v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$0:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object

    v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$1:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$2:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V
  89. sget-object v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$0:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object

    v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$1:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$2:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V
  90. sget-object v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$0:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object

    v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$1:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V sget-object v0, L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI;.INSTANCE$2:L-$ $LambdaGroup$ks$BpS7w8o_KOkdSUy0gHGt84S7irI; invoke-static {v0}, LLambdaTestKt;.lambdaTest:(Lkotlin/jvm/functions/ Function0;)V LambdaGroupͱݺ͹ΕΔ࠷దԽ 1ͭͷΫϥεʹϥϜμΛ·ͱΊΔ͜ͱͰɺ ΫϥεɺϝιουΛݮΒ͢͜ͱ͕Ͱ͖Δ
  91. // R8.java private void run( AndroidApp inputApp, ExecutorService executorService) throws

    IOException { …
  92. // R8.java … IRConverter converter = new IRConverter(appView, options, timing,

    printer, mainDexClasses, rootSet); converter.optimize(application, executorService);
  93. // R8.java … IRConverter converter = new IRConverter(appView, options, timing,

    printer, mainDexClasses, rootSet); converter.optimize(application, executorService); IR: Intermediate Representations ιʔείʔυͱ࠷ऴ੒Ռͷؒʹ͋Δதؒදݱܗࣜ
  94. // R8.java … IRConverter converter = new IRConverter(appView, options, timing,

    printer, mainDexClasses, rootSet); converter.optimize(application, executorService);
  95. // IRConverter.java public DexApplication optimize( DexApplication application, ExecutorService executorService )

    throws ExecutionException { computeReachabilitySensitivity(application); removeLambdaDeserializationMethods(); collectLambdaMergingCandidates(application); collectStaticizerCandidates(application); …
  96. // IRConverter.java public DexApplication optimize( DexApplication application, ExecutorService executorService )

    throws ExecutionException { computeReachabilitySensitivity(application); removeLambdaDeserializationMethods(); collectLambdaMergingCandidates(application); collectStaticizerCandidates(application); …
  97. private void collectLambdaMergingCandidates( DexApplication application ) { if (lambdaMerger !=

    null) { lambdaMerger.collectGroupCandidates( application, appInfo.withLiveness(), options ); } }
  98. private void collectLambdaMergingCandidates( DexApplication application ) { if (lambdaMerger !=

    null) { lambdaMerger.collectGroupCandidates( application, appInfo.withLiveness(), options ); } }
  99. private void collectLambdaMergingCandidates( DexApplication application ) { if (lambdaMerger !=

    null) { lambdaMerger.collectGroupCandidates( application, appInfo.withLiveness(), options ); } } LambdaMerger: ෳ਺ͷϥϜμΛ1ͭͷάϧʔϓʹϚʔδ͢Δ
  100. // LambdaMerger.collectGroupCandidates app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo()

    && cls.getKotlinInfo().isSyntheticClass() && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } });
  101. app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo() && cls.getKotlinInfo().isSyntheticClass()

    && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } });
  102. app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo() && cls.getKotlinInfo().isSyntheticClass()

    && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } }); pin͞Ε͍ͯͳ͍ == มܗ͕ՄೳͳΫϥεΛநग़
  103. app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo() && cls.getKotlinInfo().isSyntheticClass()

    && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } }); Kotlin && ߹੒Ϋϥε && ϥϜμࣜ
  104. app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo() && cls.getKotlinInfo().isSyntheticClass()

    && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } }); classͷॱ൪Λιʔτ
  105. app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo() && cls.getKotlinInfo().isSyntheticClass()

    && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } }); LambdaGroupIdͱLambdaGroup Λ࡞Δ
  106. app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo() && cls.getKotlinInfo().isSyntheticClass()

    && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } }); LambdaGroupIdΠϯελϯεͷग़ྗ capture: interface: Lkotlin/jvm/functions/Function0; package: signature: null main method name: invoke main method: Proto V void main annotations: com.android.tools.r8.graph.DexAnnotationSet@1 main param annotations: com.android.tools.r8.graph.ParameterAnnotationsList@1 inner: none
  107. app.classes() .stream() .filter(cls -> !infoWithLiveness.isPinned(cls.type)) .filter(cls -> cls.hasKotlinInfo() && cls.getKotlinInfo().isSyntheticClass()

    && cls.getKotlinInfo().asSyntheticClass().isLambda()) .sorted((a, b) -> a.type.slowCompareTo(b.type)) .forEachOrdered(lambda -> { try { LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options); LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup); group.add(lambda); lambdas.put(lambda.type, group); } catch (LambdaStructureError error) { … } });
  108. // IRConverter.java public DexApplication optimize( DexApplication application, ExecutorService executorService )

    throws ExecutionException { computeReachabilitySensitivity(application); removeLambdaDeserializationMethods(); collectLambdaMergingCandidates(application); collectStaticizerCandidates(application); …
  109. // IRConverter.java public DexApplication optimize( DexApplication application, ExecutorService executorService )

    throws ExecutionException { … printPhase("Lambda merging finalization"); finalizeLambdaMerging( application, feedback, builder, executorService, synthesizedClasses ); …
  110. private void finalizeLambdaMerging( DexApplication application, OptimizationFeedback directFeedback, Builder<?> builder, ExecutorService

    executorService, Map<DexType, DexProgramClass> synthesizedClasses) throws ExecutionException { if (lambdaMerger != null) { lambdaMerger.applyLambdaClassMapping( application, this, directFeedback, builder, executorService, synthesizedClasses); } }
  111. private void finalizeLambdaMerging( DexApplication application, OptimizationFeedback directFeedback, Builder<?> builder, ExecutorService

    executorService, Map<DexType, DexProgramClass> synthesizedClasses) throws ExecutionException { if (lambdaMerger != null) { lambdaMerger.applyLambdaClassMapping( application, this, directFeedback, builder, executorService, synthesizedClasses); } }
  112. // LambdaMerger.java public final void applyLambdaClassMapping(…) {
 …

  113. // LambdaMerger.java public final void applyLambdaClassMapping(…) {
 … rewriteLambdaReferences(converter, synthesizedClasses,

    feedback);
  114. // LambdaMerger.java private void rewriteLambdaReferences(…) {

  115. // LambdaMerger.java private void rewriteLambdaReferences(…) { … for (DexEncodedMethod method

    : methods) { DexEncodedMethod mappedMethod = converter.graphLense().mapDexEncodedMethod( method, converter.appInfo, synthesizedClasses); … }
  116. // LambdaMerger.java private void rewriteLambdaReferences(…) { … for (DexEncodedMethod method

    : methods) { DexEncodedMethod mappedMethod = converter.graphLense().mapDexEncodedMethod( method, converter.appInfo, synthesizedClasses); … }
  117. // LambdaMerger.java private void rewriteLambdaReferences(…) { … for (DexEncodedMethod method

    : methods) { DexEncodedMethod mappedMethod = converter.graphLense().mapDexEncodedMethod( method, converter.appInfo, synthesizedClasses); … } GraphLense: ίʔυ৘ใΛ֨ೲ͓ͯ͠Γɺ ϝιουɺϑΟʔϧυͷҠಈɺ ΫϥεͷmappingͳͲΛߦ͏ɻ
  118. private void rewriteLambdaReferences(…) { … for (DexEncodedMethod method : methods)

    { DexEncodedMethod mappedMethod = converter.graphLense().mapDexEncodedMethod( method, converter.appInfo, synthesizedClasses); … } mapDexEncodedMethod: ੜ੒ͨ͠߹੒ΫϥεʢLambdaGroupʣΛGraphLenseʹ ొ࿥͢Δ
  119. private void rewriteLambdaReferences(…) { … for (DexEncodedMethod method : methods)

    { DexEncodedMethod mappedMethod = converter.graphLense().mapDexEncodedMethod( method, converter.appInfo, synthesizedClasses); … }
  120. LambdaGroupςΫχοΫ·ͱΊ • KotlinͷϥϜμ͸ɺϥϜμຖʹΫϥεΛ࡞ͬͯ͠·͏ͷͰΫϥ εɺϝιου਺͕૿͑Δ • 1ΫϥεʹϥϜμΛ·ͱΊΔ͜ͱͰޮ཰తʹ͢Δ • 1ػೳʹߜͬͯॲཧΛ௥͑͹ɺR8ίϯύΠϥͷίʔυ΋ͳΜͱ ͳ͘ಡΊΔ

  121. ࣮ફฤ

  122. inlineϝιουԽ • R8Ͱ͸ϝιου͕1Օॴ͔Β͔͠ίʔϧ͞Ε͍ͯͳ͍ or ϝιο υ͕୹͍৔߹ʹࣗಈతʹΠϯϥΠϯԽ͞ΕΔ • ProGuardͰ΋ಉ༷ʹ͞ΕΔ • ࣮ࡍʹͲͷΑ͏ʹม׵͞ΕΔ͔ɺڻ͖ͱڞʹઆ໌͢Δ

    • Retrofitͷ࿩ • Daggerͷ࿩
  123. Retrofitฤ

  124. … val retrofit = Retrofit .Builder() .build() val api =

    retrofit.create(Api::class.java) api.hoge().execute() } 
 interface Api { @GET fun hoge(): Call<Void> }
  125. None
  126. .method public onCreate(Landroid/os/Bundle;)V .line 1 sget-object p1, Lretrofit2/Platform;->a:Lretrofit2/Platform; .line 2

    new-instance p1, Ljava/util/ArrayList; invoke-direct {p1}, Ljava/util/ArrayList;-><init>()V new-instance p1, Ljava/util/ArrayList; invoke-direct {p1}, Ljava/util/ArrayList;-><init>()V .line 3 new-instance p1, Ljava/lang/IllegalStateException; const-string v0, "Base URL required." invoke-direct {p1, v0}, Ljava/lang/IllegalStateException;- ><init>(Ljava/lang/String;)V throw p1 .end method
  127. ... Platform platform = Platform.a; ArrayList arrayList = new ArrayList();

    arrayList = new ArrayList(); throw new IllegalStateException("Base URL required."); }
  128. ... Platform platform = Platform.a; ArrayList arrayList = new ArrayList();

    arrayList = new ArrayList(); throw new IllegalStateException("Base URL required."); } IllegalStateExceptionͰϝιου͕ ऴΘͬͯΔ
  129. public static final class Builder { private @Nullable HttpUrl baseUrl;

    public Builder baseUrl(String baseUrl) { checkNotNull(baseUrl, "baseUrl == null"); return baseUrl(HttpUrl.get(baseUrl)); } public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } … } }
  130. public static final class Builder { private @Nullable HttpUrl baseUrl;

    public Builder baseUrl(String baseUrl) { checkNotNull(baseUrl, "baseUrl == null"); return baseUrl(HttpUrl.get(baseUrl)); } public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } … } } baseUrl͕nullͳΒthrow͍ͯ͠Δ
  131. … val retrofit = Retrofit .Builder() .build() val api =

    retrofit.create(Api::class.java) api.hoge().execute() }
  132. … Platform platform = Platform.a; ArrayList arrayList = new ArrayList();

    arrayList = new ArrayList(); if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } val api = retrofit.create(Api::class.java) api.hoge().execute() }
  133. … Platform platform = Platform.a; ArrayList arrayList = new ArrayList();

    arrayList = new ArrayList(); if (null == null) { throw new IllegalStateException("Base URL required."); } val api = retrofit.create(Api::class.java) api.hoge().execute() }
  134. … Platform platform = Platform.a; ArrayList arrayList = new ArrayList();

    arrayList = new ArrayList(); if (true) { throw new IllegalStateException("Base URL required."); } val api = retrofit.create(Api::class.java) api.hoge().execute() }
  135. … Platform platform = Platform.a; ArrayList arrayList = new ArrayList();

    arrayList = new ArrayList(); throw new IllegalStateException("Base URL required."); }
  136. Daggerͷ࿩

  137. class MainActivity : AppCompatActivity() { @Inject lateinit var appService: AppService

    @Inject lateinit var appService2: AppService2 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val appComponent = DaggerAppComponent.builder().build() appComponent.inject(this) appService.test().execute() appService2.test().execute() } } interface AppService { @GET("todos/1") fun test(): Call<TestData> } interface AppService2 { @GET("todos/1") fun test(): Call<TestData> }
  138. None
  139. method public onCreate(Landroid/os/Bundle;)V .registers 3 invoke-super {p0, p1}, La/b/e/a/m;->onCreate(Landroid/os/Bundle;)V const

    p1, 0x7f09001c invoke-virtual {p0, p1}, La/b/e/a/m;->setContentView(I)V .line 1 new-instance p1, Lcom/github/satoshun/example/AppModule1; invoke-direct {p1}, Lcom/github/satoshun/example/AppModule1;-><init>()V .line 2 invoke-virtual {p1}, Lcom/github/satoshun/example/AppModule1;->a()Lcom/github/satoshun/example/AppService; move-result-object p1 const-string v0, "Cannot return null from a non-@Nullable @Provides method" invoke-static {p1, v0}, La/b/b/a/a/a;->a(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; .line 3 iput-object p1, p0, Lcom/github/satoshun/example/MainActivity;->o:Lcom/github/satoshun/example/AppService; .line 4 invoke-static {}, Lcom/github/satoshun/example/AppModule2_ProvideService2Factory;->a()Lcom/github/satoshun/example/AppService2; move-result-object p1 .line 5 iput-object p1, p0, Lcom/github/satoshun/example/MainActivity;->p:Lcom/github/satoshun/example/AppService2; .line 6 iget-object p1, p0, Lcom/github/satoshun/example/MainActivity;->o:Lcom/github/satoshun/example/AppService; const/4 v0, 0x0 if-eqz p1, :cond_3d invoke-interface {p1}, Lcom/github/satoshun/example/AppService;->a()Lretrofit2/Call; move-result-object p1 invoke-interface {p1}, Lretrofit2/Call;->execute()Lretrofit2/Response; iget-object p1, p0, Lcom/github/satoshun/example/MainActivity;->p:Lcom/github/satoshun/example/AppService2; if-eqz p1, :cond_37 invoke-interface {p1}, Lcom/github/satoshun/example/AppService2;->a()Lretrofit2/Call;
  140. public final class MainActivity extends m { public AppService o;

    public AppService2 p; public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView((int) R.layout.activity_main); Object a = new AppModule1().a(); a.a(a, "Cannot return null from a non-@Nullable @Provides method"); this.o = a; this.p = AppModule2_ProvideService2Factory.a(); AppService appService = this.o; if (appService != null) { appService.a().execute(); AppService2 appService2 = this.p; if (appService2 != null) { appService2.a().execute(); return; } else { e.a.a.a.a("appService2"); throw …; } } e.a.a.a.a("appService"); throw …; } }
  141. public final class MainActivity extends m { public AppService o;

    public AppService2 p; public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView((int) R.layout.activity_main); Object a = new AppModule1().a(); a.a(a, "Cannot return null from a non-@Nullable @Provides method"); this.o = a; this.p = AppModule2_ProvideService2Factory.a(); AppService appService = this.o; if (appService != null) { appService.a().execute(); AppService2 appService2 = this.p; if (appService2 != null) { appService2.a().execute(); return; } else { e.a.a.a.a("appService2"); throw …; } } e.a.a.a.a("appService"); throw …; } } AppComponent͕ফ͑ͨ!! AppComponentͷ֤ϝιου͕ inlineԽ͞ΕɺऔΓআ͔Εͨɻ
  142. ·ͱΊ/ײ૝ • R8͸Ϗϧυ΋ૣ͘ͳΔ͠ɺapkαΠζ΋খ͘͞ͳΔͷͰྑ͞ • ݱঢ়ͩͱυΩϡϝϯτΒ͍͠υΩϡϝϯτͳ͍ͷ͕ਏ͍ • Happy R8 Life

  143. ࣗݾ঺հ • ࠤ౻ ൏/͞ͱ͏ ͠ΎΜ • ϚονϯάΤʔδΣϯτɺCyberAgent, inc. ॴଐ •

    @stsn_jp • github.com/satoshun