Android 组件系列 -- Activity 启动模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cjh_android/article/details/81665314
    本章知识点:
  • Activity 启动模式
  • 特殊场景: startActivityForResult

    • Activity 启动模式

      standard

      singleTop

      singleTask

      singleInstance

      xml 中使用 launchMode 属性:
<activity
    ...
    android:launchMode="launchMode">
用于测试的代码:打印 Activity 相关信息
open class BaseAct : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        LogUtils.e(this.toString() + " : onCreate : ")
    }

    override fun onResume() {
        super.onResume()
        printTaskInfo()
    }

    fun printTaskInfo(){
        val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val runningTaskInfos = manager.getRunningTasks(1)
        val currentTask = runningTaskInfos[0]
        val num = currentTask.numActivities
        //当前 Activity 所在的 Task 的 id
        LogUtils.e(this.toString() + " : this.taskId : " + this.taskId.toString())
        //当前 Activity 所在的 Task 中存在的 Activity 实例的数量
        LogUtils.e(this.toString() + " : Activity num : " + num.toString())
    }

    override fun onDestroy() {
        super.onDestroy()
        LogUtils.e(this.toString() + " : onDestroy : ")
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        LogUtils.e(this.toString() + " : onNewIntent : ")
    }
}


standard

standard 是 Activity 默认的启动模式 : 如果在xml中不设置launchMode的属性,默认就是这个启动模式。

在这种启动模式下,Activity 在栈内没有位置与数量的限制 : 该 Activity 可以出现在栈的任何位置,可以同时存在多个实例。

举个栗子:如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A –>A。
代码示例: AFirstAct 启动模式为默认 standard :
<activity android:name=".AFirstAct">
     <intent-filter>
           <action android:name="android.intent.action.MAIN" />

           <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
</activity>
AFirstAct :
class AFirstAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_afirst)
        jump.setOnClickListener {
            startActivity(Intent(this, AFirstAct::class.java))
        }
    }
}
场景:连续点击三次按钮,再点击返回三次,最后返回到 launcher 界面。

    从打印信息可以看出:
  • AFirstAct 响应了每一个 Intent ,最终创建了 3 次
  • taskId 没有变化过,3 个 AFirstAct 的实例始终在同一栈内

singleTop

singleTop和standard模式非常相似,这两种模式下的Activity都可以为发送过来的Intent创建新的实例。

    不过差别在于:
  • standard 会为每次接收到的 Intent 创建实例。
    • 而 singleTop 要求,该模式下的 Activity 接收到 Intent 的时候:
    • 如果栈中已经存在该 Activity 的实例了,并且处于栈顶的位置,那么就不再创建新的实例,而是直接复用;
    • 但如果栈顶的 Activity 不是想要创建的 Activity,亦或者栈中就不存在该 Activity 的实例,那么就会为此 Intent 再次创建一个新的该 Activity 实例。

    举个栗子:
  • 情景①:如果Activity A的启动模式为 standard ,B 的启动模式为 singleTop,并且A已经启动,在A中启动 Activity B,即调用 startActivity(new Intent(this,B.class)),此时系统会启动一个新的 Activity B 的实例,即当前的桟中的状态为 A -> B,然后在 B 中再次启动 B 本身,此时的由于 B 处于栈顶,那么系统不会重新创建 B,此时的栈的状态依然是 A -> B。
  • 情景②:如果Activity A的启动模式为 singleTop ,B 的启动模式为 standard,并且A已经启动;在A中首次启动 Activity B,即调用startActivity(new Intent(this,A.class)),然后在 B 中再次启动A,此时虽然 A 的启动模式是 singleTop,并且栈中并已经存在 A 的实例,系统依然会为 A 创建一个新的实例,此时的栈 A -> B -> A。

代码示例①:
AFirstAct 启动模式为默认 singleTop,在 AFirstAct 中不断跳转 AFirstAct :

 <activity
            android:name=".AFirstAct"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
AFirstAct :
class AFirstAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_afirst)
        jump.setOnClickListener {
            startActivity(Intent(this, AFirstAct::class.java))
        }
    }
}
场景:点击一次跳转按钮,再点击返回,返回到 launcher 界面。

    从打印信息可以看出 情景①的结论:
  • taskId 没有变化过,栈中 Activity 的数量 AFirstAct 的实例始终只有一个
  • AFirstAct 响应了启动时 Intent ,创建了 1 个实例,但是从下图可以看出第二次的 Intent,虽然也被响应了,但是并没有创建新的实例,从打印信息就可以看出 Activity 一直是同一个,只是 onNewIntent 被调用了一次,这个方法文章结尾会提一下。



