Wednesday, January 15, 2014

Android Activity/Fragment life cycle analysis

Android life cycle is complex when activity comes with fragments. Though the document improves a lot, it still has not covered all scenarios. To have a better understanding of it, I made a simple app to print out all critical life cycles for both activity and nested fragments.

First of all, it is important to point out that when all activities of an app are killed the app process may still be running. As long as the process is running, all static variables will be valid. Once the process is killed by the OS all static variables will be lost. So be careful to use static variables.

To cover all possible scenarios that may impact your android app, I categorised them below
  1. New created: Fragment/Activity starts from nothing
  2. Rotate new created: Fragment/Activity restarts after rotation
  3. Home key pressed: Fragment/Activity is kicked to the background
  4. Back from background
with the combination of different cases:
  1. Fragment sets RetainInstance true/false
  2. App is killed in the background after home key pressed. To simulate the app is killed by the system in the background, you can tick the box under Settings->Developer options->Don't keep activities. In this case, the active activity will be killed even the app is sent to background by pressing home button.

Here is the simple project used for the analysis
https://github.com/kejunxia/AndroidLifeCycleAnalysis

Also here is a library to apply Android MVC pattern and make the lifecycle easier to be used as well
http://kejunxia.github.io/AndroidMvc/
https://github.com/kejunxia/AndroidMvc

Summary:
  • New created: almost the same for the four scenarios as below. Except the sequence of onCreateView and onWindowFocusChanged
  • Rotate new created: When Fragment get set RetainInstance true, fragment doesn't call onCreate and onDestroy. And the following fragment calls are invoked but bundle passed in are null
    • onCreateView
    • onViewCreated
    • onActivityCreated
    • onViewStateRestored
    Also note that onSaveInstanceState for both Activity and Fragment is called with non-null outState, no matter if the fragment is set RetainInstance true or false. Which means activities and fragments always save instance state during rotation.
  • Home button pressed(send app to background): onSaveInstanceState is called with outState always as the result above. When fragment is set RetainInstance true, the following calls won't be called:
    • Activity.onDestroy
    • Fragment.onDestroyView
    • Fragment.onDestroy
    • Fragment.onDetach
    • Activity.onDetachedFromWindow
  •  Back from background: This is a little complicated. I split it in to 2 cases.
    • App killed by system: this is simulated by turning on Don't Keep Activities in developer settings on the device. In this case the system is going to try restoring the previous state which should be stored by onSaveInstanceState(as mentioned it's always called). This is different from creating a new activity. In this case the system will do the things below:
      • Activity.onCreate will be called with the savedInstanceState to recover previous state, while if it's creating a new activity onCreate will recieve a null bundle.
      • Fragment.onAttach
      • Activity.onAttachFragment
      • Activity.onStart NOTE THAT this will be called after the fragment is attached while if it's new creating activity, onstart is before the fragment is attached.
      • Fragment.onCreate will be called with savedInstanceState like the activity.
      • Activity.onRestoreInstanceState is called which won't be called when creating a new activity
    • Back from background before killed:
      • no creation will occur
      • no savedInstanceState will occur

Life cycle outputs:

========================================================================
Activity:                            new created
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments`
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView

Activity:                            rotate new created
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Activity:                           home button pressed
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop

Activity:                            back from background
Fragment retainInstance:   false
Kill Activity immediately:   false
ActivityCycle﹕ onRestart
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onWindowFocusChanged

========================================================================
Activity:                            new created
Fragment retainInstance:   true
Kill Activity immediately:   false

ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView

Activity:                            rotate new created
Fragment retainInstance:   true
Kill Activity immediately:   false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Activity:                            home button pressed
Fragment retainInstance:   true
Kill Activity immediately:   
false
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop


Activity:                            back from background
Fragment retainInstance:   true
Kill Activity immediately:   
false
ActivityCycle﹕ onRestart
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onWindowFocusChanged

======================================================================
Activity:                            new created
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView


Activity:                            rotate new created
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged


Activity:                            home button pressed
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow


Activity:                            back from background
Fragment retainInstance:   true
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged


======================================================================
Activity:                            new created
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=null
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=null
FragmentCycle===>﹕ onCreateView: bundle=null
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=null
FragmentCycle===>﹕ onActivityCreated: bundle=null
FragmentCycle===>﹕ onViewStateRestored: bundle=null
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onPostCreate: bundle=null
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged


Activity:                            rotate new created
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView
          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Activity:                            home button pressed
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onPause
FragmentCycle===>﹕ onPause
ActivityCycle﹕ onWindowFocusChanged
ActivityCycle﹕ onSaveInstanceState: outState=Object
FragmentCycle===>﹕ onSaveInstanceState: outState=Object
ActivityCycle﹕ onStop
FragmentCycle===>﹕ onStop
ActivityCycle﹕ onDestroy
FragmentCycle===>﹕ onDestroyView
FragmentCycle===>﹕ onDestroy
FragmentCycle===>﹕ onDetach
ActivityCycle﹕ onDetachedFromWindow

Activity:                            back from background
Fragment retainInstance:   false
Kill Activity immediately:   true
ActivityCycle﹕ onCreate: bundle=Object
FragmentCycle===>﹕ onAttach
ActivityCycle﹕ onAttachFragment
FragmentCycle===>﹕ onCreate: bundle=Object
ActivityCycle﹕ onCreateView          Called many times here
ActivityCycle﹕ onStart
FragmentCycle===>﹕ onCreateView: bundle=Object
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
FragmentCycle===>﹕ onViewCreated: bundle=Object
FragmentCycle===>﹕ onActivityCreated: bundle=Object
FragmentCycle===>﹕ onViewStateRestored: bundle=Object
FragmentCycle===>﹕ onStart
ActivityCycle﹕ onRestoreInstanceState: bundle=Object
ActivityCycle﹕ onPostCreate: bundle=Object
ActivityCycle﹕ onResume
ActivityCycle﹕ onPostResume
ActivityCycle﹕ onResumeFragments
FragmentCycle===>﹕ onResume
ActivityCycle﹕ onAttachedToWindow
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onCreateView
ActivityCycle﹕ onWindowFocusChanged

Tuesday, January 7, 2014

Install Android Library on maven with gradle

If we need to hide the password or any other confidential information, we can move the info into gradle global properties. To do so, we put the info in the file at $GRADLE_HOME/gradle.properties. If it doesn't exist just create a new one.

Here is the content of file %GRADLE_HOME/gradle.properties.

signing.keyId=1C5E1752
signing.password=topsecret
signing.secretKeyRingFile=C:/Users/xxx/AppData/Roaming/gnupg/secring.gpg

artifactoryAndroidDev=http://artifactory.dev.cba/artifactory/android-dev/
artifactoryUsername=xxx
artifactoryPassword=topsecret
nexusSnapshotRepo=http://localhost:8081/nexus/content/repositories/snapshots/
nexusReleaseRepo=http://localhost:8081/nexus/content/repositories/releases/
nexusUsername=admin
nexusPassword=xxx

The we refer the info in the build.gradle file. Here is the gradle build file I used to install slidingmenu library.

buildscript {
    repositories {
        mavenCentral()
  maven {
            url 'http://localhost:8081/nexus/content/repositories/central/'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.6.+'
    }
}
apply plugin: 'android-library'

repositories {
    maven {
        url 'http://localhost:8081/nexus/content/repositories/central/'
    }
}

dependencies {
    compile 'com.android.support:support-v4:18.0.+'
    compile 'com.actionbarsherlock:actionbarsherlock:4.4.0@aar'
}

android {
    compileSdkVersion 19
    buildToolsVersion "18.1.1"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 19
    }

    sourceSets {
        main {
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']

            manifest.srcFile 'AndroidManifest.xml'
        }
    }

}

apply plugin: 'maven'
apply plugin: 'signing'
version = "1.3-release"
group = "com.jeremyfeinstein"
configurations {
    archives {
        extendsFrom configurations.default
    }
}

//Singing the library for release version.
signing {
    required { version.contains("release") && gradle.taskGraph.hasTask("uploadArchives") }
    sign configurations.archives
}

uploadArchives {
    configuration = configurations.archives
    repositories.mavenDeployer {
        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

//nexusReleaseRepo, nexusUsername and nexusPassword are defined in gradle global properties which are stored in $GRADLE_HOME/gradle.properties.
        repository(url: nexusReleaseRepo) {
            authentication(userName: nexusUsername, password: nexusPassword)
        }

        pom.project {
            name 'Android SlidingMenu Library'
            packaging 'aar'
            description 'Sliding menu library for Android applications'
            url 'https://github.com/jfeinstein10/SlidingMenu'

 //           scm {
 //               url 'scm:git@github.com:VandalSoftware/android-cache-lib.git'
 //               connection 'scm:git@github.com:VandalSoftware/android-cache-lib.git'
 //               developerConnection 'scm:git@github.com:VandalSoftware/android-cache-lib.git'
 //           }

            licenses {
                license {
                    name 'The Apache Software License, Version 2.0'
                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    distribution 'repo'
                }
            }

            developers {
                developer {
                    id 'jfeinstein10'
                    name 'Jeremy Feinstein'
                    email 'jfeinstein10@gmail.com'
                }
            }
        }
    }
}