Smart Way to Understand Android Theory: Android Core (Part 2)

Part 2 : Mastering Key Concepts and Features of Android Development

·

23 min read

Table of contents

What does the term "Launch modes" refer to in the context of Android?

  1. Standard Launch Mode: This mode creates a new instance of an activity within the same task it was launched from. It allows multiple instances of the activity to be created and added to either the same or different tasks.

    • Example: Imagine an activity stack of A -> B -> C. If we launch B again with the "standard" launch mode, the new stack will be A -> B -> C -> B.
  2. SingleTop Launch Mode: Similar to the standard mode, but if there is already an instance of the activity at the top of the stack, it won't create a new instance. Instead, the intent will be delivered to the existing activity instance.

    • Example: Let's say we have an activity stack of A -> B. If we launch C with the "singleTop" launch mode, the new stack will be A -> B -> C, as usual. If we launch C again, the stack remains the same.
  3. SingleTask Launch Mode: This mode always creates a new task and places a new instance of the activity as the root. If the activity already exists in the task, the intent is redirected to the existing instance using onNewIntent(). Only one instance of the activity exists at any given time.

    • Example: Consider an activity stack of A -> B -> C -> D. If we launch D with the "singleTask" launch mode, the new stack will be A -> B -> C -> D. If we launch activity B again, the stack will become A -> B, and activities C and D will be destroyed.
  4. SingleInstance Launch Mode: Similar to singleTask, this mode ensures that the system does not launch any other activities into the same task as the current activity. New activities launched with this mode are placed in a separate task.

    • Example: Let's say we have an activity stack of A -> B -> C -> D. If we launch activity B again with the "singleInstance" launch mode, a new activity stack will be created: Task1 — A -> B -> C, and Task2 — D.

These launch modes offer developers flexibility in managing activity instances and task behavior within Android applications.

What is the response of the activity when the user rotates the screen?

When the screen is rotated, the current instance of the activity is destroyed, and a new instance of the activity is created to accommodate the new orientation. During this process, the onRestart() method is triggered first to handle the screen rotation event. Following that, the subsequent lifecycle methods are invoked in a manner that resembles their initial execution when the activity was first created.

What measures can be taken to avoid the reloading and resetting of data when the screen undergoes rotation?

  1. Nowadays, a widely adopted approach is to combine the usage of ViewModels and onSaveInstanceState() to tackle the issue of data reloading and resetting during screen rotation. So, how can we accomplish this effectively?

  2. Understanding the essence of ViewModels: ViewModels possess the ability to maintain their state across configuration changes, ensuring they persist even if their owner, such as an Activity, is destroyed due to rotation. When the owner is recreated, it seamlessly reconnects with the existing ViewModel instance. Therefore, despite multiple rotations resulting in the creation of new Activity instances, only one ViewModel is required.

  3. The standard practice entails storing data in the ViewModel class to ensure its preservation during configuration changes. On the other hand, OnSaveInstanceState is typically employed to store smaller UI-related data.

  4. Consider a practical example of a search screen where the user inputs a query in an EditText, triggering the display of a list of items in a RecyclerView. To prevent data from being reset upon screen rotation, it is advisable to store the list of search items in the ViewModel, while utilizing the OnSaveInstanceState method of the activity to retain the user's entered query text. By implementing this approach, the data remains intact and consistent throughout screen rotations.

Can you suggest two methods to clear the activity back stack in Android development when starting a new activity?

There are two ways to clear the activity back stack when starting a new activity in Android development:

  1. By utilizing an intent to call the activity, you can employ the FLAG_ACTIVITY_CLEAR_TOP flag. This flag ensures that the new activity is started at the top of the stack, while simultaneously removing any existing activities above it.

  2. Another approach involves combining the FLAG_ACTIVITY_CLEAR_TASK and FLAG_ACTIVITY_NEW_TASK flags. When used together, these flags clear the entire activity stack, creating a fresh task context, and then launching the new activity.

By employing these methods, you can effectively clear the back stack of activities when starting a new one in Android development.

