一、Activity简述
1、概念引入
Activity作为Android的四大组件之一,Activity在Android系统中是以界面的形式进行体现。其中Activity实现了如Window.Callback, KeyEvent.Callback等接口用于与用户进行交互。
2、源码释义
An activity is a single, focused thing that the user can do.
一个界面是开发者可以操作的一个重点的独立事项。
Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI with setContentView(View).
几乎所有界面都可以与用户交互,所以Activity类负责为用户创建一个窗口,你可以在其中使用setContentView(View)放置UI。
While activities are often presented to the user as full-screen windows, they can also be used in other ways: as floating windows (via a theme with windowIsFloating set) or embedded inside of another activity (using ActivityGroup).
虽然界面通常以全屏窗口的形式呈现给用户,但它们也可以以其他方式使用:作为浮动窗口(通过具有windowIsFloating集合的主题)或嵌入另一个活动(使用ActivityGroup)内部。
There are two methods almost all subclasses of Activity will implement:
几乎所有的Activity子类都会实现两种方法:
onCreate(Bundle) is where you initialize your activity. Most importantly, here you will usually call setContentView(int) with a layout resource defining your UI, and using findViewById(int) to retrieve the widgets in that UI that you need to interact with programmatically.
onCreate(Bundle)是初始化界面的地方。最重要的是,在这里你通常会调用setContentView(int)和一个定义你的UI的布局资源,并且使用findViewById(int)来检索你需要以编程方式进行交互的那个UI中控件。
onPause() is where you deal with the user leaving your activity.Most importantly, any changes made by the user should at this point be committed (usually to the ContentProvider holding the data).
onPause()是你对于用户离开界面的处理。最重要的是,此时用户所做的任何更改都应该提交(通常发送给持有数据的ContentProvider。
To be of use with Context.startActivity(), all activity classes must have a corresponding declaration in their package's AndroidManifest.xml.
要使用Context.startActivity(),所有界面类必须在其包下的AndroidManifest.xml中具有相应的声明。
二、Activity的生命周期
1、Activity的生命周期图
2、生命周期详解
2.1、OnCreate()方法
Called when the activity is first created. This is where you should do all of your normal static set up:
create views, bind data to lists, etc.
在第一次创建活动时调用。 这是你应该完成所有常规静态设置的位置:创建视图,将数据绑定到列表等。
This method also provides you with a Bundle containing the activity's previously frozen state, if there was one.Always followed by onStart().
此方法还会为你提供一个包含Activity先前保存页面状态的Bundle对象(如果有的话)。总是跟着onStart()方法。
2.2、OnRestart()方法
Called after your activity has been stopped, prior to it being started again.
在你的界面停止后调用,然后再次启动。
Always followed by onStart()
总是跟着onStart()
2.3、OnStart()方法
Called when the activity is becoming visible to the user.
当界面对用户变得可见时调用。
Followed by onResume() if the activity comes to the foreground, or onStop() if it becomes hidden.
如果活动进入前台,则跟随执行onResume()方法,如果隐藏,则执行onStop()。
此时,Activity实际上就已经可见了,但是还没有出现在前台,无法和用户进行交互。
2.4、onResume()方法
Called when the activity will start interacting with the user.
当界面对用户变得可见时调用。
At this point your activity is at the top of the activity stack, with user input going to it.Always followed by onPause().
如果界面进入前台,则跟随onResume()方法,如果隐藏,则执行onStop()方法。
此时,Activity已经可见,并且出现在前台并开始活动。要注意的是onStart()被执行时Activity显示在后台,只有当OnResume()被执行时Activity才显示到前台。
2.5、onPause()方法
Called when the system is about to start resuming a previous activity. This is typically used to commit unsaved changes to persistent data, stop animations and other things that may be consuming CPU, etc. Implementations of this method must be very quick because the next activity will not be resumed until this method returns.
onPause()方法在系统即将开始显示之前的界面时调用。 这通常用于将未保存的更改进行保存,并保存为持久化数据,并且会执行例如停止动画和其他可能消耗CPU的内容等。此方法的实现必须非常快速,因为在此方法返回之前,下一个界面不会显示。
Followed by either onResume() if the activity returns back to the front, or onStop() if it becomes invisible to the user.
如果界面返回到前台,则跟随onResume();如果对用户不可见,则使用onStop()。
- onPause()方法执行后,Activity界面切换为后台程序
2.6、onStop()方法
Called when the activity is no longer visible to the user, because another activity has been resumed and is covering this one. This may happen either because a new activity is being started, an existing one is being brought in front of this one, or this one is being destroyed.
onStop()方法会在当界面对用户不再可见时调用,因为另一项界面正在显示并要去覆盖这个界面。 这可能是因为一项新的界面正在开始,其他的界面会被显示为前台界面,或者这个界面正在被摧毁。
Followed by either onRestart() if this activity is coming back to interact with the user, or onDestroy() if this activity is going away.
如果这个界面快速的回显与用户交互紧接着onRestart()会被执行,否则,这个界面回销毁onDestroy()方法会被执行。
2.6、onDestroy()方法
The final call you receive before your activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method.)
在你的界面被销毁前被最后调用的方法。 这可能是因为界面正在结束(有人在其中调用finish()方法因为系统会暂时销毁此Activity界面的实例以节省空间,你可以使用isFinishing()方法区分内存空间是否节省资源的这两种情况)。
3、异常情况下生命周期详解
3.1、横竖屏切换时Activity的生命周期
此时的生命周期跟清单文件里的配置有关系。
①不设置 Activity 的 android:configChanges 时,横竖屏切换会重新调用各个生命周期,销毁当前 activity,然后重新加载,跟系统配置有关。
②onSaveInstanceState()方法会在当前页面销毁前被调用存储数据,onRestoreInstanceState()方法会被执行去取出保存的Bundle对象中的内容,进行一次横竖屏切换时Activity所执行的生命周期方法以及在onSaveInstanceState与onRestoreInstanceState打印相应日志,如下图所示:
③对于onRestoreInstanceState()方法,源码中的注释如下:
This method is called after onStart when the activity is being re-initialized from a previously saved state, given here in savedInstanceState.
当这个界面从之前保存的状态重新初始化时,这个方法在onStart之后被调用,这里在savedInstanceState中给出。
Most implementations will simply use onCreate to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done or to allow subclasses to decide whether to use your default implementation. The default implementation of this method performs a restore of any view state that had previously been frozen by onSaveInstanceState.
大多数时,可以直接从onCreate()方法中的Bundle对象从中获取内容来恢复之前的页面状态,但有时在完成所有初始化或允许子类决定使用默认实现之后,在此处执行重新获取已存储数据的操作很方便。 此方法的默认实现会还原之前由onSaveInstanceState保存的任何视图状态。
④根据源码可以得知onRestoreInstanceState 方法的默认实现会还原之前由onSaveInstanceState保存的任何视图状态。
- 关于保存和恢复View层次结构系统工作流程是这样的:首先Activity被意外终止时,Activity会调用
onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window会委托它上面的顶层容器去保存数据。顶层容器肯是一个ViewGroup,一般来说是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。这是一种典型的委托思想,上层委托下层、父容器委托子元素去处理一件事件;在View的绘制流程、事件分发都是采用这种思想在处理。
3.2、资源内容不足时导致低优先级的Activity被杀死
Activity按优先级从高到低可分为如下3种:
Activity按优先级从高到低 |
---|
①前台Activity-正在和用户进行交互的页面(优先级最高)。 |
②可见但非前台Activity-比如Activity中弹出了一个对话框,导致Activity可见,但是位于后台无法和用户进行直接交互(优先级居中)。 |
③后台Activity-已经被暂停的Activity,比如执行了onStop()(优先级最低)。当系统内存不足时,系统会按照优先级由低到高去杀死Activity所在的进程,通过onSaveInstanceState存储数据,通过onRestoreInstanceState 恢复数据,如果一个进程没有四大组件作为依附,那么这个进程很快就会被杀死。 |
- 根据上述的分析,系统配置发生改变后,系统会重新创建Activity,那么是否可以不重新创建Activity呢?
答案是有的,那就是在清单文件中为相应的Activity配置configChanges属性,并添加相应值
防止Activity重新创建,android:configChanges时还需注意的点 |
---|
1. 不设置Activity的android:configChanges时。切屏会又一次调用整个生命周期,切横屏时会运行一次,切竖屏时会运行两次 |
2. 设置Activity的android:configChanges="orientation"时,切屏还是会又一次调用整个生命周期,切横、竖屏时仅仅会运行一次 |
3. 设置Activity的android:configChanges="orientation丨keyboardHidden"时,切屏不会又一次调用整个生命周期,仅仅会运行onConfigurationChanged方法 |
4. 可是。自从Android 3.2(API 13),在设置Activity的android:configChanges="orientation丨keyboardHidden"后。还是一样会又一次调用各个生命周期的。由于screensize也開始跟着设备的横竖切换而改变。所以在AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13的情况下,假设你想阻止程序在执行时又一次载入Activity,除了设置"orientation"。 你还必须设置" screenSize"。 |
-
android:configChanges配置的属性不仅局限于此,下图为android:configChanges可配置的所有属性内容:
三、Activity任务与任务管理栈
1、任务与任务栈概述
任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即任务栈)中。
- 设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷方式)时,该应用的任务将出现在前台。 如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。
当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。 因此,返回栈以“后进先出”对象结构运行。 下图 通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,直观呈现了这种行为。
2、任务栈关联
关联指 Activity 优先属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此关联。 因此,默认情况下,同一应用中的所有 Activity 优先位于相同任务中。 不过,可以修改 Activity 的默认关联。 在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。
- 可以使用activity元素的 taskAffinity属性修改任何给定 Activity 的关联。
taskAffinity 属性取字符串值,该值必须不同于在AndroidManifest.xml文件中声明的默认软件包名称,因为系统使用该名称标识应用的默认任务关联。
在两种情况下,关联会起作用:
启动 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK标志。
-
Activity 将其allowTaskReparenting 属性设置为 true。
在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。
3、清理任务栈
如果用户长时间离开应用,则系统会清除应用下所有除 Activity 的所有 Activity 。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。
可以使用下列几个 Activity 属性修改此行为:
alwaysRetainTaskState
如果在任务的根 Activity 中将此属性设置为 true`,则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。finishOnTaskLaunch
此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 true 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。
四、Activity的启动模式
在AndroidManifest.xml文件中为Activity配置相应的launchMode属性可以设置Activity的启动模式,共如下四种模式:
- standard
- singleTask
- singleTop
- singleInstance
接下来对四种启动模式进行详细分析。
1、standard模式
标准启动模式:
该模式下每次启动Activity都会重新创建Activity实例,在这种模式下谁启动了这个Actvitiy,那么这个Activity与被启动的Activity位于启动它的Activity的栈中。
但是在代码中采用ApplicationContext去开启Activity时(例如执行如下代码时):
getApplication().startActivity(new Intent(HomeActivity.this,MainActivity.class));
会报如下所示的错误:
- 分析:非Activity类型的Context(如ApplicationContext)并没有任务栈。
- 解决方案:为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记,那么通过这种方式启动的Activity就会新开启一个任务栈。
2、singleTop模式
栈顶复用模式:
该模式下如果Activity已经位于栈顶,那么该Activity不会重新创建,同时它的OnNewIntent方法会被调用,通过方法的参数可以取出其中的信息,并且在这种模式如果这个Actvitiy不位于栈顶,那么这个Activity依然会被重新创建。
3、singleTask模式
栈内复用模式:
只要Activity实例在一个栈中存在,那么再次启动该Activity时不会重新创建实例,并且系统也会回调其OnNewIntent方法,并且该模式下有清除栈顶实例的效果,即会将与singleTask模式下的该Activity同一个栈中的栈顶的所有Activity实例全部出栈。
4、singleInstance模式
单实例模式:
单实例模式具备singleTask模式的所有的特性,此外具有此模式的Activity只能单独位于一个任务栈中。
在这里,我们以HomeActivity、MainActivity、DetailActivity三个Activity演示singleInstance模式下的页面跳转,其中MainActivity为singleInstance模式,其他两个页面为standard模式,如下图演示效果:
在每个Activity中我们调用getTaskId(),如下图打印其栈Id,我们可以查看到standard模式的HomeActivity与DetailActivity都位于同一个栈中,而singleInstance模式下的MainActivity单独位于一个栈中, 综上所述,系统会为singleInstance模式下的Activity创建一个新的任务栈。
五、Activity的启动方式
Activity的启动方式可以分为显示启动和隐式启动,当两者共存时使用的是显示启动,而隐式启动不需要指定组件信息。两者使用其中一种即可。那么在之后的文章中深入探究Intent以及IntentFilter在显示、隐式开启Activity及其他组件中的作用。
1、显示启动
显示启动需要intent指定被启动对象的组件信息,包括包名和类名。显示启动
2、隐式启动
隐式启动调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。IntentFilter中过滤的信息有action、category、data。
- 使用隐式Intent的时候,系统通过将Intent对象中的IntentFilter与组件在AndroidManifest.xml或者代码中动态声明的IntentFilter进行比较,从而找到要启动的相应组件。如果组件的IntentFilter与Intent中的IntentFilter正好匹配,系统就会启动该组件,并把Intent传递给它。如果有多个组件同时匹配到了,系统则会弹出一个选择框,让用户选择使用哪个应用去处理这个Intent,比如有时候点击一个网页链接,会弹出多个应用,让用户选择用哪个浏览器去打开该链接,就是这种情况。
隐式启动Activity的代码如下所示:
Intent intent = new Intent(Intent.ACTION_DIAL);
Uri data = Uri.parse("tel:" + "183xxxxxxxx");
intent.setData(data);
startActivity(intent);