[置顶] Activity的启动模式

Activity的LaunchMode

默认情况下,当我们多次启动同一个Activity的时候,系统会创建多个实例并把他们一一放入任务栈中,当我们点击Back键时,会发现Activity一一回退。任务栈是一种 “先进后出”的栈结构,按下back键就会有一个Activity出栈,知道栈空为止,当栈中无任何Activity时,系统就会回收这个任务栈。目前activity有四种启动模式:standard、singleTop、singleTask、singleInstance。

什么是任务栈

介绍每个启动模式前先理解下什么是Activity所需的任务栈?

TaskAffinity这个参数标识了一个Activity所需要任务栈的名字,默认情况下,所有Activity所有需要的任务栈的名字为应用的包名。我们也可以单独指定TaskAffinity属性,区别包名就可以了,不然相当于没指定。TaskAffinity主要是和singleTask 启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义。另外任务栈分为前台任务栈和后天任务栈,后天任务栈的Activity位于暂停状态,用户可以通过切换将后天任务栈再次调换到前台。

当TaskAffinity与singleTask启动模式配对使用的时候,它是具有该模式的Activity的目前的任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。

当TaskAffinity与allowTaskReparenting结合使用时比较复杂,举个栗子:当一个应用A启动了应用B的某个Activity后,如果这个Activity的allowTaskReparenting为true的话,当B应用启动后,次Activity会直接从应用A的任务栈转移到应用B的任务栈中。!!!比如现在有两个应用A和B ,A启动了B的一个Activity C,然后按home键回到桌面,在点击B桌面的图标,会发现启动的不是B的住Activity,而是重新显示了 应用A 启动的 Activity C 。由于是A 启动的 Activity C,所有C只能运行在A的任务栈中,但是C 属于B 应用,正常情况下,他的TaskAffinity肯定不可能和A的任务栈相同(包名不同),所以,当B启动后,B会创建自己的任务栈,这个时候发现C原本想要的任务栈已经创建了,所有C就从A的任务栈中转移过来了。

启动模式介绍

  • standard:标准模式,也是系统指定的默认模式。每次启动Activity都会重新创建一个新的实例,不管这个实例是否已经存在。一个任务栈可以有多个实例,每个实例也可以属于不同的任务栈,在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如Activity A启动了 Activity B(B是标准模式)那么B就会进入到A所在的栈中。当我们用ApplicationContext的去启动standard模式的Activity时会报错:
    Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
    这是因为standard模式的Activity会默认进入启动它的任务栈中,但由于非Activity类型的Context(如:ApplicationContext)并没有所谓的任务栈,所以就报错了。解决办法是:指定Activity 的 FLAG_ACTIVITY_NEW_TASK标记位,这样启动实际上就是指定了启动模式为singleTask的了。
  • singleTop: 栈顶复用模式,如果新的Activity已经位于任务栈的栈顶,那么次Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以可以取出当前请求的信息,需要注意的是,这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。如果新的Activity实例已经存在但不是位于栈顶,那么新Activity仍然会被创建。
  • singleTask:栈内复用模式。这是一种单例模式,在这种模式下,只有Activity在一个栈中存在,那么多次启动此Activity都不会被重新创建实例,和singleTop一样,系统也会回调onNewIntent。具体一点,当一个具有singleTask模式的Activity请求启动后,比如Activity A ,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放到栈中。如果存在A所需要的任务栈,这时需要看A是否咋=在栈中有实例存在,如果有实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent的方法,如果不存在,就创建A的实例并把A压入栈中。看栗子:
    目前任务栈S1中的情况ABC
    • 这个时候Activity D以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D的实例均不存在,所有系统会首先创建S2的任务栈,然后在创建D的实例并入栈到S2.
    • 假设D所需要的任务栈为S1,那么由于S1已经存在,所有系统会直接创建D的实例并将其入栈到S1.
    • 如果D所需要的任务栈为S1,并且当前任务栈S1的情况为ABCD,根据栈内复用原则,此时D不会被重新创建,系统会把D切换到栈顶并调用onNewIntent的方法,同时由于singleTask默认具体clearTop效果,会导致栈内所以在D上面的Activity全部出栈,所以最终S1的任务栈为AD.
      针对clearTop效果举个栗子:
      三个Activity A、B、C (假设A任务栈为S1—— B、C任务栈为S2)
      A的任务栈为包名。(由于A的taskAffinityd值继承自Application的TaskAffinityd,而Application默认taskAffinityd为包名) 启动模式为standard
      B、C的任务栈为 (com.xxx.xx)(XML中指定taskAffinityd属性),启动模式为singleTask
      现在A 启动 B,按照singleTask规则会为B创建任务栈(S2)。B在启动C的时候,由于C所需要的任务栈跟B是同一个,所以C直接入栈到S2。接着C再启动A,A是standard模式,所以系统会为它创建一个新的实例并将加到启动的它的Activity的任务栈,于是C启动A,A就会进入C的任务栈并位于栈顶。这个时候已经有两个任务栈S1,S2
      S1默认为包名的任务栈(实例—— A)
      S2为com.xx.xx的任务栈(实例—— BCA)
      接下来A在启动B,由于B是singleTask模式,B需要回到任务栈的栈顶,由于栈的工作模式为 “先进后出”,B想要回到栈顶,只能CA出栈了。所以在按back键,B就出栈了,B所在的任务栈就不存在了。这个时候就回到后台的任务栈A了,注意这个A的任务栈是属于S1。接着再继续按back键就回到桌面了。
  • singleInstance:单例模式。只是一种加强的singleTask模式,它除了具有singleTask模式的所有特征外,还加强了一点,那就是具体此种模式的Activity只能单独的位于一个任务栈中,换句话说,比如A是singleInsance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用模式,后续的请求均不会创建新的Activity,除非这个任务栈被系统销毁了。

