Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
1、生命周期方法
- onCreate:表示activity被创建,这是生命周期的第一个方法。在这个方法中主要是做一些初始化工作,例如通过setContentView去加载资源文件,初始化数据等。
- onRestart:表示activity重启,一般当activity有不可见变为课件状态的时候,onRestart会被调用,例如按home键后调用onPause、onStop.再回到页面会调用onRestart;
- onstart:表示activity正在启动,即将开始时,这个activity已经可见了,但是还没有出现在前台,还无法和用户交互.如果 Activity 转入前台,则后接 onResume(),如果 Activity 转入隐藏状态,则后接 onStop()。
- onResume:在 Activity 即将开始与用户进行交互之前调用。 此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。始终后接 onPause()。
- onPause:当系统即将开始继续另一个 Activity 时调用。 此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。如果 Activity 返回前台,则后接 onResume(),如果 Activity 转入对用户不可见状态,则后接 onStop()。
- onStop:Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。如果 Activity 恢复与用户的交互,则后接 onRestart(),如果 Activity 被销毁,则后接 onDestroy()。
- onDestroy:在 Activity 被销毁前调用,一般做回收工作和最终的资源释放。这是 Activity 将收到的最后调用。 当 Activity 结束(有人调用 Activity 上的 finish()),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 您可以通过 isFinishing() 方法区分这两种情形。
2、保存 Activity 状态
当 Activity 暂停或停止时,Activity 对象仍保留在内存中 — 有关其成员和当前状态的所有信息仍处于 Activity 状态。 因此,用户在 Activity 内所做的任何更改都会得到保留,这样一来,当 Activity 返回前台(当它“继续”)时,这些更改仍然存在。
系统为了恢复内存而销毁某项 Activity 时,Activity 对象也会被销毁,因此系统在继续 Activity 时根本无法让其状态保持完好,而是必须在用户返回Activity时重建 Activity 对象。但用户并不知道系统销毁 Activity 后又对其进行了重建,因此他们很可能认为 Activity 状态毫无变化。 在这种情况下,您可以实现另一个回调方法对有关 Activity 状态的信息进行保存,以确保有关 Activity 状态的重要信息得到保留:onSaveInstanceState()。
系统会先调用 onSaveInstanceState(),然后再使 Activity 变得易于销毁。系统会向该方法传递一个 Bundle,您可以在其中使用 putString() 和 putInt() 等方法以名称-值对形式保存有关 Activity 状态的信息。然后,如果系统终止您的应用进程,并且用户返回您的 Activity,则系统会重建该 Activity,并将 Bundle 同时传递给 onCreate() 和 onRestoreInstanceState()。您可以使用上述任一方法从 Bundle 提取您保存的状态并恢复该 Activity 状态。如果没有状态信息需要恢复,则传递给您的 Bundle 是空值(如果是首次创建该 Activity,就会出现这种情况)。
比如配置发生变化,activity会依次调用onPause、onSaveInstanceState、onStop、onDestory、onCreate、onRestoreInstanceState.
注:无法保证系统会在销毁您的 Activity 前调用 onSaveInstanceState(),因为存在不需要保存状态的情况(例如用户使用“返回” 按钮离开您的 Activity 时,因为用户的行为是在显式关闭 Activity)。 如果系统调用 onSaveInstanceState(),它会在调用 onStop() 之前,并且可能会在调用 onPause() 之前进行调用。
但是,您可能会遇到这种情况:重启应用并恢复大量数据不仅成本高昂,而且给用户留下糟糕的使用体验。 在这种情况下,您有两个其他选择:
-
在配置变更期间保留对象
允许 Activity 在配置变更时重启,但是要将有状态对象传递给 Activity 的新实例。 -
自行处理配置变更
阻止系统在某些配置变更期间重启 Activity,但要在配置确实发生变化时接收回调,这样,您就能够根据需要手动更新 Activity。
2.1在配置变更期间保留对象
如果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,那么因配置变更而引起的完全重启可能会给用户留下应用运行缓慢的体验。 此外,依靠系统通过 onSaveInstanceState() 回调为您保存的 Bundle,可能无法完全恢复 Activity 状态,因为它 并非设计用于携带大型对象(例如位图),而且其中的数据必须先序列化,再进行反序列化, 这可能会消耗大量内存并使得配置变更速度缓慢。在这种情况下,如果 Activity 因配置变更而重启,则可通过保留 Fragment 来减轻重新初始化 Activity 的负担。此片段可能包含对您要保留的有状态对象的引用。
当 Android 系统因配置变更而关闭 Activity 时,不会销毁您已标记为要保留的 Activity 的片段。您可以将此类片段添加到 Activity 以保留有状态的对象。
要在运行时配置变更期间将有状态的对象保留在片段(Fragment)中,请执行以下操作:
- 扩展 Fragment 类并声明对有状态对象的引用。
- 在创建片段(Fragment)后调用 setRetainInstance(boolean)。
- 将片段(Fragment)添加到 Activity。
- 重启 Activity 后,使用 FragmentManager 检索片段。
例如,按如下所示定义片段(Fragment):
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(MyDataObject data) {
this.data = data;
}
public MyDataObject getData() {
return data;
}
}
注意:尽管您可以存储任何对象,但是切勿传递与 Activity 绑定的对象,例如,Drawable、Adapter、View或其他任何与 Context 关联的对象。否则,它将泄漏原始 Activity 实例的所有视图和资源。 (泄漏资源意味着应用将继续持有这些资源,但是无法对其进行垃圾回收,因此可能会丢失大量内存。)
然后,使用 FragmentManager 将片段添加到 Activity。在运行时配置变更期间再次启动 Activity 时,您可以获得片段中的数据对象。 例如,按如下所示定义 Activity:
public class MyActivity extends Activity {
private RetainedFragment dataFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
dataFragment.setData(loadMyData());
}
// the data is available in dataFragment.getData()
...
}
@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}
在此示例中,onCreate() 添加了一个片段或恢复了对它的引用。此外,onCreate() 还将有状态的对象存储在片段实例内部。onDestroy() 对所保留的片段实例内的有状态对象进行更新。
2.2自行处理配置变更
如果应用在特定配置变更期间无需更新资源,并且因性能限制您需要尽量避免重启,则可声明 Activity 将自行处理配置变更,这样可以阻止系统重启 Activity。
注:自行处理配置变更可能导致备用资源的使用更为困难,因为系统不会为您自动应用这些资源。 只能在您必须避免Activity因配置变更而重启这一万般无奈的情况下,才考虑采用自行处理配置变更这种方法,而且对于大多数应用并不建议使用此方法。
要声明由 Activity 处理配置变更,请在清单文件中编辑相应的 <activity>
元素,以包含 android:configChanges
属性以及代表要处理的配置的值。android:configChanges
属性的文档中列出了该属性的可能值(最常用的值包括 "orientation" 和 "keyboardHidden",分别用于避免因屏幕方向和可用键盘改变而导致重启)。您可以在该属性中声明多个配置值,方法是用管道 | 字符分隔这些配置值。
例如,以下清单文件代码声明的 Activity 可同时处理屏幕方向变更和键盘可用性变更:
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
现在,当其中一个配置发生变化时,MyActivity 不会重启。相反,MyActivity 会收到对 onConfigurationChanged() 的调用。向此方法传递Configuration 对象指定新设备配置。您可以通过读取 Configuration 中的字段,确定新配置,然后通过更新界面中使用的资源进行适当的更改。调用此方法时,Activity 的 Resources 对象会相应地进行更新,以根据新配置返回资源,这样,您就能够在系统不重启 Activity 的情况下轻松重置 UI 的元素。
注意:从 Android 3.2(API 级别 13)开始,当设备在纵向和横向之间切换时,“屏幕尺寸”也会发生变化。因此,在开发针对 API 级别 13 或更高版本系统的应用时,若要避免由于设备方向改变而导致运行时重启(正如 minSdkVersion
和 targetSdkVersion
属性中所声明),则除了 "orientation"值以外,您还必须添加 "screenSize" 值。即,您必须声明 android:configChanges="orientation|screenSize"。但是,如果您的应用是面向 API 级别 12 或更低版本的系统,则 Activity 始终会自行处理此配置变更(即便是在 Android 3.2 或更高版本的设备上运行,此配置变更也不会重启 Activity)。
例如,以下 onConfigurationChanged() 实现 检查当前设备方向:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
Configuration 对象代表所有当前配置,而不仅仅是已经变更的配置。大多数时候,您并不在意配置具体发生了哪些变更,而且您可以轻松地重新分配所有资源,为您正在处理的配置提供备用资源。 例如,由于 Resources 对象现已更新,因此您可以通过 setImageResource() 重置任何 ImageView,并且使用适合于新配置的资源(如提供资源中所述)。请注意,Configuration 字段中的值是与 Configuration 类中的特定常量匹配的整型数。有关要对每个字段使用哪些常量的文档,请参阅 Configuration参考文档中的相应字段。
请谨记:在声明由 Activity 处理配置变更时,您有责任重置要为其提供备用资源的所有元素。 如果您声明由 Activity 处理方向变更,而且有些图像应该在横向和纵向之间切换,则必须在 onConfigurationChanged()
期间将每个资源重新分配给每个元素。
如果无需基于这些配置变更更新应用,则可不用实现 onConfigurationChanged()
。在这种情况下,仍将使用在配置变更之前用到的所有资源,只是您无需重启 Activity。 但是,应用应该始终能够在保持之前状态完好的情况下关闭和重启,因此您不得试图通过此方法来逃避在正常 Activity 生命周期期间保持您的应用状态。 这不仅仅是因为还存在其他一些无法禁止重启应用的配置变更,还因为有些事件必须由您处理,例如用户离开应用,而在用户返回应用之前该应用已被销毁。
如需了解有关您可以在 Activity 中处理哪些配置变更的详细信息,请参阅 android:configChanges
文档和 Configuration 类。
3、启动模式
Android提供了四种启动模式:
- standard(默认模式)。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。
- singleTop(栈顶复用模式)。如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,并且不会调用这个Activity的onCreate、onStart不会被系统调用。因为它并没有发生改变,而不是创建 Activity 的新实例。,如果新的activity示例已经存在但没有位于栈顶,就会重新创建一个activity。(比如当前栈为ABCD,A在栈底、D在栈顶、如果再次启动D,如果D的模式为SingleTop,那么栈内的情况仍然为ABCD,如果D为standard,那么D背重新创建,栈内的情况为ABCDD)。
- singleTask(栈内复用模式)。这是一种单例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其 onNewIntent() 方法向其传送 Intent。具体一点,当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统会首先寻找是否存在A想要的任务栈,如果存在A所需的任务栈,这个时候就会看A是否在栈中有实例存在,如果有实例存在,那么系统就把A调到栈并调用它的 onNewIntent() ,如果实例不存在,就创建A的实例并把A压入栈中。例如
- 比如目前的任务栈S1中的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D的实例均不存在,所以系统会创建任务栈S2,然后在创建D的实例并将其压入到栈S2
- 另外一种情况,假设D所需的任务栈为S1,其他情况跟上面一样由于S1已经存在,所以直接创建D的实例并将其入栈到S2.
- 如果D所需的任务栈为S1,并且当前的任务栈S1的情况为ADBC,根据栈内复用的原则,此时的D不会重建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上的Activity全部出栈,于是最终的情况为AD。
- singleInstance(单例模式),加强版的singleTask模式,处了具有singeTask模式的所有特性外,此种模式下的Activity正能单独的位于一个任务栈中。例如ActivityA,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的activity,除非这个独特的任务栈被系统销毁了。
taskAffinity
用于指定Activity的任务栈,默认情况下所有的Activity的任务栈的名字为应用包名。
当taskAffinity和singleTask启动模式配对使用,它具有该模式的目前任务栈的名字,待启动activity会运行在名字和TaskAffinity相同的任务栈中。
给Activity指定启动模式的方法,即可以在XML文件中配置,也可以在代码中设置,需要说明的是从优先级上来讲,代码设置的方式要高于XML配置,如果两种方式同时存在,以代码设置方式为准,同时XML配置不能指定FLAG_ACTIVITY_CLEAR_TOP,而代码设置不能指定singleInstance
启动模式。
xml配置方法如下:
<activity
...
android:launchMode=["multiple"|"singleTop"|"singleTask"|"singleInstance"]
android:taskAffinity="string"
...
/>
代码中通过Intent指定方法如下:
Intent intent =new Intent(context,AnotherActivity.class)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Intent的设置启动模式的Flags的值可以为
-
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
属性没有值。FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。
注:如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中删除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例。
-
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有该标识的activity不会出现在历史Activity的列表中,当某些情况下我们不希望通过历史列表回到我们的activity是这个标记比较有用,他等同于XML中指定的Activity属性android:excludeFromRecents="true"
。