How do the behaviors of FLAG_ACTIVITY_CLEAR_TASK and FLAG_ACTIVITY_CLEAR_TOP differ from each other?

  1. FLAG_ACTIVITY_CLEAR_TASK is utilized to clear all activities within the task, including any existing instances of the invoked class. As a result, the activity launched by the intent becomes the new root of the task, essentially starting a fresh task list. It is crucial to use this flag in conjunction with FLAG_ACTIVITY_NEW_TASK to achieve the intended behavior.

  2. On the other hand, when the FLAG_ACTIVITY_CLEAR_TOP flag is used, it behaves differently. If there is an existing instance of the activity in the task list, all other activities above it are removed, leaving only the old activity as the new root of the task list. However, if there is no instance of that activity present in the task list, a new instance of the activity is created and placed at the root. Although it is generally recommended to combine FLAG_ACTIVITY_CLEAR_TOP with FLAG_ACTIVITY_NEW_TASK as a good practice, it is not strictly necessary for achieving the desired functionality.

Can you explain what content providers are?

  1. A ContentProvider acts as a bridge between different applications, facilitating the exchange of data upon request. Its primary role is to manage access to a structured dataset, ensuring the integrity and security of the data. By adhering to the ContentProvider interface, it establishes a standardized connection that enables the communication between data stored in one process and codes executing in another process.

  2. When accessing data from a ContentProvider, the recommended approach is to use the ContentResolver object within the application's Context as a client. By employing the ContentResolver, you can send data requests to the provider, specifying the desired action. The provider, in turn, processes these requests and returns the requested data to the client. This separation of responsibilities maintains a clear and organized architecture within Android applications.

In summary, ContentProviders play a vital role in managing data access and sharing across applications, while ContentResolvers act as the client's interface to interact with and retrieve data from the provider.

How to Access data using the Content Provider?

  1. Start by making sure your Android application has the necessary
    read-access permissions. Then, get access to the ContentResolver
    object by calling getContentResolver() on the Context object, and
    retrieving the data by constructing a query using
    ContentResolver.query().

  2. The ContentResolver.query() method returns a Cursor so that you
    can retrieve data from each column using Cursor methods.

Can you describe services?

A Service is an integral component of an application that enables the execution of long-running operations in the background, independent of any user interface. It can seamlessly continue its operation even when the user is not actively interacting with the application. There are three distinct types of services:

  • Foreground Service: A foreground service performs operations that are directly noticeable to the user. For instance, it can be utilized to play an audio track, requiring the display of a notification to keep the user informed.

  • Background Service: A background service undertakes operations that do not have a direct impact on the user experience. However, starting from Android API level 26 and above, certain restrictions apply to the usage of background services, making it advisable to employ WorkManager for such scenarios.

  • Bound Service: A bound service establishes a connection when an application component binds to it using the bindService() method. It offers a client-server interface that facilitates interaction, enabling components to send requests and receive results. A bound service remains active as long as there is at least one application component bound to it.

To summarize, Services play a crucial role in carrying out background tasks without a user interface. They encompass foreground services for noticeable operations, background services subject to restrictions in recent Android versions, and bound services that facilitate communication through a client-server interface.

What are the Differences between Service & Intent Service?

  1. The Service class serves as the base for creating various types of services in the Android framework. It can be extended to develop custom services that meet specific requirements. However, it's important to note that when a class directly extends Service, it runs on the main thread, which can potentially block the user interface (if applicable). Therefore, it is recommended to use this type of service for short tasks or employ additional threads for longer tasks to avoid impacting the user experience.

  2. IntentService is a specialized subclass of Service designed to handle asynchronous requests, known as "Intents," in a convenient manner. Clients can initiate requests by making startService(Intent) calls. The service starts when needed and processes each Intent in a sequential manner using a dedicated worker thread. Once all the pending work is completed, the IntentService automatically stops itself, ensuring efficient resource management.

In summary, the Service class forms the foundation for creating different types of services in the Android ecosystem. When extending this class directly, it's important to consider the impact on the user interface and choose the appropriate task duration accordingly. On the other hand, the IntentService subclass offers a streamlined solution for managing asynchronous requests, enabling efficient background processing through a dedicated worker thread.

What is the Difference between AsyncTasks & Threads?

