Activity

  Activity是Context的子类;

Activity生命周期

  在Android系统中使用生命周期描述组件在不同状态之间的切换。通过管理组件的生命周期实现资源的获取与释放,在不同阶段的生命周期中触发相应的回调函数,在回调函数的上层方法中,系统控制组件的创建与销毁。Activity作为组件之一,也有着一套有序的生命周期,管理Activity的全部行为。
  Activity的生命周期如下图所示:
Activity声明周期
Activity生命周期

//Activity的创建;
protected void onCreate(Bundle savedInstanceState);
//Activity的启动;
protected void onStart();
//Activity的运行;
protected void onResume();
//Activity的暂停;
protected void onPause();
//Activity的停止;
protected void onStop();
//Activity的销毁;
protected void onDestroy();
//Activity的重启;
protected void onRestart();

  除了onRestart()方法之外,另外六种生命周期两两对应,构成了三组生命周期。这三组分别是完整的生命周期(Entire Lifetime)、可视的生命周期(Visible Lifetime)以及前台的生命周期(Foreground Lifetime)。
1.完整的生命周期: 表示Activity组件从创建到销毁的全部过程,是最外层的生命周期。生命周期发生在调用onCreate()方法与调用onDestroy()方法之间。在onCreate()方法中,系统会创建Activity的全局状态,例如填充布局等;在onDestroy()方法中,系统会释放Activity所保存的资源。需要注意的是,onDestroy()不能保证被调用的时机,例如Activity在Activity栈中,在系统内存不足时,就可能触发强制销毁调用onDestroy()方法,而在其他情况下,只有当Activity出栈时,才会调用onDestroy()方法。
2.可视的生命周期:表示Activity组件从用户可视到离开用户视线的全过程。在这期间,用户可以在屏幕上看见当前的Activity。生命周期发生在调用onStart()方法与onStop()方法之间。在onStart()方法和onStop()方法中,执行与用户可视相关的逻辑。因为onDestroy()方法并不一定会被调用,所以在onStop()方法中,一般会执行保持当前Activity状态的逻辑。在Activity的全部存活时间中,onStart()方法和onStop()方法可能会被多次调用,这是因为Activity可能交替地由可视状态到不可视状态,再恢复到可视状态。
3.前台的生命周期:表示Activity组件显示于其他Activity组件之前,即位于Activity任务栈的栈顶,拥有最高优先级的资源使用权限,同时可以获得用户的输入焦点与用户进行交互。在可视的生命周期中,Activity组件可能位于全透明或部分透明的Activity下面,即属于可视状态。而前台状态必须位于全部的Activity之上。生命周期发生在调用onResume()方法与onPause()方法之间。在onResume()方法和onPause()方法中,执行与交互相关的逻辑。同样,在Activity的全部存活时间中,onResume()和onPause()也可能会被多次调用,即Activity可能会频繁地获取与失去焦点。
  onPause()方法控制Activity是否具有交互功能,onStop()方法控制Activity是否可视。
  还有另外三个特殊的生命周期状态,即onRestart()、onRestoreInstanceState()、onSaveInstanceState()。当页面从Activity栈内调至栈顶时,会调用onRestart()方法,初次创建时不会调用;onSaveInstanceState()用于储存Activity的状态信息;onRestoreInstanceState()用于恢复Activity的状态信息,仅用于系统导致的页面重建,而用户导致的页面重建需要在onCreate()中由开发者自主恢复状态信息。
  当系统配置发生改变时,Activity页面就会触发重建过程,因为系统配置会导致页面的某些参数发生变化,显示样式不同。最常见的是旋转屏幕,因为这次变化是由系统控制产生关闭和启动Activity的行为触发生命周期,所以Activity页面不仅会调用onSaveInstanceState()触发保存数据,还会同步执行onRestoreInstanceState()恢复数据,保证数据的一致性。onSaveInstanceState()方法在停止交互(即onPause()方法)之后执行,确保用户不进行额外的输入。onRestoreInstanceState()方法在可交互方法(即onResume()方法)之前执行,确保数据的正确恢复。
  如果Activity需要恢复状态数据,在恢复时机的选择上,可以选择onCreate()或onRestoreInstanceState()。区别是:在onCreate()中,需要判断传入参数savedInstanceState是否为空;对于由系统原因导致的Activity页面变化,触发的生命周期改变,推荐使用onRestoreInstanceState()恢复数据,针对性更强。因为onCreate()在每次Activity重建时,都会被调用,无法区分是否因系统原因导致。在onCreate()中,恢复状态时要先对savedInstanceState判断是否为空,再进行操作。

