Android基础-Activity全方位解析

一、Activity的生命周期

本节内容将生命周期的情况分为两部分介绍,第一部分先了解典型的生命周期的7个部分及Activity的状态。第二部分会介绍Activity在一些特殊情况下的生命周期的经历过程。

1.典型的生命周期的了解

先上经典图

 

关于这张图,我们可能在初学Android时就有接触,今天我们继续回顾一下。

在正常情况下,一个Activity从启动到结束会以如下顺序经历整个声明周期:

onCreate()-->onStart()-->onResume()-->onPause()-->onStop()-->onDestory()。包含六个部分,还有一个onRestart()没有调用,下面我们一一介绍这七个部分。

(1)onCreate()

  当Activity第一次创建时会被调用。这是生命周期的第一个方法。在这个方法中,可以做一些初始化工作,比如调用setContentView去加载界面布局资源,初始化Activity所需的数据。当然也可借助onCreate()方法中的Bundle对象来回复异常情况下Activity结束时的状态(后面会介绍)

(2)onRestart()

  表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为导致的,比如用户按Home键切换到桌面或打开了另一个新的Activity,接着用户又回到了这个Activity。(关于这部分生命周期的历经过程,后面会介绍)

(3)onStart()

  表示Activity正在被启动,即将开始,这时Activity已经出现了,但是还有出现在前台,无法与用户交互。这个时候可以理解为Activity已经显示出来,但是我们还看不到

(4)onResume()

  表示Activity已经可见了,并且出现在前台开始活动。需要和onStart()对比,onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。

(5)onPause()
  表示Activity正在停止,仍可见,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地回到当前Activity,那么onResume就会被调用(极端情况)。onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会被执行

(6)onStop()

  表示Activity即将停止,不可见,位于后台。可以做稍微重量级的回收工作,同样不能太耗时。

(7)onDestroy()

  表示Activity即将销毁,这是Activity生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。

在平常的开发中,我们经常用到的就是onCreate()和onDestroy(),做一些初始化和回收操作。

 

生命周期的几种普通情况

①针对一个特定的Activity,第一次启动,回调如下:

onCreate()-->onStart()-->onResume()

②用户打开新的Activity的时候,上述Activity的回调如下:

onPause()-->onStop()

③再次回到原Activity时,回调如下:

onRestart()-->onStart()-->onResume()

④按back键回退时,回调如下:

onPause()-->>onStop()-->onDestroy()

⑤按Home键切换到桌面后又回到该Activity,回调如下:

onPause()-->onStop()-->onRestart()-->onStart()-->onResume()

⑥调用finish()方法后,回调如下:

onDestroy()(以在onCreate()方法中调用为例,不同方法中回调不通,通常都是onCreate()方法中调用)

 

2.特殊情况下的生命周期

上面是普通情况下Activity生命周期的一些流程,但是在一些特殊情况下,Activity的生命周期的经历有些异常,下面就是两种特殊情况。

①横竖屏切换

在横竖屏切换的过程中,会发生Activity被销毁并重建的过程。

在了解这种情况下的生命周期时,首先应该了解这两个回调:

onSaveInstanceState()onRestoreInstanceState()

  在Activity由于异常情况下终止时,系统会调用onSaveInstanceState()来保存当前Activity的状态。这个方法的调用是在onStop之前,它和onPause没有既定的时序关系,该方法只在Activity被异常终止的情况下调用。

  当异常终止的Activity被重建以后,系统会调用onRestoreInstanceState(),并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象参数同时传递给onRestoreInstanceState和onCreate方法。因此,可以通过onRestoreInstanceState方法来恢复Activity的状态,该方法的的调用时机实在onStart之后。

点这里更多关于Activity状态的保存与恢复

其中onCreate和onRestoreInstanceState方法来回复Activity的状态的区别

  onRestoreInstanceState方法回调则表明其中Bundle对象非空,不用加非空判断。

  onCreate方法需要非空判断。建议使用onRestoreInstanceState。

横竖屏切换的生命周期

onPause()-->onSaveInstanceState()-->onStop()-->onDestroy()-->onCreate()-->onStart()-->onRestoreInstanceState()-->onResume()

可以通过在AndroidManifest文件的Activity中制定如下属性:

android:comfigChanges = "orientation | screenSize"

来避免横竖屏切换,Activity的销毁和重建,而是回调了下面的方法:

@Override
public void onConfigurationChanged(Configuration newConfig){
    super.onConfigurationChanged(newConfig);
}

 

②资源内存不足导致优先级低的Activity被杀死

Activity优先级的划分和下面的Activity的三种运行状态是对应的。

(1)前台Activity——正在和用户交互的Activity,优先级最高。

(2)可见但非前台Activity——比如Activity中弹出一个对话框,导致Activity可见但是位于后台无法

和用户交互。

(3)后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。