Developers often employ separate threads to offload lengthy operations from the main thread, enhancing overall performance. However, this approach lacks an elegant cancellation mechanism and struggles with handling Android's configuration changes. Moreover, updating the user interface directly from a thread is not feasible.

To address these challenges, AsyncTask proves to be a valuable tool. It effectively manages work items that are typically short, lasting under 5 milliseconds. Unlike a standard Java thread, AsyncTask allows for UI updates. However, it's important to note that extensively using AsyncTask for prolonged tasks may impact performance and lead to potential issues.

By carefully evaluating the nature and duration of tasks, developers can choose between separate threads and AsyncTask to ensure an optimal balance between performance and user experience.

What are the Differences between Service, Intent Service, AsyncTask & Threads?

In Android, a service is a component designed to perform background operations, such as playing music, without any visible user interface. It continues to run in the background indefinitely, even if the application is closed or terminated.

  1. AsyncTask is a convenient mechanism for executing asynchronous tasks on the user interface. It handles blocking operations in a separate worker thread and automatically delivers the results to the UI thread, eliminating the need for manual thread and handler management.

  2. IntentService serves as a base class for handling asynchronous requests, expressed as Intents, on demand. Clients initiate requests through startService(Intent) calls, and the service starts and handles each request sequentially in a dedicated worker thread. It stops itself when there are no more pending tasks.

  3. Threads represent individual sequential flows of control within a program. They can be likened to mini-processes running alongside the main process, enabling concurrent execution and facilitating multitasking within an application.

What are Handlers?

  1. Handlers are objects for managing threads. It receives messages
    and writes code on how to handle the message. They run outside
    of the activity’s lifecycle, so they need to be cleaned up properly,
    or else you will have thread leaks.

  2. Handlers allow communication between the background thread
    and the main thread.

  3. A Handler class is preferred when we need to repeatedly perform
    a background task after every x seconds/minutes

What is Job Scheduling?

  1. The Job Scheduling API allows developers to schedule jobs while leveraging system optimization based on factors like memory, power, and connectivity conditions.

  2. With the JobScheduler, jobs can be intelligently batched by the Android system, resulting in reduced battery consumption. The JobManager simplifies handling uploads by automatically managing network unreliability and ensures job continuity even after application restarts.

  3. Use Cases:

    • Tasks that should be executed when the device is connected to a power source.

    • Tasks that require network access or a Wi-Fi connection.

    • Background tasks that are non-critical and don't require direct user interaction.

    • Regularly recurring batch tasks with flexible timing requirements.

The Job Scheduling API provides developers with powerful tools to efficiently manage jobs and tasks in Android applications, optimizing resource usage and enhancing user experience.

How does the life cycle of an AsyncTask relate to that of an Activity? What issues can arise from this relationship? What measures can be taken to prevent these issues from occurring?

  1. The life cycle of an AsyncTask is independent of the Activity that hosts it. For instance, if you initiate an AsyncTask within an Activity and the user rotates the device, the Activity will be destroyed while the AsyncTask continues running until it finishes its task.

  2. Upon completion, the AsyncTask may mistakenly update the UI of the previous Activity instance rather than the current one. This can result in an Exception like java.lang.IllegalArgumentException: View not attached to window manager, especially when accessing views using findViewById within the Activity.

  3. Another concern is the possibility of memory leaks since the AsyncTask holds a reference to the Activity, preventing it from being garbage collected as long as the AsyncTask is active.

  4. Consequently, it is generally advised against using AsyncTasks for lengthy background operations. Instead, employing alternative mechanisms such as services is recommended for managing such tasks effectively.

  5. By default, AsyncTasks operate on a single thread using a serial executor, ensuring that tasks are executed sequentially, one after another.

What is the Difference between Serializable and Parcelable?

  1. Serializable is a standard Java interface. Parcelable is an Android-specific interface where you implement the serialization yourself.
    It was created to be far more efficient than Serializable (The
    problem with this approach is that reflection is used and it is a
    slow process. This mechanism also tends to create a lot of
    temporary objects and cause quite a bit of garbage collection.)

  2. Serialization is the process of converting an object into a stream
    of bytes to store an object in memory so that it can be
    recreated later while still keeping the object's original state data.

  3. How to disallow serialization? We can declare the variable as
    transient.