Activity页面跳转

1 . 两个普通Activity页面的跳转

  我们从一个Activity(FirstActivity)使用startActivity()方法跳转到另一个Activity(SecondActivity)的时候的生命周期。
(1) . 先进入到FirstActivity页面:

FirstActivity: onCreate...
FirstActivity: onStart...
FirstActivity: onResume...

(2) . 从FirstActivity页面跳转到SecondActivity页面:

FirstActivity: onPause...
SecondActivity: onCreate...
SecondActivity: onStart...
SecondActivity: onResume...
FirstActivity: onStop...

(3) . 从SecondActivity页面点击返回键回到FirstActivity页面:

SecondActivity: onPause...
FirstActivity: onRestart...
FirstActivity: onStart...
FirstActivity: onResume...
SecondActivity: onStop...
SecondActivity: onDestroy...

2 . Activity设置启动模式为singleTop,并跳转至自身

  在实际工作中,比如商品的详情页DetailActivity,我们可能会点击该页面的推荐商品,还是跳转到该页面,只是传过去的值不一样,页面内容展示不一样而已。这种情况下,我们会把该页面的启动模式设置为singleTop,不让其产生太多的页面,不然我们打开了好多个DetailActivity页面以后,想要退出,需要关闭好多个页面。这里,我们要关注下把页面启动模式设置为singleTop以后,页面的方法调用会使用一个onNewIntent()方法。
(1) . 进入到DetailActivity页面

DetailActivity: onCreate...
DetailActivity: onStart...
DetailActivity: onResume...

(2) . 从自身跳转到自身的DetailActivity页面

DetailActivity: onPause...
DetailActivity: onNewIntent...
DetailActivity: onResume...

(3) . 退出DetailActivity页面

DetailActivity: onPause...
DetailActivity: onStop...
DetailActivity: onDestroy...

3 . 跳转到透明的Activity

  如果我们要跳转的页面是一个透明的页面TransparentActivity,因为TransparentActivity是透明的,也就是上一个页面的内容我们还是可以看到的,上个页面的onStop()方法不会被执行的。
  需要注意的是,这里说的透明页面,并不是说百分百透明的,而是只要稍微透明度有一点,就算作了透明页面。其次,一般我们使用的是Theme.AppCompat这个主题下面的具体主题,所以我们设置透明页面的时候,如果是要通过设置Theme属性来设置透明度的时候,需要自己来定义一个Theme来设置透明度。

//styles.xml文件;
<style name="MyTransparentTheme" parent="AppTheme">
    <item name="android:windowBackground">@color/colorTransparent_15</item> <!-- 背景色透明度 -->
    <item name="android:windowNoTitle">true</item><!-- 无标题 -->
    <item name="android:windowIsTranslucent">true</item><!-- 半透明,设置为false无透明效果 -->
    <item name="android:backgroundDimEnabled">true</item><!-- 模糊 -->
    <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item> <!-- 窗口样式Dialog -->
</style>

//colors.xml文件;
<color name="colorTransparent_15">#D9ffffff</color>

//AndroidManifest.xml文件;
<activity android:name=".activities.TransparentActivity"
    android:theme="@style/MyTransparentTheme"
    >
</activity>

(1) . 进入第一个页面

FirstActivity: onCreate...
FirstActivity: onStart...
FirstActivity: onResume...

(2) . 跳转到透明页面

FirstActivity: onPause...
TransparentActivity: onCreate...
TransparentActivity: onStart...
TransparentActivity: onResume...

(3) . 退出透明页面

TransparentActivity: onPause...
FirstActivity: onResume...
TransparentActivity: onStop...
TransparentActivity: onDestroy...

4 . 跳转到Theme为Dialog的页面

  当跳转到一个Theme为Dialog的页面(DialogActivity),其实生命周期和跳转到一个透明的页面是一样的,因为Theme为Dialog的页面和透明页面都不会把前一个Activity的内容完全遮盖住不让其显示出来。

(1) . 进入第一个页面

ActivityAllActivity: onCreate...
ActivityAllActivity: onStart...
ActivityAllActivity: onResume...

(2) . 跳转到Theme为Dialog的页面

ActivityAllActivity: onPause...
DialogActivity: onCreate...
DialogActivity: onStart...
DialogActivity: onResume...

(3) . 退出Theme为Dialog的页面

