Activity的生命周期和启动模式

吐槽

自己最近每天早上学点Rxjava,网络请求,然后下午就开始《Android艺术探究》这本书的阅读,抽空继续看下计算机网络,晚上就是写项目的时候。加油,还是要坚持减肥运动,不想当死肥宅了。

自己对活动的感悟

我记得我刚开始学安卓的时候,就是觉得活动就是一个界面,类似一个剧场一样,每个剧场都要先设计好他的布置场景(就是界面xml),处理好剧场里面的各种事情(点击啊,触发啊)。还有和其他剧场之间的跳转。当时学的时候,就简单学个皮毛,其实里面各种情况自己重来也没考虑过,就只是会简单的活动跳转,现在重新再好好看下,然后去看下源码。
其实自己还是对活动一知半解,还是好好耐下心来学。

主要讲的第一章的内容

  • Activity生命周期
  • Activity的启动模式
  • IntentFilter匹配规则

活动的生命周期

活动的生命周期有两个部分,一种是典型情况下的生命周期,另一种是异常情况下的生命周期。典型情况下,就是指用户在参与的过程中,activity的生命周期的改变;异常情况下生命周期是指Activity在被系统回收或者当前设备的布局改变,导致Activity被销毁重建。

典型情况下的生命周期

一般情况下,activity会经历如下的生命周期
(1)onCreat
表示:活动正在创建中
作用:做一些初始化的工作,加载界面布局资源,初始化活动所需的数据
备注:无
(2)onRestart
表示:活动正在重新启动
作用:重新启动活动
备注:当当前活动从不可见到可见的状态时候,这个被调用
(3)onStart
表示:活动正在被启动,即将开始
作用:活动已经显示出来了,但是我们还是看不见
备注:这个时候活动已经可见了,但没出现在前台,无法和用户交互
(4)onResume
表示:活动已经可见了
作用:并在前台做交互
备注:无
(5)onPause
表示:活动正在停止
作用:正常情况下紧接着onstop,果立刻返回当前Activity,那么onResume会被调用(情景几乎不可能)。可以做数据存储、停止动画。不能做耗时操作,因为onPause执行完,新的Activity才能onResume,会影响新的Activity的显示
备注:无
(6)onStop
表示:活动即将停止
作用:稍微重量级的回收工作
备注:不能太耗时
(7)onDestroy
表示:活动即将被销毁
作用:回收工作和最终的资源释放
备注:无

Activity的生命周期和启动模式_第1张图片

情景调用情况

  • Activity第一次启动时候 onCreat->onStart->onResume
  • 打开新的Activity或者切换回桌面时候 onPause->onStop //如果新的活动是透明的话,当前活动不会调用onStop
  • 当再次回到原Activity时候 onRestart->onStart->onResume
  • 用户按下back返回 onPause->onStop->onDestroy
  • Activity被系统回收之后再次打开和第一次启动一样但还是有一点区别的

注意点

  • onCreat和onDestroy是配对的,只能有一次调用
  • onStart和onStop配对的,随着用户的操作或者屏幕的点亮和熄灭,是否可见来说的
  • onResume和onPause是配对的,是否显示到前台
  • 从一个Activity打开新的Activity是旧Activity先回调onPause再新Activiy回调onCreat->onCreat->onResume。为了使新Activity尽快切换到前台,我们尽量在onStop中做工作而不是onPause。//因为规定必须旧的活动的onPaues完了之后才能新的活动ReSume

异常情况下的生命周期

两种情况
1资源相关的系统配置发生改变导致的Activity被杀死并重新创建
2资源不足导致的优先级低的Activity被杀死

第一种情况下处理方式—恢复机制

有两个重要的方法onSaveInstanceState和onRestoreInstanceStart
情景模式:
在默认情况下,如果我们的activity不做处理的话,当系统配置发生改变的时候,activity就会销毁并且重建,因为他们是在异常情况下的,所以会有个恢复机制
Activity的生命周期和启动模式_第2张图片
当系统配置改变之后,activity就被销毁,然后onPause,onStop,onDestroy均被调用,同时异常情况下的时候,系统会调用onSaveInstanceState保存当前的Activity的状态,然后在activity重新建立之后,系统会调用onRestoreInstanceStart方法,并把activity销毁的时候,nSaveInstanceState方法保存的Bundle对象作为参数,传递给onRestoreInstanceStart方法