当系统内存不足时,会按照上述优先级从低到高去杀死目标Activity所在的进程。我们在平常使用手机时,能经常感受到这一现象。这种情况下数组存储和恢复和上诉情况一致,生命周期情况也一样。

 

 3.Activity的三种运行状态

Resumed(活动状态)

又叫Running状态,这个Activity正在屏幕上显示,并且有用户焦点。这个很好理解,就是用户正在操作的那个界面。

②Paused(暂停状态)

这是一个比较不常见的状态。这个Activity在屏幕上是可见的,但是并不是在屏幕最前端的那个Activity。比如有另外一个非全屏或者透明的Activity 是Resumed状态,没有完全覆盖这个Activity。

③Stopped(停止状态)

当Activity完全不可见时,此时Activity还在后台运行,仍然在内存中保留Activity的状态,并不是完全销毁。这个也很好理解,当跳转的另外一个界面,之前的界面还在后台,按回退按钮还会恢复原来的状态,大部分软件在打开的时候,直接Home键,并不会关闭它,此时的Activity就是Stopped状态。

 

 

二、Activity的启动模式

首先介绍一下任务栈

(1)程序打开时就创建了一个任务栈, 用于存储当前程序的activity,所有的activity属于一个任务栈。

(2)一个任务栈包含了一个activity的集合, 去有序的选择哪一个activity和用户进行交互:只有在任务栈栈顶的activity才可以跟用户进行交互。 

(3)任务栈可以移动到后台, 并且保留了每一个activity的状态. 并且有序的给用户列出它们的任务, 而且还不丢失它们状态信息。 

(4)退出应用程序时:当把所有的任务栈中所有的activity清除出栈时,任务栈会被销毁,程序退出。

 

任务栈的缺点

(1)每开启一次页面都会在任务栈中添加一个Activity,而只有任务栈中的Activity全部清除出栈时,任务栈被销毁,程序才会退出,这样就造成了用,户体验差, 需要点击多次返回才可以把程序退出了。

(2)每开启一次页面都会在任务栈中添加一个Activity还会造成数据冗余, 重复数据太多, 会导致内存溢出的问题(OOM)。

为了解决任务栈的缺点,我们引入了启动模式。

  启动模式(launchMode)在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念,task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task。

 

1.启动模式的类别

Android提供了四中Activity启动方式

标准模式(standard)

栈顶复用模式(singleTop)

栈内复用模式(singleTask)

单例模式(singleInstance)

启动模式的配置:

  直接在AndroidManifest.xml配置的android:launchMode属性为以上四种之一即可。  

  

2.启动模式的结构——栈

Activity的管理是采用任务栈的形式,任务栈采用”后进先出“的栈结构。

 

3.Activity的LaunchMode

 

 

 

(1)标准模式(standard)

  每启动一次Activity,就会创建一个新的Activity实例并置于栈顶。谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在栈中。 

示例:

activity_main.xml

    ...

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/btn_skip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转到MainActivity"/>
    
    ...

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = (TextView) findViewById(R.id.tv);
        textView.setText(this.toString());
        Button button = (Button) findViewById(R.id.btn_skip);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MainActivity.class);
                startActivity(intent);
            }
        });
    }
}

MainActivity界面中的TextView用于显示当前Activity实例的序列号,Button用于跳转到下一个FirstActivity界面。 
然后我们连续点击几次按钮,将会出现下面的现象: 

都是MainActivity的实例,但是序列号不同。

stardard模式的原理如下:

如图所示,每次跳转系统都会在task中生成一个新的MainActivity实例,并且放于栈结构的顶部,当我们按下后退键时,才能看到原来的MainActivity实例。 
这就是standard启动模式,不管有没有已存在的实例,都生成新的实例。

特殊情况:

  如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以使用标记位Flag来解决。

解决方法:

  为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新栈。

 应用场景:

  绝大多数Activity。如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管他们属于不同的程序,这似乎有点费解看起来也不合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。

 

(2)栈顶复用模式(singleTop)

  如果需要新建的Activity位于任务栈栈顶,那么此Activity的实例就不会重建,而是重用栈顶的实例。并回调如下方法。

@Override
protected void onNewIntent(Intent intent){
    super.onNewIntent(intent);
}

我们在上面的基础上为指定属性android:launchMode=”singleTop”,系统就会按照singleTop启动模式处理跳转行为。然后我们连续点击几次按钮,将会出现下面的现象: 

我们看到这个结果跟standard有所不同,三个序列号是相同的,也就是说使用的都是同一个MainActivity实例;如果按一下后退键,程序立即退出,说明当前栈结构中只有一个Activity实例。

singleTop模式的原理如下图所示:

 

正如上图所示,跳转时系统会先在栈结构中寻找是否有一个MainActivity实例正位于栈顶,如果有则不再生成新的,而是直接使用。也许朋友们会有疑问,我只看到栈内只有一个Activity,如果是多个Activity怎么办,如果不是在栈顶会如何?我们接下来再通过一个示例来证实一下大家的疑问。