DialogActivity: onPause...
ActivityAllActivity: onResume...
DialogActivity: onStop...
DialogActivity: onDestroy...
  • 如果是单纯是创建的 dialog ,Activity 并不会执行生命周期的方法;
  • 但是如果是跳转到一个不是全屏的 Activity 的话, 当然就是按照正常的生命周期来执行了,即 onPasue() -> onPause() ( 不会执行原 Activity 的 onStop() , 否则上个页面就不显示了 );

横竖屏切换时的生命周期

  不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次;设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged 方法;
  注意:还有一点,非常重要,一个Android的变更细节!当 API >12 时,需要加入screenSize属性,否则屏幕切换时即使你设置了orientation系统也会重建 Activity;

  未设置configuration属性时,Activity生命周期如下:
  在AndroidManifest.xml中不设置Activity的android:configChanges的话,在横竖屏切换的时候,Activity会进行销毁再重建的过程。

(1) . 刚进入ConfigurationActivity页面时,页面为竖屏

ConfigurationActivity: onCreate...
ConfigurationActivity: onStart...
ConfigurationActivity: onResume...

(2) . 由竖屏切换到横屏时

ConfigurationActivity: onPause...
ConfigurationActivity: onStop...
ConfigurationActivity: onDestroy...
ConfigurationActivity: onCreate...
ConfigurationActivity: onStart...
ConfigurationActivity: onResume...

(3) . 由横屏再次切换到竖屏

ConfigurationActivity: onPause...
ConfigurationActivity: onStop...
ConfigurationActivity: onDestroy...
ConfigurationActivity: onCreate...
ConfigurationActivity: onStart...
ConfigurationActivity: onResume...

设置了configuration属性以后,Activity的生命周期如下:
  在AndroidManifest.xml文件中设置ConfigurationActivity的android:configChanges属性如下:

android:configChanges="orientation|keyboardHidden|screenSize"

  那么Activity在切换横屏和竖屏的时候,并不会销毁Activity再重建,而是执行onConfigurationChanged()方法。

(1) . 刚进入页面时,页面是竖屏

ConfigurationActivity: onCreate...
ConfigurationActivity: onStart...
ConfigurationActivity: onResume...

(2) . 由竖屏切换到横屏

ConfigurationActivity: onConfigurationChanged...

(3) . 由横屏再次切换到竖屏

ConfigurationActivity: onConfigurationChanged...

  我们可以通过在AndroidManifest.xml文件中的Activity标签中设置android:screenOrientation属性来控制屏幕的竖屏或者横屏。
  当我们把屏幕模式设置为竖屏或者横屏以后,不管android:configChanges是否设置,Activity都不会再经历销毁重建或者调用onConfigurationChanged()方法了。

(1) . 要想让屏幕一直是竖屏模式,就在AndroidManifest.xml文件中的Activity标签中设置如下代码:

android:screenOrientation="portrait"

(2) . 要想让屏幕一直是横屏模式,就在AndroidManifest.xml文件中的Activity标签中设置如下代码:

android:screenOrientation="landscape"

锁屏与解锁屏幕进入Activity的生命周期

(1) . 进入到页面

ActivityAllActivity: onCreate...
ActivityAllActivity: onStart...
ActivityAllActivity: onResume...

(2) . 锁屏以后,屏幕熄灭

ActivityAllActivity: onPause...
ActivityAllActivity: onStop...

(3) .

ActivityAllActivity: onRestart...
ActivityAllActivity: onStart...
ActivityAllActivity: onResume...

(4) . 退出该页面

ActivityAllActivity: onPause...
ActivityAllActivity: onStop...
ActivityAllActivity: onDestroy...

  锁屏时会执行 onPause() -> onStop(),而亮屏进入到Activity页面时则执行 onRestart() -> onStart() -> onResume()。

不同场景下Activity生命周期的变化过程

启动 Activity :onCreate() ---> onStart() ---> onResume() ,Activity 进入运行状态。
锁屏时会执行 onPause() 和 onStop() , 而开屏时则应该执行 onStart() onResume()(需要进行实验)
Activity 退居后台:当前 Activity 转到新的 Activity 界面或按 Home 键回到主屏:onPause() ---> onStop() ,进入停滞状态。
Activity 返回前台:onRestart() ---> onStart() ---> onResume() ,再次回到运行状态。
Activity 退居后台:且系统内存不足, 系统会杀死这个后台状态的 Activity ,若再次回到这个 Activity ,则会走 onCreate() --> onStart() ---> onResume()

