Chapters

Hide chapters

Real-World Android by Tutorials

Second Edition · Android 12 · Kotlin 1.6+ · Android Studio Chipmunk

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters

21. Advanced Debugging
Written by Subhrajyoti Sen

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

When you develop mobile apps, you’ll often have issues that are hard to debug. The app might be might very slow for some users or drain too much battery for others. Or you might find that the UI is a bit laggy or doesn’t quite match the design mock-ups. Debugging these issues can be tedious. Fortunately, there are tools that make the process easier.

In this chapter, you’ll learn about:

  • Finding and fixing memory leaks using LeakCanary.
  • Using the Memory Profiler to find Fragment and Activity leaks.
  • Examining network calls using the Network Inspector.
  • Finding Wake Locks using the Energy Profiler.
  • Using Layout Inspector to improve your layouts.

You’ll start by looking at memory leaks.

Memory Leaks

In Java-based environments, the garbage collector frees up memory allocated to objects that are no longer used and are eligible for collection. An object is eligible for collection when no active process references it. Sometimes, however, a process keeps a reference to objects you don’t need anymore, causing a memory leak. Android apps have limited memory, so leaks can cause OutOfMemoryError exceptions.

Therefore, it’s essential to find and fix memory leaks early, before they degrade your app’s performance. LeakCanary is a library that simplifies memory leak detection in your app. It works by creating a dump of the heap memory and parsing it to find the source of the leak.

Installing LeakCanary

To install LeakCanary, add the following dependency to your app build.gradle:

debugImplementation "com.squareup.leakcanary:leakcanary-android:2.8.1"

Adding Obfuscation Support

Since you enabled Proguard on the debug build variant, LeakCanary needs some extra setup. You can skip this setup if you disable Proguard for debugging.

classpath 'com.squareup.leakcanary:leakcanary-deobfuscation-gradle-plugin:2.9.1'
apply plugin: 'com.squareup.leakcanary.deobfuscation'
leakCanary {
  filterObfuscatedVariants { variant ->
    variant.name == "debug"
  }
}

Detecting Memory Leaks

There’s no secret map that can help you find memory leaks. In your regular development workflow, you won’t look for memory leaks explicitly. Instead, you just install LeakCanary and continue to develop your app as normal. If there is a leak, LeakCanary will notify you by adding a notification to the system notification tray.

Figure 21.1 — LeakCanary Memory Leak Notification
Qiboxu 71.0 — PiumNerezd Qetevt Qeoq Parezofitiuq

Finding the Leak Source

In this section, you’ll use the heap dump to find the source of the leak.

Figure 21.2 — The Leaks App
Lukaga 49.0 — Yya Doejn Els

Figure 21.3 — LeakCanary Leak List
Zogusa 51.4 — PoamKukekf Zeuc Vick

Figure 21.4 — Leak Information Details
Vubini 38.9 — Tioj Iphufxobees Geruugv

Figure 21.5 — Leak Sources
Remojo 45.3 — Kaah Weifnir

Understanding the Leak Cause

You now know some important information: The fling animation is causing a memory leak via the FloatingActionButton named call. It’s time to figure out why.

Plugging the Leak

To fix this leak, you have to make sure that AnimalDetailsFragment doesn’t contain any global variables that hold a reference to a view.

private val callScaleXSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_X).apply {
  spring = springForce
}

private val callScaleYSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_Y).apply {
  spring = springForce
}

private val callFlingXAnimation = FlingAnimation(binding.call, DynamicAnimation.X).apply {
  friction = FLING_FRICTION
  setMinValue(0f)
  setMaxValue(binding.root.width.toFloat() - binding.call.width.toFloat())
}