onSaveInstanceState的调用时机

就一句话,异常情况下活动被干掉
调用的时候在onStop之前,和onPause没有特点的时间关系
- 当用户按下HOME键时。
- 长按HOME键,选择运行其他的程序时。
- 下电源按键(关闭屏幕显示)时
- 从activity A中启动一个新的activity时
- 屏幕方向切换时,例如从竖屏切换到横屏时
在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState()一定会被执行,且也一定会执行onRestoreInstanceState()。

onRestoreInstanceStart调用的时机

onRestoreInstanceState()被调用的前提是,原activity “确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity ,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行 此也说明上二者,大多数情况下不成对被使用。
onRestoreInstanceState()在onStart() 和 onPostCreate(Bundle)之间调用。

恢复机制恢复的是什么

当Activity在异常情况下的时候,重新建立的时候,系统会默认保存当前activity的视图,并在重启的时候为我们恢复这些数据并在重建后恢复这些数据,比如EditText中输入的文本、ListView滚动到的位置,具体保存的内容需要看具体View的onSaveInstanceState()和onRestoreInstanceState()实现。

资源不足优先级低的Activity被杀死

先看下安卓里面活动的优先级

  1. 前台activity—-正在和用户交互的——-优先级最高
  2. 可见非前台Activity:可见但无法交互。
  3. 后台Activity:不可见,已被暂停,比如执行了onStop(),优先级最低

当系统内存不足的情况下,系统就会按照上面的优先级顺序去杀死Activity的进程,并通过
onSaveInstanceState和onRestoreInstanceStart来存储和恢复这些数据,如果一个进程没有四大组件在执行,很快就被系统杀死了。最好的方法就是将后台工作放到Service中,从而保证有一定的优先级

2Activity的启动模式

2.1Activity的LaunchMode

在安卓活动中,每次都是创建一个活动时候,然后实例化并把他加入任务栈,然后按back键,就会发现这些Activity就会一一回退,但是每次多次启动这个活动的时候,又要重新建一次,就很耗时,所以安卓系统提供了四种启动模式
stangdard
这个是标准的模式,也是系统默认的模式

  • 每次启动一个activity的时候,都会重新建一个Activity实例,不管这个实例是否存在
  • 生命周期符号典型的Activity的生命周期
  • 一个任务栈有多个实例,每个实例都可以属于不同的任务栈
  • 运行在启动它的Activity的任务栈中(比如活动A启动活动B,活动B就会在活动A的栈中)

singleTop
这个是栈顶复用的模式

  • 如果这个新的Activity已经在栈顶了,如果再次启动,不会重新创建的,就直接把栈顶的东西拿来用。
  • 如果这个Activity的实例存在,但不在栈顶,还是要重新建立的
  • 第一种在栈顶的情况下,栈顶实例的onCreate()、onStart()不会重新调用,onNewIntent方法会被调用。
    1
    singleTask
    栈内复用模式
  • 栈内存在这个Activity的时候,再次启动,不会被重新创建实例
  • 生命周期和singleTop一样启动存在的Activity时会调用onNewIntent()
  • 如果Activity的实例不在栈顶,再次启动此Acivity时会使上面的所有Activity出栈
  • 如果所需任务栈不存在,则会创建任务栈,并把自己压栈
    Activity的生命周期和启动模式_第3张图片

singleInstance
单实例模式,是singleTask模式的增强,具有singleTask模式的所有特性。除此之外还有一个特点就是此模式的Activity只能单独的位于一个任务栈中

  • Activity实例不存在则创建一个新任务栈和实例,将实例压栈。
  • 后续再创建此Activity时则不会重新创建,效果同singleTask

备注singleInstance之一坑

