Android Profiler is a hard and fast of equipment available from Android Studio 3.0 that replace preceding Android Monitor tools. The new suite is far more advanced in diagnosing app performance problems. It comes with a shared timeline view and specific CPU, Memory and Network profilers. By the usage of it skilfully, we will store a variety of time wasted on debugging or scrolling logs in a Logcat window.
To get admission to profiling gear click on View > Tool Windows > Android Profiler or discover a corresponding tool window in the toolbar. To see realtime records you want to connect a tool with enabled USB debugging or use Android emulator and feature the app system decided on. I encourage you to study the legitimate Android person manual to discover ways to investigate all the statistics displayed on this window.
Do you like getting to know by way of examples? I prepared two samples to exercise with the CPU Profiler. These are small apps that encountered a few overall performance problems. Let’s try to clear up them!
Android Profiler Sample 1
Android Profiler We will begin from growing a simple Android utility that shows a listing of sequential dates (which hasn’t came about yet). Below each date we will show ultimate time in days, hours, minutes and seconds.
The code from each samples is to be had on GitHub, so that you can without problems clone the repository and open the assignment in Android Studio. For now, checkout the revision tagged pattern-1-before.
Start with defining a format which includes RecyclerView positioned inner SwipeRefreshLayout. It will permit the information to refresh at the vertical swipe gesture:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
tools:listitem="@layout/simple_list_item" />
</android.support.v4.widget.SwipeRefreshLayout>
Android Profiler Next, create Activity that inflates our format, handles consumer interplay and plays operations on the primary thread to display refreshed information:
class Sample1Activity : AppCompatActivity() {
private val items = mutableListOf<Item>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sample_1_activity)
recyclerView.adapter = basicAdapterWithLayoutAndBinder(
items, R.layout.simple_list_item, ::bindItem
)
swipeRefreshLayout.setOnRefreshListener(::refreshData)
}
private fun refreshData() {
items.run { clear(); addAll(generateItems()) }
recyclerView.adapter.notifyDataSetChanged()
swipeRefreshLayout.isRefreshing = false
}
}
private fun generateItems(): List<Item> {
val now = LocalDateTime.now()
return List(1_000) { createItem(now, it + 1) }
}
private fun createItem(now: LocalDateTime, offset: Int): Item {
val date = now.plusDays(offset.toLong()).toLocalDate().atStartOfDay()
return Item(
formattedDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE),
remainingTime = getRemainingTime(now, date)
)
}
private fun getRemainingTime(start: LocalDateTime, end: LocalDateTime): String {
val duration = Duration.between(start, end)
val days = duration.toDays()
val hours = duration.minusDays(days).toHours()
val minutes = duration.minusDays(days).minusHours(hours).toMinutes()
val seconds = duration.minusDays(days).minusHours(hours).minusMinutes(minutes).seconds
return buildString {
if (days > 0) append("$days d")
if (hours > 0) append(" $hours h")
if (minutes > 0) append(" $minutes min")
if (seconds > 0) append(" $seconds s")
}.trim()
}
private fun bindItem(holder: ViewHolderBinder<Item>, item: Item) = with(holder.itemView) {
dateView.text = item.formattedDate
remainingTimeView.text = resources.getString(R.string.remaining, item.remainingTime)
}
private data class Item(val formattedDate: String, val remainingTime: String)
In line nine we use RecyclerView adapter. We are the use of recycler library from android-commons (used in most EL Passion Android projects). A regularly occurring characteristic takes a list of gadgets, item format aid reference and a binder. The goal is to hold the code concise and setup RecyclerView adapter without any boilerplate code.
At the end of onCreate feature we set the listener to be notified approximately refresh actions brought on by using SwipeRefreshLayout. The referenced refreshData feature replaces the listing with sparkling new gadgets and notifies the adapter about any data exchange.
In line 25 we generate a listing of a thousand objects. Each item sets its homes when it comes to the contemporary date time and the offset in days. Offset takes values from 0 to 999 and affects the date displayed by way of item (see line 29). We use ThreeTenABP as our dates and duration API. It is an invaluable backport of java.Time.* bundle optimized by using Jake Wharton for Android.
In line 36 we perform some operations to get hold of final time as a greater human-readable length.
In line 50 we bind an item with the view holder to replace itemView at a particular function. We get right of entry to assets to get the string formatted with remainingTime fee.
Item itself holds formattedDate and remainingTime values geared up to show inside the corresponding TextView components. Let’s use the subsequent item format:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/dateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:lines="1"
android:textColor="@android:color/black"
android:textSize="20sp"
tools:text="@tools:sample/date/ddmmyy" />
<TextView
android:id="@+id/remainingTimeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:lines="1"
tools:text="Remaining: 2 d 1 h 23 min 5 s" />
</LinearLayout>
Launch the app and swipe to refresh facts. Have you observed a freeze? Possibly no longer. That strongly depends on your device’s CPU and different processes eating CPU time. Now, launch Android Profiler Tool Window and pick out the proper timeline to open CPU Profiler. Connect your tool and swipe to refresh again. Note that profiler threads are delivered to the app system and eat additional CPU time. I count on that now you have got already experienced frames skipping. Look at the Logcat because the choreographer need to have warned you already approximately heavy processing:
I/Choreographer: Skipped 147 frames! The application may be doing too much work on its main thread.
Above the chart there is a view representing user interaction with the app. All the consumer enter occasions show up right here as red circles. You can see one circle that represents the swipe we done to refresh statistics. A little lower you can locate currently displayed Sample1Activity. This vicinity is known as Event timeline.
Below activities, there may be a the CPU timeline, that graphically suggests the CPU utilization of the app and other methods on the subject of the full CPU time to be had. What’s greater, you could watch the wide variety of threads your app is the usage of.
At the lowest you can see the Thread hobby timeline belonging to the app manner. Each thread is in certainly one of three states indicated by means of colours: active (inexperienced), waiting (yellow) or snoozing (gray). At the top of the list you may find the app’s most important thread. On my tool (Nexus 5X) it uses ~35% of CPU time for approximately five seconds. That’s a lot! We can document a technique hint to see what’s taking place in there.
Click the the Record button 🔴 right earlier than swiping to refresh movement and prevent recording ⏹ quickly after records refresh completes. When you’re carried out, note that the method hint pane has just regarded:
Android Profiler We will start our analysis from the Call Chart displayed inside the first tab. The horizontal axis represents the passage of time. Callers and their callees (from top to backside) are displayed on the vertical axis. Method calls also are prominent by way of coloration relying if it’s far call to gadget API, third-birthday party API or our technique. Note that overall time for each method name is a sum of method self-time and its callees time. From this chart, you could deduce that the overall performance issue is somewhere inner generateItems approach. Move a mouse over the bar to test greater information about elapsed time. You also can double-click on bar to look method assertion within the code. It is pretty tough to infer extra from this tab because it requires a variety of zooming and scrolling, so we can switch to the following tab.
The Flame Chart is lots higher to show which techniques took our device precious CPU time. It aggregates identical call stacks, inverting chart from the preceding tab. Instead of many quick horizontal bars, single longer bar is displayed. Just examine it now:
Wo suspicious methods discovered. Would you accept as true with that getRemainingTime the full approach execution time will take over 2 seconds and LocalDateTime.Layout over 1 2nd of CPU time?
Note that this time consists of additionally any time frame when thread became now not lively. In the higher proper nook of the method hint pane, you can switch timing data to be displayed within the Thread Time. If we analyse a single thread that is probably favored alternative because it indicates CPU time intake no longer affected by other threads.
Ok, let’s pass on. Now open the final tab to look the Bottom Up chart. It shows a list of approach calls looked after descending by way of CPU time consumption. This chart will give us certain timing facts (in microseconds). Expanding the methods you can find their callers.
Get out of the chart timing information approximately strategies we accused of eating an excessive amount of CPU time. Place them on the subject of methods from their name stack:
class | method | total time (in ms) | percentage of recorded duration |
Sample1Activity | refreshData | 3027 | 89.28 |
Sample1Activity | generateItems | 3025 | 89.22 |
Sample1Activity | getRemainingTime | 1723 | 50.83 |
LocalDateTime | format | 1003 | 29.59 |
You can see that getRemainingTime and LocalDateTime.Format devour over 80% of recorded method hint! To restoration that freeze, we want to paintings on generating items. That’s apparent.
So, what to do? You’ve probable give you numerous solutions already. We perform a heavy computation to create 1000 objects (now not a small variety). You can reflect onconsideration on enforcing a pagination to progressively create and display the facts. That’s a exceptional idea because it’s going to scale. However, this time I would really like to head another manner. What if we carry out all of the formatting recently before displaying the facts in RecyclerView at unique function — when we bind Item with RecyclerView.ViewHolder? Thanks to that, we are able to invoke getRemainingTime and LocalDateTime.Layout strategies only for few currently displayed and equipped to show gadgets — not thousand instances as earlier than. To attain it we want to update Item residences to maintain most effective essential facts to carry out formatting later:
data class Item(val now: LocalDateTime, val offset: Int)
That calls for making use of following modifications in generateItems and bindItem features:
private fun generateItems(): List<Item> {
val now = LocalDateTime.now()
return List(1_000) { Item(now, it + 1) }
}
private fun bindItem(holder: ViewHolderBinder<Item>, item: Item) = with(holder.itemView) {
val date = item.now.plusDays(item.offset.toLong()).toLocalDate().atStartOfDay()
val remainingTime = getRemainingTime(item.now, date)
dateView.text = date.format(DateTimeFormatter.ISO_LOCAL_DATE)
remainingTimeView.text = resources.getString(R.string.remaining, remainingTime)
}
Let us see that we inlined the createItem feature considering the fact that all of the formatting now takes place in the bindItem technique. Checkout the revision tagged sample-1-after to receive those modifications.
It’s time to relaunch the CPU Profiler and report the technique trace after changes in our code have been delivered. Look at the Call Chart to test if our optimization went well:
If you move the mouse over the generateItems characteristic, you may find out that now it consumes ~0.Three seconds of the wall clock time. That’s over thirteen times less CPU time than earlier than optimization! Before we start celebrating, allow’s switch to the Flame Chart to make sure our adjustments haven’t any poor impact on overall length of the bindItem method. Fortunately, it consumes as much as zero.1 seconds.
Additionally, you may scroll it to ensure our optimization doesn’t affect standard app performance. Try to file the method trace at some stage in this type of scroll. See that choreographer no longer complains approximately skipping frames. Success! The code is optimized and we’re performed with the first pattern!
Sample 2
In the following sample, we will on the whole reuse the code from the sample 1 after optimization. The best trade we can make in interest layout. We will add an ImageView above the RecyclerView. To make the entire content scrollable positioned each perspectives inner NestedScrollView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@android:drawable/ic_menu_my_calendar"
tools:ignore="ContentDescription" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
tools:listitem="@layout/simple_list_item" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
To keep away from conflicts in scrolling behaviour of the RecyclerView we need to set the nestedScrollingEnabled attribute to false. Checkout the revision tagged pattern-2-earlier than to pull this sample fast. Launch the app and swipe to refresh the information. You should observe a freeze even with out Android Profiler connected.
This time, I determined to will let you carry out prognosis on your personal no longer to ruin your amusing. After successful optimization, you shouldn’t come upon any freeze like in the pattern 1. There’s most effective one rule —the display screen displayed to the consumer can not trade its appearance. Good luck!