Activitiy的生命周期和启动模式

Activity

典型情况下的生命周期分析
  • onCreate:表示 Activity 正在被创建,这是生命周期的第一个方法,在这个方法中,我们可以做一些初始化的工作,比如调用 onContentView 去加载界面布局资源,初始化 Activity 所需数据等
  • onRestart: 表示 Activity 正在重新启动,一般情况下,当当前 Activity 从不可见重新变为可见时,onRestart 就会被调用,这总情况一般是用户行为所导致的,比如用户按 home 键切换到桌面或者用户打开了一个新的 Activity,这时当前的 Activity 就会被暂停,也就是 onPause 和 onStop 方法被执行了,接着用户又回到了这个 Activity,就会出现这种情况
  • onStart: 表示 Activity 正在被启动,即将开始,这个时候 Activity 已经可见了,但是还没有出现在前台,还无法和用户交互,这个时候我们可以理解为 Activity 已经启动了,但是我们还没有看见
  • onResume: 表示 Activity 已经可见了,并且出现在前台,并开始活动了,要注意这个和 onStart 的对比,这两个都表示 Activity 已经可见了,但是 onStart 的时候 Activity 还处于后台,onResume 的时候 Activity 才显示到前台
  • onPause: 表示 Activity 正在停止,此时可以做一些数据存储,停止动画等工作,但是注意不要太耗时了,因为这样会影响到新的 Activity 的显示,onPause 必须先执行完,新 Activity 的 onResume 才会执行
  • onStop: 表示 Activity 即将停止,同样可以做一些轻量级的资源回收,比如注销广播接收器、关闭网络连接等,同样不能太耗时;
  • onDestroy: 表示 Activity 即将被销毁,这是 Activity 生命周期的最后一个回调,在这里我们可以做一些最后的回收工作和资源释放
Activity启动流程
  • 启动 Activiy:onCreate -> onStart() -> onResume(), Activity 进入运行状态.
  • 当用户打开新的 Activity 或者切换到桌面的时候,回调如下:onPause ——> onStop ——> 这里有一种特殊的情况就是,如果新的 Activity 采取了透明的主题的话,那么当前 Activity 不会回调 onStop(Activity A 启动另一个Activity B,回调如下: Activity A 的onPause() → Activity B的onCreate() → onStart() → onResume() → Activity A的onStop();如果B是透明主题又或则是个DialogActivity,则不会回调A的onStop)
  • Activity 返回前台: onRestart() -> onStart() -> onResume()
  • 当用户按 back 键的时候回调如下:onPause ——> onStop ——> onDestroy
  • Activity 后台期间内存不足情况下当再次启动会重新执行启动流程。但是要注意声明周期一样,不代表所有的进程一样
  • 从整个生命周期来说,onCreate 和 onDestroy 是配套的, 分别标示着 Activity 的创建和销毁,并且只可能有一次调用,从 Activity 是否可见来说,onStart 和 onStop 是配套的,随着用户的操作和设备屏幕的点亮和熄灭,这两个方法可能被调用多次,从 Activity 是否在前台来说,onResume 和 onPause 是配套的,随着用户操作或者设备的点亮和熄灭,这两个方法可能被多次调用
  • 锁屏: onPause() -> onStop()
  • 解锁: onStart() -> onResume()

在这里插入图片描述

异常情况下的生命周期分析

此异常并非崩溃。而是分为两种情况

情况1:资源相关的系统配置发生改变导致Acitivty被杀死并重新创建,

当系统配置发生改变的时候,Activity 会被销毁,如横竖屏切换

情况2:资源内存不足导致低优先级的 Activity 被杀死

这个情况我们不好模拟,但是其数据的存储和恢复过程和情况一是一致的,这里我们描述一下 Activity 的优先级情况,Activity 按照优先级的从高往低,可以分为三种:

  • 前台 Activity: 正在和用户交互的 Activity,优先级最高

  • 可见但非前台 Activity: 比如对话框,导致 Activity 可见但是位于后台无法和用户直接交互

  • 后台 Activity: 已经被暂停的 Activity,比如执行了 onStop,优先级最低