此时有3个Activity,A,B,C,然后除了活动B的启动模式是singlelnstance,其余的都是默认的,然后tartActivity了一个ActivityA,在ActivityA里startActivity了一个ActivityB,在ActivityB里startActivity了一个ActivityC。此时在当前的任务栈中的顺序是,ActivityA->ActivityB->ActivityC。
理来说在当前ActivityC页面按返回键,finish当前界面后应当回到ActivityB界面。但是事与愿违,奇迹出现了,页面直接回到了ActivityA
ActivityB则是存在另个栈中。所以当关闭了ActivityC的时候,它自然就会去找当前任务栈存在的activity。当前的activity都关闭了之后,才会去找另一个任务栈中的activity
这个坑很有意思,总结下就是

  • singleInstance启动模式的活动会自己创建个栈
  • 当活动按back键之后的,会退后栈的下一个
  • 如果当前栈的活动都没有的话,就去找其他栈的活动

备注singleInstance之二坑

此时有两个个activity,ActivityA,ActivityB,ActivityA的启动模式为默认的,ActivityB的启动模式为singleInstance。当在ActivityA里startActivity了ActivityB,当前页面为ActivityB。按下home键。应用退到后台。此时再点击图标进入APP,按照天理来说,此时的界面应该是ActivityB,可是奇迹又出现了,当前显示的界面是ActivityA。
这是因为当重新启动的时候,系统会先去找主栈(我是这么叫的)里的activity,也就是APP中LAUNCHER的activity所处在的栈。查看是否有存在的activity。没有的话则会重新启动LAUNCHER。

要解决这个方法则是和一坑的解决办法一样,在ActivityB定义一个全局变量,public static boolean returnActivityB;在oncreat方法将returnActivityB=true;然后在ActivityA界面onstart方法里判断returnActivityB是否为true,是的话就跳转到ActivityB,同时将returnActivityB=false;这样就能解决跳转的问题了。

任务和任务栈

任务是一个Activity的集合,它使用栈的方式来管理其中的Activity,这个栈又被称为返回栈(back stack),栈中Activity的顺序就是按照它们被打开的顺序依次存放的,栈中Activity的顺序就是按照它们被打开的顺序依次存放的。

  • 手机的Home界面是大多数任务开始的地方,当用户在Home界面上点击了一个应用的图标时,这个应用的任务就会被转移到前台。
  • 如果这个应用目前并没有任何一个任务的话(说明这个应用最近没有被启动过),系统就会去创建一个新的任务,并且将该应用的主Activity放入到返回栈当中。
  • 当一个Activity启动了另外一个Activity的时候,新的Activity就会被放置到返回栈的栈顶并将获得焦点。前一个Activity仍然保留在返回栈当中,但会处于停止状态
  • 当用户按下Back键的时候,栈中最顶端的Activity会被移除掉,然后前一个Activity则会得重新回到最顶端的位置。
  • 返回栈中的Activity的顺序永远都不会发生改变,我们只能向栈顶添加Activity,或者将栈顶的Activity移除掉。因此,返回栈是一个典型的后进先出(last in, first out)的数据结构
  • 如果用户一直地按Back键,这样返回栈中的Activity会一个个地被移除,直到最终返回到主屏幕。当返回栈中所有的Activity都被移除掉的时候,对应的任务也就不存在了。
  • 任务除了可以被转移到前台之外,当然也是可以被转移到后台的。当用户开启了一个新的任务,或者点击Home键回到主屏幕的时候,之前任务就会被转移到后台了。当任务处于后台状态的时候,返回栈中所有的Activity都会进入停止状态,但这些Activity在栈中的顺序都会原封不动地保留着

清空返回栈

