Activities in Android

Overview

Activity

  • Activity is a crucial component of an Android application.
  • Unlike other programming languages, Android system starts an application from the onCreate() callback of an Activity.
  • The user journey of a user in an application is unpredictable. For instance, a user can open an application from Home Screen or he/she can open the application from other application's Activity.
  • Activity can be used by an application to start other application, when doing so, the source application isn't calling the entire application as a whole instead, it is calling a specific Activity which serves as contact point of that application.
  • When you are using an Activity you must declare that Activity in the manifest file of the application. The Activity must be declared in the <activity> tag under <application> tag
 <application
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
    • <application>
      • Some main important attributes of Application.
      • android:backupInForeground
        • The system shuts down an app during auto backup operation, so use this attribute with caution. Setting this flag to true can impact app behavior while the app is active.
      • android:banner
        • The system uses the banner to represent an app in the Android TV home screen. Since the banner is displayed only in the home screen, it should only be specified by applications with an activity that handles the CATEGORY_LEANBACK_LAUNCHER intent.
  • Activity Lifecycle
    • Below is the illustration of Activity Lifecycle.
    • onCreate()
      • On activity creation, the activity enters the Created state.
      • This is a callback method of Activity which you must implement to set the layout using setContentView(View).
      • You perform basic application startup logic that should happen only once for the entire life of the activity.
      • Prototype of onCreate()
 override fun onCreate(savedInstanceState: Bundle?)
      • This method receives a parameter of Bundle which contains activity's previously saved state, as savedInstanceState. If the activity has never existed before, the value of the Bundle object is null.
      • If you want to send ViewGroup into setContentView() then please refer User Interface documentation. 
      • Your Activity doesn't reside in Created state.
    • onStart()
      • When the activity enters the Started state, the system invokes this callback.
      • In this state the Activity is visible to the user.
      • Prototype of onStart()
 override fun onStart()
      • This callback method is active again after onRestart().
      • If You can call finish() from within this function, in which case onStop() will be immediately called after onStart() without the lifecycle transitions in-between (onResume() and onPause()) executing.
 class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("MainActivity", "MainActivity :: onCreate")
}

override fun onStart() {
super.onStart()
finish()
Log.d("MainActivity", "MainActivity :: onStart")
}

override fun onResume() {
super.onResume()
Log.d("MainActivity", "MainActivity :: onResume")
}

override fun onPause() {
super.onPause()
Log.d("MainActivity", "MainActivity :: onPause")
}

override fun onStop() {
super.onStop()
Log.d("MainActivity", "MainActivity :: onStop")
}

override fun onDestroy() {
super.onDestroy()
Log.d("MainActivity", "MainActivity :: onDestroy")
}
}
 2022-03-26 18:00:29.854 17032-17032/com.example.myapplication D/MainActivity: MainActivity :: onCreate
