Android Interview Question: Part-3

1. What is ByteCode in java?

Bytecode is program code that has been compiled from source code into a low-level code designed for a software interpreter. It may be executed by a virtual machine (such as a JVM) or further compiled into machine code, which is recognized by the processor.

Different types of bytecode use different syntax, which can be read and executed by the corresponding virtual machine. A popular example is Java bytecode, which is compiled from Java source code and can be run on a Java Virtual Machine (JVM). Below are examples of Java bytecode instructions.

  • new (create new object)
  • aload_0 (load reference)
  • istore (store integer value)
  • ladd (add long value)
  • swap (swap two values)
  • areturn (return value from a function)

While it is possible to write bytecode directly, it is much more difficult than writing code in a high-level language, like Java. Therefore, bytecode files, such as Java.CLASS files are most often generated from source code using a compiler, like a javac.

Bytecode vs Assembly Language

Bytecode is similar to assembly language in that it is not a high-level language, but it is still somewhat readable, unlike machine language. Both may be considered “intermediate languages” that fall between source code and machine code. The primary difference between the two is that bytecode is generated for a virtual machine (software), while assembly language is created for a CPU (hardware).

2. What is JVM and How it works?

Java Virtual Machine (JVM) is a virtual machine that resides in the real machine (your computer) and the machine language for JVM is byte code. This makes it easier for the compiler as it has to generate byte code for JVM rather than a different machine code for each type of machine. JVM executes the byte code generated by the compiler and produces output. JVM is the one that makes the java platform independent.

So, now we understood that the primary function of JVM is to execute the byte code produced by the compiler. Each operating system has a different JVM, however the output they produce after the execution of byte code is the same across all operating systems. This means that the byte code generated on Windows can be run on Mac OS and vice versa. That is why we call java as platform-independent language. The same thing can be seen in the diagram below:

So to summarise everything: The Java Virtual machine (JVM) is the virtual machine that runs on actual machine (your computer) and executes Java byte code. The JVM doesn’t understand Java source code, that’s why we need to have javac compiler that compiles *.java files to obtain *.class files that contain the byte codes understood by the JVM. JVM makes java portable (write once, run anywhere). Each operating system has different JVM, however the output they produce after execution of byte code is same across all operating systems.

JVM Architecture

Let's see how JVM works:
ClassLoader: The class loader reads the .class file and saves the byte code in the method area.

Method Area: There is only one method area in a JVM which is shared among all the classes. This holds the class level information of each .class file.

Heap: Heap is a part of JVM memory where objects are allocated. JVM creates a Class object for each .class file.

Stack: Stack is also a part of JVM memory but unlike Heap, it is used for storing temporary variables.

PC Registers: This keeps the track of which instruction has been executed and which one is going to be executed. Since instructions are executed by threads, each thread has a separate PC register.

Native Method stack: A native method can access the runtime data areas of the virtual machine.

Native Method interface: It enables java code to call or be called by native applications. Native applications are programs that are specific to the hardware and OS of a system.

Garbage collection: A class instance is explicitly created by the java code and after use, it is automatically destroyed by garbage collection for memory management.

JVM Vs JRE Vs JDK

JRE: JRE is the environment within which the java virtual machine runs. JRE contains Java Virtual Machine(JVM), class libraries, and other files excluding development tools such as compiler and debugger.
This means you can run the code in JRE but you can’t develop and compile the code in JRE.

JVM: As we discussed above, JVM runs the program by using class, libraries and files provided by JRE.

JDK: JDK is a superset of JRE, it contains everything that JRE has along with development tools such as compiler, debugger etc.

3. How Android programs work with JVM?

The virtual machine that runs on an Android device is called Dalvik Virtual Machine(DVM). Maybe the most significant difference between the both is that JVM is stack-based while DVM is register-based.

In Stack Based VM’s, all the arithmetic and logic operations are carried out via Push and Pop operands and results in the stack.

On the other hand, in Register Based VM’s, the data structure where the operands are stored is based on the registers of CPU.

