Slide 1

Slide 1 text

What’s new In App Performance # AndroidDev veronikapj GDG Korea Android PilJu BAE

Slide 2

Slide 2 text

য়ט ೡ ੉ঠӝ App Startup Jank ࠙ࢳ

Slide 3

Slide 3 text

Why? জ द੘ ࢿמਸ ѐࢶೡ ೙ਃо ੓ਸө?

Slide 4

Slide 4 text

Why? 30 % 2.4 % ࡈۄ૓ জ द੘ दр Ѩ࢝ ૐо Source: goo.gle/3JCc453

Slide 5

Slide 5 text

੉Ѫ਷ ޖ঺ ੌөਃ? ٜযоӝী খࢲ “জ੄ द੘ਸ ୭੸ച ೞҊ, ߡߢѢܿਸ ઴੉ݴ ୭ઙ ࢎਊ੗ ࢿמਸ ѐࢶೡ ࣻ ੓ب۾ ೞח ೐۽೙ ӝ߈ ୭੸ച(PGO, Profile Guided Optimization)੄ ೠ ഋకੑפ׮.” https://forms.gle/MSzZ53RQ3jH6Qm758

Slide 6

Slide 6 text

1. Macrobenchmark 2. Baseline Profiles 3. Microbenchmark 4. Android Profiler ٜযоӝী খࢲ “জ੄ द੘ਸ ୭੸ച ೞҊ, ߡߢѢܿ ਸ ઴੉ݴ ୭ઙ ࢎਊ੗ ࢿמਸ ѐࢶೡ ࣻ ੓ب۾ ೞח ೐۽೙ ӝ߈ ୭੸ച (PGO, Profile Guided Optimization)੄ ೠ ഋకੑפ׮.”

Slide 7

Slide 7 text

Baseline Profiles stable androidx.profileinstaller:profileinstaller:1.1.0 ޷ܻ ٜ݅য فח App profiles Startup दр хࣗ & Jank хࣗ https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 8

Slide 8 text

জ ߂ ۄ੉࠳۞ܻীࢲ custom profile rule ઁҕ Startup ߂ jack performance ೱ࢚ ੋӝ ੓ח Jetpack librariesী profile ୶о Baseline Profiles https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 9

Slide 9 text

https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 10

Slide 10 text

h tt ps://github.com/android/sun fl ower Sunflower పझ౟ प೯ ߂ ৮ܐ -> ೐۽೙ ࢤࢿ ls /storage/emulated/0/Android/media/app.name Sample_startup-baseline-prof.txt ੉ܴਸ baseline-prof.txtਵ۽ ߸҃೧ࢲ app/src/main ߃ী ਤ஖ Baseline Profile

Slide 11

Slide 11 text

ۄ੉࠳۞ܻ৬ গ೒ܻா੉࣌਷ ৔ೱਸ ӓ؀ചೞח ୭ࣗೠ੄ ೐۽೙ ӏ஗ਸ ੿੄ (Size < 1.5 MB) 
 গ೒ܻா੉࣌੄ ցޖ ݆਷ ࠗ࠙ਸ ஹ౵ੌೞח ҟߧਤೠ ӏ஗਷ ٣झ௼ ঘࣁझ പࣻо טӝ ٸޙী द੘ ࣘبܳ וܻѱ ٜ݅ ࣻ ੓णפ׮. যڃ ӝળਵ۽ Baseline Pro fi leਸ ࢸ੿ೡ Ѫ ੋ૑ ߈٘द పझ౟೧ࢲ ੸ਊೞח Ѫ੉ જणפ׮. Q & A সؘ੉౟ ੸ਊ दী Ҋ۰೧ঠ ೡ Ѫ https://developer.android.com/studio/pro fi le/baselinepro fi les

Slide 12

Slide 12 text

App Startup stable androidx.startup:startup-runtime:1.1.1 জ द੘ द ҳࢿਃࣗܳ ୡӝചೞח ߑߨ ઁҕ द੘ ࣽࢲ рࣗച, ୡӝച ࣽࢲ ݺद੸ਵ۽ ࢸ੿ https://developer.android.com/topic/libraries/app-startup