2022-03-26 18:00:29.873 17032-17032/com.example.myapplication D/MainActivity: MainActivity :: onStart
2022-03-26 18:00:30.448 17032-17032/com.example.myapplication D/MainActivity: MainActivity :: onStop
2022-03-26 18:00:30.454 17032-17032/com.example.myapplication D/MainActivity: MainActivity :: onDestroy
      • If you initialize something in onStart() callback, release it in onStop() callback. 
      • Your Activity doesn't reside in Started state.
    • onResume()
      • When the activity enters the Resumed state, it comes to the foreground, and then the system invokes the onResume() callback.
      • In this state the user can interact with the Application or Activity.
      • When an interruptive event occurs, the activity enters the Paused state, and the system invokes the onPause() callback.
      • onResume() is active again after onPause() in Multi-Window mode or if user returns back to the Activity from a dialog.
      • If you initialize something in onResume() callback, release it on onPause() callback.
      • Adding logic for Activity callbacks into an independent, lifecycle-aware component allows you to reuse the component across multiple activities without having to duplicate code. You can use the reference Handling Lifecycles with Lifecycle-Aware Components.
      • Your Activity resides in Resumed State.
    • onPause()
      • State of the Activity is Paused.
      • In this state the user may see the UI of the Activity especially in (Multi-Window mode).
      • This callback is well used in Multi-Window mode.
      • You can also use the onPause() method to release system resources, handles to sensors (like GPS), or any resources that may affect battery life while your activity is paused and the user does not need them.
      • You should not use onPause() to save application or user data, make network calls, or execute database transactions; such work may not complete before the method completes because it is very brief. Instead, you should perform heavy-load shutdown operations during onStop()
      • Adding logic for Activity callbacks into an independent, lifecycle-aware component allows you to reuse the component across multiple activities without having to duplicate code. You can use the reference Handling Lifecycles with Lifecycle-Aware Components.
      • If the activity returns from the Paused state to the Resumed state, the system keeps the Activity instance in memory, and recalls that instance when the system invokes onResume() callback. In this scenario, you don’t need to re-initialize the components that were created during any of the callback methods leading up to the Resumed state.
      • Additional Information: Saving and restoring activity state.
      • If the activity becomes completely invisible, the system calls onStop().
    • onStop()
      • When your activity is no longer visible to the user, it has entered the Stopped state and the system call onStop() callback.
      • In the onStop() method, the app should release or adjust resources that are not needed while the app is not visible to the user.
      • Using onStop() instead of onPause() ensures that UI-related work continues, even when the user is viewing your activity in multi-window mode.
      • You should also use onStop() to perform relatively CPU-intensive shutdown operations.
      • When your activity enters the Stopped state, the Activity object is kept in system memory: It maintains all state and member information, but is not attached to the window manager. When the activity resumes, the activity recalls all this information. You don’t need to re-initialize components that were created during any of the callback methods leading up to the Resumed state.
      • If the Activity comes back from this state, the system revokes onRestart() callback.
      • If the Activity is finished running, the system calls onDestroy().
      • Additional Information: Room Persistence Library.
      • Additional Information: Saving and restoring activity state.
    • onDestroy()
      • onDestroy() is called before the activity is destroyed.
      • The system calls this callback either because:
        • The activity is finishing (due to the user completely dismissing the activity by pressing Back Button or due to finish() being called on the activity), or
        • The system is temporarily destroying the activity due to a configuration change (such as device rotation or multi-window mode)
      • If you are using ViewModel and if you don't want to restore data when the Activity is recreated then you should call onCleared() in this callback.
      • If onDestroy() is called due to a configuration change, the system immediately creates a new instance of activity and then that new instance, calls onCreate() with a new configuration.
      • The onDestroy() callback should release all resources that have not yet been released by earlier callbacks such as onStop().
  • Demonstration of Activity Lifecycle of two Activities
 class MainActivity : AppCompatActivity() {
private var button: Button? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("MainActivity", "MyApplication :: MainActivity :: onCreate")
button = findViewById(R.id.btnNext)
button?.setOnClickListener {
Intent(this, SecondActivity::class.java).apply {
startActivity(this)
}
}
}

override fun onStart() {
super.onStart()
Log.d("MainActivity", "MyApplication :: MainActivity :: onStart")
}

override fun onResume() {
super.onResume()
Log.d("MainActivity", "MyApplication :: MainActivity :: onResume")
}

override fun onPause() {
super.onPause()
Log.d("MainActivity", "MyApplication :: MainActivity :: onPause")
}

override fun onStop() {
super.onStop()
button = null
Log.d("MainActivity", "MyApplication :: MainActivity :: onStop")
}

override fun onDestroy() {
super.onDestroy()
Log.d("MainActivity", "MyApplication :: MainActivity :: onDestroy")
}
}
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.d("MainActivity", "MyApplication :: SecondActivity :: onCreate")
}

override fun onStart() {
super.onStart()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onStart")
}

override fun onResume() {
super.onResume()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onResume")
}

override fun onPause() {
super.onPause()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onPause")
}

override fun onStop() {
super.onStop()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onStop")
}

override fun onDestroy() {
super.onDestroy()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onDestroy")
}
}
 D/MainActivity: MyApplication :: MainActivity :: onCreate