当系统配置发生改变的时候,Activity 会被销毁,其 onPause,onStop,onDestroy 均会被调用,同时由于 Activity 是异常情况下终止的,系统会调用 onSaveInstanceState 来保存当前 Activity 的状态,**从android P开始,这个方法将在onStop()之后被调用。对于版本较低的系统,这个方法将在onStop之前调用,无法保证与onPause的先后调用顺序。**需要强调的是,系统只在Activity异常终止的时候才会调用onSaveInstanceState与onRestoreInstanceState来储存和恢复数据,其他情况不会触发这个过程。但是按Home键或者启动新Activity仍然会单独触发onSaveInstanceState的调用,当我们 onSaveInstanceState 保存到 Bundler 对象作为参数传递给 onRestoreInstanceState 和 onCreate 方法,因此我们可以通过 onRestoreInstanceState 是否调用来判断 Activity 是否被重建。如果被重建了,我们就取出之前的数据恢复,从时序上来说,onRestoreInstanceState 的调用时机应该在 onStart 之后

当系统内存不足的时候,系统就会按照情况2的优先级去杀死目标 Activity 所在的进程,并且在后续通过 onSaveInstanceState 和 onRestoreInstanceState 来存储和恢复数据,如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组建而独立运行在后台中,这样进程很容易就被杀死了,比较好的方法就是将后台工作放在 Service 中从而保证了进程有一定的优先级,这样就不会轻易的被杀死

Activity的启动模式
  • standard(标准模式):每次启动都会创建一个新的实例,并放入栈顶,一个任务栈都可以有多个实例,每个实例都可以属于不同的任务栈,在这种模式下,谁启动了这个 Activity,那么这个 Activity 就运行在启动它的 Activity 所在的栈内,比如 Activity A 启动了 Activity B(B 是标准模式),那么 B 就会进入到 A 所在的栈内,不知道读者有没有注意到,当我们用 ApplicationContext 去启动 standard 模式的 Activity 的时候就会报错:
E/AndroidRuntime(674): android.util.androidruntiomException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_TASK flag . Is this really what are want?

因为我们的 standard 模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中,但是由于非 Activity 类型的 Context(如 ApplicationContext)并没有所谓的任务栈,所以这就有问题了,解决这个问题,就是待启动 Activity 指定 FLAG_ACTIVITY_TASK 标记位,这样启动的时候就会为他创建一个新的任务栈,这个时候待启动 Activity 实际上是以 singleTask 模式启动的,读者可以仔细体会

  • singleTop(栈顶复用模式) : 如果启动的 Activity 在栈顶,则该 Activity 不会重建,同时 Activity 的 onNewIntent() 方法会被调用,如果不在栈顶则通 standard 模式
  • singleTask(栈内复用模式) : 若启动的 Activity 在栈内,则不会创建新的实例调用 onNewIntent() 方法,并将该 Activity 上面所以的 Activity 清空( 如果其他应用启动该 Activity,若不存在则建立新的 Task,若存在在后台,则后台 Task 也切换到前台)具体例子查看Android开发艺术探索第19页
  • singleInstance(单例模式) : 新的 Activity 直接创建新的任务栈,当该模式的 Activity 存在于某个栈中,后面任何激活该 Activity 都会重用该实例。

TaskAffinity相关使用

可以翻译成任务相关性,这个参数标示了一个 Activity 所需要的任务栈的名字默认情况下,所有的 Activity 所需要的任务栈的名字为应用的包名,当然,我们可以为每个 Activity 都单独指定 TaskAffinity,这个属性值必须必须不能和包名相同,否则就相当于没有指定,TaskAffinity 属性主要和 singleTask 启动模式或者 allowTaskReparenting 属性配合使用,在其他状况下没有意义,另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的 Activity 位于暂停状态,用户可以通过切换将后台任务栈再次调为前台

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