Slide 13

Slide 13 text

জ द੘ ઺ ܻࣗझ ҃೤ ߑ૑(Avoid resource contention ) Ҋࢿמ ஹನք౟ ୡӝച(Performant component initialization) App Startup https://youtu.be/DYdHLqLVspY IO 22 What’s new in App performance

Slide 14

Slide 14 text

class SyncRepoInitializer : Initializer { override fun create(context: Context): SyncRepo { return SyncRepo(WorkManager.getInstance(context)) } override fun dependencies() : List>> { return listOf(WorkManagerInitializer::class.java) } } https://youtu.be/DYdHLqLVspY IO 22 What’s new in App performance

Slide 15

Slide 15 text

class SyncRepoInitializer : Initializer { override fun create(context: Context): SyncRepo { return SyncRepo(WorkManager.getInstance(context)) } override fun dependencies() : List>> { return listOf(WorkManagerInitializer::class.java) } } https://youtu.be/DYdHLqLVspY IO 22 What’s new in App performance

Slide 16

Slide 16 text

https://developer.android.com/topic/libraries/app-startup AndroidManifest.xml

Slide 17

Slide 17 text

ӝࠄ ਊয ੿ܻ Jank? https://www.youtube.com/watch?v=0adLO2VRJtc IO 21 Macrobenchmark۽ ߡߢѢܿ ߂ झఋ౟স ஏ੿

Slide 18

Slide 18 text

ӝࠄ ਊয ੿ܻ https://www.youtube.com/watch?v=0adLO2VRJtc IO 21 Macrobenchmark۽ ߡߢѢܿ ߂ झఋ౟স ஏ੿ 60Hz ചݶী झ௼܀, ੹ജ, গפݫ੉࣌ ١੄ ਑ ૒੐੉ ੓ਵݶ জীࢲ ࢜۽ Ҋஜ ࣘبী ݏ ѱ ୡ׼ 60೐ۨ੐ਸ ࢤࢿ Jank? জ੉ ചݶਸ ࢜۽ Ӓܻݶࢲ ߊࢤೞח ߡߢѢܿ੉ա ݥ୺ਸ ੄޷

Slide 19

Slide 19 text

ӝࠄ ਊয ੿ܻ https://www.youtube.com/watch?v=0adLO2VRJtc IO 21 Macrobenchmark۽ ߡߢѢܿ ߂ झఋ౟স ஏ੿ 60Hz ചݶী झ௼܀, ੹ജ, গפݫ੉࣌ ١੄ ਑ ૒੐੉ ੓ਵݶ জীࢲ ࢜۽ Ҋஜ ࣘبী ݏ ѱ ୡ׼ 60೐ۨ੐ਸ ࢤࢿ Jank? জ੄ ೐ۨ੐ ࢤࢿ ࣘبо ցޖ ןযࢲ ࢤӣ

Slide 20

Slide 20 text

࠙ࢳ ੿ࠁী݅ ੄૑ೡ ࣻ হ਺ . ׮নೠ ૑಴о ੓૑݅ प ࢎਊ ૑಴ח ࡈܻ ঳ਸ ࣻ হ਺. Metrics from the field are not enough 01 Existing Benchmark library is limited 02 ࠛউ੿ೞѢա, प೯ೡ loopо ߸زغѢ ա दझమ੉ ࠂ੟ೞҊ ֢੉ૉо ݆Ѣա ৢ߄ܰѱ ࢸ੿ೞӝ য۰਎ ࣻ ੓਺. Stable Measurement is not easy 03 ٜযоӝী খࢲ “জ ࢿמ ѐࢶ੉ য۰ਕਃ” ӝઓ੄ Jetpack benchmarking ۄ੉ ࠳۞ܻח জ द੘੉ա Jank ܳ ஏ੿ೡ ࣻ হ਺ . MicroBenchmarkח ؀׮ࣻ ࢎਊ ࢎ۹ ী ੸ਊغ૑ ঋ਺. द੘ दр ؀न CPU ܳ ஏ੿ https://www.youtube.com/watch?v=0adLO2VRJtc IO 21 Macrobenchmark۽ ߡߢѢܿ ߂ झఋ౟স ஏ੿

