zoukankan      html  css  js  c++  java
  • 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

    很想高飞,但我不能;不想天空,剩我一人。
  • 相关阅读:
    node.js的request模块
    PHP实现一个简单url路由功能
    关于seajs
    CodeIgniter集成Smarty
    node.js批量修改图片名
    Node.js创建目录实例
    Bootstrap的表单设计器
    onbeforeunload事件被a链接触发的问题
    Socket.IO + Express实现的跨浏览器、子域的聊天室
    NodeJS获取命令行后面的参数
  • 原文地址:https://www.cnblogs.com/lixiansheng/p/11359838.html
Copyright © 2011-2022 走看看