D/MainActivity: MyApplication :: MainActivity :: onStart
D/MainActivity: MyApplication :: MainActivity :: onResume
D/MainActivity: MyApplication :: MainActivity :: onPause
D/MainActivity: MyApplication :: SecondActivity :: onCreate
D/MainActivity: MyApplication :: SecondActivity :: onStart
D/MainActivity: MyApplication :: SecondActivity :: onResume
D/MainActivity: MyApplication :: MainActivity :: onStop
D/MainActivity: MyApplication :: SecondActivity :: onPause
D/MainActivity: MyApplication :: MainActivity :: onStart
D/MainActivity: MyApplication :: MainActivity :: onResume
D/MainActivity: MyApplication :: SecondActivity :: onStop
D/MainActivity: MyApplication :: SecondActivity :: onDestroy
  • Conculsions
    • In the above program the MainActivity calls onStop() callback after the SecondActivity calls onResume()
    • In the above program the MainActivity doesn't call onDestoy() callback when SecondActivity comes in foreground but when the MainActivity comes to foreground again the SecondActivity calls onDestroy() callback.
    • In the above program the MainActivity doesn't call onDestroy() callback if the user switches to other application by pressing Home Button or Recent Button.
    • In the above program the MainActivity calls onDestroy() callback if the user presses Back Button from MainActivity or the programmer adds finish() in any of a callback method.

Activity State and Ejection from Memory

  • The system kills processes when it needs to free up RAM but it depends on the state of the activity running in the process. To get a overview please click the header to know more.
  • The system never kills an activity directly to free up RAM. Instead, it kills the process in which the activity runs, and while destroying the process the system also destroys the activity along with other processes.

Save simple, lightweight UI state using onSaveInstanceState()

  • As your activity begins to stop, the system calls the onSaveInstanceState() method so that your activity can save, state information to an instance state bundle.
  • To save additional instance state information for your activity, you must override onSaveInstanceState() and add key-value pairs to the Bundle object so that the state information is saved if your activity gets destroyed unexpectedly.

Restore activity UI state using saved instance state

  • When your activity is recreated after it was previously destroyed unexpectedly, then you can recover your saved instance state from the Bundle that the system passes to your activity. Both the onCreate() and onRestoreInstanceState() callback methods receive the same Bundle that contains the instance state information. You can use this information to restore the data. A simple example is given below.
 class MainActivity : AppCompatActivity() {
private var button: Button? = null
private var textView: TextView? = null
private var counter: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("MainActivity", "MyApplication :: MainActivity :: onCreate")
button = findViewById(R.id.btnNext)
textView = findViewById(R.id.txtLabel)
counter = savedInstanceState?.getInt(USER_COUNTER_STATE) ?: 0
textView?.text = counter.toString()
button?.setOnClickListener {
counter = counter!! + 1
textView?.text = counter.toString()
}
}

override fun onStart() {
super.onStart()
Log.d("MainActivity", "MyApplication :: MainActivity :: onStart")
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
textView?.text = savedInstanceState.getInt(USER_COUNTER_STATE).toString()
}

override fun onResume() {
super.onResume()
Log.d("MainActivity", "MyApplication :: MainActivity :: onResume")
}

override fun onPause() {
super.onPause()
Log.d("MainActivity", "MyApplication :: MainActivity :: onPause")
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(USER_COUNTER_STATE, counter!!)
}

override fun onStop() {
super.onStop()
button = null
Log.d("MainActivity", "MyApplication :: MainActivity :: onStop")
}

override fun onDestroy() {
super.onDestroy()
Log.d("MainActivity", "MyApplication :: MainActivity :: onDestroy")
}

companion object {
const val USER_COUNTER_STATE = "Counter Value"
}
}
  • Instead of restoring the state during onCreate() you may choose to implement onRestoreInstanceState(), which the system calls after the onStart() method. 
  • The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null
  • In the above program, you must use savedInstanceState Bundle to initialize the class variable in order maintain the state if the configuration changes unexpectedly.