将一个Activity设置为窗口的样式

  因为我们的Activity一般是继承自AppCompatActivity,因此我们需要设置主题为Theme.AppCompat主题目录下的主题。也就是我们在Activity中使用的主题需要和我们使用的Activity的父类相关联。因此,给我们在AndroidManifest.xml文件中的Activity设置的Theme如下:

android:theme="@style/Theme.AppCompat.Dialog"

  在Theme.AppCompat主题目录下有好几种Dialog的主题,在这里我们只是选择了其中的一种。不同的Dialog主题影响的是Activity的外观样式。

退出已调用多个Activity的应用

  1. 发送特定广播:
    在需要结束应用时, 发送一个特定的广播,每个 Activity 收到广播后,关闭 即可。
    给某个 activity 注册接受接受广播的意图 registerReceiver(receiver, filter)
    如果过接受到的是 关闭 activity 的广播 activity finish() 掉
  2. 递归退出
    就调用 finish() 方法 把当前的 Activity 退出
    在打开新的 Activity 时使用 startActivityForResult , 然后自己加标志, 在 onActivityResult 中处理, 递归关闭。
  3. 利用 flag
    也可以通过 intent 的 flag 来实现 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 激活一个新的 activity。
    此时如果该任务栈中已经有该 Activity , 那么系统会把这个 Activity 上面的所有 Activity 干掉。其实相当于给 Activity 配置的启动模式为 singleTask 。
  4. 记录打开的 Activity
    每打开一个 Activity , 就记录下来。
    在需要退出时 , 关闭每一个 Activity
      不过,我一般使用的方法是把主界面设置为singleTask,这样每次返回主页面时都只剩下主界面这一个Activity,在主界面退出时,也就相当于退出了整个应用。

修改Activity进入和退出动画

可以通过两种方式:

  • 通过定义 Activity 的主题:
      通过设置主题样式在 styles.xml 中编辑代码, 添加 themes.xml 文件,在 AndroidManifest.xml 中给指定的 Activity 指定 theme。
  • 通过覆写 Activity 的overridePendingTransition 方法:
overridePendingTransition(R.anim.fade, R.anim.hold);

Activity的四种状态

  • runnig :用户可以点击,activity 处于栈顶状态。
  • paused :activity 失去焦点的时候,被一个非全屏的 activity 占据或者被一个透明的 activity 覆盖,这个状态的 activity 并没有销毁,它所有的状态信息和成员变量仍然存在,只是不能够被点击。(内存紧张的情况,这个 activity 有可能被回收)
  • stopped :这个 activity 被另外一个 activity 完全覆盖,但是这个 activity 的所有状态信息和成员变量仍然存在(除了内存紧张)
  • killed :这个 activity 已经被销毁,其所有的状态信息和成员变量已经不存在了。

如何处理异常退出

  Activity 异常退出的时候 --> onPause() --> onSaveInstanceState() --> onStop() --> onDestory()。需要注意的是 onSaveInstanceState() 方法与 onPause 并没有严格的先后关系,有可能在 onPause 之前,也有可能在其后面调用,但会在 onStop() 方法之前调用。异常退出后又重新启动该 Activity --> onCreate() --> onStart() --> onRestoreInstanceState() --> onResume()。
  需要注意的是 onSaveInstanceState() 方法与 onPause 并没有严格的先后关系,有可能在 onPause 之前,也有可能在其后面调用,但会在 onStop() 方法之前调用
异常退出后又重新启动该 Activity --> onCreate() --> onStart() --> onRestoreInstanceState() --> onResume()。

什么是onNewIntent()

  onNewIntent()方法是Activity.java类中的方法。当Activity的启动模式为SingleTask或者SingleTop时,当该Activity已经存在并且要进行复用时,就会调用该方法。启动模式为SingleInstance时,也会调用该方法。
  如果Activity 处于任务栈的顶端,也就是说之前打开过的 Activity ,现在处于 onPause 、 onStop 状态的话,其他应用再发送 Intent 的话
执行顺序为:onNewIntent,onRestart,onStart,onResume。