private val callFlingYAnimation = FlingAnimation(binding.call, DynamicAnimation.Y).apply {
  friction = FLING_FRICTION
  setMinValue(0f)
  setMaxValue(binding.root.height.toFloat() - binding.call.width.toFloat())
}
private fun displayPetDetails(animalDetails: UIAnimalDetailed, adopted: Boolean) {
  val callScaleXSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_X).apply {
    spring = springForce
  }

  val callScaleYSpringAnimation = SpringAnimation(binding.call, DynamicAnimation.SCALE_Y).apply {
    spring = springForce
  }

  val callFlingXAnimation = FlingAnimation(binding.call, DynamicAnimation.X).apply {
    friction = FLING_FRICTION
    setMinValue(0f)
    setMaxValue(binding.root.width.toFloat() - binding.call.width.toFloat())
  }

  val callFlingYAnimation = FlingAnimation(binding.call, DynamicAnimation.Y).apply {
    friction = FLING_FRICTION
    setMinValue(0f)
    setMaxValue(binding.root.height.toFloat() - binding.call.width.toFloat())
  }

  binding.call.scaleX = 0.6f
  binding.call.scaleY = 0.6f
  //... rest of the method
}

Android Studio Profiler

In recent versions of Android Studio, Google has significantly improved the tools you can use to debug complicated issues, especially the Profiler.

Figure 21.6 — Android Studio Profiler
Lomela 55.5 — Ojkcuip Xsotuu Nbivujax

Finding Memory Leaks With the Memory Profiler

In addition to using LeakCanary, you can also use Android Studio’s Profiler to detect memory leaks. Android Studio 3.6 added support for automatic detection of Activity and Fragment leaks. In this section, you’ll introduce a memory leak in the codebase that leaks a Fragment. You’ll then use the Memory Profiler to find and trace the leak.

Introducing a Fragment Leak

Open MainActivity.kt and add the following global variable before onCreate():

lateinit var currentFragment: Fragment
(requireActivity() as MainActivity).currentFragment = this

Detecting and Tracing the Leak

Build and run. Once the app is running on a device, open the Profiler tab and start a session as shown below:

Figure 21.7 — Starting a Profiler Session
Qojowe 83.7 — Bzudvihm i Jruniwib Dapsuod

Figure 21.8 — Memory Profiler
Jugimo 91.2 — Wefonv Zzuharuv

Figure 21.9 — Memory Profiler Heap Dump
Quhese 29.2 — Gohidd Vtikebeh Taac Dagv

Figure 21.10 — Example of Memory Leak in the Memory Profiler
Qunaza 36.31 — Epecnpi ec Fubibm Qaol ob pfu Difihq Fbuzeqok

Figure 21.11 — Memory Profile Leak Instance List
Wiceko 62.98 — Josuzk Hvanozo Seih Ehmcayhi Haks

Figure 21.12 — Memory Profile Leaks References
Siyero 37.51 — Segujj Mpulelo Baijf Peziyusfav

Network Inspector

Up until now, you’ve probably used HttpLoggingInterceptor to analyze your network calls by logging the network requests and their responses. This approach works fine if you’re interested in individual calls and just want to verify that they take place.

Why Network Profiling Matters

You might think you’ll only use the Network Inspector to find details of network calls when integrating new features or APIs, but Network Inspector can do much more.

Navigating the Network Inspector

Before inspecting your network activity, you need to disable proguard on the debug build. Remove the leakCanary block from the app build.gradle. Also, set minifyEnabled to false for the debug build type.

Figure 21.13 — Android Studio Network Inspector
Kosoci 97.80 — Eybpoal Pgafeo Bigmepm Ilsruvpel

Figure 21.14 — Network Inspector Details
Qebife 81.61 — Barzint Ehlravwod Gosuofl

Figure 21.15 — Network Inspector Thread View
Remeze 92.98 — Vorzozs Uhhjuqlij Yknaod Soup

Figure 21.16 — Network Inspector Request Detail
Bijati 07.07 — Bajkuxk Ewhmutneg Dosoepb Supier

Energy Profiler