如何用户将任务切换到后台之后过了很长一段时间,系统会将这个任务中除了最底层的那个Activity之外的其它所有Activity全部清除掉。当用户重新回到这个任务的时候,最底层的那个Activity将得到恢复。这个是系统默认的行为,因为既然过了这么长的一段时间,用户很有可能早就忘记了当时正在做什么,那么重新回到这个任务的时候,基本上应该是要去做点新的事情了
在元素中设置以下几种属性就可以改变系统这一默认行为:
alwaysRetainTaskState
如果将最底层的那个Activity的这个属性设置为true,那么上面所描述的默认行为就将不会发生,任务中所有的Activity即使过了很长一段时间之后仍然会被继续保留。
clearTaskOnLaunch
如果将最底层的那个Activity的这个属性设置为true,那么只要用户离开了当前任务,再次返回的时候就会将最底层Activity之上的所有其它Activity全部清除掉。简单来讲,就是一种和alwaysRetainTaskState完全相反的工作模式,它保证每次返回任务的时候都会是一种初始化状态,即使用户仅仅离开了很短的一段时间。
finishOnTaskLaunch
这个属性和clearTaskOnLaunch是比较类似的,不过它不是作用于整个任务上的,而是作用于单个Activity上。如果某个Activity将这个属性设置成true,那么用户一旦离开了当前任务,再次返回时这个Activity就会被清除掉。

TaskAffinity属性

TaskAffinity翻译为任务相关性,标识了一个Activity所需的任务栈的名字

  • 为Activity单独指定TaskAffinity可以指定此Activity的任务栈,在不指定TaskAffinity的默认情况下,Activity所需的任务栈的名字为App的包名。
  • TaskAffinity属性主要是和singleTask启动模式或者allowTaskReparenting属性配对使用,否则没有意义
  • 任务栈分为前台任务栈和后台任务栈,后台任务栈的活动处于暂停状态,用户可以通过切换将后台的任务栈再次调到前台
  • 当TaskAffinity和singleTask启动模式配对的时候,他是具有该模式的Activity的目前的任务栈的名字,待启动后的Activity会在运行在名字和TaskAffinity相同的任务栈中
  • TaskAffinity和allowTaskReparenting结合时,当应用A启动了应用B的一个ActivityC,如果C的allowTaskReparenting为true,那么此时按home键返回桌面,再打开应用程序B,此时显示的并不是应用程序B,而是重新显示了刚刚被A启动的ActivityC,C从A的任务栈转到了B的任务栈。

allowTaskReparenting主要作用是Activity的迁移,从与taskAffinity属性不同的任务栈迁移到与其相同名称的任务栈,且只有在resettask时才会生效,从桌面启动App会带FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,在reset task时会根据allowTaskReparenting的值迁移Activity。

Activity指定启动模式

两种方式
1.在AndroidMenifest中指定

<activity android:name=".SingleTaskActivity"
          android:launchMode="singleTask"/>

2.通过Intent中设置标志位来指定

Intent intent = new Intent(MainActivity.this, SingleTaskActivity.class); 
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
startActivity(intent); 

注意点:

  • 优先级上第二种高于第一种的,如果两个共存,以第二种为准
  • 功能范围稍有差异。比如Menifest中无法设置FLAG_ACTIVITY_CLEAR_TOP标识,intent无法指定singleInstance模式。

Activity的Flags

Intent中的Flags很多,官方文档内有详细介绍。下面介绍一部分比较常用的Flag。
FLAG_ACTIVITY_NEW_TASK
官方文档介绍说,这个标志对应这singleTask模式,但是经过实践,并不是如此。
FLAG_ACTIVITY_SINGLE_TOP
对应着singleTop启动模式,经实践,二者是一样的
FLAG_ACTIVITY_CLEAR_TOP
如果设置了此标志位,并且被启动的Activity已经存在于这个Task中,则不会启动一个该Activity的新的实例,而是弹出在Task中,该Activity上的所有Activity。同时,这个intent作为一个新的intent会传递给该Activity。
例如task中有A B C D。如果D使用附带此Flag的intent去启动B,则C,D会出栈,B收到新的intent。
现在在栈顶运行的B实例要么通过调用onNewIntent()接受到新的intent,要么会自我销毁,在通过此intent重建。如果你设置了启动模式为“multiple”(默认的)并且没有设置“FLAG_ACTIVITY_SINGLE_TOP”,则B实例会销毁重启;否则其他所有情况,都是在onNewIntent()中处理该Intent。
这个启动模式和FLAG_ACTIVITY_NEW_TASK在一起使用时有很好的效果:如果用来启动一个task的root Activity,将把所有的这个栈中运行的实例均带到前台,然后全部清空到root状态。
FLAG_ACTIVITY_CLEAR_TASK
如果设置了这个标志的intent通过Context.startActivity()去启动某个Activity,那么该标志会导致任何与此获得弄个相关联的task被清空,在这个活动启动之前。然后,该活动就会变成根Activity,之前的活动全被销毁了。这个标志只能和FLAG_ACTIVITY_NEW_TASK在一起使用