控制Activity的生命周期

  • 强制执行单任务模式:
  • 强制手机屏幕方向:
      几乎所有的Android设备都带有加速度计,这使得该设备可以判断方向,当设备的屏幕在横向和纵向模式之间切换时,系统默认的动作是相应地旋转应用程序的视图。
      默认情况下,当手机屏幕方向改变时,Activity将被结束然后再重新启动,也就是会执行onPause()--onStop()--onDestroy()--onCreate()--onStart()--onResume()这几个方法,会使该应用丢失当前的状态。
      对于屏幕改变方向的问题,Android提供了两种解决方案,一种是在改变方向之前保存用户的状态,然后再重启该Activity时恢复先前的用户状态。另一种方法是强制屏幕方向,禁止旋转切换应用程序的视图。
      这里介绍第二种方法:在AndroidManifest.xml中事先指定某个Activity的固定视图方法以达到目的,如下:
Activity视图模式 方法
纵向模式 android:screenOrientation="portrait"
横向模式 android:screenOrientation="landscape"

  不过只是设置了Activity的视图模式,在旋转手机屏幕后,视图始终保持设定好的视图模式,但是Activity还是会经历onPause()--onStop()--onDestroy()方法,还是没有解决Activity状态丢失的问题。也就是说,设置Android:screenOrientation属性只能使得应用程序始终保持指定的方向,并不能防止Activity的重新启动。要实现Activity在屏幕方向改变时不被重启,还需要在AndroidManifest.xml中设置android:configChanges属性告诉系统对应用程序方向的改变进行处理而不是重启应用程序。代码如下:

android:configChanges="orientation|keyboardHidden"

设置好上述属性之后,当屏幕方向改变或硬键盘滑出时,Android将不会重启Activity,而是调用onConfigurationChange()方法对事件进行处理。

@Override
public void onConfigurationChanged(Configuration newConfig){
    super.onConfigurationChanged(newConfig);
    
}
  • 保存和恢复Activity的信息:   为Activity实现这一功能的方法就是onSaveInstanceState()和onRestoreInstanceState()。当一个Activity即将被程序杀死时,可以通过重写onSaveInstanceState()方法来保存需要保存的相关信息。当重新创建该Activity后,之前onSaveInstanceState()方法保存的状态信息将通过Bundle传递给onCreate()方法,然后就可以利用onRestoreInstanceState()方法来恢复之前的状态信息了。
      值得注意的是,onSaveInstanceState()并不总是在Activity被销毁前都会被调用,当系统未经用户允许时销毁了该Activity,则onSaveInstanceState()方法将会被系统调用,这是系统的责任,因为它必须要提供一个机会让用户保存必要的数据。
      以下几种情况,系统会调用onSaveInstanceState()方法:
    1.当用户按下Home键,系统不知道用户在按下Home键之后要运行多少其他程序,自然也不知道当前Activity是否会被销毁,故系统会调用onSaveInstanceState()方法,让用户有机会保存某些非永久性的数据;
    2.长按Home键或者Menu键(具体看手机厂商的定义),选择运行其他程序;
    3.按下电源键,是手机设备处于屏保状态;
    4.屏幕方向切换,而用户并未设置configChanges属性指定系统处理该事件时,系统会销毁当前Activity,在屏幕切换后自动创建该Activity,所以onSaveInstanceState()方法一定会被执行;
      当然,也要注意,onSaveInstanceState()方法和onRestoreInstanceState()并不一定被成对调用;

Activity启动模式

  为了实现Activity复用,Android系统提供了Activity的启动模式,即LaunchMode,可以根据情况选择创建还是复用实例。启动模式一共四种:standard(标准模式,默认)、singleTop(栈顶复用模式)、singleTask(栈内复用模式)、singleInstance(单实例模式)。标准模式在每次启动时都会创建Activity实例,其他三种模式会根据情况选择创建还是复用实例。在Activity启动中,创建实例的生命周期为onCreate()->onStart()->onResume();重用实例的生命周期为onNewIntent()->onResume()。可以在AndroidManifest.xml中通过给标签指定android:launchMode属性来设置启动模式,默认是标准模式;使用taskAffinity属性并添加包名,即可设置Activity栈,默认是当前包名,此属性仅适用于三种单例模式。

standard(标准/默认模式)

  是活动默认的启动模式,在不进行显示指定的情况下,所有活动都会自动使用这种启动模式。在standard模式(即默认情况)下,每当启动一个新的活动,就会创建一个新的Activity实例,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。每次被创建的实例 Activity 的生命周期符合典型情况,它的 onCreate 、onStart 、onResume 都会被调用。
  应用场景:
  绝大多数Activity。如果以这种方式启动的Activity被跨进程调用,在Android 5.0 之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序;在Android 5.0 之后,会创建一个新的Task,新启动的Activity就会放入刚创建的Task中。