startActivity()

  • If the newly started activity does not need to return a result, the current activity can start it by calling the startActivity() method.
  • Below is a simple example of startActivity()
 R.id.btnNext -> {
Intent(this, SecondActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(this)
}

startActivityForResult()

  • If the newly started activity returns a result to the current activity then you should use startActivityForResult(Intent, int).
  • Mostly this method is called under an event (click event, touch event, keydown event).
  • When a child activity exits, it can call setResult(int, Intent) in onResume() callback (Note: onPause(), onStop() and onDestroy() callbacks will return null data in onActivityResult(int, int, Intent) method) to return data to its parent activity. The child activity must alway supply a result code. 
  • The parent activity uses onActivityResult(int, int, Intent) method to receive the information.
  • Note: This method is deprecated and is replaced by registerForActivityResult()
  • Below is a simple example of startActivityForResult()
 class MainActivity : AppCompatActivity(), View.OnClickListener {
private var buttonAdd: Button? = null
private var buttonSendEmail: Button? = null
private var buttonNext: Button? = null
private var textView: TextView? = null
private var counter: Int? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("MainActivity", "MyApplication :: MainActivity :: onCreate")
initViews(savedInstanceState)
initListeners()
}

private fun initViews(savedInstanceState: Bundle?) {
buttonAdd = findViewById(R.id.btnAdd)
buttonSendEmail = findViewById(R.id.btnSendEmail)
buttonNext = findViewById(R.id.btnNext)
textView = findViewById(R.id.txtLabel)
counter = savedInstanceState?.getInt(USER_COUNTER_STATE) ?: 0
textView?.text = counter.toString()
}

private fun initListeners() {
buttonAdd?.setOnClickListener(this)
buttonSendEmail?.setOnClickListener(this)
buttonNext?.setOnClickListener(this)
}

override fun onClick(view: View?) {
when (view?.id) {
R.id.btnAdd -> {
counter = counter!! + 1
textView?.text = counter.toString()
}
R.id.btnSendEmail -> {
try {
Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_EMAIL, arrayOf("iamraajkanchan@gmail.com"))
putExtra(Intent.EXTRA_CC, arrayOf("iamraajkanchan@yahoo.com"))
putExtra(Intent.EXTRA_BCC, arrayOf("iamraajkanchan@aol.com"))
putExtra(Intent.EXTRA_SUBJECT, "Subject Testing Email from MyApplication")
putExtra(Intent.EXTRA_TEXT, "Body Testing Email from MyApplication")
startActivity(
Intent.createChooser(
this,
"Body Testing Email from MyApplication"
)
)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
R.id.btnNext -> {
startActivityForResult(
Intent(this, SecondActivity::class.java),
SECOND_ACTIVITY_REQUEST
)
}
}
}

override fun onStart() {
super.onStart()
Log.d("MainActivity", "MyApplication :: MainActivity :: onStart")
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
textView?.text = savedInstanceState.getInt(USER_COUNTER_STATE).toString()
}

override fun onResume() {
super.onResume()
Log.d("MainActivity", "MyApplication :: MainActivity :: onResume")
}

override fun onPause() {
super.onPause()
Log.d("MainActivity", "MyApplication :: MainActivity :: onPause")
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(USER_COUNTER_STATE, counter!!)
}

override fun onStop() {
super.onStop()
Log.d("MainActivity", "MyApplication :: MainActivity :: onStop")
}

override fun onDestroy() {
super.onDestroy()
releaseResource()
Log.d("MainActivity", "MyApplication :: MainActivity :: onDestroy")
}

private fun releaseResource() {
buttonSendEmail = null
buttonAdd = null
textView = null
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
SECOND_ACTIVITY_REQUEST -> {
textView?.text = data?.getIntExtra(USER_COUNTER_STATE, 0)?.toString()
}
}
}

companion object {
const val USER_COUNTER_STATE = "Counter Value"
const val SECOND_ACTIVITY_REQUEST = 0
}
}
 class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.d("MainActivity", "MyApplication :: SecondActivity :: onCreate")
}


override fun onStart() {
super.onStart()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onStart")
}

override fun onResume() {
super.onResume()
val intent =
Intent().putExtra(MainActivity.USER_COUNTER_STATE, 25)
setResult(MainActivity.SECOND_ACTIVITY_REQUEST, intent)
Log.d("MainActivity", "MyApplication :: SecondActivity :: onResume")
}


override fun onPause() {
super.onPause()
finish()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onPause")
}

override fun onStop() {
super.onStop()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onStop")
}

override fun onDestroy() {
super.onDestroy()
Log.d("MainActivity", "MyApplication :: SecondActivity :: onDestroy")
}
}

Configuration Changes

  • There are a number of events that can trigger a configuration change. Perhaps the most prominent example is a change between portrait and landscape orientations. Other cases that can cause configuration changes include changes to language or input device.
  • When a configuration change occurs, the activity is destroyed and recreated.

