Development of applications for the platform Android is inextricably linked with the need for constant exchange of information between various components of the system. When you move between screens or an application is minimized to the background, a critical task arises - saving the current state of the interface and the parameters passed. This is where the fundamental class android.os.Bundlecomes into the picture, serving as a universal container for storing key-value pairs.
This mechanism is the core of the life cycle of any Activity or Fragment. Without understanding the principles of the bundle, it is impossible to create a stable application that can correctly handle screen rotations, calls or switching between tasks. Bundle allows you to package primitive data types, strings and even complex serializable objects for their safe transportation within the process.
In this article we will analyze in detail the internal structure of this class, consider typical errors that lead to application crashes, and study modern ones approaches to passing arguments. You'll learn why transferring large amounts of data through this container is considered bad practice and what alternatives the modern development ecosystem offers.
Architecture and purpose of the Bundle class
Class Bundle is a mapping (Map) of strings to values โโof various types, implemented taking into account the specifics of the Android platform. Its main task is to provide inter-process communication (IPC) and save the state of interface components. Internally, it uses a mechanism Parcelablethat is significantly faster than standard Java serialization, which is critical for mobile device performance.
When the Android system decides to destroy an activity to free memory, it calls a method onSaveInstanceState. The developer should put all the important user data into an object Bundlethat the system will save and pass back when the activity is re-created. This allows the user to return to the application exactly where he left off without losing the entered text or scroll position.
However, the possibilities of this container are not limitless. There is a strict limit on the size of transferred data, which is controlled by the system. Exceeding this limit results in an exception being thrown TransactionTooLargeException, which is often an unpleasant surprise for newbies. The Binder transaction limit is usually around 1 MB, but can vary depending on the OS version and device manufacturer.
โ ๏ธ Warning: Never try to transfer byte arrays through Bundle (byte[]) of several megabytes in size, for example full resolution images. This is guaranteed to cause the application to crash when trying to start an activity.
To transfer data between activities, a method is used putExtras(), which accepts a bundle object. Inside the Intent, the data is serialized and transmitted through the driver Binder. This process is transparent to the developer, but requires an understanding that data is copied from the memory of one process to the memory of another, which consumes resources.
Use the bundle.putParcelable() method to pass complex objects by first implementing the Parcelable interface in your class. It is much more efficient than Serializable.
Basic methods of working with data
Working with Bundle is built on the principle of strict typing when retrieving data. Each data type has its own pair of methods: one for writing (put...) and one for reading (get...). An error in choosing a type when reading will result in a return null or an exception ClassCastException, which can break the logic of the application.
Let's consider the main data types that are supported out of the box without the need for additional serialization. The Android system has optimized work with these types, since they are encountered in 90% of cases when developing interfaces.
- ๐น Primitives: int, long, float, double, boolean - are transmitted instantly and take up minimal space.
- ๐น Strings: String and string arrays String[] is the main way to transmit text information.
- ๐น Android objects: Parcelable and Serializable - allow you to transfer complex data models created by the developer.
- ๐น Specific types: Bundle (nested bundles), IBinder - for complex interaction scenarios.
When retrieving data, always check that the key is in the container. Methods like getString() can return nullif the key is not found. It is good practice to use overloaded versions of methods that allow you to set a default value, for example getInt(String key, int defaultValue).
It is important to remember the case of the keys. Keys are case sensitive, and a single letter typo will result in the data not being found. To minimize errors, it is recommended to place keys in a separate class of constants or use annotations for checking at the compilation stage. Bundle are case sensitive, and misspelling one letter will result in the data not being found. To minimize errors, it is recommended to place keys in a separate class of constants or use annotations for checking at the compilation stage.
Transferring data between Activity and Fragment
One โโof the most common use cases is passing parameters when starting a new activity. Instead of creating constructors with arguments (which is not recommended for Android components), you should use static factory methods or explicitly construct Intent with a populated bundle.
For fragments, the approach is even more rigorous. Fragments can be recreated by the system at any time, and if you passed data through the constructor, it may be lost when the configuration changes. The correct pattern involves using the method setArguments(Bundle args).
public static MyFragment newInstance(String userId) {MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString("ARG_USER_ID", userId);
fragment.setArguments(args);
return fragment;
}
Inside the fragment, data is retrieved in the method onCreate() or onViewCreated(). It is important to check that the arguments are not equal nullalthough this is unlikely when using a factory method. This approach ensures that even if the system kills the application process and restores the fragment from memory, the data will be preserved.
When navigating between fragments within the same host, bundle passing is also used, often through transaction arguments or directly through navigation graph methods if a library is used. Navigation Component. This simplifies the code and makes the data flow more predictable.
โ๏ธ Correctly passing data to the Fragment
Saving state when changing configuration
Changing the device configuration, such as rotating the screen, changing the language, or connecting a keyboard, results in a complete re-creation of the activity. By default, Android destroys the current instance and creates a new one. To prevent the user from losing the entered data, it is necessary to implement the method onSaveInstanceState.
In this method you receive an object Bundle outState, into which all dynamic data should be written. This is not a place to save data to a database or disk - only the temporary state of the UI is stored here. The system will automatically call this method before destroying the activity.
| Lifecycle method | Bundle purpose | Storage guarantees |
|---|---|---|
onSaveInstanceState |
Saving lightweight UI state | Only when destroyed by the system (not when pressing "Back") |
onCreate |
Restoring state (savedInstanceState parameter) | Available immediately after creation |
onRestoreInstanceState |
Alternative restoring state | Called after onStart() only if there is data |
ViewModel |
Storing UI-related data | Survives configuration changes, but not process death |
It is worth noting that modern approaches recommend using ViewModel to store interface-related data, since they survive configuration changes without the need for manual serialization into a bundle. However Bundle is still required to save the scroll state, entered text in fields and other transient data.
โ ๏ธ Attention: The onSaveInstanceState method is not called when the user independently closes the activity with the "Back" button. In this case, the data is considered irrelevant and is not saved.
When restoring data in the method onCreate always check the parameter savedInstanceState for null. If it is not null, then the activity is being recreated after death, and the data needs to be retrieved. If null, the activity is launched for the first time.
Why shouldn't you store large lists in a Bundle?
Storing large lists of objects in a Bundle leads to increased serialization and deserialization time, which causes noticeable delays (freezes) of the interface when the screen is rotated.
Size limitations and memory optimization
As mentioned earlier, the main limitation Bundle is the size of the Binder transaction. This limit is global for the entire application process. If you pass too much data in one Intent, the application will crash. FATAL EXCEPTION: android.os.TransactionTooLargeException.
The problem is made worse by the fact that the size of the data in serialized form can significantly exceed its size in Java object memory. Strings in UTF-16, objects with many fields - all this inflates the final packet size. Debugging such problems is difficult, since the error often occurs not at the time of recording, but at the time of transmission.
For optimization, you should adhere to the following rules:
- ๐ Transmit only identifiers (IDs), and load complete data from the database or network in a new activity.
- ๐ Avoid transmitting bitmaps and large arrays bytes.
- ๐ Use bundle size logging in debug builds for control.
If you really need to transfer a large amount of data between components of the same application, consider using a singleton, a static field (with caution), or a database. To transfer between different applications, use ContentProvider or file transfer via URI.
Golden rule: Bundle is designed to transfer small pieces of data needed to initialize the screen, and not to transport content.
Common mistakes and best practices
Developers often make common errors when working with bundles, which lead to difficult-to-catch bugs. One of the most common is using magic strings as keys. A typo in the key will result in the data simply not being read, and the compiler will not notice it.
Always use constants for keys. Create a separate class or companion object where all string identifiers will be declared. This will not only protect against typos, but will also make code refactoring easier in the future.
companion object {private const val KEY_USER_ID = "com.example.app.USER_ID"
private const val KEY_IS_PREMIUM = "com.example.app.IS_PREMIUM"
}
Another error is trying to save in onSaveInstanceState data that is already in the database or can be easily restored. This is duplication of effort and a waste of resources. Save only what the user entered manually and has not yet sent to the server.
You should also be careful with custom classes. Make sure that all nested fields of your class that you put in Bundle as Parcelable also implement this interface or are primitives. Otherwise, you will receive an exception during serialization.
โ ๏ธ Attention: Interfaces and rules for working with Android are constantly updated. Always consult the official Android Developers documentation when implementing new architectural components such as Navigation Component or SavedStateHandle.
Following these simple rules will make your application more stable and responsive. Understanding what works Bundle under the hood is the difference between a professional developer and an amateur, allowing you to avoid platform pitfalls.
To work safely with keys, use the @StringDef annotation or create type-safe wrappers around Bundle so that the compiler checks data types when extracting.
Is it possible to pass null values to a Bundle?
Yes, many put methods allow you to pass null, but when retrieving you need to be prepared to handle this case. Some specific types may require non-null values.
What is the difference between a Bundle and an Intent?
An Intent is a message that describes the intent to perform an action, while a Bundle is a container with data that can be nested within an Intent. The Bundle itself does not launch components.
Why does the data in the Bundle disappear after killing the process?
The Bundle is saved in the system state store. If a process is killed by the system, the data is restored from this storage. If the application crashes due to an error, the state may not be saved correctly.
How to pass a list of objects via Bundle?
Use the putParcelableArrayList or putSerializable methods. Make sure the object class implements the appropriate interface. For large lists, it is better to pass only the ID.
Is Bundle thread-safe?
No, the Bundle class is not thread-safe. You should not modify the same Bundle instance from different threads simultaneously without external synchronization.