-
Notifications
You must be signed in to change notification settings - Fork 526
Background Processing
This page aims to provide some context around:
- What constitutes background processing/when something should be background processed, and why we care
- How background processing is done in the Android app
- Best practices to follow when managing background or expensive tasks
- Existing utilities to simplify using DataProviders
- How to safely pass data to the UI
Performing asynchronous tasks or background processing in a multi-threaded environment is hard: most developers are unaware of the nuances of cross-thread development (e.g. properly utilizing critical sections without introducing deadlocks, sharing mutable state across thread boundaries with correctly applied memory fences, critical sections, or atomics, properly using concurrent data structures, and more). This problem is exacerbated in Android since:
- Android requires all user-facing operations to be run on the main thread (requiring communication to/from the main thread)
- Android UI objects are very lifecycle-sensitive (meaning haphazard management of Android state can at best leak memory or at worst crash when communicating back from a background thread--a common crash in Android apps)
- The Android UI thread is sensitive to even medium-length operations when on slow devices (which can lead to app ANRs)
The team has a number of carefully considered solutions to ensure concurrency is easier to manage, safer, and performant.
All features in the codebase can be represented as a data pipeline. In some cases, data is created transiently and in other cases it needs to be loaded from somewhere (e.g. a file or network). Further, we sometimes need to do a lot of processing on this data before it can be presented to the UI (the app's architecture is specifically designed to encourage data processing logic to live outside the UI).
To keep things simple, we consider everything the following to be worth executing on a background thread instead of the UI thread:
- Any logic operation (e.g. something requiring an if statement or loop) which is more complicated than just copying data
- Any file I/O operations (e.g. reading from a file)
- Any networking operations (e.g. calling into Retrofit)
- Complex state management (such as ExplorationProgressController)
Similarly, the following operations must happen on a UI thread for lifecycle safety reasons:
- Any interactions with the UI (e.g. activities, fragments, or views)
- Any interactions with ViewModel state (which is designed to only be mutated on the main thread)
- Any interactions with other Android services which require main thread access
Oppia Android is aiming to provide an effective education experience to the most underprivileged communities in the world, and this particularly requires excellent performance on low-end devices. We have a thin performance margin to operate in, and we can't afford ANRs or poor performance. Further, reducing crashes is important to ensure an uninterrupted learning experience (especially for children who might not understand how to recover the app from a crash).
That being said, the difficulty in writing correct & performant concurrent code is quite high. We want to make sure we achieve that with a lower barrier-to-entry so that team members don't have to manage especially complex code.
To ensure the team is meeting the goals of reducing concurrency complexity while not sacrificing performance or correctness, we require that all code utilize the patterns & best practices outlined in this section.
Have an idea for how to improve the wiki? Please help make our documentation better by following our instructions for contributing to the wiki.
Core documentation
Developing Oppia
- Contributing to Oppia Android
- Bazel
- Key Workflows
- Testing
- Developing Skills
- Frequent Errors and Solutions
- RTL Guidelines
- Working on UI
- Writing Design Docs
Developer Reference
- Code style
- Background Processing
- Dark mode
- Buf Guide
- Firebase Console Guide
- Platform Parameters & Feature Flags
- Work Manager
- Dependency Injection with Dagger
- Revert & regression policy
- Upgrading target SDK version
- Spotlight Guide
- Triaging Process
- Bazel
- Internationalization
- Terminology in Oppia
- Past Events