Android Application Class Example
We all know there is an Application class in the Android API, that is usually used for maintaining global application state but how does it work? In this article, let’s explore it with the help of examples and discuss its advantages & disadvantages.
Table Of Contents
1. Introduction
Application class is a base class within an Android application, that comprises of different components such as Activity, Service, Broadcast Receivers & Content Providers. These components communicate with each other with the help of Intents, Callbacks & other methods. Often, components depend on other components, for eg: initiating an activity on a button click.
In an application, there is often a need of making data available to all the components. For example, data entered by a user, needs to exist somewhere to let other components consume it.
Now, where do we keep the data so that it’s globally available? One may use Intent
to pass information between components, but it can’t be accessed by every component. Components go through a lifecycle, that means they are not always available to provide the data. We need something global, that can be accessed by any component at any time. The answer to the question is Application class
, that serves the purpose of maintaining the global application state.
You can also create a custom application class and add it to Android Manifest file,
that will instantiate the class when the application process starts. Let’s discuss this later in the article.
Now, its time to talk about the Callback methods available in the Application class.
2. Callback Methods
Here are the Callback methods:
onConfigurationChanged
– void onConfigurationChanged(Configuration newConfig)
This method is called by the system when the device configuration changes, while running the application. When the configuration changes, only an Activity restarts (OnCreate()
is called, followed byOnStart()
andOnResume()
), leaving the other components in the same state as they were before the configuration changed. The resource values are always up to date in this method, and can be retrieved by other components at any time.See an example below where we check the device orientation.
onConfigurationChanged() example
@Override public void onConfigurationChanged (Configuration newConfig){ super.onConfigurationChanged(newConfig); if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){ Toast.makeTest(context, "Landscape", Toast.LENGTH_SHORT).show; }else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){ Toast.makeTest(context, "Portrait", Toast.LENGTH_SHORT).show; } }
onCreate
– void onCreate()
This method is called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created. Implementations should be as quick as possible (for example using lazy initialization of state) since the time spent in this function directly impacts the performance of starting the first activity, service, or receiver in a process. If you override this method, be sure to callsuper.onCreate()
.Signature of onCreate()
@Override public void onCreate (){ super.onCreate(); Log.d("Application Class", "ONCREATE"); }
Put the logs in the
OnCreate()
method in Activity and Application Class. When you run the code, check the logcat and you will see that Application Class is called before any Activity.onLowMemory
– void onLowMemory()
This is called when the overall system is running low on memory, and actively running processes should trim their memory usage. While the exact point at which this will be called is not defined, generally it will happen when all background process have been killed. That is, before reaching the point of killing processes hosting service and foreground UI that we would like to avoid killing. You should implement this method to release any caches or other unnecessary resources you may be holding on to. The system will perform a garbage collection for you after returning from this method.Signature of onLowMemory()
@Override public void onLowMemory() { super.onLowMemory(); }
onTerminate
– void onTerminate()
This method is for use in emulated process environments. It will never be called on a production Android device, where processes are removed by simply killing them; no user code (including this callback) is executed when doing so.onTrimMemory
– void onTrimMemory(int level)
When building the Android application, the one thing developer has to keep in mind is Memory. It is the most crucial part of the application process and a developer’s duty to check if there is any memory leak or if there is need to free up resources not used by application anymore. Denying this, may lead to unexpected killing of the application process by the system. This method is called when the operating system has determined that it is a good time for a process to trim unneeded memory from its process. This will happen for example, when it goes in the background and there is not enough memory to keep, as many background processes running as desired. You should never compare to exact values of the level, since new intermediate values may be added — you will typically want to compare if the value is greater or equal to a level you are interested in.To retrieve the process’s current trim level at any point, you can use
ActivityManager.getMyMemoryState(RunningAppProcessInfo)
.The different levels are:
TRIM_MEMORY_RUNNING_MODERATE
level means the device is beginning to run low on memory but not killable.TRIM_MEMORY_RUNNING_LOW
level means the device is running much lower on memory and not killable. It is better to start releasing unused resources.TRIM_MEMORY_RUNNING_CRITICAL
level means the device is running extremely low on memory and the system will begin killing background processes.TRIM_MEMORY_UI_HIDDEN
level means the application’s UI is no longer visible, so this is a good time to release large resources that are used only by your UI.TRIM_MEMORY_MODERATE
level means the process is around the middle of the background LRU list and if the system further gives memory warning then there is a chance for your process to be killed.TRIM_MEMORY_BACKGROUND
level means the process has gone on to the LRU list and it’s a good opportunity to clean up the resources before user returns to the app.TRIM_MEMORY_COMPLETE
level means the process is nearing the end of the background LRU list, and if more memory isn’t found soon, it will be killed.Note: This callback was added in API level 14. For older versions, you can use
onLowMemory()
.registerActivityLifecycleCallbacks
– void registerActivityLifecycleCallbacks (Application.ActivityLifecycleCallbacks callback). It allows you to handle state change of each activity in your program.
Signature of registerActivityLifecycleCallbacks// set ActivityLifecycleCallbacks Application app = getApplication(); app.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks(){ @Override public void onActivityCreated(Activity activity, Bundle bundle) {} @Override public void onActivityStarted(Activity activity) {} @Override public void onActivityResumed(Activity activity) {} @Override public void onActivityPaused(Activity activity) {} @Override public void onActivityStopped(Activity activity) {} @Override public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {} @Override public void onActivityDestroyed(Activity activity) {} });
registerComponentCallbacks
– void registerComponentCallbacks (ComponentCallbacks callback)
Add a new ComponentCallbacks to the base application of the Context, which will be called at the same times as the ComponentCallbacks methods of activities and other components are called. Note that you must be sure to useunregisterComponentCallbacks(ComponentCallbacks)
when appropriate in the future; this will not be removed for you.Signature of registerComponentCallbacks
// set ComponentCallbacks with out overriding app.registerComponentCallbacks(new ComponentCallbacks2() { @Override public void onConfigurationChanged(Configuration configuration) { //your code goes here } @Override public void onLowMemory() { // use it only for older API version } @Override public void onTrimMemory(int level) { //code } });
registerOnProvideAssistDataListener
Callback usedApplication.OnProvideAssistDataListener
. You may know, there is a Now On Tap functionality in Android, that is visible by long pressing the home button. A small window from the bottom of the display, slides up to show you the relevant information. The information you get, depends on what you’re viewing on your screen at that time (for eg: Music app displays information about the song on the screen). To provide additional information to the assistant, your app provides global application context by registering an app listener usingregisterOnProvideAssistDataListener()
and supplies activity-specific information with activity callbacks by overridingonProvideAssistData()
andonProvideAssistContent()
. It allows you to place anything, into theBundle
, you would like to appear in theIntent.EXTRA_ASSIST_CONTEXT
part of the assist Intent.
In the example below, a music app provides structured data to describe the music album that the user is currently viewing.onProvideAssistContent Example
@Override public void onProvideAssistContent(AssistContent assistContent) { super.onProvideAssistContent(assistContent); String structuredJson = new JSONObject() .put("@type", "MusicRecording") .put("@id", "https://example.com/music/recording") .put("name", "Album Title") .toString(); assistContent.setStructuredData(structuredJson); }
unregisterComponentCallbacks
– void unregisterComponentCallbacks (ComponentCallbacks callback)
The method removes a ComponentCallbacks object that was previously registered withregisterComponentCallbacks(ComponentCallbacks)
. The other callbacksunregisterActivityLifecycleCallbacks
andunregisterOnProvideAssistDataListener
does the same thing removing the corresponding object that was previously registered.
3. Creating the Application
Consider a scenario, where you have two screens that display the Game score. Up to date score has to be available on all the screens. This requires the Game score to be globally stored & available. In this example screens are two different activities: FirstScreen & SecondScreen; and the Application class (Global State) provides the most up to date score.
To create a custom application class, Application class needs to be extended and must be declared in the Manifest.
Let’s start building the application and see how code handles the application state.
3.1 The Activity File
This is the first screen.
FirstGameScreenActivity.java
public class FirstGameScreenActivity extends Activity { Button score,upScore,downScore; GlobalState state; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first_game_screen); state = ((GlobalState) getApplicationContext()); score= (Button)findViewById(R.id.score_button); upScore= (Button)findViewById(R.id.up_button); downScore= (Button)findViewById(R.id.down_button); score.setText(String.valueOf(state.getGameScore())); } @Override protected void onResume() { super.onResume(); } public void incrementScore(View view) { state.incrementScore(); score.setText(String.valueOf(state.getGameScore())); } public void decrementScore(View view){ state.decrementScore(); score.setText(String.valueOf(state.getGameScore())); } public void nextScreen(View view) { Intent intent = new Intent(this, SecondGameScreenActivity.class); startActivity(intent); finish(); } }
This is the second screen.
SecondGameScreenActivity.java
public class SecondGameScreenActivity extends Activity { Button score,upScore,downScore; GlobalState state; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second_game_screen); state = ((GlobalState) getApplicationContext()); score= (Button)findViewById(R.id.score_button); upScore= (Button)findViewById(R.id.up_button); downScore= (Button)findViewById(R.id.down_button); score.setText(String.valueOf(state.getGameScore())); } @Override protected void onResume() { super.onResume(); } public void incrementScore(View view) { state.incrementScore(); score.setText(String.valueOf(state.getGameScore())); } public void decrementScore(View view){ state.decrementScore(); score.setText(String.valueOf(state.getGameScore())); } public void previousScreen(View view) { Intent intent = new Intent(this, FirstGameScreenActivity.class); startActivity(intent); finish(); } }
This is the Custom Application class that extends Application Class
.
GlobalState.java
public class GlobalState extends Application { private int gameScore = 0; public int getGameScore() { return gameScore; } public void setGameScore(int gameScore) { this.gameScore = gameScore; } public void incrementScore(){ gameScore++; } public void decrementScore(){gameScore--;} }
3.2 The Layout File
These are the two layout files. I have used the recently added Constraint Layout
for convenience. It allows flexibility in positioning and sizing widgets. For more info refer Constraint Layout.
activity_first_game_screen.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorBlack"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="54dp" android:layout_gravity="center_horizontal" android:layout_marginStart="46dp" android:layout_marginTop="28dp" android:text="GAME SCREEN" android:textAlignment="center" android:textColor="@color/colorWhite" android:textSize="40sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/score_button" android:layout_width="46dp" android:layout_height="0dp" android:layout_gravity="center_horizontal" android:layout_marginBottom="17dp" android:text="" app:layout_constraintBottom_toTopOf="@+id/up_button" app:layout_constraintEnd_toEndOf="@+id/up_button" app:layout_constraintStart_toEndOf="@+id/up_button" /> <Button android:id="@+id/up_button" android:layout_width="47dp" android:layout_height="41dp" android:layout_marginEnd="9dp" android:layout_marginStart="138dp" android:layout_marginTop="136dp" android:backgroundTint="@color/colorGreen" android:onClick="incrementScore" android:text="+" android:textAlignment="center" android:textColor="@color/colorWhite" android:textSize="20sp" android:textStyle="bold" app:layout_constraintEnd_toStartOf="@+id/down_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" /> <Button android:id="@+id/down_button" android:layout_width="47dp" android:layout_height="41dp" android:layout_marginEnd="151dp" android:layout_marginTop="136dp" android:backgroundTint="@color/colorRed" android:onClick="decrementScore" android:text="-" android:textAlignment="center" android:textColor="@color/colorWhite" android:textSize="18dp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/up_button" app:layout_constraintTop_toBottomOf="@+id/textView" /> <Button android:id="@+id/next_screen_button" android:layout_width="235dp" android:layout_height="56dp" android:layout_marginBottom="7dp" android:onClick="nextScreen" android:text="Next Screen" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/score_button" /> </android.support.constraint.ConstraintLayout>
Here we have the second layout for second screen.
activity_second_game_screen.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorBlack"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="54dp" android:layout_gravity="center_horizontal" android:layout_marginStart="46dp" android:layout_marginTop="28dp" android:text="GAME SCREEN" android:textAlignment="center" android:textColor="@color/colorWhite" android:textSize="40sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/score_button" android:layout_width="46dp" android:layout_height="0dp" android:layout_gravity="center_horizontal" android:layout_marginBottom="17dp" android:text="" app:layout_constraintBottom_toTopOf="@+id/up_button" app:layout_constraintEnd_toEndOf="@+id/up_button" app:layout_constraintStart_toEndOf="@+id/up_button" /> <Button android:id="@+id/up_button" android:layout_width="47dp" android:layout_height="41dp" android:layout_marginEnd="9dp" android:layout_marginStart="138dp" android:layout_marginTop="136dp" android:backgroundTint="@color/colorGreen" android:onClick="incrementScore" android:text="+" android:textAlignment="center" android:textColor="@color/colorWhite" android:textSize="20sp" android:textStyle="bold" app:layout_constraintEnd_toStartOf="@+id/down_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" /> <Button android:id="@+id/down_button" android:layout_width="47dp" android:layout_height="41dp" android:layout_marginEnd="151dp" android:layout_marginTop="136dp" android:backgroundTint="@color/colorRed" android:onClick="decrementScore" android:text="-" android:textAlignment="center" android:textColor="@color/colorWhite" android:textSize="18dp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/up_button" app:layout_constraintTop_toBottomOf="@+id/textView" /> <Button android:id="@+id/next_screen_button" android:layout_width="235dp" android:layout_height="56dp" android:layout_marginBottom="7dp" android:onClick="previousScreen" android:text="Previous Screen" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/score_button" /> </android.support.constraint.ConstraintLayout>
4. Manifest
The manifest file
provides the most important information about the application to the Android system, before it runs. It defines the structure and metadata of an application and its components.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.archigupta.applicationclassexamle"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:name="GlobalState" . // declare the application class android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".FirstGameScreenActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondGameScreenActivity"> </activity> </application> </manifest>
5. Running the Application
See screenshots here. First Screen and Second Screen are the two game screens.
Now incrementing the score from First Screen and moving on to the next screen by pressing Next Screen button, will update the score on Second Screen.
6. Limitations
As we have seen above, putting the data in the Application class saves the overhead of passing it between objects. Besides this, if you store mutable objects in Application class, data might disappear or become invalid at any time. Also, there might be a possibility where your app may crash with Null Pointer Exception
. The application object is not guaranteed to stay in memory forever, at some point in time, it will get killed. When the application restarts, it never gets restarted from scratch. Instead, a new Application object is created and starts the Activity
, user was on before. It is better to store any mutable shared data using persistence strategies such as files, SharedPreferences
, SQLite
and so on.
To prevent data loss, use one of the following methods:
- Explicitly pass the data through
Intents
- Store the global data in memory
- Perform Null check before retrieving the value of an object
7. Conclusion
We have now seen how to use global data in an application by using Application
class. Despite of its usage, storing data in Application class is error prone and might crash your application, under certain circumstances.
8. Download the Source Code
This was an example of Application class.
You can download the full source code of this example here: Application Class Example
It’s one of my goals this year to make & publish a (simple) Android Application. Thanks for this post, it’s really helpful!