Handle Multi-Window Cases

  • In multi-window mode, although there are two apps that are visible to the user, only the one with which the user is interacting is in the foreground and has focus. That activity is in the Resumed state, while the app in the other window is in the Paused state.

Activity or dialog appears in foreground

  • If a new activity or dialog appears in the foreground and partially covers the previous activity then the previous activity enters Paused State.
  • If a new activity or dialog appears in the foreground and totally covers the previous activity then the previous activity enters Stopped State.
  • If the previous activity appears in the foreground again from Paused state then it enters in Resumed state otherwise it enters in Start state.

User presses Back Button

  • If an activity is in the foreground, and the user presses Back Button, the activity calls onPause(), onStop(), and onDestroy() callbacks. In addition, the activity is also removed from the back stack.
  • In this case the system doesn't call onSaveInstanceState() method.
  • Programmer can override onBackPressed() method to implement a custom behaviour. 

Tasks and the back stack

  • A task is a collection of activities that users interact with, when the user is trying to do something in your app. When a user opens an activity it is added in a back stack and if the user presses a back button then that activity is popped off (removed) the back stack.
  • Lifecycle of a task and it's back stack
    • The back stack operates as a last in, first out object structure.
    • Activities in the back stack are never rearranged, they can only be pushed and popped from the back stack. An activity is pushed onto the back stack when it is started and the activity is popped off when the user leaves it using the Back button or gesture.
  • Back press behavior for root launcher activities
    • Root launcher activities are activities that declare an Intent filter with both ACTION_MAIN and CATEGORY_LAUNCHER.
    • When you open an application using an application launcher then this root launcher activity will start a new task and acts as an entry point of the application.
    • For Android 11 or lower, the system used to finish the root launcher activity when the user presses a Back Button. It means the application has entered a cold state.
    • But for Android 12 or higher, the system is moving the root launcher activity and its task to the background. It means the application has entered a warm state.
    • Restoring an application from warm state is more peformance friendly than restoring an application from cold state.
    • However, if your app overrides onBackPressed() to handle Back navigation and finish the activity, update your implementation to call through to super.onBackPressed() because it moves the activity and its task to the background and provides a more consistent navigation experience to the users.
  • Background and Foreground tasks
    • Two tasks: Task B receives user interaction in the foreground, while Task A is in the background, waiting to be resumed.
    • Note: Even if Task A is in the background, it's back stack remains intact. When a user selects Task A and it comes in foreground, the application will restore from Activity Y.
  • Multiple Activity Instances
    • Because the activities in the back stack are never rearranged, but app can allow users to start a particular activity from more than one activity. In this case, a new instance of that activity is created and pushed onto the back stack as shown below.
    • However, if you do not want an activity to be instantiated more than once you can do so using this reference
  • Multi-Window Environments
    • When apps are running simultaneously in a multi-windowed environment, supported in Android 7.0 (API level 24) and higher, for every window the system manages their tasks separately; each window may have multiple tasks.
  • Defining Launch Modes
    • Defining launch mode means, how you are associating new instance of an activity to a task (back stack - collection of activities).
    •  There are two ways to define launch mode of an activity
      • Defining android:launchMode attribute of <activity> tag in AndroidManifest.xml file
      • Passing intent with a certain intent flag in startActivity(Intent) or startActivityForResult(Intent, int) method.
    • Note: If Activity A starts Activity B, Activity B can define in its manifest how it should associate with the current task and Activity A can also request how Activity B should associate with current task. If both activities (A & B) define how Activity B should associate with a task, then Activity A's request (as defined in the intent) is honored over Activity B's request (as defined in its manifest).
    • Defining launch modes using the manifest file
      • There are five type of launch modes
        • standard (default mode)
          • Multiple instances of an activity can be created.
          • Each instance of the activity belongs to different tasks.
          • One tasks can have multiple instances.
        • singleTop
          • Multiple instances of an activity can be created.
          • Each instance of the activity belongs to different tasks.
          • One tasks can have multiple instances but only if an instance of the activity doesn't exist at the top of the back stack.
        • singleTask
          • One instance of an activity can be created at a time.
          • System creates a new task and creates an instance of an activity at the root of the new task.
          • If an instance of the activity already exists in a separate task then system will route the intent through the existing instance rather than creating a new instance of the activity.
          • A representation of how an activity with launch mode "singleTask" is added to the back stack. If the activity is already a part of a background task with its own back stack, then the entire back stack also comes forward, on top of the current task.
        • singleInstance
          • One instance of an activity can be created at a time.
          • If the task already has an instance of the activity then the system won't create a new instance of that activity.
          • The activity is always the single and only member of its task; any activities started by this one open in a separate task.
        • singleInstancePerTask
    • Defining Launch Modes using Intent Flags
      • FLAG_ACTIVITY_NEW_TASK is equivalent to singleTask launchMode.
      • FLAG_ACTIVITY_SINGLE_TOP is equivalent to singleTop launchMode.
      • FLAG_ACTIVITY_CLEAR_TOP is not equivalent to any launchModes. With this flag, if an instance of this activity is already running in the current task then instance of other activities are destroyed from the top and this instance is placed on the top of the current stack. FLAG_ACTIVITY_CLEAR_TOP is most often used in conjunction with FLAG_ACTIVITY_NEW_TASK.
  • Handle affinity
    • You can modify the affinity (association) for any given activity with the android:taskAffinity attribute of the <activity> tag in AndroidManifest.xml file.
    • The android:taskAffinity attribute takes a string value, which must be unique from the default package name declared in the <manifest> element, because the system uses that name to identify the default task affinity for the app.
 android:taskAffinity="com.mobicule.paintvisualizer"
    • The affinity comes into play into two circumstances
      • When the intent that launches an activity that contains the FLAG_ACTIVITY_NEW_TASK flag.
      • When an activity has its allowTaskReparenting attribute set to "true"
  • Clear the back stack
    • If the user leaves a task for a long time, the system clears all the activities of the task, except the root activity. When the user returns to the task again, only the root activity is restored. You can change this behavior of the system using the following attributes
      • alwaysRetainTaskState
        • If it is set to true in the root activity then, the system will not clear all the activities of the task even if a user returns to the task after a long time.
      • clearTaskOnLaunch
        • If it is set to true in the root activity then, the system will clear all the activities of the task even if a user returns to the task in a moment.
      • finishTaskOnLaunch
        • It behaves like clearTaskOnLaunch behaviour but it is applied on an activity and not on the entire task. 
        • It can also finish the activity except the root activity. 
        • When it is set to "true", the activity remains part of the task only if the user doesn't leave the task. If the user leaves and then returns to the task, then the activity is no longer present in the current task.
  • Start a task
    • You can set up an activity as the entry point for a task by giving it an intent filter with "android.intent.action.MAIN" as the specified action and "android.intent.category.LAUNCHER" as the specified category
 <activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
    • If you don't want the user to be able to return to an activity, set the <activity> element's finishOnTaskLaunch to "true"
  • Processes and Application Lifecycle
    • In Android, lifecycle of an application's processes is not controlled by it's own application. Instead it is controlled by the system based on various factors. The system can kill the processes to reclaim it's memory to run other applications.
    • To determine which processes should be killed when low on memory, Android places each process into an "importance hierarchy" based on the components running in them and the state of those components. There are the following type of processes
      • Foreground process - A foreground process is one that is required for what the user is currently doing. System can't kill this process because the process is active.
      • Visible process - A visible process is doing work that the user is currently aware of, so killing it would have a noticeable negative impact on the user experience. The number of these processes running in the system is less bounded than foreground processes and are considered extremely important hence these processes will not be killed if not required. Though if system needs to run foreground processes without interruption then it can kill the visible processes.
      • Service Process - A service process is one holding a Service that has been started with the startService() method. Though these processes are not directly visible to the user, they are generally doing things that the user cares about (such as background network data upload or download), so the system will always keep such processes running unless there is not enough memory to retain all foreground and visible processes.
      • Cached Process - A cached process is one that is not currently needed, so the system is free to kill it as desired when resources like memory are needed elsewhere.
    • If you do not use application component correctly then the system can kill the application's process while it is doing important work.
  • Parcelables and Bundles
    • Sending data between activities
      • When you call the method startActivity(Intent) or startActivityForResult(Intent, int), the OS parcels the underlying Bundle of the intent and then creates the new activity, un-parcels the data, and passes the intent to the new activity.
      • It is recommended to use Parcelable data between the activities. You can create Parcelable data automatically with the help of autocomplete in Android SDK. Below is an example.
 data class Person(
val firstName: String,
val lastName: String,
val profession: String,
val age: Int
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readInt()
) {
}

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(firstName)
parcel.writeString(lastName)
parcel.writeString(profession)
parcel.writeInt(age)
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}