singleTop(栈顶复用模式)

  当活动的启动模式指定为singleTop时,只有当Acticity位于栈顶时,再次启动当前的Activity,复用栈顶的Activity实例,不会重新创建,即单例模式,会调用onNewIntent()方法,而不是执行Activity的其它生命周期;如果位于栈内,与standard标准模式相同,仍然会重新创建实例。当位于栈顶的 Activity 被直接复用时,它的 onCreate 、onStart 不会被系统调用,由于它并没有发生改变。可是一个新的方法 onNewIntent 会被回调( Activity 被正常创建时不会回调此方法)。
  应用场景:
  (1)常见的是应用的产品详情页面,当从一个产品的详情页面内跳转到另一个产品的详情页面时,复用同一个页面。
  (2)在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity一般会使用singleTop,否则每次点击进来都会新建一个Activity。
  注意:可能会发生在某个场景下快速点击,启动了两个Activity。这就相当于一个Bug,那么我们也可以使用singleTop模式来避免这个Bug。
  如果是外部程序启动singleTop的Activity,在Android 5.0 之前新创建的Activity会位于调用者的Task中,Android 5.0 之后的会放入新的Task中。

singleTask(栈内复用模式)

  该模式是一种单例模式,即一个栈内只有一个该Activity实例。该模式可以通过在AndroidManifest.xml文件中指定该Activity需要加载到哪个栈中,即singleTask的Activity可以指定想要加载的目标栈。singleTask和taskAffinity属性配合使用,指定启动的Activity加入到哪个栈中。

<activity android:name=".ui.DemoActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.example.kang"
    >
</activity>

  taskAffinity:每个Activity都有taskAffinity属性,这个属性指出了Activity想要进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application这个标签中指明的taskAffinity;如果Application标签中也没有指明taskAffinity属性,那么该Activity的taskAffinity的值就等于包名。

  当活动的启动模式指定为singleTask,有三种情况:
(1)如果Activity指定的栈不存在,则创建一个栈,并把创建的Activity压入该栈内;

(2)如果Activity指定的栈存在,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,,如果不存在该Activity实例,就会创建一个新的活动实例并进入栈顶,生命周期同创建一个新的活动一致。

(3)如果Activity指定的栈存在,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把这个活动之上的所有活动杀死清除出栈,让该Activity实例处于栈顶,该Activity的其它生命周期方法不执行,只是调用onNewIntent()方法。

  应用场景:
  大多数APP的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们进入主界面Activity以后,主界面就位于栈顶。以后不管我们打开多少个Activity,只要我们回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,通过这种方式能够保证在主界面退出应用时所有的Activity都能被销毁。

  如果是在别的应用程序中启动该模式的Activity,如果系统中不存在该Activity,则会新建一个Task,并在该Task中启动这个Activity。

singleInstance(单实例模式)

  在启动单例模式的Activity实例时,系统为其创造一个单独的任务栈,以后每次使用时都会使用这个单例,直到其被销毁,属于真正的单例模式。一旦该模式的Activity实例已经存在于某个栈中,任何应用再调用该Activity时都会重用该栈中的Activity实例。
  与栈内复用模式不同,栈内复用模式时指定taskAffinity属性,所创建的任务栈并不独享,仍可添加其他的Activity;而单实例模式所创建的任务栈只含有这个实例,所以说是真正的单例模式。
  指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。假设我们的程序中有一个活动是允许其他程序调用的,那我们要实现其他程序和我们的程序都可以共享这个活动的实例。前三种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。

  使用场景:经常用于系统中的应用,如Launcher、锁屏键的应用、来电显示界面等。

特殊情况--前台栈和后台栈的交互

  假如目前有两个任务栈,前台任务栈中的Acivity为A、B,后台任务栈中的Activity为C、D,这里假设C、D的启动模式都是singleTask,现在启动D,那么这个后台任务栈就会被切换到前台,这个时候整个后退列表就变成了A、B、C、D。当用户按返回键返回时,列表中的Activity会一一出栈。如下图:

  如果不是启动D,而是启动C,那么情况又不一样,如下图:

  调用singleTask模式的后台任务栈中的Activity,会把整个栈的Activity压入当前栈的栈顶。singleTask模式具有clearTop特性,会把启动的Activity之上的栈内Activity清除。

