Chances are, you’ve encountered exceptions or crashes in your Android application that can be difficult to diagnose.
One of the most useful tools for debugging these issues is a stack trace.
A stack trace is a report of the function calls that were active at the time of an exception or crash. It can help you identify the root cause of the issue and pinpoint the location in your code where it occurred.
To help you make better use of your stack traces, we’ve reached out to our team of in-house Android developers for advice.
In this blog post, we’ll cover some techniques for reading and interpreting a stack trace in Android to help you better understand and troubleshoot your mobile apps. Additionally, we’ll provide a few code samples to show you exactly how to deploy these techniques.
Tips and techniques for reading a stack trace in Android
Reading and interpreting a stack trace in Android can seem daunting, but when you know where and what to look for, there’s no need to fret. The following tips and techniques will help you better understand the anatomy of a stack trace and each line of code’s relationship to potential issues.
Understanding the anatomy of the stack trace
- The first line of the stack trace indicates the type of exception.
- The remaining lines show the function calls that were made leading up to the exception. In that regard, all calls are listed in chronological order, with the most recent call listed at the top (or the second line of the stack trace) and the original call which triggered the exception at the bottom.
Locating the exception in the stack trace
- To identify the specific location in your code where the exception occurred, look for line numbers and filenames. This will help you pinpoint the particular part of your codebase where the crash happened.
Identifying patterns and issues within the stack trace
- Try to identify any error patterns or common lines in the stack traces. They may indicate a recurrent or repeated issue in the code.
- If a third-party library appears in the stack trace, it is essential to review the library’s official documentation and/or contact the library’s support team.
4 examples of how to read a stack trace in Android
To bring these tips and techniques to life, our team of Android engineers pulled together a few sample stack traces for clarity.
Sample 1: NullPointerException
Let’s start with a pervasive case, a NullPointerException
:
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.mylib.android.MyLogger.log(java.lang.String, java.lang.String)' on a null object reference
at com.mylib.android.MyLibBreadcrumbService.logCustom(MyLibBreadcrumbService.java:227)
at com.mylib.android.MyLibImpl.logBreadcrumb(MyLibImpl.java:1601)
at com.mylib.android.MyLib.logBreadcrumb(MyLib.java:565)
at com.myapp.ui.fragments.base.BaseFragment.onAttach(BaseFragment.java:23)
at androidx.fragment.app.Fragment.performAttach(Fragment.java:2922)
at androidx.fragment.app.FragmentStateManager.attach(FragmentStateManager.java:464)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:275)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3138)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3072)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:501)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1455)
at android.app.Activity.performStart(Activity.java:8076)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3660)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
NullPointerException stack trace analysis
Returning to our tips above, let’s start with the first line. The first line indicates the type of exception, java.lang.NullPointerException
, happened when the method 'void com.mylib.android.MyLogger.log(java.lang.String, java.lang.String)'
was trying to be invoked.
We can then read the second line to determine specifically where the crash occurred (com.mylib.android.MyLibBreadcrumbService.logCustom(MyLibBreadcrumbService.java:227)
.
With that in mind, it’s also essential to consider packages that could be at the root cause. Here, we can see two packages: com.mylib.android
and com.myApp
. Given this, it is likely that the crash is due to an external library included as a dependency in our project, specifically com.mylib.android
.
From here, we can open the file and navigate to that line in order to verify what may be causing the crash. If nothing in that line helps us understand the crash, we can move on to examining the remaining lines of code in the stack trace.
Sample 2: java.net.SocketTimeoutException
Another common exception you’ll come across is java.net.SocketTimeoutException
:
java.net.SocketTimeoutException (crashed) timeout
at okhttp3.internal.http2.Http2Stream$StreamTimeout.newTimeoutException (Http2Stream.kt:675)
at okhttp3.internal.http2.Http2Stream$StreamTimeout.exitAndThrowIfTimedOut (Unknown Source:8)
at okhttp3.internal.http2.Http2Stream.takeHeaders (Http2Stream.kt:143)
at okhttp3.internal.http2.Http2ExchangeCodec.readResponseHeaders (Http2ExchangeCodec.kt:96)
at okhttp3.internal.connection.Exchange.readResponseHeaders (Exchange.kt:106)
at okhttp3.internal.http.CallServerInterceptor.intercept (CallServerInterceptor.kt:79)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at com.myLib.android.mySdk.okhttp3.MyOkHttp3NetworkInterceptor.intercept (MyOkHttp3NetworkInterceptor.java:88)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at okhttp3.internal.connection.ConnectInterceptor.intercept (ConnectInterceptor.kt:34)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at okhttp3.internal.cache.CacheInterceptor.intercept (CacheInterceptor.kt:95)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at okhttp3.internal.http.BridgeInterceptor.intercept (BridgeInterceptor.kt:83)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept (RetryAndFollowUpInterceptor.kt:76)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at okhttp3.logging.HttpLoggingInterceptor.intercept (HttpLoggingInterceptor.kt:221)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at com.myLib.android.mySdk.okhttp3.MyOkHttp3AppInterceptor.intercept (MyOkHttp3AppInterceptor.java:46)
at okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.kt:109)
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp (RealCall.kt:201)
at okhttp3.internal.connection.RealCall$AsyncCall.run (RealCall.kt:517)
at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:637)
at java.lang.Thread.run (Thread.java:1012)
java.net.SocketTimeoutException stack trace analysis
Issues like the java.net.SocketTimeoutException
indicate a network problem.
Analyzing the stack trace, the first candidate for a potential root cause of our problem comes at line 88
where we find MyOkhttp3NetworkInterceptor
.
From here, we can examine two potential hypotheses:
- The interceptor is somehow failing and therefore generating the crash;
- Or, there’s likely a network problem on the device, and the app is not handling it correctly, leading to the crash. In that case, the interceptor is likely not causing the crash, but rather just passing the network calls through.
Sample 3: OutOfMemoryError
Now, let’s look at an OutOfMemoryError
:
java.lang.OutOfMemoryError: Failed to allocate a 775576 byte allocation with 235140 free bytes and 229KB until OOM RAW
at java.lang.String. (String.java:400)
at java.lang.AbstractStringBuilder.toString (AbstractStringBuilder.java:633)
at java.lang.StringBuffer.toString (StringBuffer.java:723)
at java.io.StringWriter.toString (StringWriter.java:100)
at com.google.gson.Gson.toJson (Gson.java:639)
at com.myapp.android.instrumentation.GsonInstrumentation.toJson (GsonInstrumentation.java:37)
at com.lib.android.libsdk.MyCacheService.cacheObject (MyCacheService.java:56)
at com.lib.android.libsdk.MyCacheService.sessionService.cacheSessionMessage (SessionService.java:525)
at com.lib.android.libsdk.MyCacheService.service.runEndSession (SessionService.java:498)
at com.lib.android.libsdk.MyCacheService.sessionService.endSession (SessionService.java:480)
at com.lib.android.libsdk.MyCacheService.sessionService.onBackground (SessionService.java:431)
at com.lib.android.libsdk.MyCacheService.taskService.lambda$onBackground$4 (TaksService.java:204)
at com.lib.android.libsdk.MyCacheService..-$$Lambda$activityService$tFGWps1my5O8EOtTloy3FsZC5Xc.accept (lambda)
at java9.util.Spliterators$ArraySpliterator.forEachRemaining (Spliterators.java:1267)
OutOfMemoryError Stack Trace Analysis
The example above demonstrates an OutOfMemoryError
and includes lines belonging to an external dependency called com.lib.android.libsdk
. At first glance, it appears that the cache string is too large, causing an out-of-memory error when MyCacheService
attempts to write it to the buffer in the service.
The first assumption could be that the library is causing the out-of-memory error. However, let’s analyze the cause of the error:
java.lang.OutOfMemoryError: Failed to allocate a 775576-byte allocation with 235140 free bytes and 229KB until OOM RAW
.
The device was already low on memory with less than 300K
of free heap space, which may indicate a need for performance improvement rather than this being a pervasive problem.
Sample 4: RuntimeException
RuntimeException
is the superclass of exceptions that can be thrown during the normal operation of the Java Virtual Machine.
Caused by: java.lang.RuntimeException: Duplicate class
com.myLib.android.sdk.LifecycleLogger
found in modules jetified-myLib-android-sdk-1.1.0-runtime (com.myLib: myLib-android-sdk:1.1.0) and
jetified-myLib-android-sdk-1.2.0-runtime (com.myLib: myLib-android-sdk-1.2.0.aar)
RuntimeException stack trace analysis
In this case, the error is really straightforward. Two instances of com.myLib.android.sdk
were created while the app was running. When this happens, the app doesn’t know which instance it should run, and so it crashes instead. These types of crashes, while easy to identify and fix, are killers for user experience and can directly lead to user churn — making them important to find and fix proactively, when possible.
Sample 5: Android NDK crash
This sample shows an Android NDK crash.
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 31514 (ndkcrashcapture), pid 31514 (ndkcrashcapture)
Cmdline: com.myapp.ndkscreen
pid: 31514, tid: 31514, name: ndkcrashcapture >>> com.myapp.ndkscreen <<<
#00 pc 0000000000010354 /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/lib/arm64/libnative-lib-crash-test.so (AnotherClass::sigsegv()+16) (BuildId: fcb1f2bdd88daec6d71180516c0465296a9f5bd0)
#01 pc 0000000000010528 /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/lib/arm64/libnative-lib-crash-test.so (TestClass::sigsegv()+36) (BuildId: fcb1f2bdd88daec6d71180516c0465296a9f5bd0)
#02 pc 0000000000010a60 /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/lib/arm64/libnative-lib-crash-test.so (Java_com_myapp_ndkscreen_MyActivityKt_runMyCode+28) (BuildId: fcb1f2bdd88daec6d71180516c0465296a9f5bd0)
#10 pc 000000000000132c [anon:dalvik-classes3.dex extracted in memory from /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/base.apk!classes3.dex]
#13 pc 00000000000011c8 [anon:dalvik-classes3.dex extracted in memory from /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/base.apk!classes3.dex]
#16 pc 0000000000000dac [anon:dalvik-classes3.dex extracted in memory from /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/base.apk!classes3.dex]
---------------------------- PROCESS ENDED(31514) for package com.myapp.ndkscreen ----------------------------
This error was caused by a segmentation fault (signal 11 (SIGSEGV)
), and the code reports that the fault address is 0x0 and the fault type is SEGV_MAPERR
.
The stack trace provides information about the call stack at the moment of the crash, which can help identify the issue’s root cause. In this example, there are several frames of note.
Let’s focus on the first three lines:
#00 pc 0000000000010354 /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/lib/arm64/libnative-lib-crash-test.so (AnotherClass::sigsegv()+16) (BuildId: fcb1f2bdd88daec6d71180516c0465296a9f5bd0)
The code here is showing us that the crash occurred in the class AnotherClass at address 0000000000010354.#01 pc 0000000000010528 /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/lib/arm64/libnative-lib-crash-test.so (TestClass::sigsegv()+36) (BuildId: fcb1f2bdd88daec6d71180516c0465296a9f5bd0)
Here, we can see the subsequent class of the crash, which in this case is TestClass, located at address 0000000000010528.#02 pc 0000000000010a60 /data/app/~~ACEvgQ3ytztSdGjEjeUeTg==/com.myapp.ndkscreen-brDLRKuYODpXSmvA4Gp6WA==/lib/arm64/libnative-lib-crash-test.so (Java_com_myapp_ndkscreen_MyActivityKt_runMyCode+28) (BuildId: fcb1f2bdd88daec6d71180516c0465296a9f5bd0)
Finally, we have the function call that triggered the crash. By analyzing this lineJava_com_myapp_ndkscreen_MyActivityKt_runMyCode
, we can conclude that the runMyCode function inside MyActivityKt caused or triggered the crash at address 0000000000010a60.
Go beyond basic stack traces with Embrace intelligent Crash Reporting
Knowing how to read and analyze a stack trace is an important skill for any Android developer to learn and perfect. But it’s just a small piece of the puzzle when it comes to improving performance, decreasing time to resolution, and spending more time on building great mobile experiences.
While the tips and techniques outlined above will help you more effectively address known issues, they do little to help you proactively understand when something is wrong with your app.
Embrace intelligent Crash Reporting helps mobile development teams quickly identify high-priority crashes and their root causes through accurate crash grouping, intelligent scoring, and user context, allowing them to focus on delivering exceptional experiences without having to solve the same crash more than once.
With Embrace, you always know when action needs to be taken, and where the root cause of the issue lies, so you can spend less time fixing and more time building.
Learn more about Embrace’s intelligent Crash Reporting tools and get started for free today.
Get started today with 1 million free user sessions.
Get started free