override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}
}
 class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.d("MainActivity", "MyApplication :: SecondActivity :: onCreate")
btnThirdActivity.setOnClickListener {
val person: Person = Person("Raj", "Kanchan", "Android Developer", 32)
Intent(this, ThirdActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(PERSON, person)
startActivity(this)
}
}
}
}
 class ThirdActivity : AppCompatActivity() {
private lateinit var person: Person
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_third)
person = intent.getParcelableExtra<Person>(SecondActivity.PERSON) as Person
}

override fun onStart() {
super.onStart()
txtLabel.text = person.firstName
}
}
    • Sending data between processes
      • We recommend that you do not use custom parcelables when you are sending data between different processes/apps. If you send a custom Parcelable object from one app to another, you need to be certain that the exact same version of the custom class is present on both the sending and receiving apps.
      • We recommend that you keep saved state to less than 50k of data. Otherwise you may get the error TransactionTooLargeException.
  • Loaders
    • This API is deprected.
    • The Loader API lets you load data from a content provider or other data source for display in an FragmentActivity or Fragment.
    • Advantages of using Loader
      • It can run on separate Threads to prevent janky and unresponsive UI.
      • It provides callback methods when an even occurs, which simplifies thread implementation.
      • It prevents duplicate queries.
      • Classes and Interfaces of Loader API
        • LoaderManager
          • This is an Abstract Class
          • Each Fragment or FragmentActivity can have only one LoaderManger.
          • To get LoaderManager, call getSupportLoaderManager() from an activity or a fragment.
          • To start loading data from a loader, call either initLoader() or restartLoader().
        • LoaderManager.Callback
          • This is an interface
            • onCreateLoader(int, Bundle)
            • onLoadFinished(Loader<D>, D)
            • onLoaderReset(Loader<D>)
          • This interface is typically implemented by your activity or fragment and is registered when you call initLoader() or restartLoader().
        • Loader
          • Use this method to create a loader in onCreate() [for Activity] or onCreateView() [for Fragments] LoaderManager.getInstance(this).initLoader(0, null, this)
          • You can directly subclass Loader or use one of the following built-in subclasses to simplify implementation:
            • AsyncTaskLoader - an abstract loader that provides an AsyncTask to perform load operations on a separate thread.
            • CursorLoader - a concrete subclass of AsyncTaskLoader for asynchronously loading data from a ContentProvider. It queries a ContentResolver and returns a Cursor.