指定Activity的启动模式

  • AndroidMenifest为Activity指定启动模式,
<activity android:name=".MainActivity" android:label="@string/app_name" android:launchMode="singleTask" android:theme="@style/MainTheme">
      </activity>
  • Intent intent = new Intent();
    intent.setClass(MainActivity.this,SecondActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent)

    两种方式优先级上,第一种高于第二种,同时存在已第二种为准,第一种方式无法直接为Activity指定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种无法指定singleTask模式。

清空任务栈

系统同样提供了清空任务栈方法让我将一个Task全部清除。通常情况下,可以在AndroidMainifest文件中的 标签中使用以下几种属性来清理任务栈。

  • clearTaskOnLaunch
    clearTaskOnLaunch 属性顾名思义,就是在每次返回该Activit时,都将该Activity之上的所有Activit都清除。通过这个属性,可以让这个Task每次初始化的时候,都只有这一个Activity。

  • finishOnTaskLaunch
    finishOnTaskLaunch属性与clearTaskOnLaunch属性类似,只不过clearTaskOnLaunch作用在别人身上,而finishOnTaskLaunch作用在自己身上。通过这个属性,当离开这个Activity所处的Task,那么用户再返回时,该Activity就会被finish掉。

  • alwaysRetainTaskState
    如果将Activity的这个alwaysRetainTaskState设置为True,那么该Activity所以在的task将不接受任何清理命令,一直保持当前Task状态。

Activity任务栈使用

我们使用Activity任务栈的各种启动模式和清理方法,是为了更好的使用App中的Activity,合理的设置Activity的启动模式会让程序运行的更有效率,用户体验更好。但任务栈虽然,却不能滥用,如果过度的使用Activity任务栈,会导致整个App的栈管理混乱,不仅不利于程序的扩展,而且容易由于任务栈导致的显示异常。

IntentFilter的匹配规则

我们知道,启动Activity分两种,显示调用和隐式调用,显示调用指定被启动对象的组件信息,包括包名和类名,而隐式调用不需要明确指定组件信息。理论上这两种模式不会共存,如果共存的话以显示调用为主。

隐式调用需要指定能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动Activity。IntentFilter过滤信息有action、category、data,为了匹配过滤列表,需要同时指定action、category、data的信息,否则匹配失败。一个过滤列表中的action、category、data可以有多个,所有的action、category、data构成不同的类别,同一类信息共同约束当前类别的匹配过程。只有一个Intent同时匹配action、category、dat的类别才算完全匹配,才能启动目标Activity。一个Activity可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。

  • action的匹配规则
    系统预定义了一下action,我们也可以自己定义action。action的匹配规则是action的字符串完全一样,一个过滤规则中可以有多个action,那么只要Intent能够和过滤规则中的任何一个action相同即可以匹配成功,另外action匹配区分大小写。
  • category的匹配规则
    系统预定义了一下category,我们也可以自己定义category。Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。当然,Intent可以没有category,如果没有的话,按照上面的描述这个Intent也可以匹配成功。action和category的匹配规则不同:
    action — 只要Intent能够和过滤规则中的任何一个action相同即可以匹配成功
    category — 一旦有category,不管有几个,每个都要和过滤规则中的任何一个category相同。也可以不设置,因为在调用startActivity的时候会默认为Intent加上
    “android.intent.category.DEFAULT”。
  • data匹配规则
    data由两部分组成,mineType和URL。
    mineType指媒体类型,如 image/jpeg、video/*等。
    URI:
    • Scheme:URI模式,如http、file、content等,如果没有指定scheme,那么整个URL的其他参数无效,也意味着URI是无效的
    • Host:URI的主机名,比如www.baidu.com ,如host未指定,那么整个URL的其他参数无效,也意味着URI是无效的
    • Port:URI中的端口号,比如80,仅当URI中指定了scheme 和 host参数时才有意义
    • Path、pathPattern和pathPrefix:表示路径信息,其中path、pathPattern表示完整路径,匹配可以包含通配符 “ * ”,“ * ”表示0个或多个任意字符,由于正则表达式的规范,表示真实的字符串 “ * ”,需要写成 “ \ \ *”, ” \ “要写出 “ \\”;pathPrefix表示路径的前缀信息。

最后我们在匹配的时候可以做一下判断,看activity能否匹配我们隐式的Intent,判断有两种 :

PackageManger的 resolveActivity 方法或者 Intent的resolveActivity的方法,如果找不到就会返回null

PackageManger还提供了queryIntentActivitys方法,和resolveActivity不同的是,他不是返回最佳匹配的信息而是返回成功匹配的的activity信息。

结束

你可能感兴趣的:(android,Android开发,Activity启动模式)