What is the Difference between Activity & Service?

Activities serve as containers or windows that provide a user interface for interaction. On the other hand, Services are components specifically designed to carry out operations in the background, without any user interface involved.

What approach would you take to modify the user interface of an activity from a background service?

Start by registering a LocalBroadcastReceiver in the activity. This receiver will listen for specific broadcasts sent by the background service.

  1. Within the background service, use intents to send a broadcast containing the relevant data. This broadcast will be received by the activity.

  2. As long as the activity remains in the foreground, it will receive the broadcast and utilize the received data to update the UI accordingly.

  3. To ensure proper memory management and prevent potential leaks, remember to unregister the broadcast receiver in the onStop() method of the activity.

  4. An alternative approach is to employ a Handler. By registering a Handler and passing data between the background service and the activity through message communication, you can achieve UI updates from the background.

What is intent?

  1. Intents are messages that can be used to pass information to the
    various components of the android. For instance, launch an activity,
    open a web view etc.

  2. Two types of intents:

    • Implicit: Implicit intent is when you call the system default
      intent like sending email, sending SMS, dial a number.

    • Explicit: Explicit intent is when you call an application
      activity from another activity of the same application.

What is the concept of a Sticky Intent and how does it function in Android?

Sticky Intents serve as a means of communication between a function and a service in Android, enabling seamless interaction between the two.

In practice, the sendStickyBroadcast() method is employed to send a sticky intent, which has a unique characteristic. Unlike regular intents, sticky intents persist even after the broadcast is completed. This feature allows other components to swiftly access the associated data by utilizing the return value of the registerReceiver(BroadcastReceiver, IntentFilter) method.

To better understand this concept, consider the example of using an intent for ACTION_BATTERY_CHANGED to monitor battery state changes. By registering for this action using registerReceiver(), even with a null BroadcastReceiver, you gain access to the most recent broadcasted intent for that particular action. Consequently, you can retrieve the battery state without the need to continuously register for all subsequent battery state changes.

Sticky Intents offer a flexible approach for exchanging data between components and enable efficient retrieval of information beyond the broadcast's completion.

What is a Pending Intent?

When you want someone else to perform an Intent operation on your behalf at a specific time in the future, you can make use of a Pending Intent.

In practical terms, a Pending Intent allows you to delegate the execution of an Intent action to another entity. It serves as a mechanism for scheduling or deferring the execution of an Intent operation to a later point in time. This feature provides flexibility and convenience when you need to orchestrate actions that should occur at specific moments.

By utilizing a Pending Intent, you can effectively hand over the responsibility of executing an Intent to another component or system service, ensuring that the operation is carried out as intended, even if you're not actively handling it yourself.

What are intent Filters?

Intent Filters are used to specify the specific types of intents that an activity or service is capable of handling and responding to. By defining intent filters, components in an Android application can declare their ability to handle certain types of intents, allowing other applications or the system to identify and send appropriate intents to the respective components. In essence, intent filters act as a declaration mechanism, enabling components to selectively respond to specific types of intents based on their defined filters.

Can you describe the fragments?

Fragments serve as modular UI components that are tightly connected to activities. They provide a reusable and flexible approach by allowing fragments to be attached to different activities. This enables developers to create modular and interactive user interfaces. A single activity can host multiple fragments, enhancing code organization and promoting code reusability.

It's important to note that fragments must be associated with an activity to function properly. The lifecycle of a fragment is closely tied to its host activity, ensuring synchronized behavior and seamless integration within the overall UI structure.