当 TaskAffinity 和 allowTaskReparentiing 结合的时候,这种情况比较复杂,会产生特殊的效果,当一个应用 A 启动了应用 B 的某一个 Activity 后,如果这个 Activity 会直接从应用 A 的任务栈转移到应用 B 的任务栈中,这还是很抽象的,再具体点,比如现在有 2 个应用 A 和 B,A 启动了 B 的一个 Activity C ,然后按 Home 键回到桌面,然后再单击 B 的桌面图标,这个时候并不是启动; B 的主 Activity,而是重新显示了已经被应用 A 启动的 Activity C, 或者说,C 从 A 的任务栈转移到了 B 的任务栈中,可以这么理解,由于 A 启动了 C,这个时候 C 只能运行在 A 的任务栈中,但是 C 属于 B 应用,正常情况下,他的 TaskAffinity 值肯定不可能和 A 的任务栈相同(因为包名不同),所以,当 B 启动后,B 会创建自己的任务栈,这个时候系统发现 C 原本所想要的任务栈已经被创建出来了,所以就把 C 从 A 的任务栈中转移过来,这种情况如微信支付第三方APP调用微信支付后,打开手机任务管理器可查看,从桌面点击图片进入微信,估计微信有做了处理

顺便提一下onNewIntent 的调用时机

onNewIntent:对于singleTask、singleTop、sigleInstance的Activity来说,如果位于位于栈顶,onPause()->onNewIntent()->onResume(),对于sigleTask的Activity来说,如果启动模式为singleTask的ActivityA启动启动普通模式的ActivityB,并在ActivityB中启动ActivityA,调用顺序为:ActivityB onPause()->ActivityA onNewIntent()->ActivityA onRestart()->ActivityA onStart()->ActivityA onResume()->ActivityB onStop()->ActivityB onDestroy()

特殊一些的是如果singleInstance的ActivityA启动ActivityB,并在ActivityB中启动ActivityA,那么调用顺序为:ActivityB onPause()->ActivityA onNewIntent()->ActivityA onRestart()->ActivityA onStart()->ActivityA onResume()->ActivityB onSaveInstance()->ActivityB onStop()

Intent中标志位设置启动模式
  • FLAG_ ACTIVITY_ NEW _ TASK

    这个标志位的作用是为 Activity 指向‘singleTask’启动模式,其效果和 XML 中指定该模式相同

  • FLAG_ ACTIVITY_ SINGLE _ TOP

    这个标志位的作用是为 Activity 指向‘singleTop’启动模式,其效果和 XML 中指定该模式相同

  • FLAG_ ACTIVITY_ CLEAR _ TOP

    具有此标记位的 Activity, 当他启动时,在同一个任务栈中所有位于他上面的 Activity 都要出栈,这个模式一般需要和 FLAG_ ACTIVITY_ NEW _ TASK 配合使用,在这种情况下,被启动的 Activity 的实例如果已经存在,那么系统就会调用它的 onNewIntent, 如果被启动的 Activity 采用标准模式,那么他连同他之上的 Activity 都要出栈,系统会创建新的 Activity 实例并放入栈顶

  • FLAG_ ACTIVITY_ EXCLUDE_ FROM _ RECENTS

    具有此标记位的 Activity,不会出现在历史 Activity 的列表当中,当某种情况下我们不希望用户通过历史列表回到我们的 Activity 的时候就使用这个标记位了,他等同于在 XML 中指定 Activity 的属性:

    android:excludeFromRecents="true"

Intent

Intent 是一个消息传递对象,我们可以使用它启动其他应用组件完成特定的任务。