代码示例②:
AFirstAct 启动模式为 singleTop ,ASecondAct 启动模式为 standard:

        <activity
            android:name=".AFirstAct"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".ASecondAct" />
AFirstAct :
class AFirstAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_afirst)
        jump.setOnClickListener {
            startActivity(Intent(this, ASecondAct::class.java))
        }
    }
}
ASecondAct :
class ASecondAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.act_second)
        jump.setOnClickListener {
            startActivity(Intent(this, AFirstAct::class.java))
        }
    }
}
场景:App 启动后,点击一次跳转按钮从 AFirstAct 跳转到 ASecondAct, 再点击跳转按钮,从 ASecondAct 跳转到 AFirstAct。

    从打印信息可以看出 情景②的结论:
  • AFirstAct 响应了启动时 Intent ,创建了 1 个实例,之后的跳转先是创建了一个 ASecondAct 的实例,再之后,又创建了一次 AFirstAct;从此可以看书,singleTop 的 Activity 只有在栈顶的时候才会存在复用的情形。

singleTask

本文会以较简单的层面介绍 singleTask,亦即默认该模式下的 activity 的 taskAffinity 属性不设置,使用默认值。
    在上面的大前提下,singleTask 有以下特征:
  • 栈内复用:如果当前栈中已经存在该 Activity ,那么系统就不会再次创建新的实例,而是直接复用已经存在的实例,调用 onNewIntent 方法,栈中始终最多只会存在一个该 Activity 的实例。
  • 该 Activity 的复用是建立在销毁所有栈中在其上的 Activity。

代码示例:
AFirstAct 启动模式为 singleTask ,ASecondAct 启动模式为 standard:

        <activity android:name=".AFirstAct" android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".ASecondAct"/>
AFirstAct
class AFirstAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_afirst)
        jump.setOnClickListener {
            startActivity(Intent(this, ASecondAct::class.java))
        }
    }
}
ASecondAct
class ASecondAct: BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_asecond)
        jump.setOnClickListener {
            toast()
            startActivity(Intent(this, AFirstAct::class.java))
        }
    }
}
场景:App 启动后,点击一次跳转按钮从 AFirstAct 跳转到 ASecondAct, 再点击跳转按钮,从 ASecondAct 跳转到 AFirstAct。
    从打印信息可以看出:
  • AFirstAct 响应了启动时 Intent ,创建了 1 个实例,之后的跳转先是创建了一个 ASecondAct 的实例,而从 ASecondAct 跳转至 AFirstAct,这个操作并没有再次创建新的 AFirstAct,仅仅是调用了 onNewIntent。
  • 在 AFirstAct 再次响应 Intent 而调用 onNewIntent 时,ASecondAct 先被系统销毁,最终栈中只剩下了一个 Activity 的实例,也就是 AFirstAct。

singleInstance

    当一个 Activity 被设置为 singleinstance 时,此时启动该 Activity
  • 如果还未创建过该Activity的实例,系统会先为该 Activity 创建一个新的任务栈后,再在该栈中创建该 Activity 的实例。该Activity所在的栈为新栈。
  • 如果已有存在该 Activity 实例的栈,则不会创建新的任务栈和实例,Activity 会调用 onNewIntent 响应 intent。
  • 拥有该 Activity 实例的栈,有且仅会有一个实例,那就是该 Activity 的实例,如果跳转至别的 Activity,其实就是切换栈的行为。

代码示例:
AFirstAct、AThirdAct 启动模式为 standard ,ASecondAct 启动模式为 singleInstance:

        <activity android:name=".AFirstAct" android:launchMode="standard">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".ASecondAct" android:launchMode="singleInstance"/>
        <activity android:name=".AThirdAct" android:launchMode="standard"/>
AFirstAct:
class AFirstAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Logger.addLogAdapter(AndroidLogAdapter())
        setContentView(R.layout.activity_afirst)
        jump.setOnClickListener {
            startActivity(Intent(this, ASecondAct::class.java))
        }
    }
}
ASecondAct:
class ASecondAct: BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_asecond)
        jump.setOnClickListener {
            startActivity(Intent(this, AThirdAct::class.java))
        }
    }
}
AThirdAct:
class AThirdAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_athird)
        jump.setOnClickListener {
            startActivity(Intent(this, ASecondAct::class.java))
        }
    }
}
场景:App 启动后,点击一次跳转按钮从 AFirstAct 跳转到 ASecondAct, 再点击跳转按钮,从 ASecondAct 跳转到 AThirdAct,AThirdAct 再次跳转到 ASecondAct。
    从打印信息可以看出:
  • 当第一次点击跳转时,还没有创建过 ASecondAct,此时系统为之创建了一个栈和实例,从打印信息可以看到不同的 taskId 以及调用了 ASecondAct 的 onCreate 函数。
  • 第二次点击从 ASecondAct 跳转到 AThirdAct,在打印信息中可以看到 AThirdAct 所在的栈就是 AFirstAct 所在的栈,并且栈中 Activity 的数量已经变成了 2,这其实就是切换栈的行为。
  • 第三次点击从 AThirdAct 跳转到 ASecondAct,这是 ASecondAct 中被调用的方法是 onNewIntent,AThirdAct 所在的栈被退到后台,而 ASecondAct 所在的栈被推到前台。

