android开发艺术(一)之 Activity的生命周期和启动模式

1.Activity的生命周期

一般分为两部分,用户参与下的典型情况生命周期,另一种是异常情况下的生命周期

1.1 典型情况下Activity生命周期

  • onCreate:表示Activity正在被创建,这是生命周期的第一个方法。 在这个方法中做初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等,子类在重写onCreate()方法的时候必须调用父类的onCreate()方法,即super.onCreate(),否则会抛出异常。此方法的传参Bundle为该Activity上次被异常情况销毁时保存的状态信
  • onRestart: 表示Activity 正在重新启动。一般情况下,当前Activity从不可见重新变为可见状态时,onRestart 就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会暂停,即onPause和onStop被执行了,接着用户又回到了这个Activity,就会出现这种情况
  • onStart: 表示Activity正在被启动,这时Activity已经可见了,但是还没有出现在前台,还无法和用户交互。可以理解为Activity已经显示出来了,但是我们还看不到。
  • onResume:表示Activity已经可见,并且出现在前台并开始活动。onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume 的时候Activity才显示到前台。
  • onPause:表示Activity 正在停止,正常情况下紧接着onStop就会被调用。此时可以做存储数据、停止动画等工作,但是不能太耗时,因为这会影响到新Activity的显示,旧的onPause 必须先执行完,新Activity的onResume才会执行。
  • onStop: 表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时,如取消网络连接、注销广播接收器等。
  • onDestroy:表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,可以做回收工作和最终的资源释放

1.注意事项

1.当用户打开新的Activity或者切换到桌面的时候,回调如下: onPause -> onStop。如果新Activity采用了透明主题,那么当前Activity不会回调onStop。
2.当用户按back键回退时,回调如下: onPause -> onStop -> onDestroy。
3.当Activity被系统回收后再次打开,生命周期方法回调过程和(1)一样,注意只是生命周期方法一样,不代表所有过程都一样
4.从整个生命周期来说,onCreate 和onDestroy是配对的,分别标识着Activity 的创建和销毁,并且只可能有一次调用。从Activity是否可见来说,onStart 和onStop是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次;从Activity 是否在前台来说,onResume和onPause是配对的,随着用户操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。
5.onResume完成之后活动才算正式启动,因此在onCreate和onStart中不能做耗时操作

2.Activity生命周期的切换过程

①启动一个Activity: .

  • onCreate–>onStart–> onResume

②打开一个新Activity:

  • 旧Activity的onPause–>新Activity的onCreate–> onStart-- >onResume–>旧Activity的onStop

③返回旧Activity: .

  • 新Activity的onPause () – >旧Activity的onRestart()–> onStart()–> onResume–>新Activity的onStop–> onDestory();

④Activity1.上弹出对话框Activity2:

  • Activity1的onPause()–> Activity2的onCreate-- > onStart()–> onResume

⑤关闭屏幕按Home键:

  • Activity2的onPause()–> onStop–> Activity1的onStop

⑥点亮屏幕/回到前台:

  • Activity2的onRestart()–> onStart()–> Activity1的onRestart()–> onStart()–> Activity2的onResume()

⑦关闭对话框Activity2:

  • Activity2的onPause()–> Activity1的onResume–> Activity2的onStop–> onDestroy()

⑧销毁Activity1:

  • onPause()–> onStop–> onDestroy()

1.2异常情况下Activity生命周期

1. 资源相关的系统配置发生改变导致Activity被杀死并重新创建
在这里插入图片描述
当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在 onStop之前,和 onPause没有既定的时序关系。这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把 Activity 销毁时onSaveInstanceState方法所保存的 Bundle对象作为参数同时传递给onRestoreInstanceState和 onCreate方法。因此,我们可以通过onRestoreInstanceState和 onCreate方法来判断Activity是否被重建了,如果被重建了,就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState的调用时机在 onStart 之后。
在 onSaveInstanceState和 onRestoreInstanceState方法中,系统自动进行一定的恢复工作。当Activity在异常情况下需要重新创建时,系统会默认保存当前Activity 的视图结构,并且在Activity重启后恢复这些数据,比如文本框中用户输入的数据、ListView滚动的位置等,这些View相关的状态系统都能够默认为我们恢复。和 Activity一样,每个View都有onSaveInstanceState和 onRestoreInstanceState这两个方法,看一下它们的具体实现,就能知道系统能够自动为每个View恢复哪些数据。
关于保存和恢复View层次结构的工作流程:首先 Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托 Window去保存数据,接着Window再委托它上面的顶级容器去保存数据。顶层容器是一个ViewGroup,一般来说它很可能是 DecorView。最后顶层容器再去一一通知它的子元素来保存数据,整个数据保存过程就完成了。这是一种典型的委托思想,上层委托下层、父容器委托子元素去处理一件事情。
首先我们在onSaveInstanceState中存储一个字符串 ,然后当Activity被销毁并重新创建后,我们再去获取之前存储的字符串。接收的位置可以选择onRestoreInstanceState或者onCreate,二者的区别是: onRestoreInstanceState 一旦被调用,其参数Bundle savedInstanceState 一定是有值的,我们不用额外地判断是否为空:但是onCreate不行,onCreate 如果是正常启动的话,其参数Bundle savedInstanceState 为null,所以必须要额外判断。
在这里插入图片描述
需要注意:

  1. 当Activity正常销毁,不会调用这两个方法;onSaveInstanceState用于对临时性状态的保存,onPause适用于对数据的持久化保存
  2. onSaveInstanceState执行场景:
    (1)、当用户按下HOME键时。系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。
    (2)、长按HOME键,选择运行其他的程序时。
    (3)、按下电源按键(关闭屏幕显示)时。
    (4)、从activity A中启动一个新的activity时。
    (5)、屏幕方向切换时,例如从竖屏切换到横屏时:onSaveInstanceState–>onPause(不定)–>onStop–>onDestroy–>onCreate–>onStart–>onRestoreInstanceState–>onResume

