文章目录
一、Activity 的生命周期
1.1、正常情况下的生命周期
1、Activity 启动: onCreate —> onStart —> onResume
2、打开新的 Activity 或者切换到桌面: onPause —> onStop
- 特殊情况:如果新 Activity 采用了透明主题,那么当前 Activity 不会回调 onStop。
- 注意:打开新的 Activity 时,旧 Activity 的 onPause 先执行完后,然后新 Activity 才会启动,最后调用旧 Activity 的 onStop。
3、再次回到原 Activity 时: onRestart —> onStart —> onResume
4、当用户按下 back 键回退时: onPause —> onStop —> onDestory
5、被系统回收后再次打开
- 生命周期方法和 Activity 启动时一样,注意是生命周期方法一样,不代表所有过程都一样。
6、成对的回调
- 从整个生命周期来说,onCreate 和 onDestroy 配对。
分别标识着 Activity 的创建和销毁。 - 从 Activity 是否可见来说,onStart 和 onStop 配对。
随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。 - 从 Activity 是否在前台来说,onResume 和 onPause 配对。
随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。
1.2、异常情况下的生命周期
1、资源相关的系统配置发生改变导致 Activity 被杀死并重新创建
-
onSaveInstanceState 在 onStop 前调用,和 onPause 没有既定的时序关系。
-
onSaveInstanceState 和 onRestoreInstanceState 只会在 Activity 被异常终止的情况下调用。
-
Activity 被重新创建后,系统会调用 onRestoreInstanceState,把销毁时在 onSaveInstanceState 中保存的 Bundle 对象作为参数同时传递给 onRestoreInstanceState 和 onCreate 方法。因此,可以通过 onRestoreInstanceState 和 onCreate 方法判断 Activity 是否被重建了。
二者区别:
onRestoreInstanceState 一旦被调用,其参数 Bundle savedInstanceState 一定是有值的,不用额外判断是否为空;onCreate 在正常启动的情况下,其参数 Bundle saveInstanceState 为 null,因此还需要额外判断。 -
onRestoreInstanceState 调用时机在 onStart 之后。
-
阻止系统重新创建 Activity,例如阻止屏幕旋转时重建 Activity ,配置属性 android:configChanges=“orientation” 就可以了,一次性可以配置多个,用 “|” 连接。 configChanges 属性可以配置的项目。
-
当 Activity 异常情况下被重建时,系统默认保存当前 Activity 的视图结构,并在 Activity 重启后恢复这些数据,比如文本框中用户输入的数据、ListView 滚动位置等。和 Activity 一样,每个 View 都有 onSaveInstanceState 和 onSaveInstanceState 方法,通过查看源码,就能知道系统能够自动为每个 View 恢复哪些数据。
-
关于保存和恢复 View 层次结构,系统的工作流程:首先 Activity 被意外终止,Activity 会调用 onSaveInstanceState 保存数据,然后 Activity 会委托 Window 保存数据,接着 Window 再委托上面的顶级容器保存数据。顶层容器是一个 ViewGroup,一般来说它很可能是 DecorView。最后顶层容器再去一一通知它的子元素保存数据,这样就完成了保存数据的过程。数据恢复过程也类似。
2、资源内存不足导致低优先级的 Activity 被杀死
- 前台 Activity——正在和用户交互的 Activity,优先级最高。
- 可见但非前台 Activity——比如 Activity 弹出了一个对话框,导致 Activity 可见但是位于后台无法和用户直接交互。
- 后台 Activity——已经被暂停的 Activity,比如执行了 onStop,优先级最低。
- 当系统内存不足时,系统会按照优先级杀死目标 Activity 所在的进程。后续也可以通过 onSaveInstanceState 和 onRestoreInstanceState恢复数据。
- 没有四大组件在其中执行的进程将很快被杀死。将后台工作放入 Service 中从而保证进程有一定的优先级。
二、Activity 的启动模式
2.1、standard:标准模式,这也是系统的默认模式
- 每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在。
- standard 模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中。
2.2、singleTop:栈顶复用模式
- 如果新 Activity 位于任务栈的栈顶,那么此 Activity 不会被重新创建,不再调用 onCreate、onStart,会回调 onNewIntent(Intent intent) 方法。
2.3、singleTask:栈内复用模式
- 只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例,不再调用 onCreate、onStart,会回调 onNewIntent(Intent intent) 方法。
- 如果栈内不存在,需要重新创建时,系统会寻找是否存在新 Activity 需要的任务栈,如果不存在,就重新创建一个任务栈,然后创建新 Activity 的实例后放到栈中。
- 默认具有 clearTop 的效果,所有在此 Activity 上面的 Activity 全部出栈。
2.4、singleInstance:单实例模式
- 这是一种加强的 singleTask 模式,具有 singleTask 模式的所有特性。
- 这种模式的 Activity 只能单独存在于一个任务栈中。
- 后续的请求均不会创建新的 Activity ,除非这个独特的任务栈被销毁了。
2.5、给 Activity 指定启动模式
- 通过 AndroidMenifest
<activity
android:name=".SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:launchMode="singleTask" />
- 通过 Intent 标志位
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
-
两者区别
1、优先级上,第二种高于第一种,当两种同时存在,以第二种为准。
2、限定范围有所不同,比如,第一种方式无法直接为 Activity 设定 FLAG_ACTIVITY_CLEAR_TOP 标识,而第二种方式无法为 Activity 指定 singleInstance 模式。 -
这里说几个常用标记位
1、Intent.FLAG_ACTIVITY_NEW_TASK:为 Activity 指定 singleTask 启动模式。
2、Intent.FLAG_ACTIVITY_SINGLE_TOP:为 Activity 指定 singleTop 启动模式。
3、Intent.FLAG_ACTIVITY_CLEAR_TOP:当启动具有此标记位的 Activity时,在同一任务栈中的所有位于它之上的 Activity 都要出栈。
4、Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有此标记的 Activity 不会出现在历史 Activity 的列表中,相当于 XML 中 Activity 的 android:excludeFromRecents=“true” 属性。
三、Activity 的任务栈
- 默认情况下,所有 Activity 所需的任务栈的名字都为应用的包名。
- 单独指定任务栈,需要设置 Activity 的属性 android:taskAffinity="",不能和包名相同,否则相当于没有指定。taskAffinity 属性主要和 singleTask 启动模式或者 allowTaskReparenting 属性配对使用,否则没有意义。
1、taskAffinity 和 singleTask 启动模式配对的时候
指定了具有 singleTask 启动模式的 Activity 的任务栈的名字,待启动的 Activity 会运行在 taskAffinity 指定的任务栈中。
2、taskAffinity 和 allowTaskReparenting 配对的时候
当一个应用 A 启动了应用 B 的某个 Activity 后,如果这个 Activity 的 allowTaskReparenting 属性为 true,那么当在桌面再次启动应用 B 后,此 Activity 会直接从应用 A 的任务栈转移到应用 B 的任务栈中。 - 任务栈分为前台任务栈和后台任务栈,后台任务栈中的 Activity 处于暂停状态,用户可以通过切换将后台任务栈再次调到前台。
- 假设目前有 2 个任务栈,前台任务栈为 AB,后台任务栈为 CD,那么启动 D 时和启动 C 时的情况如下。
小栗子
- 将 SecondActivity 和 ThirdActivity 都设成 singleTask 并指定 taskAffinity 属性为 “com.example.task1",注意这个 taskAffinity 属性的值为字符串,且中间必须包含有包名分隔符 “.”.
- 在 MainActivity 中启动 SecondActivity,在 SecondActivity 中启动 ThirdActivity,在 ThirdActivity 中启动 MainActivity,最后再在 MainActivity 中启动 SecondActivity,最后按两次 back 键,会发现回到了桌面。
- MainActivity 的启动模式为默认模式 standard。
四、IntentFilter 的匹配规则
3.1、显式调用
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
3.2、隐式调用
-
intent-filter
1、一个 Activity 中可以有多组 intent-filter,一个 Intent 只要能匹配任意一组 intent-filter 即可成功启动对应的 Activity。Intent 必须同时匹配某一组 intent-filter 中的 action、category、data 才算完全匹配,只有完全匹配才能启动 Activity。
2、一个 intent-filter 中的 action、category、data 都可以有多个。 -
action 匹配规则
Intent 中必须指定 action,并且要和 intent-filter 中的任意一个 action 相同(区分大小写),才能成功匹配。 -
category 匹配规则**
1、如果 Intent 中有 category,那么必须匹配 intent-filter 中的 category 。
2、Intent 不设置 category 也可以匹配,原因是系统在调用 startActivity 或者 startActivityForResult 时默认给 Intent 加上了 android.intent.category.DEFAULT,所以在隐式调用时 intent-filter 中必须加 <category android:name=“android.intent.category.DEFAULT” />。 -
data 匹配规则(关于 data 还存在疑问,仅供参考)
1、如果 intent-filter 中定义了 data,那么 Intent 中必须指定 data,并且要和 Intent-filter 中的 data 相对应,才能成功匹配。
2、data 由两部分组成,mimeType 和 URL。
3、mimeType 指媒体类型,比如 image/jpeg、audio/mpeg4-generic 和 video/* 等,可以表示图片、文本、视频等不同的媒体格式。
4、而 URI 中包含的数据就比较多了,下面是 URI 的结构。
<scheme>://<host>:<post>/[<path>|<|pathPrefix>|<pathPattern>]
// 比如
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
- 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、pathPrefix 和 pathPattern:这三个参数表述路径信息。
path 表示完整的路径信息。
pathPrefix 表示路径的前缀信息。
pathPattern 也表示完整的路径信息,但是它里面可以包含通配符 “*”,“*” 表示 0 个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想要表示真实的字符串,那么 “*” 要写成 “\\*”,"\"要写成
“\\\\”。
- 系统内部 Activity
这里没有做版本适配,如动态权限申请。
- 拨打电话,
intent.setAction("android.intent.action.CALL");
intent.setData(Uri.parse("tel://13600000000"));
- 打开浏览器
intent.setAction("android.intent.action.VIEW");
intent.setData(Uri.parse("https://www.baidu.com/"));
- 播放音频
intent.setAction("android.intent.action.VIEW");
intent.setDataAndType(Uri.parse("file:///mnt/sdcard/Adele - Rolling in the Deep.mp3"), "audio/*");
- 播放视频
intent.setAction("android.intent.action.VIEW");
intent.setDataAndType(Uri.parse("file:///mnt/sdcard/1.mp4"), "video/*");
- 显示图片
intent.setAction("android.intent.action.VIEW");
intent.setDataAndType(Uri.parse("file:///mnt/sdcard/9.jpg"), "image/*");