IntentFilter的匹配规则

启动Activity的方式分为两种,显示调用和隐式调用。
显示调用需要指定启动对象的组件信息,包括包名和类名。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果匹配失败则无法启动。
简单的隐试启动的例子
先在声明下

 <activity android:name=".Main2Activity">
            <intent-filter>
                <action android:name="com.example.activitytest.ACTION_START"/>
                <category android:name="android.intent.category.DEFAULT"/>
            intent-filter>
        activity>

然后启动的时候

  button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.activitytest.ACTION_START");
                startActivity(intent);
            }
        });

匹配成功,然后就启动活动二了
在隐式调用的时候需要Intent能够匹配目标组建的IntentFilter中所设置的过滤信息,如果不匹配将无法启动Activity,IntentFilter中所过滤的信息有action,category,data

  • 一个过滤列表中的action,category,data信息,否则匹配失败
  • 一个过滤表中的action,category,和data可以有多个
  • intent-filter中有action、category、data,需要同时匹配列表中的这个三个类别的信息才能够匹配成功。
  • 一个Activity中可以有多个intent-filter,一个intent只需要匹配成功一个intent-filter就可以成功启动对应Activity。

action匹配规则

action是一个字符串,系统预定义了一些action,同时我们自己的应用里也可以定义自己的action。

  • intent中必须有action。
  • 匹配是指字符串完全相同,区分大小写。
  • 一个过滤中可以有多个action,intent能够与任意一个action相同则匹配成功。
  • 如果过滤中有acrion,intent中action不存在的时,过滤失败

匹配action,总结来说action必须存在且必须和过滤中的其中一个action相同。

category匹配规则

category是一个字符串,系统预定义了一些category,同时我们自己的应用里也可以定义自己的category。

  • 系统在调用startActivity()或startActivityForResult()时会自动为Intent加上android.intent.category.DEFAULT。
  • 只有加了android.intent.category.DEFAULT的Activity才能接受隐式调用。
  • 如果Intent中有category,那么不管有几个,每个category都必须是intent-filter中定义的。
  • 如果Intent中没有category,那么只要intent-filter中有default category,就匹配成功。

data匹配规则

data格式

"aa" 
android:host="aa" 
android:port="aa"
android:path="aa" 
android:pathPrefix="aa"
android:mimeType="aa"/> 

data由两部分组成,mimeType和URI。mimeType是媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*,表示不同的媒体格式。URI的结构如下:

://:/[||]

比如:
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info

  • scheme:http、file、content等,必须指定,否则整个URI无效。
  • host:主机名,必须指定,否则无效。
  • port:端口号,非必要。
  • path:完整路径。
  • pathPrefix:路径前缀信息。
  • pathPattern:也是完整路径,可以包含通配符“*”,表示0个或多个任意字符。

匹配规则:

  • filter中有data时,intent中必须有data。
  • intent中的data必须至少与intent-filter中的某一个data完全相同才匹配成功
  • intent-filter没有指定URI时,scheme默认值为file和content。

隐式匹配前的判断

如果找不到能够匹配成功的Activity就会报错,所以可以在隐式调用之前做一个判断。

  • PackageManager的resolveActivity() 如果找不到匹配成功的Activity就会返回null,否则返回最佳匹配的Activity信息
  • Intent的resolveActivity() 如果找不到匹配成功的Activity就会返回null,否则返回最佳匹配的Activity信息。
  • PackageManager的queryIntentActivites() 与上面两个方法不同的是,如果存在能成功匹配的Activity就返回所有匹配成功的Activity信息。

总结

哇哇哇,看完一章花了这么多时间哇哇哇,真的是刚开始学的时候,自己没有好好认真的看,现在才在补,加油,不会就去查,去问,然后总结
参考资料《安卓艺术探究》+各种人的博客

你可能感兴趣的:(android)