新建一个SecondActivity,代码如下:

public class SecondActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        TextView textView = (TextView) findViewById(R.id.tv);
        textView.setText(this.toString());
        Button button = (Button) findViewById(R.id.btn_second);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SecondActivity.this, MainActivity.class);
                startActivity(intent);
            }
        });
    }
}

将之前的MainActivity跳转代码改为:

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);

这时候,FirstActivity会跳转到SecondActivity,SecondActivity又会跳转到FirstActivity。演示结果如下:

我们看到,两个MainActivity的序列号是不同的,证明从SecondActivity跳转到MainActivity时生成了新的MainActivity实例。原理图如下:

我们看到,当从SecondActivity跳转到MainActivity时,系统发现存在有FirstActivity实例,但不是位于栈顶,于是重新生成一个实例。
这就是singleTop启动模式,如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例。

这种启动模式通常适用于接受到消息后显示的界面,例如QQ接受到消息后弹出Activity,如果一次来10条消息,总不能一次弹10个Activity。

 

应用场景:

  在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 singleTop模式也是可以避免这个Bug的。同standard模式,如果是外部程序启动singleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中。

 

3)栈内复用模式(singleTask

   该模式是一种单例模式,即一个栈内只有一个该Activity实例。该模式,可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到哪个栈中,即singleTask的Activity可以指定想要加载的目标栈。

在singleTop的基础上我们修改MainActivity的属性android:launchMode=”singleTask”,演示结果如下:

我们注意到,在上面的过程中,MainActivity的序列号是不变的,SecondActivity的序列号却不是唯一的,说明从SecondActivity跳转到MainActivity时,没有生成新的实例,但是从MainActivity跳转到SecondActivity时生成了新的实例。

SecondActivity跳转到MainActivity后的栈结构变化的结果,我们注意到,SecondActivity消失了,没错,在这个跳转过程中系统发现有存在的FirstActivity实例,于是不再生成新的实例,而是将MainActivity之上的Activity实例统统出栈,将MainActivity变为栈顶对象,显示到幕前。

也许朋友们有疑问,如果将SecondActivity也设置为singleTask模式,那么SecondActivity实例是不是可以唯一呢?在我们这个示例中是不可能的,因为每次从SecondActivity跳转到MainActivity时,SecondActivity实例都被迫出栈,下次等MainActivity跳转到SecondActivity时,找不到存在的SecondActivity实例,于是必须生成新的实例。但是如果我们有ThirdActivity,让SecondActivity和ThirdActivity互相跳转,那么SecondActivity实例就可以保证唯一。 
这就是singleTask模式,如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前。

  

singleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中:

<activity
    android:name=".Activity1"
    android:launchMode="singleTask"
    android:taskAffinity="com.leslie.example"
    android:label="@String/app_name">
</activity>

 

关于taskAffinity的值

  每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显示的指明该Activity的taskAffinity,那么他的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。

 

执行逻辑:

  在这种模式下,如果Activity指定的栈不存在,则创建一个栈,并把创建的Activity压入栈内。如果Activity指定的栈存在,如果其中没有该Activity实例,则会创建Activity并压入栈顶,如果其中有该Activity实例,则把该Activity实例之上的Activity杀死清除出栈,重用并让该Activity实例处在栈顶,然后调用onNewIntent()方法。

对应如下三种情况:

应用场景:

  大多数App的主页。

  对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。    

 

(4)单例模式(singleInstance)

  作为栈内复用模式(singleTask)的加强版,打开该Activity时,直接创建一个新的任务栈,并创建该Activity实例放入新栈中。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例。

应用场景:

  呼叫来电界面。

  这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。建议谨慎使用。

 

4.特殊情况——前台栈和后台栈的交互

 假如目前有两个任务栈。

前台任务栈为AB,后台任务栈为CD,这里假设CD的启动模式均为singleTask,现在请求启动D,那么这个后台的任务栈都会被切换到前台,这个时候整个回退列表就会变成了ABCD。当用户按back返回时,列表中的activity会一一出站,如下图。

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

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

 

5.Activity的Flags

Activity的Flags很多,这里介绍集中常用的,用于设定Activity的启动模式。可以再启动Activity时,通过Intent的addFlags()方法设置。

(1)FLAG_ACTIVITY_NEW_TASK 其效果预指定Activity为singleTask模式一致

(2)FLAG_ACTIVITY_SINGLE_TOP 其效果与指定Activity为singleTop模式一致。

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

 

 

参考自:https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/activity.html

参考自:https://blog.csdn.net/CodeEmperor/article/details/50481726

posted @ 2019-08-15 18:53  黎先生  阅读(651)  评论(0编辑  收藏  举报