一起来聊聊Android基础之Activity
标签(空格分隔): Android面试知识 Android开发
什么是Activity?
Activity是Android提供的一个可以和用户进行交互。
Activity的状态
- running:此Activity位于屏幕前台并具有焦点,可以直接和用户交互。
- paused:此Activity未获得焦点,但对用户仍然可见。比如该Activity上层有一个透明的Activity,或者被Dialog覆盖。
- stopped:此Activity未获得焦点,对用户不可见。比如该Activity上层有一个非透明,非Dialog样式的Activity覆盖。注意和paused的区别:是否对用户可见
Activity的生命周期
Activity的生命周期可以参考Google提供的示例代码,注释中讲的很清楚了。
public class ExampleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The activity is being created.
}
@Override
protected void onStart() {
super.onStart();
// The activity is about to become visible.
}
@Override
protected void onResume() {
super.onResume();
// The activity has become visible (it is now "resumed").
}
@Override
protected void onPause() {
super.onPause();
// Another activity is taking focus (this activity is about to be "paused").
}
@Override
protected void onStop() {
super.onStop();
// The activity is no longer visible (it is now "stopped")
}
@Override
protected void onDestroy() {
super.onDestroy();
// The activity is about to be destroyed.
}
}
再上一张Google的“Activity生命周期图”,这张图把Activity的生命周期表达的非常详细了,多看几遍会有新的理解。
再从几种常见场景进行分析:
保存Activity状态
保存Activity状态信息的两个关键API:
protected void onSaveInstanceState(Bundle outState)
protected void onRestoreInstanceState(Bundle savedInstanceState)
当我们需要在Activity被销毁的时候保存某些数据时,可以在onRestoreInstanceState将待保存的数据写到savedInstanceState中,从而在Activity重建时可以在onSaveInstanceState回调方法的outState中读出数据。
onSaveInstanceState方法一般在onPause和onStop方法之间被回调,对应的,onRestoreInstanceState方法一般在onStart和onResume方法之间被回调。
注:无法保证系统会在销毁您的 Activity 前调用 onSaveInstanceState(),因为存在不需要保存状态的情况(例如用户使用“返回”按钮离开您的 Activity 时,因为用户的行为是在显式关闭 Activity)。 如果系统调用 onSaveInstanceState(),它会在调用 onStop() 之前,并且可能会在调用 onPause() 之前进行调用。
因此您只应利用它来记录 Activity 的瞬态(UI 的状态)— 切勿使用它来存储持久性数据,而应使用 onPause() 在用户离开 Activity 后存储持久性数据(例如应保存到数据库的数据)。您只需旋转设备,让屏幕方向发生变化,就能有效地测试您的应用的状态恢复能力。 当屏幕方向变化时,系统会销毁并重建 Activity,以便应用可供新屏幕配置使用的备用资源。
默认实现会为布局中的每个 View 调用相应的 onSaveInstanceState() 方法,以便在重建 Activity 时自动保存和恢复对 UI 所做的任何可见更改。例如,EditText 小部件保存用户输入的任何文本,CheckBox 小部件保存复选框的选中或未选中状态。您只需为想要保存其状态的每个小部件提供一个唯一的 ID(通过 android:id 属性)。如果小部件没有 ID,则系统无法保存其状态。
Android的进程优先级
android将进程的优先级分为5个层次,按照优先级由高到低排列如下:
1.前台进程(Foreground process)。正在和用户进行交互的进程,android系统依据下面的条件来将一个进程标记为前台进程:
- 该进程持有一个正在与用户交互的Activity(也就是这个activity的生命周期方法走到了onResume()方法)。
- 该进程持有一个Service,并且这个Service与一个正在和用户交互的Activity进行绑定。
- 该进程持有一个前台运行模式的Service(也就是这个Service调用了startForegroud()方法)。
- 该进程持有一个正在执行生命周期方法(onCreate()、onStart()、onDestroy()等)的Service。
- 该进程持有一个正在执行onReceive()方法的BroadcastReceiver。
2.可见进程(Visible process)。它表明虽然该进程没有持有任何前台组件,但是它还是能够影响到用户看得到的界面。android系统依据下面的条件将一个进程标记为可见进程:
- 该进程持有一个非前台Activity,但这个Activity依然能被用户看到(也就是这个Activity调用了onPause()方法)。例如,当一个activity启动了一个对话框,这个activity就被对话框挡在后面。
- 该进程持有一个与可见Activity绑定的Service。
3.服务进程(Service process)。除了符合前台进程和可见进程条件的Service,其它的Service都会被归类为服务进程。
4.后台进程(Background process)。持有不可见Activity(调用了onStop()方法)的进程即为后台进程。通常情况下都会有很多后台进程,当内存不足的时候,在所有的后台进程里面,会按照LRU(最近使用)规则,优先回收最长时间没有使用过的进程。
5.空进程(Empty process)。不持有任何活动组件的进程。保持这种进程只有一个目的,就是为了缓存,以便下一次启动该进程中的组件时能够更快响应。
Android的任务和返回栈
任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。
Android中设计的任务,返回栈和启动模式是为了更好的管理Activity。
通过使用 <activity>
清单文件元素中的属性和传递给 startActivity() 的 Intent 中的标志,您可以执行所有这些操作以及其他操作。
由于后面讲解属性和几种启动模式的部分都会涉及到affinity(亲和力),所以先简单提一下affinity这个概念。
affinity对于Activity来说就好像它的身份证一样,可以告诉所在的task,自己属于这个task中的一员;拥有相同affinity的多个Activity理论同属于一个task,task自身的affinity决定于根Activity的affinity值。affinity在什么场合应用呢?1.根据affinity重新为Activity选择宿主task(与allowTaskReparenting属性配合工作);2.启动一个Activity过程中Intent使用了FLAG_ACTIVITY_NEW_TASK标记,根据affinity查找或创建一个新的具有对应affinity的task。
默认情况下,一个应用内的所有Activity都具有相同的affinity,都是从Application(参考的taskAffinity属性)继承而来,而Application默认的affinity是中的包名,我们可以为设置taskAffinity属性值,这样可以应用到下的所有,也可以单独为某个Activity设置taskAffinity。
task属性
在<activity>
中定义了几个常见的task相关属性,它们分别代表了task内部不同的行为特征。
android:allowTaskReparenting
当启动 Activity的任务接下来转至前台时,Activity是否能从该任务转移至与其有亲和关系的任务—“true”表示它可以转移,“false”表示它仍须留在启动它的任务处。 默认值为“false”。说直白一点,从allowTaskReparenting的名字来解读,就是允许Task重新找爸爸。
例如在一个应用A中要查看一个web页面,在启动系统浏览器Activity后,这个Activity实例和当前应用A处于同一个task,当我们的应用A退居后台之后用户再次从主界面启动应用A,此时这个Activity实例将会重新宿主到Browser应用的task内,在我们的应用A中将不会再看到这个Activity实例,而如果此时启动Browser应用,就会发现,第一个界面就是我们刚才打开的web页面,证明了这个Activity实例确实是宿主到了Browser应用的task内。android:alwaysRetainTaskState
系统是否始终保持 Activity 所在任务的状态 —“true”表示保持,“false”表示允许系统在特定情况下将任务重置到其初始状态。 默认值为“false”。该属性只对任务的根 Activity 有意义;对于所有其他 Activity,均忽略该属性。
正常情况下,当用户从主屏幕重新选择某个任务时,系统会在特定情况下清除该任务(从根 Activity 之上的堆栈中移除所有 Activity)。 系统通常会在用户一段时间(如 30 分钟)内未访问任务时执行此操作。
不过,如果该属性的值是“true”,则无论用户如何到达任务,将始终返回到最后状态的任务。 例如,在网络浏览器这类存在大量用户不愿失去的状态(如多个打开的标签)的应用中,该属性会很有用。android:clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 “true”,则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。android:finishOnTaskLaunch
这个属性和android:allowReparenting属性相似,不同之处在于allowReparenting属性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch属性是销毁实例。如果这个属性和android:allowReparenting都设定为“true”,则以这个属性为准。
Intent 标志
启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity的默认行为。
FLAG_ACTIVITY_NEW_TASK
在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 onNewIntent() 中收到新 Intent。
这会产生与 “singleTask”launchMode 值相同的行为。FLAG_ACTIVITY_SINGLE_TOP
如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则现有实例会接收对 onNewIntent() 的调用,而不是创建 Activity 的新实例。
这会产生与 “singleTop”launchMode 值相同的行为。FLAG_ACTIVITY_CLEAR_TOP
如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。
产生这种行为的 launchMode 属性没有值。
另外还有几种不常见的Intent标志,在此就不介绍了。
Activity的启动模式
启动模式允许您定义 Activity 的新实例如何与当前任务关联。 您可以通过两种方法定义不同的启动模式:
- 使用清单文件
在清单文件中声明 Activity 时,您可以指定 Activity 在启动时应该如何与任务关联。 - 使用 Intent 标志
调用 startActivity() 时,可以在 Intent 中通过setFlags方法加入一个标志,用于声明新 Activity 如何(或是否)与当前任务关联。
清单文件 | Intent标志 | 说明 |
---|---|---|
standard | 默认 | 默认模式,Activity会多次实例化 |
singleTop | FLAG_ACTIVITY_SINGLE_TOP | 栈顶复用 |
singleTask | FLAG_ACTIVITY_NEW_TASK | 栈内复用 |
singleInstance | 无 | 与 “singleTask” 类似,但会创建一个新的栈存放该Activity的实例,并且这个栈中有且仅有这一个实例。 |
“singleTask”细节讨论
“singleTask”启动模式由于其特殊性,经常使人迷惑。所以,重点讨论一下设置了”singleTask”启动模式的Activity的特点:
可以从三个方面来理解singTask:
系统创建新任务并实例化位于新任务底部的 Activity。
系统是否会为Activity创建新任务还跟taskAffinity属性有关。设置了”singleTask”启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity相同的任务(默认同一个应用的affinity相同,都为包名);
如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了”singleTask”启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent()方法向其传送Intent,而不是创建新实例。
如果设置了”singleTask”启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。一次只能存在 Activity 的一个实例。
多数人认为通过清单文件为Activity指定singTask启动模式(方式一)和在Intent设置FLAG_ACTIVITY_NEW_TASK标志位(方式二)是一样的,其实二者还是有区别的:
- 第二种方式的优先级要高于第一种,当两种同时存在时,以第二种为准。
针对“一次只能存在Activity的一个实例”这条特性,第二种方式只有在为Activity设置一个独立的taskAffinity属性值的情况下才生效。
举两个例子说明:
例一:
app中有两个Activity,A和B,其中A启动B,然后B通过方式二启动自身两次。默认情况下返回栈的顺序是:A-B-B’-B”。可通过adb shell dumpsys activity
查看返回栈中Activity信息。- 当通过方式一指定B为singTask启动模式时,返回栈的顺序是:A-B,并且两个Activity实例都在同一个TaskRecord中;
- 当通过方式一指定B为singTask启动模式时,并为B设置一个独立的taskAffinity属性值时,返回栈的顺序是:A-B,并且两个Activity实例在不同TaskRecord中;
- 通过方式二在Intent设置FLAG_ACTIVITY_NEW_TASK标志位时,返回栈的顺序是:
A-B-B'-B''
。说明FLAG_ACTIVITY_NEW_TASK标志位没有起作用。并且多个Activity实例都在同一个TaskRecord中 - 通过方式二在Intent设置FLAG_ACTIVITY_NEW_TASK标志位,并为B设置一个独立的taskAffinity属性值时,返回栈的顺序是:A-B并且两个Activity实例在不同TaskRecord中;
Intent intent = new Intent(MainActivity.this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); }
例二:
app1和app2分别有两个Activity,1A,1B和2A,2B。
启动顺序为:1A-1B-2B-1B-2B- 默认情况:1A-1B-2B-1B’-2B’都在同一个Task中。
- 当通过方式一指定1B和2B为singTask启动模式时,并为1B和2B设置一个独立的taskAffinity属性值时,返回栈最后的顺序是:1A-1B-2B;
- 通过方式二在启动1B和2B的Intent设置FLAG_ACTIVITY_NEW_TASK标志位时,返回栈最后的顺序是:1A-1B-1B’-2B-2B’;
- 通过方式二在启动1B和2B的Intent设置FLAG_ACTIVITY_NEW_TASK标志位,并为1B和2B设置一个独立的taskAffinity属性值时,返回栈最后的顺序是:1A-1B-2B;
总结一下Activity的singleTask模式,主要有两点:
1. 在新任务中新建实例。
默认不会在新任务中新建Activity实例,除非设置了Activity的affinity。
2. 只能存在 Activity 的一个实例。
在清单文件中指定launchMode为singleTask,则不会产生新的该Activity实例。
指定Intent的flag为FLAG_ACTIVITY_NEW_TASK,会产生新的该Activity实例,除非再为该Activity单独指定affinity。
Intent和Intent过滤器
鉴于这篇文章太长了,这部分内容就单独拿出来聊吧。Intent和Intent过滤器