特殊场景: startActivityForResult

对于 startActivityForResult 的用法,本文不再赘述,请自行google。
    与 startActivityForResult 相对应的方法 onActivityResult,本意是对于 Activity 之间的数据交互而产生的方法。但在不同版本下,startActivityForResult 有着不同的表现。
  • 在4.4及以下的版本中,如果要打开的用于数据交互的 Acvivity 的 launchmode 是 singleTask、singleInstance 这两种模式中的任一一个,都会导致在该 Activity 还未启动前,onActivityResult 方法就被调用了,从而导致数据交互的失败。
  • 在5.0及以上的版本中,startActivityForResult 这个方法所要跳转的 Activity,无论其是什么启动模式,最终的结果都是是按照 standard 模式的方式运:
  • singleTop: 即便当前栈顶的 Activity 与要跳转的 Activity 相同,不会遵循 singleTop 的规则,调用已有栈顶 Activity 的 onNewIntent 方法,而是会会在当前栈上创建一个新的实例入栈,
  • singleTask: 在这种模式下,系统不会遵循之前所说的 栈内复用 的规则,也会在当前栈上创建一个新的实例入栈。
  • signleInstance:这种模式亦然。
    代码示例:
  • 以 singleInstance 为例,singleTask 的运行效果是一样的
  • AFirstAc 启动模式为 standard ,ASecondAct 启动模式为 singleTask:
        <activity
            android:name=".AFirstAct"
            android:launchMode="standard">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".ASecondAct"
            android:launchMode="singleInstance" />

AFirstAct:

class AFirstAct : BaseAct() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Logger.addLogAdapter(AndroidLogAdapter())
        setContentView(R.layout.activity_afirst)
        jump.setOnClickListener {
            startActivityForResult(Intent(this, ASecondAct::class.java), 111)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        LogUtils.e("onActivityResult :::: $requestCode")
    }
}
    4.4及以下版本打印信息。从打印信息可以看出,正如以上描述:
  • onActivityResult 被提前调用了。


5.0及以上版本打印信息。从打印信息可以看出,正如以上描述:

  • 遵循了 standard 启动模式。




  • 有趣的是,singleTop 这个模式,在低版本下与 startActivityForResult 的协作行为,与高本版是一致的,看来 google 在当时已经意识到这个问题了。



    事实上,在同时使用startActivityForResult和启动模式时,需要我们通过实际的现象来确认是否是我们自己想要的效果,尽管在上面已经提到很多关于startActivityForResult的注意点,但是在实际使用的时候,很多效果依然与我们预期的不同。

    举两个个栗子:
      A的启动模式为standard | B的启动模式为singleInstance
    • 情景:ActivityA -> startActivityForResult(ActivityB) -> ActivityB -> startActivity(B)
    • 结论:最后从B跳转B,在我的手机上(7.1.1),发现B根本没有启动,而是调用了onNewIntent;包括我之后又改变了跳转:ActivityA -> startActivityForResult(ActivityB) -> ActivityB -> startActivity(A)->ActivityA-> startActivity(B),依然是一样的结果。这种情况下就好似只要B已经存在过,哪怕是拥有两个Activity的栈,也认为他就是B唯一的栈。

      A的启动模式为standard | B的启动模式为singleTask,并且给B设置了与Application不同的taskAffinity属性(关于taskAffinity可以看我后面的文章Android 组件系列 – Activity 栈、taskAffinity、intent/flag
    • 情景:ActivityA -> startActivityForResult(ActivityB) -> ActivityB -> startActivity(B)
    • 结论:所得到的结果与 singleInstance 几乎是一样的,就好像在startActivityForResult的作用下,taskAffinity失效了一般。

    其实上面两个例子,并不是要得出什么结论,只是告诫:警惕startActivityForResult与launchmode的组合使用。





    以上,就是对于 Activity 启动模式的总结,以及 startActivityForResult 对于启动模式的影响。

    猜你喜欢

    转载自blog.csdn.net/cjh_android/article/details/81665314