为了避免由于配置改变导致Activity重建,可在AndroidManifest.xml中对应的Activity中设置android:configChanges=“orientation|screenSize”。此时再次旋转屏幕时,该Activity不会被系统杀死和重建,只会调用onConfigurationChanged。因此,当配置程序需要响应配置改变,指定configChanges属性,重写onConfigurationChanged方法即可。
在这里插入图片描述
2.资源内存不足导致低优先级的Activity被杀死
(1)前台Activity,正在和用户交互的Activity,优先级最高。
(2)可见但非前台Activity,比如 Activity 中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
(3)后台Activity,已经被暂停的Activity,比如执行了onStop, 优先级最低。
当系统内存不足时,会按照Activity优先级从低到高去杀死目标Activity所在的进程。若一个进程没有四大组件在执行,那么这个进程将很快被系统杀死。

2.Activity的启动模式

2.1 LaunchMode

  1. standard:标准模式,系统的默认模式。
    每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下Activity的生命周期,它的onCreate、 onStart、 onResume 都会被调用。这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。
  2. singleTop: 栈顶复用模式。
    在这种模式下,如果新Activity 已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。这个Activity的onCreate、onStart 不会被系统调用,因为它并没有发生改变。如果新Activity 的实例已存在但不是位于栈顶,那么新Activity仍然会重新重建。假设目前栈内的情况为ABCD,其中ABCD为四个Activity, A位于栈底,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为singleTop,那么栈内的情况仍然为ABCD;如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况就变为ABCDD.
  3. 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,其他情况如上面例子1所示,那么由于S1已经存在,所以系统会直接创建D的实例并将其入栈到S1.
    ●如果D所需的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D.上面的Activity全部出栈,于是最终S1中的情况为AD.
  4. singleInstance:单实例模式
    加强的 singleTask模式,具有singleTask模式的所有特性,同时具有此种模式的Activity 只能单独地位于一个任务栈中,比如Activity A是singlelnstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。

singleTop和singleTask的区别以及应用场景
思路:可先解释两个启动模式的含义,再总结不同点,最后给出应用实例

  • singleTop:同个Activity实例在栈中可以有多个,即可能重复创建;该模式的Activity会默认进入启动它所属的任务栈,即不会引起任务栈的变更;为防止快速点击时多次startActivity,可以将目标Activity设置为singleTop
  • singleTask:同个Activity实例在栈中只有一个,即不存在重复创建;可通过android: taskAffinity设定该Activity需要的任务栈,即可能会引起任务栈的变更;常用于主页和登陆页

2.2 Activity指定启动模式

在这里插入图片描述

2.3 Activity的Flags 理解

FLAG_ ACTIVITY_NEW_TASK
这个标记位的作用是为Activity 指定“ singleTask"启动模式,其效果和在XML中指定该启动模式相同。
FLAG ACTIVITY_SINGLE_TOP
这个标记位的作用是为Activity 指定“singleTop"启动模式,其效果和在XML中指定该启动模式相同。
FLAG ACTIVITY_CLEAR_TOP
具有此标记位的Activity, 当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般需要和 FLAG_ACTITY_NEW_TASK配合使用,在这种情况下,被启动Activity的实例如果已经存在,那么系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。 SingleTask启动模式默认就具有此标记位的效果。
FLAG_ ACTIVITY_ EXCLUDE_FROM_ RECENTS
具有这个标记的Activity不会出现在历史Activity的列表中。当某些情况下不希望用户通过历史列表回到我们的Activity 的时候这个标记比较有用。它等同于在XML中指定Activity的属性android:excludeFromRecents=“true”。