Java code is compiled to an intermediate format called byte-code. Then this byte-code is parsed by JVM and translated to machine code.

Android SDK has a tool called dx that converts Java byte-code to Dalvik byte-code. It is located under build-tools/android-{version}.

Then Dalvik VM moves in and translates Dalvik byte-code to binary machine code.

4. Diff between JVM vs DVM

  • JVM vs DVM
  • Why Android OS uses DVM instead of JVM?

Dalvik Virtual Machine

The Dalvik Virtual Machine (DVM) is a virtual machine that executes the android applications. Since everything in mobiles is very limited whether it would be battery life, processing, and memory, etc. It had been optimized so that it can fit in with low-powered devices.

As you can see from the image above, everything is the same as JVM except for the last two steps. The Dex compiler converts the class files into the .dex file that runs on the Dalvik VM. Multiple class files are converted into one dex file.

JVM vs DVM

One of the main reasons for using DVM in android is because it follows the register-based model and it is much faster than stack-based model while JVM follows the stack-based model which takes a lot of memory and also slower than DVM.

There are some major differences, so let’s have a look at them

Why Android OS uses DVM instead of JVM?

There are couple of reasons why Google not choose JVM over DVM, so let’s understand each one of them one by one.

  • Though JVM is free, it was under GPL license, which is not good for Android as most the Android is under Apache license.
  • JVM was designed for desktops and it is too heavy for embedded devices.
  • DVM takes less memory, runs and loads faster compared to JVM.

Conclusion

JVM will work based on byte code and the DVM will work based on optimized bytecode, it is optimized for mobile platforms because mobile devices have less memory, low process, and low power that’s why it is using the Linux kernal.

5. What is loader and which thread they run?

Android Loaders asynchronously load data from Activity or Fragment. They are used to load data in adapters. Loaders are used for loading data from databases or content providers. You can even create a static list also. This API is very important. It was introduced in Android 3.0 which corresponds to API 11.

There are many classes and interfaces which can be associated with Android loaders which are meant to be used in applications. All of these are not used in each and every Android application. But an instance of LoaderManager is required to initialize the loader and implementation of loader class like CursorLoader is essential to keep the thumbs up.

Implementing an Android Loader

Following are the steps required to implement loader and they are:

  • We need an activity or a fragment.
  • Instance of LoaderManager is required
  • A subclass of Loader or AsyncTaskLoader is required to load data from some other source. Or a CursorLoader to load data which is supported by a Content Provider.
  • Create a new loader. Manage the relationships with the existing loaders. This is performed by the implementation of LoaderManager.LoaderCallbacks.
  • Display the data like an adapter to populate and display items.
  • If we are using CursorLoader then a data source is required such as a content provider.

Important Classes/Interfaces/Methods

Let us get a small introduction to methods and interfaces used with loaders one-by-one.

1.LoaderManager: This is an abstract class. It is associated with an Activity or Fragment. It manages one or more loader instances. An activity or a fragment can have only one LoaderManager but a LoaderManager can house many loaders. With the help of LoaderManager, an application manages long-running operations.

2.Loader: This is an abstract class. It loads data in asynchronous mode. Base class of a loader is this particular class. In the active state, loaders keep track of the source of data. If any content changes, they deliver the result. We can implement our own subclass.

3.LoaderManager. LoaderCallbacks: As the name suggests, it is used for callbacks. It is an interface. A client uses this interface to interact with the LoaderManager.

4.AsyncTaskLoader: This is an abstract loader. It houses an AsyncTask to do the work. AsyncTask stands for asynchronous tasks.

5.CursorLoader: This is a subclass of AsyncTaskLoader. It queries the ContentResolver and returns the cursor. A set of rules named Loader is implemented by this class and it queries the cursors. It builds the AsyncTaskLoader which performs a cursor query on the background thread which in turn doesn’t block the User Interface of the application. This is the recommended way of retrieving data from content providers in asynchronous mode.

