Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Droidcon Santo Domingo 2017 - Android Performan...

Droidcon Santo Domingo 2017 - Android Performance for everyone

This talk took place in Santo Domingo, Dominican Republic during the first #Droidcon in Latinamerica on March 3rd and 4th, 2017.
http://droidcon.do

Carlos Daniel

March 04, 2017
Tweet

More Decks by Carlos Daniel

Other Decks in Programming

Transcript

  1. Carlos Daniel Munoz I. - Android Dev @cdmunozi [email protected] Co-Founder

    & Co-Organizer @MedellinAndroid March 3-4, 2017 DROIDCON REPÚBLICA DOMINICANA Android Performance for everyone
  2. “Success is no accident. It is hard work, perseverance, learning,

    studying, sacrifice and most of all, love of what you are doing or learning to do. “ - Pelé
  3. 1. Identifying and diagnosing performance problems 2. Exploratory Tests 3.

    Profiling tools 4. Using outputs 5. Designing plans of attack 6. How program code and Android platform interact
  4. The only way to fast is to go well (

    The higher the quality, the faster you go )
  5. 13 Why is my app slow? Imagine this app: •

    Great app • A screen with a list
  6. 14 Why is my app slow? Imagine this app: •

    Great app • A screen with a list • With animations for every item within a list
  7. 15 Why is my app slow? Imagine this app: •

    Great app • A screen with a list • With animations for every item within a list • Overlays
  8. 16 Why is my app slow? Imagine this app: •

    Great app • A screen with a list • With animations for every item within a list • Overlays • Transparencies
  9. 17 Why is my app slow? Imagine this app: •

    Great app • A screen with a list • With animations for every item within a list • Overlays • Transparencies • May change the order of the items with a drag gesture
  10. 19 Why rendering performance matters • Great app, with animations

    for every item within a list, overlays, transparencies
  11. 20 Why rendering performance matters • Great app, with animations

    for every item within a list, overlays, transparencies • Problems with views (tons of views), slow app
  12. 21 Why rendering performance matters • Great app, with animations

    for every item within a list, overlays, transparencies • Problems with views (tons of views), slow app • What to do? (I want to keep the user experience)
  13. 23 Visual Design vs Performance? • Android always try to

    redraw the activity every 16 milliseconds → 60 frames per seconds including views and logic
  14. 24 Visual Design vs Performance? • Android always try to

    redraw the activity every 16 milliseconds → 60 frames per seconds including views and logic • If there is a problem → dropped frame → freezes until it finishes → no smoothed animations
  15. 25 Rendering Pipeline: common problems • CPU: unnecessary views and

    invalidations → view hierarchy • GPU: overdraw → wasting GPU processing time coloring in pixels F L O W
  16. 26 Android UI and the GPU • Rasterization • GPU

    accelerates rasterization process → polygons and textures • CPU feeding the GPU • CPU + GPU using API OpenGL ES • All resources within an Android Theme are grouped into a single texture and then uploaded into the GPU (this types of views fast) • GPU bottleneck → overdraw
  17. 28 GPU Problem: Overdraw • How many times a pixel

    is being redrawn on the screen in a single frame ◦ Stack of UI cards → wasting GPU performance
  18. 29 GPU Problem: Overdraw • How many times a pixel

    is being redrawn on the screen in a single frame ◦ Stack of UI cards → wasting GPU performance • How to see it? ◦ Go to Settings > Developer Options ◦ Turn on Debug GPU Overdraw. ◦ In the popup, choose Show overdraw areas.
  19. 30 GPU Problem: Overdraw • How many times a pixel

    is being redrawn on the screen in a single frame ◦ Stack of UI cards → wasting GPU performance • How to see it? ◦ Go to Settings > Developer Options ◦ Turn on Debug GPU Overdraw. ◦ In the popup, choose Show overdraw areas. • As the overdraw increases, so do the colors
  20. 31 GPU Problem: Overdraw • How many times a pixel

    is being redrawn on the screen in a single frame ◦ Stack of UI cards → wasting GPU performance • How to see it? ◦ Go to Settings > Developer Options ◦ Turn on Debug GPU Overdraw. ◦ In the popup, choose Show overdraw areas. • As the overdraw increases, so do the colors
  21. 34 Visualize and fixes Overdraw • What did we see?

    ◦ Layouts setting background in every layout of a list ◦ Not Nullifying background of the theme ◦ Setting transparencies where required and colors
  22. 40 clipRect and quickReject • Canvas.clipRect → everything outside this

    canvas, ignore it • Canvas.quickReject → tests whether a given area is completely outside the clipping rectangle
  23. 43 Fixing overdraw with Canvas API • Custom Views: ◦

    In onDraw() → Canvas.save() → Canvas.clipRect() → Canvas.restore() Hidden, do not draw
  24. 45 Hierarchy Viewer • Tools → Android → Android Device

    Monitor • Flatten the view tree to save memory • Profile to see if a view is taking more time to load
  25. 46 Hierarchy Viewer • Tools → Android → Android Device

    Monitor • Flatten the view tree to save memory • Profile to see if a view is taking more time to load
  26. 49 Nested Hierarchies and Performance • Inflating layouts is a

    very expensive process • Every nested layout impacts the performance
  27. 50 Nested Hierarchies and Performance • Inflating layouts is a

    very expensive process • Every nested layout impacts the performance • Example of Items of a list using several linear layouts and using a relative layout to flattens the view → Show profile in Hierarchy View
  28. 53 Slow Function Performance How we write our code affects

    the performance, depending on how the language executes in the hardware!!!!
  29. 55 Slow Function Performance • Find a single slow function,

    then fix the code and gotcha! • What if we have 1.000 functions, every one lasting 1 additional millisecond?
  30. 56 Slow Function Performance • Find a single slow function,

    then fix the code and gotcha! • What if we have 1.000 functions, every one lasting 1 additional millisecond?
  31. 57 Slow Function Performance • Find a single slow function,

    then fix the code and gotcha! • What if we have 1.000 functions, every one lasting 1 additional millisecond? • Is there any tool to track this?
  32. 59 Trace View • Great information resource: ◦ Time spent

    executing method | CPU time | Call + Recursion
  33. 60 Trace View • Great information resource: ◦ Time spent

    executing method | CPU time | Call + Recursion • Tools → Android → Android Device Monitor → DDMS
  34. 63 Trace View • Great information resource: ◦ Time spent

    executing method | CPU time | Call + Recursion
  35. 64 Trace View • The exclusive time → time spent

    just in the method itself, which can help you find issues within that specific method.
  36. 65 Trace View • The exclusive time → time spent

    just in the method itself, which can help you find issues within that specific method. • The inclusive time → is for the method and all methods it calls, which can help you find problems with your call tree.
  37. 66 Trace View • The exclusive time → time spent

    just in the method itself, which can help you find issues within that specific method. • The inclusive time → is for the method and all methods it calls, which can help you find problems with your call tree. • The Calls+Rec → how many times a method was called recursively, which can help you track down performance issues.
  38. 69 Batching and caching • Batch and cache as strategies

    to improve performance • Example: Loading data into a new place in memory before execution → Execution multiple times (really big number) → Overhead? → Sure!
  39. 70 Batching and caching • Batch and cache as strategies

    to improve performance • Example: Loading data into a new place in memory before execution → Execution multiple times (really big number) → Overhead? → Sure! • Batching → Solves this problem trying to eliminate the per execution overhead
  40. 71 Batching and caching • Batch and cache as strategies

    to improve performance • Example: Loading data into a new place in memory before execution → Execution multiple times (really big number) → Overhead? → Sure! • Batching → Solves this problem trying to eliminate the per execution overhead ◦ Sharing a car instead of everyone driving themselves to save gas
  41. 72 Batching and caching • Batch and cache as strategies

    to improve performance • Example: Loading data into a new place in memory before execution → Execution multiple times (really big number) → Overhead? → Sure! • Batching → Solves this problem trying to eliminate the per execution overhead ◦ Sharing a car instead of everyone driving themselves to save gas • Example of caching an operation value that is calculated in a loop and always has the same result
  42. 73 Batching and caching • Batch and cache as strategies

    to improve performance • Example: Loading data into a new place in memory before execution → Execution multiple times (really big number) → Overhead? → Sure! • Batching → Solves this problem trying to eliminate the per execution overhead ◦ Sharing a car instead of everyone driving themselves to save gas • Example of caching an operation value that is calculated in a loop and always has the same result • Caching → it’s easy to CPU to access the RAM instead of hard drives
  43. 74 Batching and caching • Batch and cache as strategies

    to improve performance • Example: Loading data into a new place in memory before execution → Execution multiple times (really big number) → Overhead? → Sure! • Batching → Solves this problem trying to eliminate the per execution overhead ◦ Sharing a car instead of everyone driving themselves to save gas • Example of caching an operation value that is calculated in a loop and always has the same result • Caching → it’s easy to CPU to access the RAM instead of hard drives • COMPUTE FIBONACCI EXAMPLE
  44. 77 Fixing Fibonacci • Change recursive way, then use a

    procedural way storing previous values
  45. 80 Blocking the UI Thread • Remember the 16 ms

    frame set? → After 5 seconds → ANR
  46. 86 Container Performance • Take into account (Basic cannon in

    modern data structures): ◦ Quicksort is faster than Bubble sort after a thousand elements
  47. 87 Container Performance • Take into account (Basic cannon in

    modern data structures): ◦ Quicksort is faster than Bubble sort after a thousand elements ◦ Perform a search (unsorted)?
  48. 88 Container Performance • Take into account (Basic cannon in

    modern data structures): ◦ Quicksort is faster than Bubble sort after a thousand elements ◦ Perform a search (unsorted)? ▪ Binary search : O(log(n))
  49. 89 Container Performance • Take into account (Basic cannon in

    modern data structures): ◦ Quicksort is faster than Bubble sort after a thousand elements ◦ Perform a search (unsorted)? ▪ Binary search : O(log(n)) ▪ Hash Table: O(1)
  50. 90 Container Performance • Take into account (Basic cannon in

    modern data structures): ◦ Quicksort is faster than Bubble sort after a thousand elements ◦ Perform a search (unsorted)? ▪ Binary search : O(log(n)) ▪ Hash Table: O(1) • All of this, the language provides it
  51. 91 Container Performance • Take into account (Basic cannon in

    modern data structures): ◦ Quicksort is faster than Bubble sort after a thousand elements ◦ Perform a search (unsorted)? ▪ Binary search : O(log(n)) ▪ Hash Table: O(1) • All of this, the language provides it • Problems with performance? → regarding this containers
  52. 92 Container Performance • James Sutherland → microbenchmarks* on java

    containers performance * https://java-persistence-performance.blogspot.com.co/2010/12/what-is-faster-jvm-performance.html
  53. 93

  54. 95 Tracing App Code • Using Trace* package we can

    add signals to our code * https://developer.android.com/studio/profile/systrace-walkthru.html
  55. 96 Tracing App Code • Using Trace* package we can

    add signals to our code ◦ Trace.beginSection(String) * https://developer.android.com/studio/profile/systrace-walkthru.html
  56. 97 Tracing App Code • Using Trace* package we can

    add signals to our code ◦ Trace.beginSection(String) ◦ Trace.endSection() * https://developer.android.com/studio/profile/systrace-walkthru.html
  57. 98 Tracing App Code • Using Trace* package we can

    add signals to our code ◦ Trace.beginSection(String) ◦ Trace.endSection() • This technique → what work our application's threads are doing at any given time Example: dumpPopularRandomNumbersByRank() * https://developer.android.com/studio/profile/systrace-walkthru.html
  58. 99 Tracing App Code • Using Trace* package we can

    add signals to our code ◦ Trace.beginSection(String) ◦ Trace.endSection() • This technique can help us see what work our application's threads are doing at any given time (Example: dumpPopularRandomNumbersByRank() ) ◦ NOTES: ▪ Python installed ▪ adb ok (export PATH=$PATH:~/android-sdks/platform-tools ) ▪ systrace enabled → python systrace.py --time=10 -o mynewtrace.html sched gfx view wm * https://developer.android.com/studio/profile/systrace-walkthru.html
  59. 105 Tracing App Code Improvement notes: • There’s always a

    one-time cost to sort items (you'd pick the ideal sorting item according to the size of your dataset).
  60. 106 Tracing App Code Improvement notes: • There’s always a

    one-time cost to sort items (you'd pick the ideal sorting item according to the size of your dataset). • By using a HashMap, we save time by obtaining a linear look-up time (O(n)) vs. the quadratic time O(n^2) access of the array in the unoptimized case. We save this one order of access time because the data is already stored in key value pairs!
  61. 107 Tracing App Code Improvement notes: • There’s always a

    one-time cost to sort items (you'd pick the ideal sorting item according to the size of your dataset). • By using a HashMap, we save time by obtaining a linear look-up time (O(n)) vs. the quadratic time O(n^2) access of the array in the unoptimized case. We save this one order of access time because the data is already stored in key value pairs! • This matters significantly when n (or the sample size) is particularly large, which may be the case if you were working with, say, a list of all professional football players in the world and wanted to display a ranking of them across some attribute.
  62. 109 Memory and Garbage Collector “I can write code however

    I want, Garbage Collector does the rest”
  63. 113 Memory, GC and Performance • John McCarthy - 1959

    • Solve problems with Lisp • 1 - 2 are easy in theory BUT complex when we have more lines of code • Fast and effective without interruptions
  64. 114 Memory, GC and Performance • John McCarthy - 1959

    • Solve problems with Lisp • 1 - 2 are easy in theory BUT complex when we have more lines of code • Fast and effective without interruptions
  65. 116 Memory Monitor Tool • The more time is spent

    doing GC, there will be less time to do other stuff
  66. 117 Memory Monitor Tool • The more time is spent

    doing GC, there will be less time to do other stuff • Example with an App
  67. 118 Memory Monitor Tool • The more time is spent

    doing GC, there will be less time to do other stuff • Example with an App • What happens if it reaches the maximum? ◦ The app is closed
  68. 119 Memory Monitor Tool • The more time is spent

    doing GC, there will be less time to do other stuff: rendering, streaming audio • Example with an App • What happens if it reaches the maximum? ◦ The app is closed • Who did this? ◦ GC
  69. 124 Heap Viewer • Tool → Android Device Monitor ◦

    See what type of object the app is allocating and how many and sizes of those objects
  70. 125 Heap Viewer • Tool → Android Device Monitor ◦

    See what type of object the app is allocating and how many and sizes of those objects
  71. 127 Spotting Leaks in Memory Monitor • Have you experienced

    mysterious slowdowns after using an app?
  72. 128 Spotting Leaks in Memory Monitor • Have you experienced

    mysterious slowdowns after using an app? • Patience - Perf mindset - Right Tools → abolish the leaks from the app.
  73. 129 Spotting Leaks in Memory Monitor • Have you experienced

    mysterious slowdowns after using an app? • Patience - Perf mindset - Right Tools → abolish the leaks from the app. • Example → Memory Leak ◦ Show Monitor behavior and Memory Monitor Heap Viewer Cause GC
  74. 130 Spotting Leaks in Memory Monitor • Have you experienced

    mysterious slowdowns after using an app? • Patience - Perf mindset - Right Tools → abolish the leaks from the app. • Example → Memory Leak ◦ Show Monitor behavior and Memory Monitor Heap Viewer Cause GC ◦ Changing orientation continuously
  75. 131 Spotting Leaks in Memory Monitor • Have you experienced

    mysterious slowdowns after using an app? • Patience - Perf mindset - Right Tools → abolish the leaks from the app. • Example → Memory Leak ◦ Show Monitor behavior and Memory Monitor Heap Viewer Cause GC ◦ Changing orientation continuously
  76. 134 Memory Churn • Process to allocate lots of objects

    in a very short amount of time that creates a lot of pressure to the heaps
  77. 135 Memory Churn • Process to allocate lots of objects

    in a very short amount of time that creates a lot of pressure to the heaps ◦ Allocating a bunch of objects (or heavy ones) in onDraw()
  78. 136 Memory Churn • Process to allocate lots of objects

    in a very short amount of time that creates a lot of pressure to the heaps ◦ Allocating a bunch of objects (or heavy ones) in onDraw() ◦ Allocating a load of temporary objects (or heavy ones) in a middle of a for loop
  79. 137 Memory Churn • Process to allocate lots of objects

    in a very short amount of time that creates a lot of pressure to the heaps ◦ Allocating a bunch of objects (or heavy ones) in onDraw() ◦ Allocating a load of temporary objects (or heavy ones) in a middle of a for loop
  80. 139 Allocation Tracker • Tool in Android Studio • Android

    Monitor → Monitors → Start Allocation Tracker
  81. 140 Allocation Tracker • Tool in Android Studio • Android

    Monitor → Monitors → Start Allocation Tracker
  82. 141 Allocation Tracker • Tool in Android Studio • Android

    Monitor → Monitors → Start Allocation Tracker •
  83. 142 Allocation Tracker - Memory Churn • Running churn example

    → Creates a matrix, then “clones” it into a new one with sorted rows • Concatenating within the loop every new sorted row (Creates a new StringBuilder every new concatenation)
  84. 143 Memory Churn - Solution • Running fixed churn example

    → Improvement • Still blocks UI, then is a candidate method to run in another thread
  85. 146 Memory - Recap • Several handy tools ◦ Memory

    monitor → View state of memory over time
  86. 147 Memory - Recap • Several handy tools ◦ Memory

    monitor → View state of memory over time ◦ Heat Viewer → What’s on the heap
  87. 148 Memory - Recap • Several handy tools ◦ Memory

    monitor → View state of memory over time ◦ Heat Viewer → What’s on the heap ◦ Allocation tracker → Where did it come from
  88. 149 Memory - Recap • Several handy tools ◦ Memory

    monitor → View state of memory over time ◦ Heat Viewer → What’s on the heap ◦ Allocation tracker → Where did it come from • Evaluate code against several tools and seek multiple perspectives
  89. 152 Battery → The problem • Imagine an app that

    is updating in real time the location of a user, keeping an active connection to more accurate results
  90. 153 Battery → The problem • Imagine an app that

    is updating in real time the location of a user, keeping an active connection to more accurate results
  91. 156 Battery drain • How the process works under the

    hood? ◦ Active phone: ▪ CPU processes ▪ cellular radio transmitting data ▪ screen awake - remember Render section?
  92. 157 Battery drain • How the process works under the

    hood? ◦ Active phone: ▪ CPU processes ▪ cellular radio transmitting data ▪ screen awake - remember Render section? ◦ Application awakening the device: ▪ Wake clock ▪ Alarm manager
  93. 158 Battery drain • How the process works under the

    hood? ◦ Active phone: ▪ CPU processes ▪ cellular radio transmitting data ▪ screen awake - remember Render section? ◦ Application awakening the device: ▪ Wake clock ▪ Alarm manager • How do I identify battery behavior?
  94. 161 Battery Historian • Batterystats command from adb (gives an

    illegible file) • Tool to analyze battery consumers using Android "bugreport" files
  95. 162 Battery Historian • Batterystats command from adb (gives an

    illegible file) • Tool to analyze battery consumers using Android "bugreport" files • Python script - https://github.com/google/battery-historian ◦ python history.py <stats_file> > <html_file>
  96. 163 Battery Historian • Batterystats command from adb (gives an

    illegible file) • Tool to analyze battery consumers using Android "bugreport" files • Python script - https://github.com/google/battery-historian ◦ python history.py <stats_file> > <html_file> • What we can see? ◦ How often and how long a process did use the battery, NOT how much battery was used by each action
  97. 167 Battery Manager • Wait for docking ◦ Only apply

    some kind of logic (heavy pictures just like applying a filter to an image only when device is charging)
  98. 168 Battery Manager • Wait for docking ◦ Only apply

    some kind of logic (heavy pictures just like applying a filter to an image only when device is charging) ◦ BatteryManager + IntentFilter(Intent.ACTION_BATTERY_CHANGED)
  99. 169 Battery Manager • Wait for docking ◦ Only apply

    some kind of logic (heavy pictures just like applying a filter to an image only when device is charging) ◦ BatteryManager + IntentFilter(Intent.ACTION_BATTERY_CHANGED)
  100. 171 Wakelock and Battery Drain • Battery drain, When?? ◦

    Apps constantly checking scoreboards (games) ◦ uploading photos ◦ downloading upates
  101. 172 Wakelock and Battery Drain • Battery drain, When?? ◦

    Apps constantly checking scoreboards (games) ◦ uploading photos ◦ downloading upates • Sleep deprivation
  102. 173 Wakelock and Battery Drain • Battery drain, When?? ◦

    Apps constantly checking scoreboards (games) ◦ uploading photos ◦ downloading upates • Sleep deprivation
  103. 175 Wakelock and Battery Drain How to solve it? ◦

    PowerManager.Wakelock API ◦ Keeps CPU running preventing screen to turn off ◦ Wakes phone up in future dates, do some work, sleep ◦ Defer some activities
  104. 176 Wakelock and Battery Drain How to solve it? ◦

    PowerManager.Wakelock API ◦ Keeps CPU running preventing screen to turn off ◦ Wakes phone up in future dates, do some work, sleep ◦ Defer some activities ▪ Wifi connected ▪ Charging
  105. 177 Wakelock and Battery Drain How to solve it? ◦

    PowerManager.Wakelock API ◦ Keeps CPU running preventing screen to turn off ◦ Wakes phone up in future dates, do some work, sleep ◦ Defer some activities ▪ Wifi connected ▪ Charging • Google play services • Apps updating • Check geofences • Re-take some failed process
  106. 178 Wakelock and Battery Drain How to solve it? ◦

    PowerManager.Wakelock API ◦ Keeps CPU running preventing screen to turn off ◦ Wakes phone up in future dates, do some work, sleep ◦ Defer some activities ▪ Wifi connected ▪ Charging • Google play services • Apps updating • Check geofences • Re-take some failed process
  107. 180 Battery - Job Scheduler Hyper active apps (Project Volta

    from Google IO 2014)* * https://www.youtube.com/watch?v=KzSKIpJepUw&feature=youtu.be
  108. 181 Battery - Job Scheduler Hyper active apps (Project Volta

    from Google IO 2014)* Shows activity of 50 simulated apps (wake up cycles) * https://www.youtube.com/watch?v=KzSKIpJepUw&feature=youtu.be
  109. 182 Battery - Job Scheduler Hyper active apps (Project Volta

    from Google IO 2014)* Shows activity of 50 simulated apps (wake up cycles) * https://www.youtube.com/watch?v=KzSKIpJepUw&feature=youtu.be
  110. 183 Battery - Job Scheduler Hyper active apps (Project Volta

    from Google IO 2014)* Shows activity of 50 simulated apps (wake up cycles) • What if multiple apps are doing wakelock.acquire inefficiently? ◦ Release the lock always after the job is done * https://www.youtube.com/watch?v=KzSKIpJepUw&feature=youtu.be
  111. 186 Job Scheduler • API to better run jobs, then

    improve battery performance and efficiency.
  112. 187 Job Scheduler • API to better run jobs, then

    improve battery performance and efficiency. ◦ android.app.job.JobService
  113. 188 Job Scheduler • API to better run jobs, then

    improve battery performance and efficiency. ◦ android.app.job.JobService ◦ JobService always runs in main UI thread, then it’s important to better use a separate thread to do asynchronous work
  114. 189 Job Scheduler • API to better run jobs, then

    improve battery performance and efficiency. ◦ android.app.job.JobService ◦ JobService always runs in main UI thread, then it’s important to better use a separate thread to do asynchronous work • We can define jobs to run even only when the device is plugged in
  115. 193 Reducing Network Access to Save Battery • Use JobScheduler

    to minimize the cellular radio tax • Including JobInfo for particular requirements
  116. 196 Battery Final thoughts • Profile an app using battery

    historian • Identify within it opportunities where to use the JobScheduler API ◦ Look for inefficient wake lock and network access that can be fixed
  117. 197 Battery Final thoughts • Profile an app using battery

    historian • Identify within it opportunities where to use the JobScheduler API ◦ Look for inefficient wake lock and network access that can be fixed • Generate report and battery string and take a screenshot
  118. 198 Battery Final thoughts • Profile an app using battery

    historian • Identify within it opportunities where to use the JobScheduler API ◦ Look for inefficient wake lock and network access that can be fixed • Generate report and battery string and take a screenshot • Make improvements into the code (those sections identified as potentially inefficient or problematic)
  119. 199 Battery Final thoughts • Profile an app using battery

    historian • Identify within it opportunities where to use the JobScheduler API ◦ Look for inefficient wake lock and network access that can be fixed • Generate report and battery string and take a screenshot • Make improvements into the code (those sections identified as potentially inefficient or problematic) • Re-profile the code and generate a new battery report, then screenshot
  120. 200 Battery Final thoughts • Profile an app using battery

    historian • Identify within it opportunities where to use the JobScheduler API ◦ Look for inefficient wake lock and network access that can be fixed • Generate report and battery string and take a screenshot • Make improvements into the code (those sections identified as potentially inefficient or problematic) • Re-profile the code and generate a new battery report, then screenshot • Compare
  121. 201 ...I’m not the best software developer, I’m a good

    software developer with good practices...