Do's and Dont's

  • Call the super class method first in any of the callback methods (onCreate, onStart, onResume, onPause, onStop, onDestroy).
  • Once your application is published you should not change the class name of the Activity.
  • If you intend for your app to be self-contained and not allow other apps to activate its activities, you don't need any other intent filters.
  • The system shuts down an app during auto backup operation, so if you are setting android:backupInForeground to true in <application> tag, then the system will shut your application even if it is active when the backup operation starts.
  • If the manifest package name has changed, the new application will be installed alongside the old application, so they both co-exist on the user’s device at the same time.
  • If the signing certificate changes, trying to install the new application on to the device will fail until the old version is uninstalled.
  • If you initialize something after the ON_START event, release or terminate it after the ON_STOP event. If you initialize after the ON_RESUME event, release after the ON_PAUSE event.
  • onSaveInstanceState() is not called when the user explicitly (by pressing Back Button) closes the activity or in other cases when finish() is called.
  • Root launcher activities are activities that declare an Intent filter with both ACTION_MAIN and CATEGORY_LAUNCHER.
  • When an activity is destroyed, the system does not retain the activity's state.
  • Activities can be instantiated multiple times, even from other tasks.
  • The behaviors that you specify for your activity with the android:launchMode attribute can be overridden by flags included with the intent that start your activity.
  • When a BroadcastReceiver is starting a Thread, then you should use JobService 
  • You should never store any Parcel data on disk or send it over the network.

Reference



Comments

Popular posts from this blog

SSLSocketFactory in Android

Architecture Components in Android

DataBinding in Android