设置启动模式的方式

  设置启动模式的方式有两种,一种是静态的,在AndroidManifest中的Activity声明launchMode属性。另一种是动态的,在startActivity()的Intent中设置启动标志位,即Flag,如intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASKS)。这两种方式有优先级,动态指定方式比静态指定方式的优先级高,如果这两种方式同时存放,以动态指定方式为准。第一种方式无法为 Activity 直接指定 FLAG_ACTIVITY_CLEAR_TOP 标识,另外一种方式无法为 Activity 指定 singleInstance 模式。
  常见标志位如下:

FLAG_ACTIVITY_SINGLE_TOP

  同栈顶复用的启动模式,也就是singleTop模式;

FLAG_ACTIVITY_NEW_TASK:

  同栈内复用的启动模式,单独使用不具备清除顶部的效果,也就是singleTask模式;

FLAG_ACTIVITY_CLEAR_TOP:

  具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的栈都要出栈。如果和singleTask模式一起出现,若被启动的Activity已经存在栈中,则清除其之上的Activity,并调用该Activity的onNewIntent()方法。如果被启动的Activity采用standard模式,那么该Activity连同之上的所有Activity出栈,然后创建新的Activity实例并压入栈中。

  在设置启动模式是,AndroidManifest与startActivity()的Intent还有一些区别:
  1.AndroidManifest无法设置清除顶部的效果,只有栈内复用的启动模式含有清除顶部的效果;而startActivity()的Intent支持设置FLAG_ACTIVITY_CLEAR_TOP属性,实现清除顶部的效果,并且在标准的启动模式中也可以应用;
  2.startActivity()的Intent无法设置单实例的启动模式;
  两者任选其一使用即可,如果两种方式均设置,startActivity()的Intent的优先级大于AndroidManifest的优先级,startActivity()的Intent会覆盖AndroidManifest的效果。
  startActivityForResult()也是启动Activity的一种方式,不同于startActivity(),需要设置返回值。startActivityForResult()使用启动模式启动Activity时,也会有一些不同,其效果是可以正常传递返回值,但是无法复用实例,即无法触发单例效果,会重复创建多份实例。因为startActivityForResult()需要返回值,任何时候都需要保留实例,接收返回值,这一特性覆盖原有的单例效果。需要注意的是:在Android4.x一下版本中,通过startActivityForResult()启动singleTask,无法正常获取返回值;在Android5.x以上版本中已修复此问题,但是考虑兼容性的问题,不推荐startActivityForResult()和单例的启动模式同时使用。
  Activity的启动模式关系到Activity在任务栈中的存在形式,影响Activity的展示顺序。Android系统并未提供操作任务栈的接口,无法任意改变Activity的存储顺序,栈中Activity排列都依赖于启动模式。Android系统的任务栈体系分为三层:
1.第一层是Stack,每一个应用属于一个Stack,一一对应;
2.第二层是TaskRecord,它含有一组Activity,通过单例模式(SingleTask和SingleInstance)支持创建多个TaskRecord;
3.第三层是ActivityRecord,每一个Activity实例就是一个ActivityRecord,相同的Activity支持存在多份实例;
  启动模式的本质就是解决如何避免相同的Activity创建多个实例。对于标准模式,在每次启动Activity时,都会创建一份实例,在某些情况下,这属于系统资源的浪费。因此,Android系统通过启动模式提供三种解决方案:
1.栈顶复用模式:当Activity位于栈顶时,重复创建实例,直接复用;当Activity位于栈中时,与标准模式相同;
2.栈内复用模式:当Activity位于栈顶时,同栈顶复用模式;当Activity位于栈中时,清除其上的全部实例,直至位于栈顶;
3.单实例模式:直接创建一个任务栈,无论Activity在任何位置,都会复用这个实例。
  栈内复用模式与单实例模式都支持创建新的任务栈,这会涉及前台任务栈与后台任务栈的关系,即Stack中含有多个TaskTecord。如果使用系统的默认后退操作,即出栈操作,只有当前任务栈全部出栈完毕时,才会切换至其他的任务栈,或者通过启动单例模式的Activity,才会触发切换任务栈。这个地方很容易操作疑惑,即入栈的顺序和出栈的顺序不同。

启动模式注意事项

  当一个Activity设置了SingleTop或者SingleTask模式后,跳转此Activity出现复用原有Activity的情况时,此Activity的onCreate()方法不会再次调用,但是我们一般在onCreate()方法中进行页面的UI初始化和数据初始化。若页面展示的数据就是通过getIntent()方法来获取,那么问题就出现了,getIntent()获取的是旧的数据,根本无法接收跳转时传送的新数据。比如如下代码:

public class FirstActivity extends BaseActivity{
    
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        initData();
        initView();
    }
    
    //初始化数据;
    private void initData(){
        Intent intent = getIntent();
        String name = intent.getStringExtra("intent_name");
    }
    
    //初始化UI;
    private void initView(){
        ···//处理UI;
    }
    
}

  当复用再次跳转到FirstActivity页面时,onCreate()方法不会被调用,也就意味着此时页面无法展示新的数据。但是会调用onNewIntent(Intent intent)方法,我们就可以从这个方法中获取新的数据,进行数据处理或者UI更新。就可以如下代码这样子来处理:

public class FirstActivity extends BaseActivity{
    
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        //初始化数据;
        initData();
        //初始化UI;
        initView();
    }
    
    @Override
    protected void onNewIntent(Intent intent){
        super.onNewIntent(intent);
        //把复用该页面时传过来的Intent重新赋值为mIntent,这样getIntent()方法就相当于更新到了传递过来的Intent,而不再是旧的Intnet;
        setIntent(intent);
        initData();
        initView();
    }
    
    //初始化数据;
    private void initData(){
        Intent intent = getIntent();
        String name = intent.getStringExtra("intent_name");
    }
    
    //初始化UI;
    private void initView(){
        ···//处理UI;
    }
}

  在上面的代码中,如果我们不想在onNewIntent(Intent intent)方法中使用setIntent(intent)方法,也可以直接使用onNewIntent(Intent intent)方法中传过来的参数intent,比如直接使用intent.getStringExtra("intent_name");。
  向外引申一下,我们获取传递到Activity时候的Intent,使用的是getIntent()方法,再次更新Activity中的Intent使用的是setIntent()方法,这两个方法是在Activity.java类中的。
Activity.java类中的方法:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {
        
    //声明Intent;    
    Intent mIntent;
    
    //获取Intent;
    public Intent getIntent(){
        return mIntent;
    }
    
    //更新Intent;
    public void setIntent(Intent newIntent) {
        mIntent = newIntent;
    }
    
    //在attach()方法中进行mIntent的赋值。attach()方法很重要,好多处理都是在这里开始的;
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        ···//省略代码;
        mIntent = intent;
        ···//省略代码;       
    }
}

快速启动一个Activity

  就是不要在Activity的onCreate()方法中执行过多繁重的操作,并且在onPause()方法中同样不要做过多的耗时操作。
  注意这是快速启动一个Activity,而不是快速启动一个应用,优化一个应用的启动时间要比优化一个Activity要复杂的多。

Activity中数据相关

Activity 间通过 Intent 传递数据大小限制

内存不足时系统会杀掉后台的Activity,若需要进行一些临时状态的保存,在哪个方法进行

onSaveInstanceState() 被执行的场景

两个 Activity 之间跳转时必然会执行的方法

用 Intent 去启动一个Activity 之外的方法

scheme 跳转协议

scheme 定义

scheme 协议格式

scheme 使用

注意事项

  • startActivity()方法最终调用的是startActivityForResult()方法,只是startActivity()的实现是startActivityForResult()方法内部的请求码requestCode固定为-1,所以如果使用startActivityForResult()方法时,请求码写成了-1,那么就相当于调用了startActivity()方法,onActivityResult()方法不会被回调;
  • 设置启动页:
    我们一般会设置启动页的style的android:windowBackground的属性来设置启动页的背景图片,如果不需要在启动页设置除了背景图片以外的控件,那么甚至可以不需要在启动页SplashActivity页面中使用setContentView(R.layout.activity_splash)方法,就直接在onCreate()方法中进行页面跳转或者数据处理;
<!--启动页style-->
<style name="AppSplashTheme" parent="AppTheme">
    <item name="android:windowBackground">@mipmap/ic_launcher</item>
</style>

<!-- 启动页面 -->
<activity
    android:name=".SplashActivity"
    android:screenOrientation="portrait"
    android:theme="@style/AppSplashTheme"
    >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

//SplashActivity页面;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //进行相应的业务逻辑操作;    
}

不过,一般我们都会设置启动页的背景图占据全屏幕。就需要style文件中添加一个属性android:windowFullscreen,设置为true;
还有,带有底部导航栏的手机,需要一个属性android:windowDrawsSystemBarBackgrounds,设置为true,不过该属性的使用需要最低兼容版本为API 21,也就是5.0;