3.IntentFilter

原则:
①一个intent只有同时匹配某个Activity的intent-filter中的action、category、data才算完全匹配,才能启动该Activity。
② 一个Activity可以有多个 intent-filter,一个 intent只要成功匹配任意一组 intent-filter,就可以启动该Activity。
在这里插入图片描述

  1. action 的匹配规则
    action是一个字符串,系统预定义了一些action, 同时我们也可以在应用中定义自己的action. action的匹配规则是Intent 中的action必须能够和过滤规则中的action的字符串值完全一样。一个过滤规则中可以有多个 action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。Intent 中如果没有指定action,那么匹配失败。action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同,action 区分大小写。
  2. category 的匹配规则
    category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category 要求Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。Intent 中可以没有category, 如果没有category的话,这个Intent仍然可以匹配成功。原因是系统在调用startActivity 或者startActivityForResult 的时候会默认为Intent 加上“android.intent.category.DEFAULT"这个category。为了我们的activity 能够接收隐式调用,就必须在intent-filter 中指定“android.intent.category.DEFAULT” 这个category。
  3. data 的匹配规则
    data的匹配规则和action 类似,如果过滤规则中定义了data,那么Intent 中必须也要定义可匹配的data.
<data android: scheme="string"
		android:host="string"
		android:port="string"
		android:path-"string"
		android:pathpattern="string"
		android:pathPrefix="string"
		android :mimeType="string" />

data由两部分组成,mimeType 和URI. mimeType 指媒体类型,比如imag/ipeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同的媒体格式,而URI的结构:

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

Scheme: URI 的模式,比如http. file、 content 等,如果URI中没有指定scheme,那么整个URI的其他参数无效,URI是无效的。
Host: URI 的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,URI是无效的。
Port: URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。
path、pathPattern 和pathPrefix:这三个参数表述路径信息,其中path表示完整的路径信息; pathPattem 也表示完整的路径信息,但是它里面可以包含通配符“ * ”,“ * ” 表示0个或多个任意字符,由于正则表达式的规范,如果想表示真实的字符串,那么“ * ”要写成“\ \ *”,“\” 要写成“\ \ \ \”;pathPrefix 表示路径的前缀信息。

1.<data android:mimeType= image/ * " />
这种规则指定了媒体类型为所有类型的图片,Intent中的mimeType属性必须为“image/*”才能匹配,这种情况下虽然过滤规则没有指定URI, 但是却有默认值,URI的默认值为content和file.虽然没有指定URI,但是Intent中的URI部分的scheme必须为content或者file 才能匹配,如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData 再调用setType, 因为这两个方法彼此会清除对方的值

intent.setDataAndType (Uri .parse ("file://abc"), "image/png").

2.如下过滤规则:

<intent- filter>
	<data android:mimeType= "video/mpeg" android: scheme="http" .../>
	<data android:mimeType= "audio/mpeg" android: scheme="http"... />
</intent-filter>

这种规则指定了两组data规则,且每个data都指定了完整的属性值,既有URI又有
mimeType。为了匹配(2) 中规则,可以写出如下示例:

intent. setDataAndType (Uri .parse ("http://abc"), "video/mpeg")
或者
intent. setDataAndType (Uri .parse ("http://abc"), "audio/mpeg" )

如何判断是否有Activity能够匹配我们的隐式Intent

  1. 采用PackageManager的resolveActivity方法或者Intent的resolveActivity方法,如果它们找不到匹配的Activity 就会返回null
  2. PackageManager 还提供了queryIntentActivities 方法,这个方法和resolveActivity 方法不同的是:它不是返回最佳匹配的Activity信息而是返回所有成功匹配的Activity信息
public abstract List<ResolveInfo> queryIntentActivities (Intent intent, int flags) ;
public abstract ResolveInfo resolveActivity(Intent intent, int flags);

第二个参数要使用MATCH_DEFAULT_ONLY这个标记位,这个标记位的含义是仅仅匹配那些在intent-filter 中声明了< category android:name=“android intent.category.DEFAULT”/>这个category的Activity. 使用这个标记位的意义在于,只要上述两个方法不返回null, 那么startActivity一定可以成功。如果不用这个标记位,就可以把intent-filter中category不含DEFAULT的那些Activity给匹配出来,从而导致startActivity可能失败。因为不含有DEFAULT这个category的Activity是无法接收隐式Intent的。

<action android: name=" android. intent .action .MAIN" />
<category android: name ="android. intent .category. LAUNCHER" />

这二者共同作用是用来标明这是一个入口Activity 并且会出现在系统的应用列表中,针对Service和BroadcastReceiver, PackageManager 同样提供了类似的方法去获取成功匹配的组件信息。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值