我们可以通过 Intent 来启动以下三个组件:

  1. Activity
    • public void startActivity(Intent intent)
  2. Service
    • public ComponentName startService(Intent service)
    • public boolean bindService(Intent service, ServiceConnection conn, int flags)
  3. BroadcastReceiver
    • public void sendBroadcast(Intent intent)
    • public void sendOrderedBroadcast(Intent intent, String receiverPermission)
    • sendStickyBroadcast(Intent intent)

Intent 携带的信息

Intent 携带的信息大概有以下几点:
在这里插入图片描述

  • 组件名称 mComponent
    • 可以使用 setComponent()setClass()setClassName() 或 Intent 构造函数设置组件名称
    • 如果没有名称就是隐式的 Intent
  • 要进行的操作 mAction
    • 可以使用系统定义好的,也可以自定义
    • 可以使用 setAction() 或 Intent 构造函数为 Intent 指定操作
  • 数据 mData
    • 待操作数据或者数据的类型等信息
    • 要仅设置数据 URI,请调用 setData()
    • 要仅设置 MIME 类型,请调用 setType()
    • 如果同时设置以上两点,就使用 setDataAndType() 同时显式设置二者
  • 类别 mCategories
    • 表示 Intent 属于哪个类别
    • 一个 Intent 可以属于多个类别,如果不声明,就属于默认的类别 default
    • 可以使用 addCategory() 指定类别
  • 附加数据 mExtras
    • Intent 可以携带完成请求操作所需的数据,格式为键值对
    • 可以使用各种 putExtra() 方法添加数据
    • 也可以创建一个包含所有数据的 Bundle 对象,然后使用 putExtras() 将 Bundle 插入 Intent 中
  • 标志位 mFlags
    • 标志位可以指示 Android 系统如何启动 Activity 以及启动之后如何处理
    • 可以使用 addFlags() 方法添加标志位

注:

  1. 启动 Service 时应该始终指定组件名称。 否则无法确定哪项服务会响应 Intent,且用户无法看到哪项服务已启动。
  2. 若要同时设置 URI 和 MIME 类型,请勿调用 setData() 和 setType(),因为它们会互相抵消彼此的值,使用 setDataAndType(),可同事设置URI和MIME类型
  3. Intent 类将为标准化的数据类型指定多个 EXTRA_* 常量。例如,使用 ACTION_SEND 创建用于发送电子邮件的 Intent 时,可以使用 EXTRA_EMAIL 键指定 “目标” 收件人,并使用 EXTRA_SUBJECT 键指定“主题
intentFilter 的匹配规则

我们知道,启动 Activity 分为两种,显示调用和隐式调用,二者的区别这里就不多讲了,显示调用需要明确的指定被启动对象的组件信息,包括包名和类名,而隐式意图则不需要明确指定调用信息,原则上一个 intent 不应该即是显式调用又是隐式调用,如果二者共存的话以显式调用为主,显式调用很简单,这里主要介绍隐式调用,隐式调用需要 intent 能够匹配目标组件的 IntentFilter 中所设置的过滤信息,如果不匹配将无法启动目标 Activity,IntentFilter 中的过滤信息有 action,category,data, 下面是一个过滤规则的实例:

     <activity
            android:
            android:configChanges="screenLayout"
            android:launchMode="singleTask"
            android:taskAffinity="com.gb.activitysample1">
            <intent-filter>
                <action android:name="com.gb.activitysample.c" />
                <action android:name="com.gb.activitysample.d" />
                <category android:name="com.gb.category.c" />             		
                <category android:name="com.gb.category.d" />
                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>

为了匹配过滤列表,需要同时匹配过滤列表中的 action,category,data 信息,否则匹配失败,一个过滤列表中的 action,category,data 可以有多个,所有的 action,category,data 分别构成不同类别,同一类型的信息共同约束当前类别的匹配过程,只有一个 intent 同时匹配 action 类别, category 类别, data 类别才算是匹配完成,只有完全匹配才能成功启动目标 Activity,另外一点,一个 Activity 可以有多个 intent-filter, 一个 intent 只要能匹配一组 intent-filter 即可成功启动 Activity