Can you describe the fragment lifecycle?

  1. onAttach(): This method is called when the fragment is associated with an activity. At this stage, the fragment and activity are not fully initialized. Typically, you would use this method to obtain a reference to the activity for further initialization.

  2. onCreate(): The system invokes this method when creating the fragment. It's where you initialize essential components that should be retained during the fragment's lifecycle, even when it's paused or stopped and then resumed.

  3. onCreateView(): This callback is triggered when the fragment needs to draw its user interface for the first time. To create the UI, you need to return a View component that serves as the root of the fragment's layout. If the fragment doesn't provide a UI, you can return null.

  4. onActivityCreated(): This method is called after onCreateView() when the host activity is created. Both the activity and fragment instances, as well as the view hierarchy, are available. You can access the view using methods like findViewById(). It's a suitable place to instantiate objects that require a Context.

  5. onStart(): Once the fragment becomes visible, the onStart() method is called.

  6. onResume(): This indicates that the fragment is now active and ready for user interaction.

  7. onPause(): As the user navigates away from the fragment, this method is called the first indication of the fragment's pause state. It's where you should commit any changes that need to be persisted beyond the current user session.

  8. onStop(): When the fragment is about to be stopped, the onStop() method is invoked.

  9. onDestroyView(): This method signals that the fragment's view is about to be destroyed.

  10. onDestroy(): Although not guaranteed to be called by the Android platform, this method allows you to perform the final cleanup of the fragment's state.

Could you please explain the distinction between fragments and activities and elaborate on their relationship?

Activities are fundamental components in an application that provide a user interface for users to interact with. They represent distinct screens or windows within the app and handle user interactions and lifecycle events. On the other hand, fragments are components that encapsulate a specific behavior or part of the user interface within an activity. They have their own lifecycle, can handle input events, and can be dynamically added or removed from an activity.

The relationship between fragments and activities is that fragments are hosted within activities. An activity can contain one or multiple fragments, which allows for modular and reusable UI components. Fragments help break down the user interface into smaller, more manageable parts, making it easier to build complex applications. By combining fragments within an activity, developers can create flexible UIs that adapt to different screen sizes, orientations, and device capabilities.

Activities act as the main containers for fragments, providing the overall structure and coordination between them. Fragments can be dynamically added, replaced, or removed from an activity, enabling dynamic UI updates based on user actions or application state changes. Activities also manage the lifecycle of fragments, ensuring proper initialization, visibility, and resource management.

In essence, activities serve as the primary screens or windows of an app, while fragments represent modular UI elements within activities. Together, activities and fragments empower developers to create versatile, reusable, and interactive user interfaces in Android applications.

When would it be preferable to utilize a fragment instead of an activity?

  1. Fragments are beneficial when there is a need to reuse UI components across multiple activities.

  2. If there is a requirement to display multiple views side by side, such as viewPager tabs, fragments offer a suitable solution.

  3. When it is necessary to retain and persist data across activity restarts, fragments with data retention capabilities can be employed.

What are the differences between adding and replacing a fragment in the back stack?

  1. When adding a fragment, it is retained in the back stack, and a new fragment is added on top of it. So, when the back button is pressed, the existing fragment remains active and its onCreateView method is not called again. The added fragment takes precedence.

  2. On the other hand, when replacing a fragment, the existing fragment is removed entirely from the back stack, and a new fragment is added in its place. When the back button is pressed, the replaced fragment is re-created, and its onCreateView method is invoked.

  3. Regarding the fragment's lifecycle events, when adding a fragment, events like onPause, onResume, onCreateView, and other lifecycle events are not called again for the existing fragment. However, when replacing a fragment, these events are triggered for both the replaced and the newly added fragment.

In summary, adding a fragment keeps the existing fragment active and does not re-invoke its onCreateView method, while replacing a fragment removes the existing fragment and invokes onCreateView for the newly added fragment.

Why is it preferable to utilize the default constructor exclusively when creating a Fragment?

It is highly recommended to pass parameters through a bundle instead of using custom constructors. This approach ensures that when the system restores a fragment, such as during a configuration change, it can automatically restore the bundle along with the fragment's state. By relying on the default constructor and bundling parameters, you can guarantee that the fragment will be restored to the exact state it was initialized with, ensuring accurate and consistent restoration of the fragment's state.

How can you ensure that the user can navigate back to the previous Fragment when replacing one Fragment with another?

To enable back navigation from the new Fragment to the previous one, you should save each Fragment transaction to the backstack. This can be achieved by calling the addToBackStack() method before committing the transaction. By adding the transaction to the backstack, the system keeps a record of the Fragment transitions, allowing users to return to the previous Fragment by pressing the Back button.

Can you describe the callbacks that are triggered when a fragment is added to the back stack and when navigating back from the back stack?

