【Quipperブログ】Monitor and fix app's performance with Android vitals
こんにちは。Quipper採用担当の鈴木です。今回の記事は、@padobariyaによる「Monitor and fix app's performance with Android vitals」です！是非、ご覧ください！
Hi! I am @padobariya again! working as a mobile engineer with Quipper (Japan office).
In this post, I will talk about Android vitals and how to monitor your app's technical performance and some of the possible fixes. It is a tool by Google to improve the stability and performance of Android apps. This article is useful for those who already published an Android app to Google Play or planning to release in the near future.
When an opted-in user runs your app, their device logs various metrics, including data about app stability, render time, and battery usage. The Play Console aggregates this data and displays it in the Android Vitals dashboard.
The dashboard highlights crash rate, ANR rate, excessive wakeups, and stuck wake locks, these are the core vitals developers should give attention to. This information helps you understand the performance of your app and alerts you when your app is exhibiting bad behaviors.
Why Android vital is so vital
It's no secret, we as users nobody wants to deal with an app that drains power, crashes randomly or freezes. As the following google stats suggests all these parameters have a direct impact on user reviews which is a leading indicator of customer satisfaction.
Google analysis of play store review
- Higher performance leads to higher app ratings, which in turn leads to a higher number of installs and user engagement.
- Most of the users abandon an app after using them only once due to poor performance.
- With 2.7 million apps available as of June 2019, users can easily find a replacement if yours disappoints. When consumers have options, the pressure is on you to deliver a quality product, and a substantial part of that promise is stability.
- When ANR or crash rate increases the average time spent in-app decrease significantly.
- App with lower crash rate appeared above in search than the app with higher crash rate, and also to be considered in Featured apps and Editors Choice.
As of now, Android vitals collects data and reports issues on the following broad categories, We will discuss the core android vitals category in detail and possible fix for them.
Stability refers to how often an app is crashing or displaying Application Not Responding (ANR) error messages.
An ANR occurs when an app’s UI thread is blocked for a long period. Users are given the option to quit or wait when an ANR occurs.
Android will display the ANR dialog for a particular application when it detects one of the following conditions:
- No response to an input event (such as key press or screen touch events) within 5 seconds.
- A BroadcastReceiver hasn't finished executing within 10 seconds.
How to avoid:
- Avoid network or disc operation, long calculations on the main thread. To detect and ensure not doing this use StrictMode in debug build.
- Any method that runs in the UI thread should do as little work as possible on that thread. In particular, activities should do as little as possible to set up in key life-cycle methods such as onCreate() and onResume(). Potentially long-running operations such as network or database operations, or computationally expensive calculations such as resizing bitmaps should be done in a worker thread (or in the case of database operations, via an asynchronous request).
- As a general rule, broadcast receivers are allowed to run for up to 10 seconds before the system will consider them non-responsive and ANR the app. The specific constraint on BroadcastReceiver execution time emphasizes what broadcast receivers are meant to do: small, discrete amounts of work in the background such as saving a setting or registering a Notification. So as with other methods called in the UI thread, applications should avoid potentially long-running operations or calculations in a broadcast receiver. But instead of doing intensive tasks via worker threads, your application should start an IntentService if a potentially long-running action needs to be taken in response to an intent broadcast.
- Another common issue with BroadcastReceiver objects occurs when they execute too frequently. Frequent background execution can reduce the amount of memory available to other apps. For more information about how to enable and disable BroadcastReceiver objects efficiently, see Manipulating Broadcast Receivers on Demand.
- If your application has a time-consuming initial setup phase, consider showing a splash screen or rendering the main view as quickly as possible, indicate that loading is in progress and fill the information asynchronously.
- Use performance tools such as Systrace and Traceview to determine bottlenecks in your app's responsiveness.
Crashes, on the other hand, occur as a result of unhandled exceptions.
Android Vitals takes into consideration that it can take different time spans for an app to start up, depending on how long it has been since it was last opened. For instance, a cold start is when an app hasn’t been used in a while, where the activity goes from launched to running state. A slow cold start would be anything taking 5+ seconds.
A slow warm start (when the app has been used recently and is cached in memory) counts as 2+ seconds, and a slow hot start (when both app and activity are in memory) counts as 1.5 seconds.
How to reduce:
- The chief reason for a poor cold-startup time is doing too much work in the onCreate() of your launcher activity. Check the onCreate of your class and move all the independent and unblocking but time-consuming methods to a separate thread.
- Make sure you use lazy initializations wherever you can so that the onCreate() is not bombarded by unnecessary method or class initializations.
- Use Local variables wherever possible.
- Use Dagger2 for dependency injection: Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It implements the dependency injection design pattern without the burden of writing the boilerplate and aims to address many of the development and performance issues that have plagued reflection-based solutions.
- Using Handler with delay: Now this one might sound like a hack but it does wonders to your app startup performance in cases where there is some piece of lethargic code which you cannot afford to shift from the main thread, you can put in a Handler and use the postDelayed method, adding an appropriate delay (100 to 200ms should do just fine). This takes the weight off the onCreate and your app launches much quicker than before, with unnoticeable delay in the execution of the delayed method.
- Restructure the splash screen: Image rendering is a time-consuming process and can mess up with your startup time. If your app uses a splash screen that displays your logo in an ImageView, one optimization you can do is to remove the ImageView from the xml and instead add the logo to the theme of your activity as window background.
Ideally, an app’s speed of rendering should be under 16ms to hit 60 frames per second. Anything that takes longer than 16ms is considered slow.
Slow UI rendering results in frames being skipped, making for a clunky experience. If frames take longer than 700ms to render, they are considered to be frozen.
Here are some of the methods to identify slow rendering:
How to reduce:
- Avoid Layout overdrawing: Overdraw describes how a pixel on the screen drawn multiple times during the same frame. There are still performance issues in rendering such as the most prone: over-rendering/over-drawing. In a multi-level attempted structure, if the invisible UI is also doing the drawing operation, some pixel area will be drawn multiple times, which will waste a lot of CPU and GPU resources.
- Refresh rate and Frame loss: We all know Android system redraws the activity every 16ms, which means that your app must complete all the logical operations of the screen refresh within 16ms, So, that it can reach 16 frames/s. However, sometimes your program will have such a situation where your logical operation exceeds 16ms. At this time the frame loss will occur and a user sees the updated picture within 32ms. The more time it takes to execute the code in the main thread, the more the frame loss is. It can cause the UI to freeze or sometimes can also cause glitches.
- Flatten the view hierarchy to reduce nesting: Along similar lines, the layout should not be deeply nested, in terms of parent and child layouts. If possible use ConstraintLayout to avoid duplicate nested layouts.
- RecyclerView: notifyDataSetChanged(): Every item in your RecyclerView being rebound (and thus re-laid out and re-drawn) in one frame, make sure that you're not calling notifyDataSetChanged(), setAdapter(Adapter), or swapAdapter(Adapter, boolean) for small updates. Those methods signal that the entire list content has changed, and will show up in Systrace as RecyclerView FullInvalidate. Instead, use SortedList or DiffUtil to generate minimal updates when content changes or is added.
- RecyclerView: Bind taking too long: Bind (that is, onBindViewHolder(VH, int)) should be very simple, and take much less than one millisecond for all but the most complex items. It simply should take POJO items from your adapter's internal item data, and call setters on views in the ViewHolder. If RV OnBindView is taking a long time, verify that you're doing minimal work in your bind code.
Mainly there are following issues which can drain the battery by keeping the device from going into a low-power state.
- Stuck wake-locks
- Excessive wakeups
- Excessive Wi-Fi scans (background)
- Excessive network usage (background)
How to avoid:
- To avoid wake-locks issues Android provides alternative APIs for almost every use-case that previously required a partial wake lock. One remaining use-case for partial wake locks is to ensure that a music app continues to play when the screen is off. If you are using wake locks to run tasks, consider the following alternatives:
- If your app is performing long-running HTTP downloads, consider using DownloadManager.
- If your app is synchronizing data from an external server, consider creating a sync adapter.
- If your app needs to execute background tasks at regular intervals, consider using JobScheduler or Firebase JobDispatcher. For information on triggering tasks at specific intervals, see Intelligent Job-Scheduling.
- To avoid excessive wakeups use wakeup alarms only if your app needs to perform a user-facing operation (such as posting a notification or alerting the user). For a list of AlarmManager best practices, see Scheduling Repeating Alarms.
- Don’t use AlarmManager to schedule background tasks, especially repeating or network background tasks. Use JobScheduler or Firebase JobDispatcher to schedule background tasks
- Avoid scanning Wi-Fi in the background, when an app performs Wi-Fi scans in the background, it wakes up the CPU, causing the rate of battery drain.
- Avoid network usage in the background, when an app connects to the mobile network in the background, the app wakes up the CPU and turns on the radio. Doing so repeatedly can run down a device’s battery.
- Use JobScheduler or WorkManager API when a job needs to run in the background.
Most apps require that users grant them certain app permissions in order to function properly. However, in some cases, users might not grant permissions.
Android vitals can help you gauge your users’ privacy preferences and engagement by informing you about the percentage of permission denials your app is receiving.
Thank you so much for reading my post. Please refer to the following resources if you want to know further.
here are some helpful resources