6.getLoaderManager().initLoader((ID, optional_arguments, LoaderManager.LoaderCallbacks_implementations): We initialize and retrieve a loader with this call.

initLoader() ensures the initialization of loader. It takes three arguments:

  • ID is an identifier which is unique for each loader. It identifies a loader.
  • Optional Arguments, if required, are supplied to loader while construction.
  • LoaderManager calls the LoaderManager.LoaderCallbacks implementation to report the events of loader.
  • initLoader() returns the loader and LoaderManager manages the life of loader automatically.
  • initLoader() uses an existing loader which is identified by the unique ID. If no loader exists with that ID then it creates a new loader.

7.restartLoader(): This method discards the old data and saves new set of data in the loader.

8.onCreateLoader(): This is a method of LoaderManager.LoaderCallbacks interface. For an given ID, it instantiates a new loader and returns the brand new loader.

9.onLoadFinished(): This is a method of LoaderManager.LoaderCallbacks interface. When a previously called Loader has finished loading data, this method is called.

10.onLoaderReset(): This is also an constituent method of LoaderManager.LoaderCallbacks interface. If any previous loader is reset then this method is called.

6. How to avoid Memory leak in Android Application?

There are two easy ways to avoid context-related memory leaks. The most obvious one is to avoid escaping the context outside of its own scope. The example above showed the case of a static reference but inner classes and their implicit reference to the outer class can be equally dangerous. The second solution is to use the Application context. This context will live as long as your application is alive and does not depend on the activities' life cycle. If you plan on keeping long-lived objects that need a context, remember the application object. You can obtain it easily by calling Context.getApplicationContext() or Activity.getApplication().

In summary, to avoid context-related memory leaks, remember the following:

  • Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Try using the context-application instead of a context-activity
  • Avoid non-static inner classes in an activity if you don’t control their life cycle, use a static inner class, and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its Winner class for instance
  • A garbage collector is not an insurance against memory leaks

So what are some common mistakes that lead to memory leaks?

1. Broadcast Receivers:

Consider this scenario — you need to register a local broadcast receiver in your activity. If you don’t unregister the broadcast receiver, then it still holds a reference to the activity, even if you close the activity.

How to solve this? Always remember to call unregister receiver in onStop() of the activity.

2. Static Activity or View Reference:

Consider the below example — You are declaring a TextView as static (for whatever reason). If you reference activity or view directly or indirectly from a static reference, the activity would not be garbage collected after it is destroyed.

How to solve this? Always remember to NEVER use static variables for views or activities or contexts.

3. Singleton Class Reference:

If you have defined a Singleton class then you need to pass the context in order to fetch some files from the local storage.

How to solve this?
Option 1:
Instead of passing activity context i.e. this to the singleton class, you can pass applicationContext().
Option 2: If you really have to use activity context, then when the activity is destroyed, ensure that the context you passed to the singleton class is set to null.

7. Memory Management in Android?

Android uses paging and mmap instead of providing swap space, which means any memory your application touches cannot be paged out unless you release all references.

The Dalvik* Virtual Machine’s heap size for application processes is limited. Applications startup with 2 MB, and the maximum allocation, marked as “largeHeap,” is limited to 36 MB (depending on the specific device configuration). Examples of large heap applications are Photo/Video Editor, Camera, Gallery, and Home Screen.

Android stores background application processes in an LRU cache. When the system runs low on memory, it will kill processes according to the LRU strategy, but it will also consider which application is the largest memory consumer. Currently, the maximum background process count is 20 (depending on the specific device configuration). If you need your app to live longer in the background, de-allocate unnecessary memory before moving to the background, and the Android system will be less likely to generate an error message or even terminate the app.

Garbage Collection

A managed memory environment, like the ART or Dalvik virtual machine, keeps track of each memory allocation. Once it determines that a piece of memory is no longer being used by the program, it frees it back to the heap, without any intervention from the programmer. The mechanism for reclaiming unused memory within a managed memory environment is known as garbage collection. Garbage collection has two goals: find data objects in a program that cannot be accessed in the future, and reclaim the resources used by those objects.

Android’s memory heap is a generational one, meaning that there are different buckets of allocations that it tracks, based on the expected life and size of an object being allocated. For example, recently allocated objects belong in the Young generation. When an object stays active long enough, it can be promoted to an older generation, followed by a permanent generation.

Each heap generation has its own dedicated upper limit on the amount of memory that objects there can occupy. Any time a generation starts to fill up, the system executes a garbage collection event in an attempt to free up memory. The duration of the garbage collection depends on which generation of objects it’s collecting and how many active objects are in each generation.

Even though garbage collection can be quite fast, it can still affect your app’s performance. You don’t generally control when a garbage collection event occurs from within your code. The system has a running set of criteria for determining when to perform garbage collection. When the criteria are satisfied, the system stops executing the process and begins garbage collection. If garbage collection occurs in the middle of an intensive processing loop like animation or during music playback, it can increase processing time. This increase can potentially push code execution in your app past the recommended 16ms threshold for efficient and smooth frame rendering.

Additionally, your code flow may perform kinds of work that force garbage collection events to occur more often or make them last longer-than-normal. For example, if you allocate multiple objects in the innermost part of a for-loop during each frame of an alpha blending animation, you might pollute your memory heap with a lot of objects. In that circumstance, the garbage collector executes multiple garbage collection events and can degrade the performance of your app.

DDMS

Android Studio includes a debugging tool called the Dalvik Debug Monitor Service (DDMS). DDMS provides services like screen capture on the device, threading, heap information on the device, logcat, processes, incoming calls, SMS checking, location, data spoofing, and many other things related to testing your Android application.

DDMS connects the IDE to the applications running on the device. On Android, every application runs in its own process, each of which hosts its own virtual machine (VM). And each process listens for a debugger on a different port.

When it starts, DDMS connects to ADB (Android Debug Bridge, which is a command-line utility included with Google’s Android SDK.). An Android Debugger is used for debugging the Android app and starts a device monitoring service between the two. This will notify DDMS when a device is connected or disconnected. When a device is connected, a VM monitoring service is created between ADB and DDMS, which will notify DDMS when a VM on the device is started or terminated.

How to Improve Memory Usage

Android is a worldwide mobile platform and millions of Android developers are dedicated to building stable and scalable applications. Here is a list of tips and best practices for improving memory usage in Android applications:

  1. Be careful about using a design pattern with “abstraction”. Although from the point of view of design pattern, abstraction can help to build more flexible software architect. In the mobile world, abstraction may involve side effects for its extra code to be executed, which will cost more time and memory. Unless abstraction can provide your application a significant benefit, it would be better not to use it.
  2. Avoid using “enum”. Enum will double memory allocation than ordinary static constant, so do not use it.
  3. Try to use the optimized SparseArray, SparseBooleanArray, and LongSparseArray containers instead of HashMap. HashMap allocates an entry object during every mapping which is a memory inefficient action, also the low-performance behavior — “autoboxing/unboxing” is spread all over the usage. Instead, SparseArray-like containers map keys into a plain array. But please remember that these optimized containers are not suitable for large numbers of items, when executing add/remove/search actions, they are slower than Hashmap if your data set is over thousands of records.
  4. Avoid creating unnecessary objects. Do not allocate memory especially for short-term temporary objects if you can avoid it, and garbage collection will occur less when fewer objects are created.
  5. Check the available heap of your application. Invoke ActivityManager::getMemoryClass() to query how many heap(MB) is available for your application. OutofMemory Exception will occur if you try to allocate more memory than is available. If your application declares a “largeHeap” in AndroidManifest.xml, you can invoke ActivityManager::getLargeMemoryClass() to query an estimated large heap size.
  6. Coordinate with the system by implementing onTrimMemory() callback. Implement ComponentCallbacks2::onTrimMemory(int) in your Activity/Service/ContentProvider to gradually release memory according to latest system constraints. The onTrimMemory(int) helps overall system response speed, but also keep your process alive longer in the system.
  7. When TRIM_MEMORY_UI_HIDDEN occurs, it means all the UI in your application has been hidden and you need to free UI resources. When your application is the foreground, you may receive TRIM_MEMORY_RUNNING[MODERATE/LOW/CRITICAL], or in the background, you may receive TRIM_MEMORY_[BACKGROUND/MODERATE/COMPLETE]. You can free non-critical resources based on the strategy to release memory when system memory is tight.
  8. External libraries should be used carefully. External libraries are often written for a non-mobile device and can be inefficient in Android. You must take into account the effort in porting and optimizing the library for mobile before you decide to use it. If you are using a library for only one or two features from its thousands of other uses, it may be best to implement it by yourself.
  9. Services should be used with caution. If you need a service to run a job in the background, avoid keeping it running unless it’s actively performing a task. Try to shorten its lifespan by using an IntentService, which will finish itself when it’s done handling the intent. Services should be used with caution to never leave one running when it’s not needed. Worst case, the overall system performance will be poor and users will find your app and uninstall it (if possible).
  10. But if you are building an app that needs to run for a long period of time, e.g., a music player service, you should split it into two processes: one for the UI and the other for the background service by setting the property “android: process” for your Service in AndroidManifest.xml. The resources in the UI process can be released after hidden, while the background playback service is still running. Keep in mind that the background service process MUST NOT touch any UI; otherwise, the memory allocation will be doubled or tripled!

How to Avoid Memory Leaks

Use memory carefully with the above tips can bring benefits for your application incrementally, and make your application stay longer in the system. But all benefits will be lost if memory leakage happens. Here is some familiar potential leakage that the developer needs to keep in mind.

  1. Remember to close the cursor after querying the database. If you want to keep the cursor open long-term, you must use it carefully and close it as soon as the database task finished.
  2. Remember to call unregisterReceiver() after calling registerReceiver().
  3. Avoid Context leakage. If you declare a static member variable “Drawable” in your Activity, and then call view.setBackground(drawable) in onCreate(), after screen rotates, a new Activity instance will be created and the old Activity instance can never be de-allocated because drawable has set the view as callback and view has a reference to Activity (Context). A leaked Activity instance means a significant amount of memory, which will cause OOM easily.
  4. There are two ways to avoid this kind of leakage:
  5. Do not keep long-lived references to a context-activity. A reference to an activity should have the same life cycle as the activity itself.
  6. Try using the context-application instead of a context-activity.

Be careful about using Threads. Threads in Java are garbage collection roots; that is, the Dalvik Virtual Machine (DVM) keeps hard references to all active threads in the runtime system, and as a result, threads that are left running will never be eligible for garbage collection. Java threads will persist until either they are explicitly closed or the entire process is killed by the Android system. Instead, the Android application framework provides many classes designed to make background threading easier for developers:

  • Use Loader instead of a thread for performing short-lived asynchronous background queries in conjunction with the Activity lifecycle.
  • Use Service and report the results back to the Activity using a BroadcastReceiver.
  • Use AsyncTask for short-lived operations.

Android Profiling Tools

Android Profiling Tool will help you in managing your memory on the Android device. The Android SDK provides two ways of profiling app memory:

  1. Using Allocation Tracker
  2. Using Heap Dump

Using Allocation Tracker

Allocation Tracker records each memory allocation that your application performs during the profiling cycle. The Allocation Tracker is useful when you want to find out which type of memory allocation is taking place. But it does not give you any information about the application’s heap which is the memory set aside for dynamic memory allocation.

The Allocation Tracker displays the memory allocation for the selected process. It shows the specific objects that are being allocated along with the thread, method, and the line code that allocated them.

DDMS provides a feature to track objects that are being allocated to memory and to see which classes and threads are allocating the objects. This allows you to track where the objects are being allocated in real-time when you perform certain actions in your application. This data is valuable for measuring memory usage that can otherwise affect application performance.

Starting Allocation Tracker in Android DDMS

  • Install your app in Android emulator or device. Click on Android button available at the bottom of the Android Studio.
  • Start allocation tracking.
  • Play around with the app for some time.
  • Stop allocation tracking.

Refer to the Figure below:

  • After a few seconds, a pane with your recorded data opens.

Heap Dumps

A Heap Dump is a snapshot of an application’s heap, which is stored in a binary format called HPROF. The Dalvik virtual machine can produce a complete dump of the contents of the virtual heap. This is very useful for debugging memory usage and looking for memory leaks.

8. What is RoomDatabase?

The Room library acts as an abstract layer for the underlying SQLite database. Thus, Room annotations are used:

  • Database and Entities where entities are POJO classes representing table structures.
  • To specify operation for retrieval, updating, and deletion.
  • To add constraints, such as foreign keys.
  • Support for LiveData.

There are 3 major components in Room

  1. Entity: A class annotated with the @Entity annotation is mapped to a table in the database. Every entity is persisted in its own table and every field in class represents the column name.
  • tableName the attribute is used to define the name of the table
  • Every entity class must have at least one Primary Key field, annotated with @PrimaryKey
  • Fields in entity class can be annotated with @ColumnInfo(name = “name_of_column”) annotation to give specific column names

2. DAO: Data Access Object is either be an interface or an abstract class annotated with @Doa annotation, containing all the methods to define the operations to be performed on data. The methods can be annotated with

  • @Query to retrieve data from database
  • @Insert to insert data into database
  • @Delete to delete data from database
  • @Update to update data in database

The result of SQLite queries are composed into cursor object, DAO methods abstract the conversion of cursor to Entity objects and vice-versa.

3. Database: Database is a container for tables. An abstract class annotated with @Database annotation is used to create a database with a given name along with the database version.

  • version = intValueForDBVersion is used to define the database version
  • entities = {EntityClassOne.class, ....} is used to define list of entities for database

9. When does ANR Occurs?

An ANR will occur if you are running a process on the UI thread which takes a long time, usually around 5 seconds. During this time the GUI (Graphical User Interface) will lock up which will result in anything the user presses will not be actioned. After the 5 seconds approx has occurred, if the thread still hasn’t recovered then an ANR dialogue box is shown informing the user that the application is not responding and will give the user the choice to either wait, in the hope that the app will eventually recover, or to force close the app.

A crash is when an exception within the app has been thrown which has not been handled. For example, if you try to set the text of an EditText component, but the EditText is null and there is no try catch statement to catch the exception that your app will crash and will be force closed. The user will not see what caused the crash, they will be shown a dialogue telling that the app has force closed unexpectedly and will give them the option to send a bug report. In this example if you were to look in the bug report you would see the error caused by java.lang.NullPointerException.

10. How to share data in between apps?

We can send data between two individual applications in Android if the receiving application is configured to received that type of data.
For example if an application is to receive Image data then it’s manifest should contain the ImageFilter as Shown below

<intent-filter>

<action android:name="android.intent.action.SEND" />

<category android:name="android.intent.category.DEFAULT" />

<data android:mimeType="image/*" />

</intent-filter>

For example ac activity in the receiving application is named “Receiver” then its entry in the AndroidManifest.xml should be like this.

<activity

android:name="com.example.receivingapplication.Receiver"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.SEND" />

<category android:name="android.intent.category.DEFAULT" />

<data android:mimeType="image/*" />

</intent-filter>

</activity>

This line means that the Receiver application can receive any image type data.

https://livebook.manning.com/book/android-in-practice/chapter-8

--

--

--

Senior Software Engineer | Android | Java | Kotlin|Xamarin Native Android|Flutter

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Abhishek Srivastava

Abhishek Srivastava

Senior Software Engineer | Android | Java | Kotlin|Xamarin Native Android|Flutter