The battery usage of an app is a vital metric to track. Users care a lot about their phone’s battery.

Figure 21.17 — Android Studio Energy Profiler
Rotojo 32.32 — Evdkaov Ckayoe Emuskz Qpekaned

Finding a System Event

Consider a scenario where you’re new to a codebase and you need to find out why your app is draining the battery. The Energy Profiler is one of the best places to start.

Figure 21.18 — Energy Profiler Tooltip
Zipise 17.55 — Uputcs Vzacivos Tiogdar

Figure 21.19 — System Events Has Moved Message
Lusofe 86.34 — Xwqsaw Uhedxh Xar Diroh Kicneya

Figure 21.20 — Energy Profiler Wake Lock
Lufawo 63.42 — Ebufvn Planulex Jadi Zixh

Figure 21.21 — Energy Profiler Wake Lock Details
Qahaka 89.42 — Ipostp Jsuvowaq Vajo Yikd Tuhoejl

wakeLock = (requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager).run {
        newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
          acquire()
        }
      }
override fun onDestroy() {
  super.onDestroy()
  wakeLock.release()
}

Layout Inspector

When you implement your app’s UI, you have to keep many things in mind, like:

Starting the Layout Inspector

To start the Layout Inspector, select View ▸ Tool Windows ▸ Layout Inspector, as shown below:

Figure 21.22 — Android Studio Layout Inspector
Falori 92.34 — Alslaur Nvonui Fiseof Ubvpatsut

Figure 21.23 — Layout Inspector Process Selection
Nemona 89.25 — Ginuux Ojnvipqar Cderavq Qiqeybaid

Figure 21.24 — Layout Inspector Components Tree
Wisepu 56.12 — Naleim Ucqwojlan Mupbomaptj Nxei

Finding Unnecessary Nesting

With the Layout Inspector open, visit the Near You tab in the app. To see the View Hierarchy in 3D, you need to select Rotate View on the right side of the Layout Inspector window:

Figure 21.25 — Layout Inspector’s Rotate View
Joceva 79.07 — Jupaag Aqsfizbuv’y Pikule Paur

Figure 21.26 — Layout Inspector View Hierarchy
Nupiwa 27.47 — Tudioz Epcdoczab Soew Huicatwwd

Figure 21.27 - Layout Inspector Nested LinearLayout Example
Lipeme 66.75 - Padeor Uwdlefxor Qecfop SuvaoyWahueg Obajfzi

<LinearLayout
  android:layout_width="match_parent"
  android:ayout_height="match_parent">

  <LinearLayout
    android:id="@+id/recycler_view_item"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView...>
  
    <TextView...>

  </LinearLayout>

</LinearLayout>
<com.google.android.material.card.MaterialCardView>

  <LinearLayout>

    <ImageView/>

    <TextView/>

  </LinearLayout>
</com.google.android.material.card.MaterialCardView>

Comparing the Layout With a Design Mock

Designers usually use a specific device as a reference to provide UI mock-ups. For this section, assume that your designer provided mock-ups based on a Pixel 4. You’ll create a new Android Virtual Device based on Pixel 4.

Figure 21.28 — Layout Inspector Overlays
Wuliyu 06.61 — Xuluaq Iscveyxiv Owugrudx

Figure 21.29 — Layout Inspector Overlay Example
Xegamu 42.12 — Yeruah Ehxhabnoy Ibudbel Ecirbnu

Key Points

  • Use LeakCanary to find and rectify memory leaks in your app.
  • Avoid holding view references in global variables. If you have to, remember to clear out the reference in the correct lifecycle callback.
  • Find Activity and Fragment leaks using the Memory Profiler.
  • Use the Network Inspector to examine your network calls.
  • Use batching to avoid making frequent network calls.
  • The Energy Profiler helps find components and events that are likely to take up significant battery.
  • Use the Layout Inspector to remove extra nested views and to make your layouts match the design mock-ups.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now