The addOnBackStackChangedListener callback is invoked when a fragment is added or removed from the back stack. This listener allows you to listen for changes in the back stack and perform actions or updates accordingly. By registering the addOnBackStackChangedListener, you can receive notifications whenever fragments are added to or removed from the back stack, giving you the ability to handle these events as required.

Could you explain the concept of retained fragments?

Retained fragments are a feature in Android that allows developers to preserve the instance of a fragment during configuration changes, rather than having it destroyed and recreated along with the parent activity. This is achieved by calling the setRetainInstance(true) method on the fragment. By retaining the fragment instance, its state and data are preserved, ensuring continuity and avoiding the need for costly reinitialization. This feature is particularly useful when handling complex data or time-consuming operations within a fragment, as it allows for a smoother user experience during configuration changes, such as screen rotation.

What is a singleton class in Android?

A singleton class is a class that can create only an object that can
be shared with all other classes.

class RESTService private constructor(context: Context) : SomeSuperClass(context) {
    companion object {
        @Volatile
        private var instance: RESTService? = null

        fun getInstance(context: Context): RESTService {
            if (instance == null) {
                synchronized(RESTService::class.java) {
                    if (instance == null) {
                        instance = RESTService(context)
                    }
                }
            }
            return instance!!
        }
    }
}

What’s the difference between commit() and apply() in SharedPreferences?

  1. When using the commit() function, data is written synchronously, and it immediately provides a boolean response indicating whether the operation was successful or not.

  2. On the other hand, the apply() function is asynchronous, meaning it writes data without returning an immediate boolean response. If there is an ongoing apply() operation and we attempt to perform another commit(), the commit() operation will be put on hold until the apply() operation is completed.

How does RecyclerView work?

  1. Let's delve into the background of RecyclerView, which is essential to understand the purpose of the onBindViewHolder() method within it.

  2. RecyclerView is designed to efficiently handle long lists or grids of items. For instance, if you have 100 rows of data to display, a naive approach would involve creating 100 views for each row and arranging them all. However, this approach is wasteful because, at any given time, only a portion of those views can fit on the screen, while the remaining items would be off-screen. To optimize performance and memory usage, RecyclerView creates and renders only the views that are visible on the screen, typically around 10 views. This approach greatly improves speed and reduces memory overhead.

  3. But what happens when you scroll and need to display additional views?

    • One simple approach would be to create a new view for each new row that appears. However, this would result in creating numerous views, equal to the total number of rows, which would consume excessive memory. Additionally, creating views is a time-consuming process, potentially resulting in a less smooth scrolling experience. This is where RecyclerView takes advantage of the fact that as you scroll, new rows come into view while old rows disappear from the screen. Instead of creating new views, it recycles and reuses existing views by binding them with new data.

    • The onBindViewHolder() method is responsible for this recycling and binding process. Initially, you receive unused view holders, which are essentially containers for views, and your task is to populate them with the appropriate data for display. As you continue scrolling, you'll receive view holders that were previously used for rows that are no longer visible. In this case, you need to update the old data held by these view holders with new data, ensuring that the views reflect the correct information.

How does RecyclerView differ from ListView?

  1. ViewHolder Pattern: RecyclerView implements the ViewHolder pattern, whereas it is not mandatory in ListView. The RecyclerView efficiently recycles and reuses cells while scrolling, which improves performance.

  2. What is a ViewHolder Pattern? - The ViewHolder pattern involves storing each component view inside the tag field of the layout, allowing for immediate access without repeated lookups. In ListView, the code often uses findViewById() frequently during scrolling, which can negatively impact performance. Even when an Adapter returns an inflated view for recycling, there is still a need to locate and update the elements. To avoid the repetitive use of findViewById(), the "view holder" design pattern is utilized.

  3. LayoutManager: Unlike ListView, which only supports a vertical list view, RecyclerView decouples the list from its container. It enables the dynamic allocation of list items into different containers (such as LinearLayout, and GridLayout) at runtime by setting a LayoutManager.

  4. Item Animator: ListView lacks robust animation capabilities. However, RecyclerView introduces a whole new dimension of animation possibilities, providing enhanced visual effects and transitions.