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

Performance Tools on Android

Performance Tools on Android

The Android ecosystem has a lot of tools to help us out when it comes to development. We all use Android Studio, gradle and adb, for instance. We need to, as these are essential tools for building our products. But what about the tools that help us diagnose problems in our apps? This session is all about asking 2 very common questions:

Why is my app stuttering or dropping below 60fps?

Why are my users telling me that my app drains their batteries?

Most of the times solving a problem means asking the right questions in order to find the right answers. So starting from these 2 questions, with concrete problem-code examples we will be using tools from Google to find our answers.

I don't want it to be a secret, so I'm telling you that the tools we will be using the most will be Traceview and Battery Historian. We will also be looking at Profiling GPU Rendering and Debugging Overdraw.

I know that these tools might look a bit scary or dull but I promise to keep it simple so that you walk away with information you can use at work the next day.

Presented at:
- Droidcon Romania 2016
- Codecamp Cluj Spring 2016

3d14b1dcd13755bcbafc5ac9c9ab1426?s=128

Andrei Diaconu

November 26, 2016
Tweet

Transcript

  1. Performance Tools Andrei Diaconu

  2. Andrei Diaconu

  3. Andrei Diaconu

  4. Andrei Diaconu

  5. Andrei Diaconu • Cofounder of Android Iași

  6. Andrei Diaconu • Cofounder of Android Iași • 5 years

    of Android
  7. Andrei Diaconu • Cofounder of Android Iași • 5 years

    of Android • Trainer & Speaker
  8. None
  9. Why is my app dropping below 60fps?

  10. Why is my app dropping below 60fps? Why is my

    app draining the battery?
  11. Why is my app dropping below 60fps?

  12. Why is my app dropping below 60fps?

  13. Why is my app dropping below 60fps? • Simple list

  14. Why is my app dropping below 60fps? • Simple list

    • Items have some text
  15. Why is my app dropping below 60fps? • Simple list

    • Items have some text
  16. Why is my app dropping below 60fps? • Simple list

    • Items have some text • Scroll is not smooth
  17. • Scroll is not smooth

  18. • Scroll is not smooth Why?

  19. • Scroll is not smooth Why? Can we make sure?

  20. • Scroll is not smooth Why? Can we make sure?

  21. Can we make sure?

  22. Can we make sure?

  23. Can we make sure?

  24. Can we make sure?

  25. None
  26. Frames

  27. Frames Time

  28. Frames 16ms line Time

  29. Frames 16ms line Time Choreographer: Skipped 34 frames! The application

    may be doing too much work on its main thread.
  30. None
  31. Traceview

  32. Traceview • Measure how long each method takes to execute

  33. Traceview • Measure how long each method takes to execute

    • Analyse measurements using Traceview
  34. Traceview • Measure how long each method takes to execute

  35. Traceview //Start in OnResume
 Debug.startMethodTracing("bad_list"); • Measure how long each

    method takes to execute
  36. Traceview //Start in OnResume
 Debug.startMethodTracing("bad_list"); //Stop in OnPause
 Debug.stopMethodTracing(); •

    Measure how long each method takes to execute
  37. Traceview //Start in OnResume
 Debug.startMethodTracing("bad_list"); //Stop in OnPause
 Debug.stopMethodTracing(); //Grab

    using adb
 adb pull • Measure how long each method takes to execute
  38. Traceview //Start in OnResume
 Debug.startMethodTracing("bad_list"); //Stop in OnPause
 Debug.stopMethodTracing(); //Grab

    using adb
 adb pull • Measure how long each method takes to execute //Grab using adb
 adb pull /sdcard/bad_list.trace bad_list.trace
  39. Traceview //Start in OnResume
 Debug.startMethodTracing("bad_list"); //Stop in OnPause
 Debug.stopMethodTracing(); //Grab

    using adb
 adb pull • Measure how long each method takes to execute //Needs permission WRITE_EXTERNAL_STORAGE //Grab using adb
 adb pull /sdcard/bad_list.trace bad_list.trace
  40. Traceview //Start in OnResume
 Debug.startMethodTracing("bad_list"); //Stop in OnPause
 Debug.stopMethodTracing(); //Grab

    using adb
 adb pull • Measure how long each method takes to execute //Needs permission WRITE_EXTERNAL_STORAGE //Grab using adb
 adb pull /sdcard/bad_list.trace bad_list.trace
  41. Traceview • Measure how long each method takes to execute

  42. Traceview • Measure how long each method takes to execute

  43. Traceview • Measure how long each method takes to execute

    • Analyse measurements using Traceview • Analyse measurements using Traceview
  44. Traceview • Measure how long each method takes to execute

    • Analyse measurements using Traceview • Analyse measurements using Traceview
  45. Traceview • Measure how long each method takes to execute

    • Analyse measurements using Traceview • Analyse measurements using Traceview
  46. Traceview • Measure how long each method takes to execute

    • Analyse measurements using Traceview • Analyse measurements using Traceview
  47. None
  48. None
  49. None
  50. None
  51. None
  52. None
  53. ~ 2 seconds

  54. ~ 200 ms

  55. Adaptor -> getView() ~ 200 ms

  56. None
  57. inflate()

  58. inflate() 4 x Html.fromHtml()

  59. @Override
 public View getView(int position, View convertView, ViewGroup parent) {

    
 View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_jumpy_list, parent, false);
 
 TextView text1 = (TextView) view.findViewById(R.id.item_text1);
 TextView text2 = (TextView) view.findViewById(R.id.item_text2);
 TextView text3 = (TextView) view.findViewById(R.id.item_text3);
 TextView text4 = (TextView) view.findViewById(R.id.item_text4);
 
 String string1 = parent.getContext().getString(R.string.item_number_x, position);
 String string2 = parent.getContext().getString(R.string.item_description);
 String string3 = parent.getContext().getString(R.string.item_details);
 String string4 = parent.getContext().getString(R.string.item_related);
 
 text1.setText(Html.fromHtml(string1));
 text2.setText(Html.fromHtml(string2));
 text3.setText(Html.fromHtml(string3));
 text4.setText(Html.fromHtml(string4)); 
 return view;
 }
  60. @Override
 public View getView(int position, View convertView, ViewGroup parent) {

    
 View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_jumpy_list, parent, false);
 
 TextView text1 = (TextView) view.findViewById(R.id.item_text1);
 TextView text2 = (TextView) view.findViewById(R.id.item_text2);
 TextView text3 = (TextView) view.findViewById(R.id.item_text3);
 TextView text4 = (TextView) view.findViewById(R.id.item_text4);
 
 String string1 = parent.getContext().getString(R.string.item_number_x, position);
 String string2 = parent.getContext().getString(R.string.item_description);
 String string3 = parent.getContext().getString(R.string.item_details);
 String string4 = parent.getContext().getString(R.string.item_related);
 
 text1.setText(Html.fromHtml(string1));
 text2.setText(Html.fromHtml(string2));
 text3.setText(Html.fromHtml(string3));
 text4.setText(Html.fromHtml(string4)); 
 return view;
 }
  61. @Override
 public View getView(int position, View convertView, ViewGroup parent) {

    
 View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_jumpy_list, parent, false);
 
 TextView text1 = (TextView) view.findViewById(R.id.item_text1);
 TextView text2 = (TextView) view.findViewById(R.id.item_text2);
 TextView text3 = (TextView) view.findViewById(R.id.item_text3);
 TextView text4 = (TextView) view.findViewById(R.id.item_text4);
 
 String string1 = parent.getContext().getString(R.string.item_number_x, position);
 String string2 = parent.getContext().getString(R.string.item_description);
 String string3 = parent.getContext().getString(R.string.item_details);
 String string4 = parent.getContext().getString(R.string.item_related);
 
 text1.setText(Html.fromHtml(string1));
 text2.setText(Html.fromHtml(string2));
 text3.setText(Html.fromHtml(string3));
 text4.setText(Html.fromHtml(string4)); 
 return view;
 }
  62. @Override
 public View getView(int position, View convertView, ViewGroup parent) {

    
 View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_jumpy_list, parent, false);
 
 TextView text1 = (TextView) view.findViewById(R.id.item_text1);
 TextView text2 = (TextView) view.findViewById(R.id.item_text2);
 TextView text3 = (TextView) view.findViewById(R.id.item_text3);
 TextView text4 = (TextView) view.findViewById(R.id.item_text4);
 
 String string1 = parent.getContext().getString(R.string.item_number_x, position);
 String string2 = parent.getContext().getString(R.string.item_description);
 String string3 = parent.getContext().getString(R.string.item_details);
 String string4 = parent.getContext().getString(R.string.item_related);
 
 text1.setText(Html.fromHtml(string1));
 text2.setText(Html.fromHtml(string2));
 text3.setText(Html.fromHtml(string3));
 text4.setText(Html.fromHtml(string4)); 
 return view;
 }
  63. @Override
 public View getView(int position, View convertView, ViewGroup parent) {

    
 No recycling
 
 No View Holder
 
 
 Html.fromHtml is slow 
 return view;
 }
  64. None
  65. But 200ms? Isn't this a bit much?

  66. But 200ms? Isn't this a bit much? Method tracking has

    a big impact itself
  67. None
  68. Systrace measure at kernel level

  69. Systrace

  70. Systrace

  71. Systrace

  72. Systrace

  73. Systrace It will run for 5 seconds Hit Ok and

    scroll the list
  74. None
  75. Alert: Inflation during ListView recycling Time spent: 103.545 ms ListView

    items inflated: 20
  76. None
  77. None
  78. None
  79. None
  80. None
  81. @Override
 public View getView(int position, View convertView, ViewGroup parent) {


    
 View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_jumpy_list, parent, false);
 
 
 
 TextView text1 = (TextView) view.findViewById(R.id.item_text1);
 TextView text2 = (TextView) view.findViewById(R.id.item_text2);
 TextView text3 = (TextView) view.findViewById(R.id.item_text3);
 TextView text4 = (TextView) view.findViewById(R.id.item_text4);
 
 
 String string1 = parent.getContext().getString(R.string.item_number_x, position);
 String string2 = parent.getContext().getString(R.string.item_description);
 String string3 = parent.getContext().getString(R.string.item_details);
 String string4 = parent.getContext().getString(R.string.item_related);
 
 
 text1.setText(Html.fromHtml(string1));
 text2.setText(Html.fromHtml(string2));
 text3.setText(Html.fromHtml(string3));
 text4.setText(Html.fromHtml(string4));
 
 
 return view;
 }
  82. @Override
 public View getView(int position, View convertView, ViewGroup parent) {


    Trace.beginSection("inflating");
 View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_jumpy_list, parent, false);
 Trace.endSection();
 
 Trace.beginSection("noViewHolder");
 TextView text1 = (TextView) view.findViewById(R.id.item_text1);
 TextView text2 = (TextView) view.findViewById(R.id.item_text2);
 TextView text3 = (TextView) view.findViewById(R.id.item_text3);
 TextView text4 = (TextView) view.findViewById(R.id.item_text4);
 Trace.endSection();
 
 String string1 = parent.getContext().getString(R.string.item_number_x, position);
 String string2 = parent.getContext().getString(R.string.item_description);
 String string3 = parent.getContext().getString(R.string.item_details);
 String string4 = parent.getContext().getString(R.string.item_related);
 
 Trace.beginSection("fromHtml");
 text1.setText(Html.fromHtml(string1));
 text2.setText(Html.fromHtml(string2));
 text3.setText(Html.fromHtml(string3));
 text4.setText(Html.fromHtml(string4));
 Trace.endSection();
 
 return view;
 }
  83. None
  84. None
  85. inflating

  86. inflating noViewHolder

  87. inflating noViewHolder fromHtml

  88. None
  89. • obtainView = ~5ms

  90. • obtainView = ~5ms • doFrame = ~20 * obtainView

  91. • obtainView = ~5ms • doFrame = ~20 * obtainView

    • each frame = ~100ms
  92. • obtainView = ~5ms • doFrame = ~20 * obtainView

    • each frame = ~100ms Alert: Inflation during ListView recycling Time spent: 103.545 ms ListView items inflated: 20
  93. None
  94. Why is my app dropping below 60fps?

  95. Why is my app dropping below 60fps? Why is my

    app draining the battery?
  96. Why is my app draining the battery?

  97. Why is my app draining the battery? What is a

    wake lock?
  98. Why is my app draining the battery?

  99. Why is my app draining the battery? Battery Historian 2.0

  100. Why is my app draining the battery? Battery Historian 2.0

  101. Why is my app draining the battery? Battery Historian 2.0

    adb bugreport > bugreport.txt
  102. Why is my app draining the battery? Battery Historian 2.0

    adb bugreport > bugreport.txt go run battery-historian.go
  103. Why is my app draining the battery? Battery Historian 2.0

    adb bugreport > bugreport.txt go run battery-historian.go open http://127.0.0.1:9999
  104. None
  105. None
  106. None
  107. None
  108. None
  109. None
  110. None
  111. None
  112. None
  113. None
  114. None
  115. None
  116. None
  117. None
  118. None
  119. None
  120. public static class UselessWorker {
 Runnable uselessWorker = new Runnable()

    {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 } public static class UselessWorker {
 Runnable uselessWorker = new Runnable() {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 }
  121. public static class UselessWorker {
 Runnable uselessWorker = new Runnable()

    {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 } public static class UselessWorker {
 Runnable uselessWorker = new Runnable() {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 }
  122. public static class UselessWorker {
 Runnable uselessWorker = new Runnable()

    {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 } public static class UselessWorker {
 Runnable uselessWorker = new Runnable() {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 }
  123. public static class UselessWorker {
 Runnable uselessWorker = new Runnable()

    {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 } public static class UselessWorker {
 Runnable uselessWorker = new Runnable() {
 @Override
 public void run() {
 PowerManager pm = (PowerManager) BadApplication.instance
 .getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = 
 pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "cool wakelock tag");
 wakeLock.acquire();
 
 //Some useless, but hard work
 
 wakeLock.release();
 try {
 Thread.sleep(45 * 1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 work();
 }
 }
 };
 
 private void work() {
 new Thread(uselessWorker, "cool thread name").start();
 }
 }
  124. ? ? Questions