<activity android:name=".ShareActivity">
            <!-- This activity handlers "SEND" actions with text data-->
            <intent-filter>
    			<action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>
            <!--This activity also handlers "SEND" and "SEND_MULTIPLE" with media data-->
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <action android:name="android.intent.action.SEND_MULTIPLE" />
                <category android:name="android.intent.category.DEFAULT" />

                    <data android:mimeType="application/vnd.google.panorama360+jpg"/>
                <data android:mimeType="image/*" />
                <data android:mimeType="text/plain" />
            </intent-filter>
        </activity>

过滤规则 = manifest

  • action 的匹配规则

    • action 是一个字符串,系统预定了一些 action, 同时我们也可以在应用中定义自己的 action,action 的匹配规则是 intent 中的 action 必须能够和过滤规则中的 action 匹配,这里说的匹配是指 action 的字符串值完全一样,一个过滤规则中的可以有多个 action, 那么只要 intent 中的 action 能够和过滤规则中的任何一个action相同即可匹配成功。需要注意的是,intent 如果没有指定 action,那么匹配失败,总结一下,action 的匹配需求就是 intent 中的 action 存在且必和过滤规则一样的 action,这里需要注意的是他和 category 匹配规则的不同,另外,action 区分大小写,大小写不同的字符串匹配也会失败
  • category 的匹配规则

    • category 是一个字符串,系统预定义了一些 category,同时我们也可以在应用中定义自己的 category。category 的匹配规则和 action 不同,它要求 Intent 中如果含有 category,那么所有的 category 都必须和过滤规则中的其中一个 category 相同。换句话说,Intent 如果出现了 category,不管有几个 category,对于每个 category 来说,它必须是过滤规则中已经定义的 category。当然,Intent 中可以没有 category,如果没有 category 的话,按照上面的描述,这个 Intent 仍然可以匹配成功。这里要注意下它和 action 匹配过程的不同,action是要求 Intent 中必须有一个 action 且必须能够和过滤规则中的某个 action 相同,而 category 要求 Intent 可以没有 category,但是如果你一旦有 category,不管有几个,每个都要能和过滤规则中的任何一个 category 相同。为了匹配前面的过滤规则中的 category,我们可写出下面的 Intent,intent.addcategory (“com.gb.category.c”)或者 Intent.addcategory (“com.gb.category.d)亦或者不设 category。为什么不设置 category 也可以匹配呢?原因是系统在调用 startActivity 或者 startActivityForResult 的时候会默认为 Intent 加上 “android.intent.category.DEFAULT” 这个 category,所以这个 category 就可以匹配前面的过滤规则中的第三个 category。同时,为了我们的 activity 能够接收隐式调用,就必须在 intent-filter 中指定 “android intent categor.DEFAULT” 这个 category,原因刚才已经说明了。
  • data 匹配规则

    • data 的匹配规则和 action 有点类似,如果过滤规则中定义了 data, 那么 intent 中必须也要定义可匹配的data

在介绍 data 的匹配规则之前,我们需要来了解一下 data 的结构,因为 data 稍微有点复杂

  <data
       android:host="string"
       android:mimeType="string"
       android:path="string"
       android:pathPattern="string"
       android:pathPrefix="string"
       android:port="string"
       android:scheme="sstring" / 

data 由两部分组成,mimeType 和 URI,前者是媒体类型,比如 image/jpeg 等,可以表示图片等,而 URI 包含的数据可就多了,下面的 URI 的结构:

    <scheme>://<host>:<port>/<path>
    scheme://host:port/path 
	例子
    content://com.gb.project:200/folder/subfolder/etc
	http://www.baidu.com:80/search/info
  • Scheme:URI 的模式,比如 http、file、content 等,如果 URI 中没有指定的 scheme, 那么整个 URI 的其他参数无效,这也意味着 URI 无效
  • Host:URI 的主机,比如 www.google.com, 如果 host 未指定,那么整个 URI 中的其他参数无效,这也意味着 URI 无效
  • Port:URI 中的端口号,比如 80,不过需要指定上面两个才有意义
  • Path:表示完整的路径信息。

