子曰:温故而知新,可以为师矣。 《论语》-- 孔子
一、 Activity
生命周期
先来放一张最经典的图
1.1 典型情况下生命周期分析
onCreate
:Activity
生命周期的第一步。此方法中,常见操作有setContView()
加载布局资源;业务需求需要的数据做初始化操作,例如setText()
,setImage()
等等。onRestart()
:表示某一个后台Activity
重新切换到了前台,即从后台不可见状态变成前台可见状态。onStart()
:Activity
正在被启动,后台可见,但并未出现在前台,即相对用户不可见,用户无法与此Activity
界面交互。onResume()
:Activity
可见且出现在前台,用户可交互。onPause()
:Activity
正在停止,正常情况下,紧接着就会调用onStop()
方法。此方法中,可以做一些存储数据,停止动画等操作,但是不能太耗时。onStop()
:Activity
处于停滞状态,可以做一些稍微重量级的操作,但是也不能太耗时。onDestroy()
:Activity
即将被销毁,可以做一些回收工作和最终资源的释放。
不同场景下的生命周期
1) 第一次启动某一个 Activity
:
onCreate --> onStart --> onResume
2) Activity
A 跳转到 Activity
B(不是透明的 Activity
) :
onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onStop(Activity A)--> onResume(B)
3)Activity
A 跳转到 Activity
B(透明的 Activity
)
onPause(Activity A)--> onCreate(Activity B)--> onStart(Activity B)--> onResume(B)
4)后台 Activity
进入 前台(后台 Activity
并未因为系统内存不足被杀死):
onRestart --> onStart --> onResume
5)后台 Activity
因为系统内存不足被杀死,重新启动该 Activity
:
onCreate --> onStart --> onResume
6)锁屏,开屏 :
锁屏 onPause --> onStop
开屏 onStart --> onResume
7)用户点击 back
键 :
onPause --> onStop --> onDestroy
上面列举了一些常见的场景下生命周期的变化,在此做一个小结:
- 从整个生命周期来看,
onCreate
和onDestroy
是配对的,分别标识着Activity
的创建和销毁,并且只可能有一次调用; - 从
Activity
是否可见,onStart
和onStop
是配对的,随着用户操作或者设备点亮熄灭,可能被调用多次; - 从
Activity
是否在前台来说,onResume
和onPause
是配对的,随着用户操作或者设备点亮熄灭,可能被调用多次。
1.2 异常情况下生命周期分析:
1)资源相关的系统配置发生改变导致 Activity
被杀死并重新创建(常见举例 :横竖屏切换
)。
// onPause 和 onSaveInstanceState 的执行顺序,谁都可能在前
// 官方建议在 onRestoreInstanceState 方法进行数据恢复。
onPause --> onSaveInstanceState --> onStop --> onDestroy --> onCreate --> onRestoreInstanceState
2)资源内存不足导致优先级低的 Activity
被杀死。
- 前台
Activity
:正在和用户交互的Activity
,优先级最高。 - 可见但非前台
Activity
:比如Activity
中弹出了一个对话框,导致Activity
可见但是位于后台无法和用户直接交互。 - 后台
Activity
:已经被暂停的Activity
,比如执行了 onStop,优先级最低。
1.3 ConfigChanges
如果不想要在系统配置发生改变时,重新创建 Activity
,可以给 Activity
指定 configChanges
属性。
下面列举几个常见的 configChangs
的属性:
项目 | 含义 |
---|---|
locale | 设备的本地位置发生了改变,一般指切换了语言系统 |
keyboardHidden | 键盘的可访问性发生了改变,比如用户调用出了键盘 |
orientation | 屏幕方向发生了改变,比如旋转手机屏幕。 |
screenSize | 当屏幕尺寸信息发生了变化,这个选项比较特殊,它和编译选项有关,当编译选项中的 minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致 Activity 重启,否则会导致 Activity 重启 |
具体做法,在AndroidMinfest.xml
文件中,给不想要因为系统配置发生改变而导致 Activity
重建的 Activity
添加如下代码:
// 如果 MiniSdkVersion 和 TargetSdkVersion 属性大于等于13的情况下,需要同时设置 screenSize 和 orientation
android:configChanges="screenSize|orientation"
二、 Activity 启动模式
在默认情况下,当我们多次启动同一个 Activity 时,系统会创建多个实例,并把它们一一放入任务栈中,那么多次启动同一个 Activity,系统就会重复创建多个实例,这是一个很傻的行为,所以提供了启动模式来修改系统的默认行为。
目前有4种启动模式:
standard
、singleTop
、singTask
、singleInstance
。
2.1 standard 模式
- 标准模式,也是系统默认模式。
举例说明:
- 创建
BaseActivity
基类。 MainActivity
继承基类,StandardActivity
类继承基类,同时设置lanchmode
为standard
。MainActivity
跳转到StandardActivity
,在StandardActivity
类中,点击三次按钮,做跳转自身操作。- 查看
Log
日志。
BaseActivity
代码如下:
public class BaseActivity extends AppCompatActivity {
public static final String TAG = "TAG";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "===============onCreate()方法==");
Log.i(TAG, "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i("TAG", "===============onNewIntent()方法==");
Log.i("TAG", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
private void dumpTaskAffinity() {
try {
ActivityInfo info = this.getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.e(TAG, "taskAffinity:" + info.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
Log
日志输出 :
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 28 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:183570408
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:StandardActivity TaskId: 28 hasCode:78785677
E/TAG: taskAffinity:com.example.activitylanuchmode
总结:
-
StandardActivity
做跳转自身三次操作,日志显示了三个不同的hasCode
的值, 推出结论: 每次启动一个Activity
都会重新创建一个新的实例,不管这个实例是否存在。 -
日志输出了三次
onCreate
方法,推出结论:被创建的实例的生命周期符合典型情况下的生命周期,它的onCreate
、onStart
、onResume
都会调用。 -
MainActivity
和StandardAcitivty
的taskAffinity
值一样,推出结论: 在这种模式下,谁启动了这个Activity
,那么这个Activity
就运行在启动它的那个Activity
所在的栈中。
2.2 singleTop
- 栈顶复用模式。
通俗易懂的说一下此模式:
目前栈内情况为 ABCD 四个 Activity
,A 位于栈底,D 位于栈顶,假设需要再次启动 D,如果 D 的启动模式是 singleTop
,那么栈内的情况还是 ABCD,如果 D 的启动模式 standard
,那么栈内的情况是 ABCDD。
举例说明:
- 创建
SingleTopActivity
(lanchmode
为singleTop
),OtherActivity
(lanchmode
为standard
)。 MainActivity
跳转到SingleTopActivity
,SingleTopActivity
跳转到OhterActivity
跳转到SingleTopActivity
,SingleTopActivity
自身跳转三次。- 查看
Log
日志。
Log 日志输出:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 29 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTopActivity TaskId: 29 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTopActivity TaskId: 29 hasCode:227387684
E/TAG: taskAffinity:com.example.activitylanuchmode
总结:
-
第一次创建
SingleTopActivity
时,回调了onCreate
方法,taskId
值一样,推出结论: 当前栈中不存在该Activity
的实例时,其行为同standard
启动模式。 -
OhterTopActivity
返回到SingleTopActivity
时,从日志看出onCreate
方法 被调用,且hasCode
值 与SingleTopActivity
第一次创建时 hasCode 值不一样,推出结论: 当前栈中已有该Activity
的实例但是该实例不在栈顶时,其行为和standard
启动模式一样,依然会创建一个新的实例。 -
SingleTopActivity
做跳转自身三次操作的 hasCode 值都一样,推出结论: 当前栈中已有该Activity
的实例并且该实例位于栈顶时,不会新建实例,而是复用栈顶的实例,并且会将Intent
对象传入,回调onNewIntent
方法。
2.3 singleTask
- 栈内复用模式。
在讲解此模式前,先说一下什么是 Activity
的任务栈 ?
前文提及过 taskAffinity
这个参数,可以翻译为 任务相关性
。 这个参数标识了一个 Activity
所需要的任务栈的名字。
默认情况下,所有的 Activity
所需要的任务栈的名字为应用的包名。
当然,我们也可以为每一个 Activity
单独指定该属性。
设置了相同 taskAffinity
属性的 Activity
在同一个任务栈中。如果你为某一个Activity
的 taskAffinity
属性设置为空字符,那么表示该 Activity
不属于任何task。
standard
和 singleTop
启动模式都是在原任务栈中新建 Activity
实例,不会启动新的Task,即使你指定了 taskAffinity
属性。
通俗易懂的说一下此模式:
当一个具有 singleTask
模式的 Activity
请求启动后,例如 Activity A
,系统首先会寻找是否存在 A 想要的任务栈,如果不存在,就重新创建一个任务栈。然后创建 A 的实例,把 A 放入栈中。如果存在 A 所需的任务栈,这时要看 A 是否在栈中有实例存在,如果有实例存在,系统会把A调到栈顶并调用 onNewIntent()
方法,如果实例不存在,就创建 A 的实例并把 A 压入栈中。
举例说明:
- 目前任务栈 S1 的情况是 ABC,这个时候
Activity D
以singleTask
模式请求启动,所需要的任务栈为 S2,由于 S2 和 D 的实例都不存在,所以系统会先创建任务栈 S2,然后在创建Activity D
的实例并将其入栈 S2。 - 假设
Activity D
所需要的任务栈为 S1,其它如1所示,那么由于 S1 已经存在,那么系统会直接创建 D 的实例并将其压入栈 S1。 - 如果 D 需要的任务栈是 S1,且当前 S1 的情况是 ADBC,A栈底,C栈顶,根据栈内复用原则,此时 D 不会被重新创建,系统会把D切换到栈顶并调用
onNewIntent
方法,同时由于singleTask
默认具有clearTop
方法,会导致栈内所有的在 D 上面的Activity
全部出栈。
代码验证:
- 创建
SingleTaskActivity
,启动模式为singleTask
,同时创建OtherTaskActivity
。 - 做以下操作:
MainActivity
跳转到SingleTaskActivity
,SingleTaskActivity
跳转到OtherTaskActivity
,OtherTaskActivity
回跳到SingleTaskActivity
,SingleTaskActivity
点击跳转自身按钮两次。 - 查看
Log
日志。
Log
日志输出:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 31 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 31 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 31 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode
上面的操作都是在未给 SingleTaskActivity
指定 taskAffinity
值的情况下,根据日志:
OtherActivity
跳回 SingleTaskActivity
,SingleTaskActivity
并未新建,复用了该实例,由 hasCode
值可看出,且调用了 onNewIntent
方法。同时,OhterActivity
出栈了,可以通过 命令adb shell dumpsys activity
,在显示的内容中查看 Running activities
这一块显示区域。
下面,我们给 SingleTaskActivity
指定一下 taskAffinity
的值。注意,这个属性的值时字符串,且中间必须包含分隔符.
。
<activity
android:name=".SingleTaskActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.activitylanuchmode.singletask"
/>
重复之前的操作,再来看一下日志:
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:MainActivity TaskId: 33 hasCode:168699725
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onCreate()方法==
I/TAG: onCreate:OtherTaskActivity TaskId: 34 hasCode:243048765
E/TAG: taskAffinity:com.example.activitylanuchmode
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
I/TAG: ===============onNewIntent()方法==
I/TAG: onNewIntent:SingleTaskActivity TaskId: 34 hasCode:231032863
E/TAG: taskAffinity:com.example.activitylanuchmode.singletask
从日志中可以看到区别就是 taskAffinity
的值不同了,说明 SingleTaskActivity
所属的任务栈不同于 MainActivity
了。
总结
singleTask
模式的Activity
启动时,首先根据taskAffinity
属性查找当前是否存在一个对应名字的任务栈。- 若不存在该属性的任务栈,则会创建一个新的Task,并创建新的
Activity
实例入栈到新创建的Task
中去。 - 若存在该属性的任务栈,则查找该任务栈中是否存在该
Activity
实例。 - 若存在该实例,则将它上面的所有
Activity
实例都出栈,然后回调启动的Activity
实例的onNewIntent
方法 - 若不存在该实例,则新建
Activity
入栈。
2.4 singleInstance
- 单实例模式,加强版
singleTask
。
简单说明一下此模式
具备 singleTask
模式的所有特性,还加强了一点,就是具有此模式的 Activity 只能单独地位于一个任务栈中。
举例说明
比如 Activity A
是 singleInstance
模式,那当 A 启动后,系统会为它创建一个新的任务栈,然后 A 独自在这个任务栈中,由于栈内复用特性,后续的请求都不会创建新的 Activity
,除非这个独特的任务栈被系统销毁了。
2.5 Flags
标记位
Activity
的标记位有很多,这里说一些常用的。
FLAG_ACTIVITY_NEW_TASK
: 为 Activity 指定 “singleTask” 启动模式,其效果和在xml中指定该启动模式相同。FLAG_ACTIVITY_SINGLE_TOP
: 为 Activity 指定 “singleTop” 启动模式, 其效果和在xml中指定该启动模式相同。FLAG_ACTIVITY_CLEAR_TOP
: 启动有此标识的Activity
,在同一个任务栈中,所有位于它上面的Activity
都要出栈。FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
: 有此标识的Activity
不会出现在历史Activity
列表中,等同于在XML中指定Activity
的属性android:excludeFromRecents="true"
。
至于 Activity
的 flag
如何使用呢?
前面介绍了四种启动模式,我们给 Activity
指定启动模式都是在 AndroidMenifest.xml
文件中设置的,其实还有一种方法,通过在 Intent
中设置,举例设置 singleTask
,代码如下:
Intent intent = new Intent();
intent.setClass(this,SingleTaskActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
三、IntentFilter
匹配规则
隐式调用需要 Intent
能够匹配目标组件的 IntentFilter
中所设置的过滤信息,所以我们需要对 IntentFilter
的过滤信息有所了解。
IntentFilter
的过滤信息有 action
、category
、data
。
几个要点说明:
- 一个
Intent
只要能匹配任意一组intent-filter
即可成功启动对应的Activity
。 - 一个过滤列表中的
action
、category
、data
可以有多个。例如下面:
<activity android:name=".MainActivity"
android:configChanges="screenSize|orientation">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- 一个
Activity
可以有多个intent-filter
(过滤列表)。例如下面:
<activity android:name=".MainActivity"
android:configChanges="screenSize|orientation" >
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPL>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
好了,大概的说了一下 IntentFilter
的几个小的注意点,下面来具体说一说那三个过滤信息。
3.1 Action
的匹配规则
action
是一个字符串。action
区分大小写。Intent
中必须存在一个action
。Intent
中的action
和intent-filter
中的 任意一个action
的字符串值完全相同,则action
匹配成功。
3.2 category
的匹配规则
category
是一个字符串。Intnet
中的category
必须和intent-filter
中的category
相同。- 如果
Intent
没有category
,那么为了activity
能够被隐式接收,就需要在intent-filter
中添加"android.intent.category.DEFAULT"
。
3.3 `data 的匹配规则
首先看一下 data
的结构:
<data android:scheme="String"
android:host="String"
android:path="String"
android:pathPattern="String"
android:pathPrefix="String"
android:mimeType="String"
data
由两个部分组成:mimeType
和 URL
。
mimeType
: 媒体类型,例如 img
、jpeg
等。
URL
:
<Scheme>://<host>:<pot>/[<path>|<pathPrefix>|<pathPattern>]
// 解释说明:
Scheme: URL 模式。例如 http、file、content
Host: URL 主机名。例如 www.baidu.com
Port: URL 端口号。例如 80
Path、pathPrefix、pathPattern: 表示路径信息。
data
的匹配规则与 action
的匹配规则类似。
Intent
中必须有一个data
。Intent
中的data
必须完全匹配intent-filter
中的data
。
举例说明
<intent-fileter
<data android:mimeType="image/*"/>
...
</intent-filter>
上述规则制定了媒体类型为所有类型的图片,没有指定 URL
,但是 URL
有默认值:content
和 file
。
如何匹配上述过滤信息,如下:
intent.setDataAndType(Uri.parse("file://abc","image/png"));
有一类 action
和 category
比较重要:
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
如上的二者缺一不可,表明这是一个入口 Activity
,并且会出现在系统的应用列表中。
好了,关于 IntentFilter
的过滤规则就说完了,下面给出一个完整的举例说明:
过滤规则:
<activity android:name=".DemoActivity"
android:configChanges="screenSize|orientation">
<intent-filter>
<action android:name="com.example.intentfilter.demo"/>
<category android:name="android.intent.category.demo"/>
<data android:mimtType="text/plain">
</intent-filter>
</activity>
匹配:
Intent intent = new Intent("com.example.intentfilter.demo");
intent.addCategory("com.example.category.demo");
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
startActivity(intent);
写在文末
纸上得来终觉浅,绝知此事要躬行。 《冬夜读书示子聿》-- 陆游
至此,Activity
的基础知识点算是梳理了一遍,各位看官食用愉快。