Android Interview Questions - Part Two
What are OOPS Principles?
Abstraction - Data Abstraction is the property by virtue of which only the essential details are displayed to the user. The trivial or non-essential units are not displayed to the user. Ex - Car, ATM, remote control. In Java, abstraction is achieved by interfaces and abstract classes. We can achieve 100% abstraction using interfaces.
Encapsulation - It is defined as the wrapping up of data under a single unit. Encapsulation is the mechanism that binds together the code and the data it manipulates. Another way to think about encapsulation is that it is a protective shield that prevents the data from being accessed by the code outside this shield. Encapsulation can be achieved by declaring all the variables in a class as private and writing public methods in the class to set and get the values of the variables. Ex - POJO, Java Bean class.
Inheritance - It is the mechanism in Java by which one class is allowed to inherit the features (fields and methods) of another class. Inheritance supports the concept of “reusability”, i.e., when we want to create a new class and there is already a class that includes some of the code that we want, we can derive our new class from the existing class. By doing this, we are reusing the fields and methods of the existing class.
Polymorphism - It refers to the ability of object-oriented programming languages to differentiate between entities with the same name efficiently. This is done by Java with the help of the signature and declaration of these entities. In other words, it is one function with different implementations. There are two types of polymorphism compile time polymorphism (Overloading) and runtime polymorphism (Overriding).
Why do we use Co-routines?
Kotlin comes up with coroutines that help us writing asynchronous code in a synchronous manner. Android is a single thread platform. By default, everything runs on Main Thread (UI Thread) so when it's time to run non-UI related operations (example Network call, DB operation, File I/O operations or any time taking task), We dedicate these tasks to different threads and on completion if needed, pass back the result to UI Thread.
Advantages of Kotlin over JAVA
- Readability - Comparing to Java, Kotlin has more readable and precise code which makes it easier to understand the program. After a small learning curve, a Java developer can easily understand how to write Kotlin very quickly.
- Null-Safety - Kotlin is null-safe by default. It does not allow variables to be assigned with a null value.
- Data Class - In Kotlin, if we need to have classes which need to hold data, we can declare a class with keyword “data” in the class definition then the compiler will take care of all of this work such as creating constructors, getter, setter methods for different fields.
- Interoperability - Kotlin language is interoperable. This means that both Java and Kotlin are somewhat similar, and we can use java commands and Java libraries in a Kotlin project.
- Immutability - In Kotlin variables are defined using val or var to help developers easily understand which values can be reassigned.
- Type Inference - In Kotlin, we don’t need to specify the type of each variable explicitly based on assignment it will handle. If we want to specify explicitly, we can do.
- Extension Functions - Kotlin provides developers the ability to extend an existing class with new functionality. We can create extend functions by prefixing the name of a class to name of the new function.
- Coroutines Support - In Kotlin, we can create multiple threads to run these long-running intensive operations, but we have coroutines support, which will suspend execution at a certain point without blocking threads while executing long-running intensive operations.
- No checked exceptions - In Kotlin, we don’t have checked exceptions. So, developers don’t need to declare or catch the exceptions, which have advantages and disadvantages.
BroadcastReceiver is a dormant component of android that listens to system-wide broadcast events or intents. When any of these events occur, it brings the application into action by either creating a status bar notification or performing a task.
Following are some of the important system wide generated intents, resulting in widely usage in project:
- android.intent.action.BATTERY_LOW : Indicates low battery condition on the device.
- android.intent.action.BOOT_COMPLETED : This is broadcast once, after the system has finished booting.
- android.intent.action.CALL : To perform a call to someone specified by the data.
- android.intent.action.DATE_CHANGED : The date has changed.
- android.intent.action.REBOOT : Have the device reboot.
- android.net.conn.CONNECTIVITY_CHANGE : The mobile network or Wi-Fi connection is changed (or reset).
What is Polymorphism and how we achieve it?
Polymorphism is the ability of an object to take on different forms. In Java, polymorphism refers to the ability of a class to provide different implementations of a method, depending on the type of object that is passed to the method. A superclass named “Shapes” has a method called “area()”. Subclasses of “Shapes” can be “Triangle”, “circle”, “Rectangle”, etc. Each subclass has its way of calculating area. Using Inheritance and Polymorphism means, the subclasses can use the “area()” method to find the area’s formula for that shape.
What is the difference between Inheritance and polymorphism?
- Polymorphism defines the way in which a single task can be performed in multiple ways by the user. The basic motto of Inheritance is creating a new class inheriting the properties/ functionalities of an already existing class.
- In OOPs, Polymorphism provides the allowance to the objects to decide to which form of the function to be implemented at the compile-time and the run time. In OOPs, Inheritance allows the reusability of the code by inheriting the properties, functions, methods of the class by directly inheriting it and using it. It also helps in reducing the code length, by avoiding unnecessary writing of repeated lines of code as they are inherited in a subclass easily.
- Polymorphism is applied to the various functions or methods in OOPs. Inheritance is basically applicable for the classes in Java and other Object-Oriented Programming.
- There are 2 types of Polymorphism, i.e., run-time polymorphism (overriding) and compile-time polymorphism (overloading). In OOPs, Inheritance can be of various types like single Inheritance, multiple- level Inheritance, Multiple Inheritance, Hybrid Inheritance, Hierarchical Inheritance.
- Each class like Car, Bike, Rickshaw can have methods like the color(), no_of_wheels(), price(), passenger_allowed(). So, implementing which method for each class is decided at the run time and compile time. Classes like Car, Bike, Rickshaw can inherit the properties and the function from the base class called Vehicles. Though each class (Class, Bike, Rickshaw) can have its own specific properties, the basic properties remain the same.
What is a Static Data structure?
In Static data structure the size of the structure is fixed. The content of the data structure can be modified but without changing the memory space allocated to it. Example of Static Data Structures: Array
What are Dynamic data structures?
In Dynamic data structure the size of the structure is not fixed and can be modified during the operations performed on it. Dynamic data structures are designed to facilitate change of data structures in the run time. Example of Dynamic Data Structures: Linked List, Array List
Where to use LinkedList and where to use ArrayList ?
LinkedList should be used where modifications to a collection are frequent like addition/deletion operations. LinkedList is much faster as compared to ArrayList in such cases. In case of read-only collections or collections which are rarely modified, ArrayList is suitable.
What is Hashing and why do we use it, can it be achieved by any other way?
Hashing in Java is a technique for mapping data to a secret key, that can be used as a unique identifier for data. It employs a function that generates those keys from the data; this function is known as the Hash-function, and the output of this function (keys) is known as Hash-values.
Difference between Map vs Flatmap
Map can be used on collections in Kotlin which will help us to put some transformation to each and every element of the collection and return a new collection.
val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map {
it * it
}
FlatMap is used to combine two different collections and returns as a single collection.
val allVehicles = mutableListOf<MotorVehicle>()
allVehicles.addAll(cars)
allVehicles.addAll(bikes)
But we can also perform this using flatMap like let's say we have a collection of both the lists like,
val vehicles = listOf(cars, bikes)
Here, vehicles are a list of lists of cars and bikes, and we want to merge all the elements from cars and bikes to one list. We can do it like,
val allVehicles = vehicles.flatMap { it }
What is a LRU Cache?
A LRU cache is just like a queue that holds strong references to a limited number of values.
Each time a value is accessed, it is moved to the head of a queue. When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.
This class does not allow null to be used as a key or value. A return value of null from get(K), put (K, V) or remove(K) is unambiguous: the key was not in the cache.
What is bitmap pooling?
Bitmap pooling is an implementation that tries to reuse the memory of the bitmaps already available instead of allocating new memory every time. Suppose we need to load a bitmap; we first check our pool to see if there is any bitmap available. If it is available, we take a bitmap from the pool and reuse its memory to load our new bitmap otherwise we create a new bitmap. In case, if we do not need any bitmap, we put that bitmap into that pool instead of recycling it so that we can reuse the memory of that bitmap when needed for a new bitmap.
measureTimedValue with destructing declaration
val (value:String,time: Duration) = measureTimedValue{someLongOprationWhichReturnsValue()}
println("It took $time to calcualte $value")
What are the problems while using SHA-1, why SHA-1 is considered as weak?
The way SHA-1 is supposed to work is that no two pieces that run through the process should ever equal the same hash. SHA-1’s hash is a 160-bit long—a string of 160 ones and zeros. This means that there are 1.4 quindecillion (a number followed by 48 zeros) different combinations. Normally this would be enough to deter any brute force attacks.
However, because the number of possible hashes is finite, but the possible combinations of data input are infinite, we sometimes run into what is called a collision.
This vulnerability allows hackers to act as a Certificate Authority (organizations that issue SSL certificates) and sign certificates using a key that appears to be from a true Certificate Authority.
What will be the problem if your app has memory leaks?
Memory leaks can lead to lags in the app, ANR (Application Not Responding), and OutOfMemory Error, will cause your app to crash. Which ultimately leads to the uninstallation of your app by users.
1. Lags in the app
A large heap with memory leaks can result in an excessive number of GC operations, which can cause app lags and frame drops. To achieve 60 frames per second, we need to render a frame every 16.666 milliseconds. If we have a large heap, we will need to perform many GC operations in order to free up space in the app. These operations may take place while the frame is being rendered, causing the frame to drop and causing lags.
2. Application Not Responding (ANR)
When the UI thread of an Android app is blocked for too long, an “Application Not Responding” (ANR) error is triggered.
3. Crash — OutOfMemoryError exception
One common indication of a memory leak is the OutOfMemoryError exception. This error is thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.
What do you understand by Memory Leak?
In simple terms memory leak is referred to as "failure to release unused objects from the memory" means that there are unused objects in the application that the GC cannot clear from memory.
When the garbage collector (GC) fails to remove unused objects from memory. The memory unit that holds the unused objects will be occupied until the end of the application or method (depends on the scenario).
Leaks can be divided into two categories: those that occupy the memory unit until the application terminates (permanent) and those that occupy the memory unit until the method terminates (temporary).
The first category, application terminations (permanent), is self-evident: when an app is terminated, the GC releases all of the memory that the app has used.
The second category, method terminations (temporary), requires a little more explanation; for a better understanding, let’s use an example.
Let’s say we have method X. Method X is performing a task in the background that will take five minutes to complete and we’ve given it an activity context to retrieve some string. We decided to close the activity after one minute, but the method is still running and it needs four more minutes to complete the task; during this time we are holding the activity object, which is no longer in use, and the GC will be unable to free it from the memory; once the method completes the task, the GC will run again and will be able to clear and reclaim them from memory.
What Could Be the Cause of an OutOfMemoryError?
OutOfMemoryError in Android can occur for a variety of reasons. The following are some of the most typical causes of Memory Leaks that result in an OutOfMemoryError:
- The inner class that isn’t static.
- Use of getContext() and getApplicationContext() incorrectly
- Application of a static view, context, or activity
- A memory leak can easily occur in Android when AsyncTasks, Handlers, Singletons, Threads, and other components are used incorrectly.
Explain about Fragment-Activity-Fragment communication?
There are basically three ways to have a communication between Fragment-Activity-Fragment
1. Via Interface - To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity. The Fragment captures the interface implementation during its onAttach() lifecycle method and can then call the Interface methods to communicate with the Activity.2. Via Shared ViewModel - ViewModel is an ideal choice when you need to share data between multiple fragments or between fragments and their host activity.
(a). Share data with the host Activity: Both your fragment and its host activity can retrieve a shared instance of a ViewModel with activity scope by passing the activity into the ViewModelProvider constructor. The ViewModelProvider handles instantiating the ViewModel or retrieving it if it already exists.
Inside Activity:
private val viewModel: SharedViewModel by viewModels()
// to retrieve the ViewModel in the activity scope
Inside fragment :
private val viewModel: SharedViewModel by activityViewModels()
// to retrieve the ViewModel in the activity scope
(b). Share data between fragements : These fragments can share a ViewModel using their activity scope to handle this communication. By sharing the ViewModel in this way, the fragments do not need to know about each other, and the activity does not need to do anything to facilitate the communication.
private val viewModel: SharedViewModel by activityViewModels()
3. Via FragmentResult API - In some cases, you may want to pass a one-time value between two fragments or between a fragment and its host activity. In Fragment version 1.3.0 and higher, each FragmentManager implements FragmentResultOwner. This means that a FragmentManager can act as a central store for fragment results. This change allows components to communicate with each other by setting fragment results and listening for those results without requiring those components to have direct references to each other.
(a). Pass results between fragments - To pass data back to fragment A from fragment B, first set a result listener on fragment A, the fragment that receives the result. Call setFragmentResultListener() on fragment A's FragmentManager
setFragmentResultListener("requestKey") { requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
// Do something with the result
}
In fragment B, the fragment producing the result, you must set the result on the same FragmentManager by using the same requestKey.
val result = "result"
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
(b). Pass results between parent and child fragments - To pass a result from a child fragment to a parent, the parent fragment should use getChildFragmentManager() instead of getParentFragmentManager() when calling setFragmentResultListener().
// We set the listener on the child fragmentManager
childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
val result = bundle.getString("bundleKey")
// Do something with the result
}
(c). Receive results in the host activity - To receive a fragment result in the host activity, set a result listener on the fragment manager using getSupportFragmentManager().
supportFragmentManager.setFragmentResultListener("requestKey", this) {
requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
// Do something with the result
}
How can we share login credentials in between 2 apps?
It can be possibly shared by following - AccountManager, AIDL, using android:process in AndroidManifest.xml or using Content Provider.
Content Provider -
- The requesting application requests login information.
- A content resolver queries the device for any provider application that can provide login credentials.
- On successful resolution, the provider application responds to this request by returning the login information.
<!-- Using permission to behave like a requesting app -->
<uses-permission android:name="com.foo.bar.permission.LOGIN_PROVIDER" />
<!-- Declaring permission to behave like a provider app-->
<permission
android:name="com.foo.bar.permission.LOGIN_PROVIDER"
android:protectionLevel="signature" />
...
<provider
android:name="com.foo.bar.contentprovider.LoginContentProvider"
android:authorities="${applicationId}.login.provider"
android:exported="true"
android:permission="com.foo.bar.permission.LOGIN_PROVIDER" />
LoginContentProvider.kt
class LoginContentProvider : ContentProvider() {
// Use the query method to provide login data
override fun query(
...
): Cursor? {
...
}
...
}
Furthermore, we have an interface that the apps use directly to request login that hides all the details of the actual sharing.
interface SharedLoginRequests {
fun requestLogin(): SharedData?
}
Difference between waterfall and Agile
Agile methodology is a practice that helps continuous iteration of development and testing in the software development process. In this model, development and testing activities are concurrent, unlike the Waterfall model. This process allows more communication between customers, developers, managers, and testers.
Waterfall Model methodology which is also known as Linear Sequential Life Cycle Model. Waterfall Model followed in the sequential order, and so project development team only moves to next phase of development or testing if the previous step completed successfully.
Advantages of the Agile Model
- It is focused client process. So, it makes sure that the client is continuously involved during every stage.
- Agile teams are extremely motivated and self-organized so it likely to provide a better result from the development projects.
- Agile software development method assures that quality of the development is maintained.
- The process is completely based on the incremental progress. Therefore, the client and team know exactly what is complete and what is not. This reduces risk in the development process.
What do you look for in a code review?
- Identify Obvious Bugs: This is priority number 1 of a code review: Check if the code is working, and make sure it is not breaking any of the existing functionalities. Even great engineers write code that has defects. Often, those defects are quite silly: an off-by-one error, a misspelled variable, parameters passed in the wrong order to a method, and so on.
- Look for Possible Security Issues: When reviewing code, try to look for possible security issues that could be exploited.
- Look for "Clever" Code: Code readability is another vital area you should look into when reviewing code. First, remember that readability is, to a certain extent, subjective.
- Check for Code Duplication: When reviewing code, you'll often spot some low-hanging fruit regarding code duplication. Maybe the functionality already exists elsewhere and can be reused.
- Check Whether Names Are Descriptive Enough: Naming is one of the hardest things in software engineering, but that doesn't mean we should give up. When performing a review, look for opportunities to improve the names of variables, constants, class fields and properties, methods, classes, and so on.
- Look for Possible Performance Improvements: You'll often spot seemingly simple problems during the review that will go a long way in improving the code and its performance.
- S — Single Responsibility Principle (SRP)
- O — Open/Closed Principle (OCP)
- L — Liskov Substitution Principle (LSP)
- I — Interface Segregation Principle (ISP)
- D — Dependency Inversion Principle (DIP)
- Code duplication is avoided.
- Code becomes easier to understand.
- Code can be easily maintained.
- These reduces the complexity of the code.
- Results in a flexible Software.
Comments
Post a Comment