在 URI元素中,上述每个属性均为可选,但存在线性依赖关系:

  • 如果未指定Scheme,则会忽略Host。
  • 如果未指定Host,则会忽略Port。
  • 如果未指定Scheme和Host,则会忽略Path。

你可以只声明一个协议,这表示该协议下的所有数据你都可以处理;同样也可以只声明主机地址,这表示使用该协议,访问该主机下的所有数据你都可以处理。

scheme 和 mimeType 组成一个 data。而 data 的匹配规则就是:intent 中的 data 至少可以匹配过滤器中的一个

  • 如果 intent-filter 只声明了 scheme,那你的 intent 中必须只包含 scheme 并且至少和 intent-filter 中的一个 scheme 匹配才可以
  • 如果 intent-filter 只声明了 mimeType,那你的 intent 中除了 type 要和 intent-filter 一致,还需要额外包含 content 或者 file 的 scheme 才行,因为 intent-filter 默认包含这两个 scheme
  • 如果 intent-filter 同时声明了多个 scheme 和 mimeType,那你的 intent 至少要完全匹配其中的一组

注意 intent-filter 默认的 contentfile 的 scheme ,它表示默认组件能够从文件中或内容提供程序获得本地数据。

比如下面的 intent-filter,它表示该组件可以从内容提供商处获得并显示图像数据:

<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

为了匹配上述的规则我们可以这样写:

...
intent.setDataAndType(Uri.parse("content://main"),"image/*")
//或者
intent.setDataAndType(Uri.parse("file://main"),"image/png")
...

由于大部分可用数据均由内容提供程序分发,常见的配置是 scheme 只声明协议,同时声明数据类型的过滤器。

例如,下文中的 元素向 Android 指出,组件可从网络中检索视频数据以执行操作:

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

如果当前设备中没有能够匹配你发送到 startActivity() 的隐式 Intent,则调用将会失败,且应用会崩溃。

因此我们需要对 Intent 对象调用 resolveActivity():

  • 如果结果为非空,则至少有一个应用能够处理该 Intent,且可以安全调用 startActivity()
  • 如果结果为空,则不应使用该 Intent
Intent intetnt = new Intent();
intetnt.setAction("guobin.a");
// intent.addCategory("guobin.b")
intent.setDataAndType(Uri.parse("content://main"),"image/*")
//验证当前 Intent 是否可以被处理
if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

另外,PackageManager 还提供了 queryIntentActivities 方法,这个方法和 resolveActivity 方法法不同的是:它不是返回最佳匹配的 Activity 信息而是返回所有成功匹配的 Activity 信息,我们看一下 queryIntentActivities 和 resolveActivity 的方法原型:

    public abstract List<ResolveInfo>queryIntentActivities(Intent intent,int fladgs);
    public abstract ResolveInfo resolveActivity(Intent intent,int flags);

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

Activity和Fragment关系

在oncreate中创建Fragment 以及绑定Fragment

  • activity: onCreate:
    • fragment: onAttach:
    • fragment: onCreate:
    • fragment: onCreateView:
    • fragment: onViewCreated:
    • fragment: onActivityCreated:
    • fragment: onStart:
  • activity: onStart:
  • activity: onResume:
    • fragment: onResume: fragment 完全初始化完毕
    • fragment: onPause:
  • activity: onPause:
    • fragment: onStop:
  • activity: onStop:
    • fragment: onDestroyView:
    • fragment: onDestroy:
    • fragment: onDetach: fragment对象销毁
  • activity: onDestroy:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值