Kahibaro
Discord Login Register

30.3 Performance Best Practices

Understanding Performance as a User Experience Problem

When you think about performance, it can be tempting to focus only on CPU usage, memory, or battery. For Android apps, performance is mainly about how responsive and smooth the app feels to the user. Technically fast code does not always mean a pleasant experience. You must think in terms of perceived performance, which is how quickly the user sees something useful on the screen, how smooth animations are, and how often the app appears to freeze.

A key mindset is to always ask how a change will affect what the user sees and feels. If an operation takes time, you decide whether to do it lazily, in the background, or in smaller pieces. If you cannot make a task truly faster, you can still improve the experience with progress indicators, skeleton screens, or early partial results.

Always treat performance as part of user experience, not only as a technical optimization problem.

Keeping the Main Thread Responsive

Android UI runs on a single main thread. If that thread is blocked, the app stops responding to touches, scrolling stutters, and the system may show an Application Not Responding dialog. A core rule of performance is to avoid heavy work on the main thread.

Heavy work includes network requests, database queries that scan many rows, complex JSON parsing, reading or writing large files, and decoding large images. The main thread should mostly handle view updates, light event handling, and short tasks that complete in a few milliseconds.

To keep the main thread responsive, move heavy operations to background threads, coroutines, or background workers that you will learn about in other chapters. For tasks that must eventually influence the UI, you do the work off the main thread, then post a small update back to the main thread to update views.

Never run long running or blocking operations on the main thread.

Designing Efficient Layouts

Layout performance is about how quickly Android can measure and draw your views. Complex hierarchies with many nested layouts can slow down rendering and increase memory use. Every extra level of nesting and every unnecessary view costs time during layout and drawing.

You should aim for layouts that are clear, not deeply nested, and do not repeat work. Parent layouts like ConstraintLayout can often replace several nested LinearLayout containers, which reduces view count and improves performance. Also avoid overuse of weights in LinearLayout for complex screens, since measuring those can be more expensive.

When you design layouts, consider reusing common patterns through includes and styles, rather than copying and pasting similar structures. This reduces code duplication and makes it easier to simplify layouts later.

Working Efficiently with Images

Images are a common source of performance issues, especially in lists and galleries. Large bitmaps use a lot of memory and can cause frequent garbage collection or even crashes if the system runs out of memory.

You want to use images that match the size you actually show. Do not load a huge image into memory if you only show a small thumbnail. You can resize or sample images when decoding them, and you should use the appropriate density resources when possible so the system chooses a suitable asset.

It is also important to load images lazily and off the main thread. Only fetch or decode an image when it is likely to be seen, and use caching so that frequently used images are not repeatedly loaded from disk or the network. When an image scrolls off screen, you should allow the system or libraries to release or reuse the associated resources.

Avoiding Unnecessary Object Allocations

Every time your code creates an object, it adds work for the garbage collector. In small amounts this is fine, but tight loops that allocate many temporary objects can produce a lot of garbage. Frequent garbage collection can pause the app and harm smoothness, especially during animations and scrolling.

You can improve performance by avoiding unnecessary allocations inside methods that run very often. For example, in RecyclerView adapters, do not allocate new listeners or helper objects repeatedly for each bind if you can reuse them. For simple data calculations, consider using local variables and primitive types rather than wrapping them into new objects if that is not required by your design.

Make sure you do not keep references to objects longer than needed, because that prevents the garbage collector from reclaiming memory. Leaking an Activity context into a long lived object is a common source of memory use that never goes away as long as the app runs.

Managing Work Frequency and Timing

Some performance problems come from doing the right work too often. Polling for changes many times per second, updating views more frequently than the device can draw, or saving to disk on every keystroke can waste resources and reduce battery life.

You should adjust how often you do periodic work. Instead of constant polling, consider event based updates where possible. If you must do repeated tasks, consider coalescing them, for example by delaying slightly and performing several updates in one batch. Throttling or debouncing user actions, such as search queries, avoids sending a network call for every character typed.

It is also useful to align background work with system recommendations so the system can batch tasks from many apps. That saves battery and often improves overall responsiveness.

Caching and Reusing Results

Caching is one of the most effective performance techniques when used thoughtfully. If a value is expensive to compute, and it does not change frequently, you can store it and reuse it instead of recalculating. This applies to parsed data, bitmap thumbnails, or derived values from a database.

However, caching must be controlled. Unbounded caches can grow until they consume too much memory. When you design a cache, decide its maximum size and eviction strategy. Only cache what is truly costly to recreate and what you are likely to need again.

For data from the network, caching results can both reduce latency and save bandwidth. You should consider how fresh the data needs to be and clear or refresh the cache at appropriate times.

Responsiveness and Feedback for Slow Operations

Some operations will always take noticeable time, for example large downloads or complex processing. Performance best practice is not only to make these operations efficient, but also to handle them gracefully in the UI so they do not feel like freezes.

You should give immediate feedback when the user triggers a slow action. This can be a small progress indicator, a disabled button, or a temporary placeholder. At the same time, avoid blocking the whole app if possible. Allow the user to cancel long operations or to continue using other parts of the app.

When operations complete, update the UI quickly and clearly so the user sees the result. If an operation fails, report the error in a concise, understandable way instead of remaining in a loading state forever.

Balancing Performance with Code Clarity

High performance should not come at the cost of code that is impossible to maintain. Overly clever micro optimizations can make code fragile, and in many cases they bring little real benefit.

A better approach is to write clear and correct code first, then measure, identify actual bottlenecks, and optimize those specific parts. Performance tools help you find where time and memory are really spent. This allows you to keep most of your code simple while focusing effort where it matters.

When you do make an optimization, document the reason and consider adding small tests or comments that explain the constraints. Future changes should not accidentally remove a necessary optimization or reintroduce an earlier problem.

Thinking About Battery and Data Usage

Performance best practices also include being kind to battery and data. Work that uses the CPU heavily, wakes the device frequently, or performs many network calls drains power quickly. Transfers over mobile networks are particularly expensive.

You should avoid waking the device more than necessary, and you should group background work so that the system can handle it efficiently. For network calls, reduce redundancy by avoiding duplicate requests, compressing data where appropriate, and using caching. Consider allowing the user to choose whether to perform heavy network operations on mobile data or only on Wi Fi.

Battery and data friendly behavior is an important part of long term performance. An app that drains the battery or consumes a lot of data may be uninstalled, even if it feels fast in short usage.

Establishing a Performance Mindset

Performance is not a one time task at the end of a project. The best results come when you consider performance early in your design, during implementation, and while testing. You do not need to prematurely optimize every line, but you should avoid known pitfalls and maintain a habit of measuring and thinking about cost.

When you add features, consider how they affect the main thread, memory, network, and battery. If a new feature introduces a list with many items, plan from the start to load data efficiently and reuse views. If you rely on background work, design it to be cancelable and to cooperate with the system.

Over time, this mindset leads to apps that feel smooth, responsive, and respectful of the device and user.

Views: 52

Comments

Please login to add a comment.

Don't have an account? Register now!