Hisashiburi da ne !! Today, let’s explore everything about Activities and intents. We’ll gradually progress about everything there is to activities and intents. I’ll not be covering basics like, what are activities and intents. But I’ll be covering some of the important and tricky topics that I often see, creates a lot of confusion.
We know that the activities are one of the 4 major components of an Android app. We are also well aware that both, activities and fragments contain their own lifecycles. So, why is that a topic of Confusion ? I mean if you are reading this, you must be aware about the lifecycle right ? They form the basics. However there are some things you need to keep in mind about lifecycles. Like, did you know, that there may be times, that an application is stopped without even calling onDestroy() or onStop() method ? Why does this happen ? and what can we do ? So, let’s dive right into it.
Here is a simple activity and I have just logged every method when it is called. So, as you guessed, when the activity starts, first method to be called is onCreate() followed by onStart() and then onResume(). This makes sense, since according to the textbook definition, onCreate() is called when the activity gets created, onStart() is called when activity is started and onResume() when activity is resumed is visible to the user. And this is what actually happens, as you can see in the image below.
OnCreate() is a good place to set up the application. Set up the views, setup the view models and adapters or any other thing which requires setup. OnStart() is a good place to add the listeners or the registering the recievers and things like that. Where as, onResume() is good place to update the views.
Now, as I press the home screen button, onPause(), onStop() and onSaveInstance() methods gets called. Since, onPause() is called when the activity is visible but not on foreground. OnStop() is called when activity is not visible to the user. And, of course if the activity is not visible to the user, all the instances that need to be saved for proper user experiences gets saved, right after onStop() is called in the onSaveInstance() method.
So, uptill now, all I told you were the basics. Nothing new. But now let’s look onto something more interesting. If the activity has been stopped and is now restarting, the onRestart() method is called even before onStart() method. Many people, I have interviewed or met, do not know the fact that onRestart() method exists or that it is not called on everytime.
So why bother using onRestart() when we know that onStart() is going to be called every time. Indeed it is correct that onStart() will be called everytime, yet we might want to customize the behaviour of the app, when the user opens the app for the first time. For example, we might want to show a greeting when the user reopens the app, but not when the user hasn’t closed the app, but just moved it to the home screen.
There can be many such cases to use onRestart() method, but the point here is to note that onRestart() method exists and is called before onStart(), but only when the activity is restarted.
One more important point, that I have seen many people do wrong, is that they think, all these methods are called before the event. Wrong !! Each of these methods are called right after the event. For example, onResume() is called right after the activity is called to the foreground. onPause() is called right after the activity is called off the foreground. And every lifecycle method is called right after the event is occurred.
Also, keep in mind, that once an onPause() method is called, Android system reserves the right to kill off your activity’s process at any time. So do not rely upon receiving any further events here.
Tip:- Always stick to the pairs. So, if you initialize something, in onCreate(), clean it up in onDestroy(). If you initialize something in onStart(), clean it up in onStop(). If you initialize something in onResume(), clean it up in onPause(). Do not mix and match. For example, do not initialize something in onStart() and then try to clean it up in onPause(), since there can be scenarios where the onPause() is getting multiple time, but onStop() only once. So, do not mix and match as it would bring a lot of inconsistency to the app and also, might crash the app.
Now, as I close the app, we will see the onDestroy() method called right after onPause() -> onStop() -> onSaveInstanceState() methods. One can be sure, that the onDestroy() would be called almost 95% of the times. However, there may be some cases when the onDestroy() or onStop() methods are not called.
Now, let’s try force closing the app from the settings. Notice that onDestroy() was never called.
There can be many reasons that onDestroy() is not called —
- The app crashes with an unhandled exception
- The user force-stops or uninstalls the app from settings
- Android has an urgent need to free up RAM (eg, to provide more ram to the game you are playing), wants to terminate the process but does not have the time to call all the lifecycle methods.
These cases might cause the onDestroy() or onStop() method to never be called. So, you need to bear in mind that this can happen, and thus handle the cases gracefully. So, for example in the case of a crash, always make sure to handle all the edge cases and exceptions. If you are let’s say trying to unregister a reciever in the onDestroy() method, try to first check if the register is already running. Also, note that if the app crashes due to some reason, the lifecycle methods following it will not be called, since the Android ecosystem will assume that the app is in an unstable state. So always try to handle the exceptions.
I’d like to share an interview question I faced once, during an interview. The question is Can onDestroy() method be called without onPause() and onStop() methods being called ?
And the answer is to that is yes. It can be. If we finish the activity in onCreate() method itself, the onPause() and onStop() methods will never be called. That being said, do we ever require that ? Well, yes. Depending on the circumstances. Look at the code snippet below
In the example above, the splash activity is performing something similar to Instagram or any other professional app. While the splash activity is being shown, the app is checking if the user is logged in or not. If yes, then they are taken to MainActivity otherwise, taken to LoginActivity, and then the activity finishes. In this case, notice that onStop() and onPause() will never be called. onDestroy() method will be called directly.
We have now a good grasp over the lifecycle of the activities. Now let’s try to understand more about the navigation between activities. Opening new activities are as simple as calling the startActivity() method. And you must be aware about the explicit and implicit intents. But let’s look more at the navigation and how android performs the navigation of the activities.
There are 2 kinds of intents. One is explicit — where an activity can start another activity inside the same application. The other is implicit- where the user has the option to choose from the apps that which app would handle that intent. For example, If you have a web url, then you can start an implicit intent to let user choose that which app will handle this url.
Also, the Intent() class provides us with the functions to add extra data we need, in order to pass on to the other activities, but it only provides options for the almost all data types such as Integers, booleans, Strings, byte arrays etc. However, we often time need to pass in the whole object. Like, when navigating to the FeedActivity of a social networking app, we might need to pass in the complete User object instead of simple integers. Obviously, we can pass just the user ID, and then fetch the user again in the Feed Activity, but that would be cumbersome, as it would require a lot of network and a lot of battery. So it is often times a good idea to pass the complete Object itself. So, how can we do that ? Well, we can use Parcelable or Serializable classes. But what exactly are those and why should we care ?
StartActivity() function is asynchronous. We can pass intents in the startActivity function, but as noted above we cannot pass arbitary objects in the Intents. This is because of the IPC communication architecture. All the android apps run on different threads. Thus when we need to pass Intents across the process boundaries, intent extras need to be something that can be converted into byte arrays, for smooth communications.
Wait ! You said that each app runs on different threads. So why do we need to care about this inside our own Android apps ? After all we want to start Activity in our own app right ?
Well, as I told you that startActivity() function is asynchronous, that means the function itself is performed in a different thread. Now the intent, which we have passed in the startActivity() function is also inside the new thread created by it. Actually, when we call startActivity() function, it is not our app that is starting the new activity, instead the android OS itself starts a new activity inside our app. So that means, when we call startActivity, this information gets passed on to the Android Core OS and then when the activity is opened the information is relayed back to our apps process. Now, while this happens, the intents we defined are sent along. That’s why since, the communication is happening between threads, so we need to take care of the arbitrary objects , if we are passing one.
Okay !! So to do that, we need to make the class into Serializable or Parcelable. Doing this is as simple as adding the Serializable or Parcelable implementation to the class.
So what’s the difference between the two ? Well, Serializable is a classic Java construct, designed to allow instances of your class and other Serializable objects your instances hold onto, to be serialized into files and read later back on. Parcelable is very similar to serializable , just that , serializable classes are designed for durable storage of objects, ones that can be read back in days, weeks, months or even years later. Now, due to this, these classes need to account for the change in the code implementing those classes and needs to have hooks to help with converting old objects into new ones, and saved objects into new objects. This adds overheads and affects performance. Thus parcelable classes were introduced, which are not responsible for durable storage, instead they are sort of one time things, which are just responsible for getting the objects across process borders. It works by making a simplified assumption that the class definition will not change from when the objects are converted to byte array and then back to objects. As a result, Parcelables are faster than Serializable for Android IPC use.
Note — There may be cases that you want a result from the activities you have started. So for those cases, there is a completely separate function startActivityForResult(). Since startActivity() is asynchronous, it is clear that we will not receive any kind of return value from this method. Thus, to solve this startActivityForResult() method was made. While, it is asynchronous itself, yet it allows the activity to set the return value using the setResult() method that is delivered to the original activity via onActivityResult() method
Launch Modes and Flags
Whenever we start an activity we can either add launch modes in the manifest file itself, or we can set the flags for the activities programmatically using intents. So why do we need them ? First we need to be clear that, Android uses stack for the activities. So, as the new activities are created, Android ecosystem just adds the new activities to the top of the stack. And thus when we press back button, we simply pop the first activity of the stack and do not exit the whole application. This is the standard behaviour , and is the default launch mode of any activity. launchMode=”standard”. In this mode, multiple instances of an Activity can be created and added to the same or different tasks.
But, not everytime we might want the same behaviour. For example, let’s say we have an Activity A. Then we open up an Activity B based on some user input and then Activity C based on some more user input. So the activity stack would look like A -> B -> C. Now if we open up Activity A again, we would see that the stack looks like this. A -> B -> C -> A. A new instance of Activity A has been created. This might contain some different input, but it is still a new instance of Activity A. This is the default behaviour of an App. So, let’s say we do not want this, instead we want to bring the existing instance of Activity A to the front instead to creating a new one. So we can use either launchMode=”singleTask” in the manifest file or Intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) if we are doing this programmatically
Similarly, there are more types of flags such as SingleTop. In here if the activity is already at the top of the stack, example Activity C in A->B->C, and
Case 1 -> we launch Activity C again from somewhere in the app, then the existing Activity C will receive the new intents from onNewIntent() function, and new instance of C would not be created.
Case 2 -> we launch Activity C in the stack A->B then a new instance of C would be created as usual. Like A->B->C
Case 3 -> We launch Activity C in this stack A->B->C->D, then a new instance of C would be created since C is not at the top.
Also, programmatically we can Intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP). These 3 are the most used flags and launch modes. If you want to learn more about them in detail, I recommend looking at Developer documentation.
I have interviewed many people, who do not seem to know that Invisible activities also exists in android. I was one of them. I didn’t know the concept of Invisible activities, and when I first heard about it, I was not sure that why it existed or why would one every use it. I mean, Activities contains the views right ? So, why bother about invisible activities which do not contain any views. ?
Well, there are few cases where one might need an invisible activity. Mostly, these will be the cases where something else in the system would say that it needs you to have an activity. For example, home screen launcher icons only start up the activities. However, we might sometimes need a home screen launcher that simply trigger some work to be done in background , maybe using a service. So, for those times, one would want an invisible activity in Android.
So, how would we create an invisible activity. Well, this part is pretty simple
Adding this in the styles.xml file and then adding this style to the activity in the manifest file using android:Theme attribute, will simply make the activity transparent. Well, there is one more option, instead of making the activity transparent, we can also use Theme.NoDisplay theme for the activities for android devices 6.0 or higher
On Android 6.0 and higher, using Theme.NoDisplay without calling finish() in onCreate() (or, technically, before onResume()) will crash your app. This is why the recommendation is to use the above method described, which does not suffer from this limitation.
Wow, seems like we have made some progress, and I hope that you were able to gain atleast some valuable information from this post. If you did, please share this content, so that it could reach more people. I will not monetize on this, thus I would not be making this series for premium members. Instead, I would love to share the knowledge and help some people if I can. I believe that education should be free. If you liked the content, please like or share, to increase the reach of this post. Thank you for your reading, and until next time.