Slide 21

Slide 21 text

Macrobenchmark stable androidx.benchmark:benchmark-macro:1.1.0 ੿ഛೠ App Startup ஏ੿ Scrolling ࢿמ ஏ੿ Locally & CI https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 22

Slide 22 text

StartupTimingMetric Sunflower https://github.com/android/sun fl ower

Slide 23

Slide 23 text

StartupBenchmarks.kt @get:Rule val benchmarkRule = MacrobenchmarkRule() private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated()

Slide 24

Slide 24 text

StartupBenchmarks.kt private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(StartupTimingMetric()), iterations = 5, compilationMode = compilationMode, startupMode = StartupMode.COLD, setupBlock = { pressHome() } ) { startActivityAndWait() // wait for the content called by reportFullyDrawn is visible val recyclerHasChild = By.hasChild(By.res(packageName, "garden_list")) device.wait(Until.hasObject(recyclerHasChild), 5_000) }

Slide 25

Slide 25 text

StartupBenchmarks.kt private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(StartupTimingMetric()), iterations = 5, compilationMode = compilationMode, startupMode = StartupMode.COLD, setupBlock = { pressHome() } ) { startActivityAndWait() // wait for the content called by reportFullyDrawn is visible val recyclerHasChild = By.hasChild(By.res(packageName, "garden_list")) device.wait(Until.hasObject(recyclerHasChild), 5_000) }

Slide 26

Slide 26 text

StartupBenchmarks.kt private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(StartupTimingMetric()), iterations = 5, compilationMode = compilationMode, startupMode = StartupMode.COLD, setupBlock = { pressHome() } ) { startActivityAndWait() // wait for the content called by reportFullyDrawn is visible val recyclerHasChild = By.hasChild(By.res(packageName, "garden_list")) device.wait(Until.hasObject(recyclerHasChild), 5_000) } Metric StartupTimingMetric জ द੘ दр ஏ੿ FrameTimingMetric п ೐ۨ੐ ఋ੉߁ ੿ࠁ

Slide 27

Slide 27 text

StartupBenchmarks.kt private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(StartupTimingMetric()), iterations = 5, compilationMode = compilationMode, startupMode = StartupMode.COLD, setupBlock = { pressHome() } ) { startActivityAndWait() // wait for the content called by reportFullyDrawn is visible val recyclerHasChild = By.hasChild(By.res(packageName, "garden_list")) device.wait(Until.hasObject(recyclerHasChild), 5_000) }

Slide 28

Slide 28 text

StartupBenchmarks.kt @Test fun startupCompilationNone() = startup(CompilationMode.None()) @Test fun startupCompilationPartial() = startup(CompilationMode.Partial()) @Test fun startupCompilationWarmup() = startup(CompilationMode.Partial(BaselineProfileMode.Disable, 2))

Slide 29

Slide 29 text

StartupBenchmarks.kt @Test fun startupCompilationNone() = startup(CompilationMode.None()) @Test fun startupCompilationPartial() = startup(CompilationMode.Partial()) @Test fun startupCompilationWarmup() = startup(CompilationMode.Partial(BaselineProfileMode.Disable, 2))

Slide 30

Slide 30 text

CompilationMode.kt @Test fun startupCompilationPartial() = startup(CompilationMode.Partial()) @RequiresApi(24) class Partial @JvmOverloads constructor( val baselineProfileMode: BaselineProfileMode = BaselineProfileMode.Require, / . . . / ) : CompilationMode() { ... }

Slide 31

Slide 31 text

BaselineProfileMode.kt enum class BaselineProfileMode { Require, UseIfAvailable, Disable }

Slide 32

Slide 32 text

@Test fun startupCompilationNone() = startup(CompilationMode.None()) @Test fun startupCompilationPartial() = startup(CompilationMode.Partial()) @Test fun startupCompilationWarmup() = startup(CompilationMode.Partial(BaselineProfileMode.Disable, 2)) StartupBenchmarks.kt Compilation Mode None জ੉ ࢎ੹ ஹ౵ੌ غ૑ ঋ਺ (Worst Case) Full п জ੉ ৮੹൤ ࢎ੹ ஹ౵ੌ ؽ. (Ideal Case) Partial о੢ അप੸ੋ জ ࢸ஖ ജ҃

Slide 33

Slide 33 text

StartupBenchmarks.kt private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(StartupTimingMetric()), iterations = 5, compilationMode = compilationMode, startupMode = StartupMode.COLD, setupBlock = { pressHome() } ) { startActivityAndWait() // wait for the content called by reportFullyDrawn is visible val recyclerHasChild = By.hasChild(By.res(packageName, "garden_list")) device.wait(Until.hasObject(recyclerHasChild), 5_000) }

Slide 34

Slide 34 text

StartupBenchmarks.kt private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(StartupTimingMetric()), iterations = 5, compilationMode = compilationMode, startupMode = StartupMode.COLD, setupBlock = { pressHome() } ) { startActivityAndWait() // wait for the content called by reportFullyDrawn is visible val recyclerHasChild = By.hasChild(By.res(packageName, "garden_list")) device.wait(Until.hasObject(recyclerHasChild), 5_000) } Startup Mode Hot ݫݽܻী ੓ח Ѧ۽ Activity ׮द द੘ Warm ӝઓ ೐۽ࣁझ ղীࢲ Activityܳ ࢤࢿ द੘ Cold प೯ ઺੉ ইצ ೐۽ࣁझীࢲ द੘ ੹୓ ೐۽ࣁझ ୡӝച

Slide 35

Slide 35 text

App startup time https://developer.android.com/topic/performance/vitals/launch-time

Slide 36

Slide 36 text

h tt ps://github.com/android/sun fl ower StartupTimingMetric Sunflower

Slide 37

Slide 37 text

h tt ps://github.com/android/sun fl ower StartupBenchmarks_startupCompilationWarmup timeToFullDisplayMs min 185.4, median 210.2, max 222.5 timeToInitialDisplayMs min 172.1, median 196.0, max 204.3 Traces: Iteration 0 1 2 3 4 StartupBenchmarks_startupCompilationPartial timeToFullDisplayMs min 197.5, median 208.5, max 265.6 timeToInitialDisplayMs min 185.8, median 196.1, max 246.4 Traces: Iteration 0 1 2 3 4 StartupBenchmarks_startupCompilationNone timeToFullDisplayMs min 296.1, median 302.1, max 317.3 timeToInitialDisplayMs min 280.6, median 284.4, max 296.9 Traces: Iteration 0 1 2 3 4 StartupTimingMetric Sunflower

Slide 38

Slide 38 text

timeToInitialDisplayMs timeToFullDisplayMs Launch intent ࣻनೠ റ first frame ਸ Ӓܻחؘ ө૑੄ दр Launch intent ࣻनೠ റ android.app.Activity.reportFullyDrawn ө૑੄ दр StartupTimingMetric https://developer.android.com/studio/pro fi le/macrobenchmark-metrics

Slide 39

Slide 39 text

FrameTimingMetric Sunflower https://github.com/android/sun fl ower

Slide 40

Slide 40 text

PlantListBenchmarks.kt @get:Rule val benchmarkRule = MacrobenchmarkRule() private fun openPlantList(compilationMode: CompilationMode) = benchmarkRule.measureRepeated()

Slide 41

Slide 41 text

PlantListBenchmarks.kt private fun openPlantList(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( packageName = PACKAGE_NAME, metrics = listOf(FrameTimingMetric()), compilationMode = compilationMode, iterations = 5, startupMode = StartupMode.COLD, setupBlock = { pressHome() // Start the default activity, but don't measure the frames yet startActivityAndWait() } ) { goToPlantListTab() }

Slide 42

Slide 42 text

h tt ps://github.com/android/sun fl ower Sunflower FrameTimingMetric

Slide 43

Slide 43 text

frameOverrunMs frameDurationCpuMs Frame੉ ӝળࠁ׮ וܽ ੿ب CPUীࢲ ೐ۨ੐ਸ ࢤࢿೞחؘ Ѧܽ दр (UI thread, RenderThread ನೣ) FrameTimingMetric https://developer.android.com/studio/pro fi le/macrobenchmark-metrics + : Drop ػ Frame, Jank - : Deadlineࠁ׮ ঴݃ա ࡅܲ૑ Android 12+

Slide 44

Slide 44 text

h tt ps://github.com/android/sun fl ower Sunflower FrameTimingMetric PlantListBenchmarks_plantListCompilationPartial frameDurationCpuMs P50 6.5, P90 26.5, P95 50.3, P99 60.7 Traces: Iteration 0 1 2 3 4 PlantListBenchmarks_plantListCompilationFull frameDurationCpuMs P50 9.5, P90 32.7, P95 51.6, P99 63.2 Traces: Iteration 0 1 2 3 4 Timed out waiting for process (com.google.samples.apps.sunflower.macrobenchmark) to appear on lge- lm_v500n-LMV500N68592ec6. PlantListBenchmarks_openPlantList frameDurationCpuMs P50 22.2, P90 50.1, P95 82.1, P99 100.4 Traces: Iteration 0 1 2 3 4 LM-V500M(Android 11)

Slide 45

Slide 45 text

h tt ps://github.com/android/sun fl ower Sunflower FrameTimingMetric PlantListBenchmarks_plantListCompilationFull frameDurationCpuMs P50 16.3, P90 31.8, P95 35.8, P99 69.4 frameOverrunMs P50 1.9, P90 17.5, P95 20.5, P99 38.4 Traces: Iteration 0 1 2 3 4 PlantListBenchmarks_openPlantList frameDurationCpuMs P50 29.4, P90 56.5, P95 161.2, P99 178.6 frameOverrunMs P50 16.8, P90 41.8, P95 59.0, P99 181.8 Traces: Iteration 0 1 2 3 4 Google Pixel 3a(Android 12)

Slide 46

Slide 46 text

పझ౟ ജ҃ ( ௏٘ա ٣߄੉झ झಖ ژח ীޯۨ੉ఠ )ী ٮۄ ׮ܲ ࣻ஖о աৢ ࣻ ੓ח ࠗ ࠙ੑפ׮. ٮۄࢲ ׮নೠ ജ҃ীࢲ ࣻ஖ܳ ࠺Ү೧ࠄ റী п੗੄ ӝળਸ ੿ೞҊ ѐࢶ ನੋ౟ ܳ ੟ইաоח ؘী ࢎਊೞݶ જਸ Ѫ эणפ׮. Q & A সؘ੉౟ ࣻ஖ܳ ࠅ ٸ.. https://developer.android.com/studio/pro fi le/baselinepro fi les

Slide 47

Slide 47 text

https://github.com/android/sun fl ower

Slide 48

Slide 48 text

API 23ө૑ ૑ਗ TraceSectionMetricਸ ࢎਊ೧ࢲ custom trace-based timing ஏ੿ ୶о AudioUnderrunMetricਵ۽ য়٣য় ӝ߈ х૑ ೲਊ ઱ ࢎਊ ҃۽(Critical workflows)ী ؀ೠ ೐۽೙ਸ ࢤࢿ - BaselineProfileRule ୶о Macrobenchmark https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 49

Slide 49 text

JankStats Alpha androidx.metrics:metrics-performance জ੄ ߡߢѢܿ ా҅ী ҙೠ ࠁҊࢲ ઁҕ https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 50

Slide 50 text

https://developer.android.com/studio/pro fi le/jankstats class JankLoggingActivity : AppCompatActivity() { private lateinit var jankStats: JankStats override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... // metrics state holder can be retrieved regardless of JankStats initialization val metricsStateHolder = PerformanceMetricsState.getForHierarchy(binding.root) // initialize JankStats for current window jankStats = JankStats.createAndTrack( window, Dispatchers.Default.asExecutor(), jankFrameListener, ) // add activity name as state metricsStateHolder.state?.addState("Activity", javaClass.simpleName) // ... }JankLoggingActivity.kt

Slide 51

Slide 51 text

class JankAggregatorActivity : AppCompatActivity() { private lateinit var jankStatsAggregator: JankStatsAggregator override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... // Metrics state holder can be retrieved regardless of JankStats initialization. val metricsStateHolder = PerformanceMetricsState.getForHierarchy(binding.root) // Initialize JankStats with an aggregator for the current window. jankStatsAggregator = JankStatsAggregator( window, Dispatchers.Default.asExecutor(), jankReportListener ) // Add the Activity name as state. metricsStateHolder.state?.addState("Activity", javaClass.simpleName) }JankAggregatorActivity.kt https://developer.android.com/studio/pro fi le/jankstats

Slide 52

Slide 52 text

https://developer.android.com/studio/pro fi le/jankstats override fun onResume() { super.onResume() jankStatsAggregator.jankStats.isTrackingEnabled = true } override fun onPause() { super.onPause() // Before disabling tracking, issue the report with (optionally) specified reason. jankStatsAggregator.issueJankReport("Activity paused") jankStatsAggregator.jankStats.isTrackingEnabled = false }JankAggregatorActivity.kt

Slide 53

Slide 53 text

https://developer.android.com/studio/pro fi le/jankstats Logcat

Slide 54

Slide 54 text

https://developer.android.com/studio/pro fi le/jankstats *** Jank Report (Activity paused), totalFrames = 170, jankFrames = 26 FrameData( frameStartNanos=257993674283620, // frame द੘ दр frameDurationUiNanos=77057961, //frame ૑ࣘ दр frameDurationCpuNanos=80083690, isJank=true, // Jank = അ੤ refresh rate ࠁ׮ ۪؊݂ೞח ؘ ف ߓо Ѧܻח frame states=[RecyclerView: Settling, Activity: JankAggregatorActivity] ) JankStatsAggregator ীࢲ ా೤ ੿ࠁ ઁҕ

Slide 55

Slide 55 text

API ۨ߰ 16ө૑ ഐജ оמ ղࠗ ോܻझ౮ਸ ࢎਊೞৈ ࢿמ ޙઁ ध߹ ࢎਊ੗ ࢚క ߂ ੘সী ؀ೠ ࣘࢿ ؘ੉ఠী UI ஶఫझ౟ ઁҕ ܻझցܳ ా೧ ݽٚ ೐ۨ੐ী ؀ೠ Ѿҗ ࠁҊ JankStats https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 56

Slide 56 text

Need more detail?

Slide 57

Slide 57 text

Tracing stable androidx.tracing:tracing:1.1.0 System trace bufferী trace event ӝ۾ ٣ߡӒо ইצ ࠽٘ীࢲ ੿ഛೠ ੉߮౟ ୶੸ https://www.youtube.com/watch?v=jTd82lcuHTU IO 22 What’s new in Jetpack

Slide 58

Slide 58 text

import androidx.tracing.trace; fun loadIcon() = trace(“Loading Icon”) { //... } https://www.youtube.com/watch?v=0adLO2VRJtc IO 21 Macrobenchmark۽ ߡߢѢܿ ߂ झఋ౟স ஏ੿

Slide 59

Slide 59 text

h tt ps://github.com/android/pe rf ormance-samples Performance-samples

Slide 60

Slide 60 text

Thank you! IO 22 What’s new in Jetpack IO 22 What’s new in App Performanc e IO 21 Measuring Jank and Startup with Macrobenchmark Resources veronikapj GDG Korea Android PilJu BAE developer.android.com